...

Source file src/github.com/google/go-github/v45/github/search.go

Documentation: github.com/google/go-github/v45/github

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"strconv"
    12  
    13  	qs "github.com/google/go-querystring/query"
    14  )
    15  
    16  // SearchService provides access to the search related functions
    17  // in the GitHub API.
    18  //
    19  // Each method takes a query string defining the search keywords and any search qualifiers.
    20  // For example, when searching issues, the query "gopher is:issue language:go" will search
    21  // for issues containing the word "gopher" in Go repositories. The method call
    22  //   opts :=  &github.SearchOptions{Sort: "created", Order: "asc"}
    23  //   cl.Search.Issues(ctx, "gopher is:issue language:go", opts)
    24  // will search for such issues, sorting by creation date in ascending order
    25  // (i.e., oldest first).
    26  //
    27  // If query includes multiple conditions, it MUST NOT include "+" as the condition separator.
    28  // You have to use " " as the separator instead.
    29  // For example, querying with "language:c++" and "leveldb", then query should be
    30  // "language:c++ leveldb" but not "language:c+++leveldb".
    31  //
    32  // GitHub API docs: https://docs.github.com/en/rest/search/
    33  type SearchService service
    34  
    35  // SearchOptions specifies optional parameters to the SearchService methods.
    36  type SearchOptions struct {
    37  	// How to sort the search results. Possible values are:
    38  	//   - for repositories: stars, fork, updated
    39  	//   - for commits: author-date, committer-date
    40  	//   - for code: indexed
    41  	//   - for issues: comments, created, updated
    42  	//   - for users: followers, repositories, joined
    43  	//
    44  	// Default is to sort by best match.
    45  	Sort string `url:"sort,omitempty"`
    46  
    47  	// Sort order if sort parameter is provided. Possible values are: asc,
    48  	// desc. Default is desc.
    49  	Order string `url:"order,omitempty"`
    50  
    51  	// Whether to retrieve text match metadata with a query
    52  	TextMatch bool `url:"-"`
    53  
    54  	ListOptions
    55  }
    56  
    57  // Common search parameters.
    58  type searchParameters struct {
    59  	Query        string
    60  	RepositoryID *int64 // Sent if non-nil.
    61  }
    62  
    63  // RepositoriesSearchResult represents the result of a repositories search.
    64  type RepositoriesSearchResult struct {
    65  	Total             *int          `json:"total_count,omitempty"`
    66  	IncompleteResults *bool         `json:"incomplete_results,omitempty"`
    67  	Repositories      []*Repository `json:"items,omitempty"`
    68  }
    69  
    70  // Repositories searches repositories via various criteria.
    71  //
    72  // GitHub API docs: https://docs.github.com/en/rest/search#search-repositories
    73  func (s *SearchService) Repositories(ctx context.Context, query string, opts *SearchOptions) (*RepositoriesSearchResult, *Response, error) {
    74  	result := new(RepositoriesSearchResult)
    75  	resp, err := s.search(ctx, "repositories", &searchParameters{Query: query}, opts, result)
    76  	if err != nil {
    77  		return nil, resp, err
    78  	}
    79  
    80  	return result, resp, nil
    81  }
    82  
    83  // TopicsSearchResult represents the result of a topics search.
    84  type TopicsSearchResult struct {
    85  	Total             *int           `json:"total_count,omitempty"`
    86  	IncompleteResults *bool          `json:"incomplete_results,omitempty"`
    87  	Topics            []*TopicResult `json:"items,omitempty"`
    88  }
    89  
    90  type TopicResult struct {
    91  	Name             *string    `json:"name,omitempty"`
    92  	DisplayName      *string    `json:"display_name,omitempty"`
    93  	ShortDescription *string    `json:"short_description,omitempty"`
    94  	Description      *string    `json:"description,omitempty"`
    95  	CreatedBy        *string    `json:"created_by,omitempty"`
    96  	CreatedAt        *Timestamp `json:"created_at,omitempty"`
    97  	UpdatedAt        *string    `json:"updated_at,omitempty"`
    98  	Featured         *bool      `json:"featured,omitempty"`
    99  	Curated          *bool      `json:"curated,omitempty"`
   100  	Score            *float64   `json:"score,omitempty"`
   101  }
   102  
   103  // Topics finds topics via various criteria. Results are sorted by best match.
   104  // Please see https://help.github.com/en/articles/searching-topics for more
   105  // information about search qualifiers.
   106  //
   107  // GitHub API docs: https://docs.github.com/en/rest/search#search-topics
   108  func (s *SearchService) Topics(ctx context.Context, query string, opts *SearchOptions) (*TopicsSearchResult, *Response, error) {
   109  	result := new(TopicsSearchResult)
   110  	resp, err := s.search(ctx, "topics", &searchParameters{Query: query}, opts, result)
   111  	if err != nil {
   112  		return nil, resp, err
   113  	}
   114  
   115  	return result, resp, nil
   116  }
   117  
   118  // CommitsSearchResult represents the result of a commits search.
   119  type CommitsSearchResult struct {
   120  	Total             *int            `json:"total_count,omitempty"`
   121  	IncompleteResults *bool           `json:"incomplete_results,omitempty"`
   122  	Commits           []*CommitResult `json:"items,omitempty"`
   123  }
   124  
   125  // CommitResult represents a commit object as returned in commit search endpoint response.
   126  type CommitResult struct {
   127  	SHA         *string   `json:"sha,omitempty"`
   128  	Commit      *Commit   `json:"commit,omitempty"`
   129  	Author      *User     `json:"author,omitempty"`
   130  	Committer   *User     `json:"committer,omitempty"`
   131  	Parents     []*Commit `json:"parents,omitempty"`
   132  	HTMLURL     *string   `json:"html_url,omitempty"`
   133  	URL         *string   `json:"url,omitempty"`
   134  	CommentsURL *string   `json:"comments_url,omitempty"`
   135  
   136  	Repository *Repository `json:"repository,omitempty"`
   137  	Score      *float64    `json:"score,omitempty"`
   138  }
   139  
   140  // Commits searches commits via various criteria.
   141  //
   142  // GitHub API docs: https://docs.github.com/en/rest/search#search-commits
   143  func (s *SearchService) Commits(ctx context.Context, query string, opts *SearchOptions) (*CommitsSearchResult, *Response, error) {
   144  	result := new(CommitsSearchResult)
   145  	resp, err := s.search(ctx, "commits", &searchParameters{Query: query}, opts, result)
   146  	if err != nil {
   147  		return nil, resp, err
   148  	}
   149  
   150  	return result, resp, nil
   151  }
   152  
   153  // IssuesSearchResult represents the result of an issues search.
   154  type IssuesSearchResult struct {
   155  	Total             *int     `json:"total_count,omitempty"`
   156  	IncompleteResults *bool    `json:"incomplete_results,omitempty"`
   157  	Issues            []*Issue `json:"items,omitempty"`
   158  }
   159  
   160  // Issues searches issues via various criteria.
   161  //
   162  // GitHub API docs: https://docs.github.com/en/rest/search#search-issues-and-pull-requests
   163  func (s *SearchService) Issues(ctx context.Context, query string, opts *SearchOptions) (*IssuesSearchResult, *Response, error) {
   164  	result := new(IssuesSearchResult)
   165  	resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opts, result)
   166  	if err != nil {
   167  		return nil, resp, err
   168  	}
   169  
   170  	return result, resp, nil
   171  }
   172  
   173  // UsersSearchResult represents the result of a users search.
   174  type UsersSearchResult struct {
   175  	Total             *int    `json:"total_count,omitempty"`
   176  	IncompleteResults *bool   `json:"incomplete_results,omitempty"`
   177  	Users             []*User `json:"items,omitempty"`
   178  }
   179  
   180  // Users searches users via various criteria.
   181  //
   182  // GitHub API docs: https://docs.github.com/en/rest/search#search-users
   183  func (s *SearchService) Users(ctx context.Context, query string, opts *SearchOptions) (*UsersSearchResult, *Response, error) {
   184  	result := new(UsersSearchResult)
   185  	resp, err := s.search(ctx, "users", &searchParameters{Query: query}, opts, result)
   186  	if err != nil {
   187  		return nil, resp, err
   188  	}
   189  
   190  	return result, resp, nil
   191  }
   192  
   193  // Match represents a single text match.
   194  type Match struct {
   195  	Text    *string `json:"text,omitempty"`
   196  	Indices []int   `json:"indices,omitempty"`
   197  }
   198  
   199  // TextMatch represents a text match for a SearchResult
   200  type TextMatch struct {
   201  	ObjectURL  *string  `json:"object_url,omitempty"`
   202  	ObjectType *string  `json:"object_type,omitempty"`
   203  	Property   *string  `json:"property,omitempty"`
   204  	Fragment   *string  `json:"fragment,omitempty"`
   205  	Matches    []*Match `json:"matches,omitempty"`
   206  }
   207  
   208  func (tm TextMatch) String() string {
   209  	return Stringify(tm)
   210  }
   211  
   212  // CodeSearchResult represents the result of a code search.
   213  type CodeSearchResult struct {
   214  	Total             *int          `json:"total_count,omitempty"`
   215  	IncompleteResults *bool         `json:"incomplete_results,omitempty"`
   216  	CodeResults       []*CodeResult `json:"items,omitempty"`
   217  }
   218  
   219  // CodeResult represents a single search result.
   220  type CodeResult struct {
   221  	Name        *string      `json:"name,omitempty"`
   222  	Path        *string      `json:"path,omitempty"`
   223  	SHA         *string      `json:"sha,omitempty"`
   224  	HTMLURL     *string      `json:"html_url,omitempty"`
   225  	Repository  *Repository  `json:"repository,omitempty"`
   226  	TextMatches []*TextMatch `json:"text_matches,omitempty"`
   227  }
   228  
   229  func (c CodeResult) String() string {
   230  	return Stringify(c)
   231  }
   232  
   233  // Code searches code via various criteria.
   234  //
   235  // GitHub API docs: https://docs.github.com/en/rest/search#search-code
   236  func (s *SearchService) Code(ctx context.Context, query string, opts *SearchOptions) (*CodeSearchResult, *Response, error) {
   237  	result := new(CodeSearchResult)
   238  	resp, err := s.search(ctx, "code", &searchParameters{Query: query}, opts, result)
   239  	if err != nil {
   240  		return nil, resp, err
   241  	}
   242  
   243  	return result, resp, nil
   244  }
   245  
   246  // LabelsSearchResult represents the result of a code search.
   247  type LabelsSearchResult struct {
   248  	Total             *int           `json:"total_count,omitempty"`
   249  	IncompleteResults *bool          `json:"incomplete_results,omitempty"`
   250  	Labels            []*LabelResult `json:"items,omitempty"`
   251  }
   252  
   253  // LabelResult represents a single search result.
   254  type LabelResult struct {
   255  	ID          *int64   `json:"id,omitempty"`
   256  	URL         *string  `json:"url,omitempty"`
   257  	Name        *string  `json:"name,omitempty"`
   258  	Color       *string  `json:"color,omitempty"`
   259  	Default     *bool    `json:"default,omitempty"`
   260  	Description *string  `json:"description,omitempty"`
   261  	Score       *float64 `json:"score,omitempty"`
   262  }
   263  
   264  func (l LabelResult) String() string {
   265  	return Stringify(l)
   266  }
   267  
   268  // Labels searches labels in the repository with ID repoID via various criteria.
   269  //
   270  // GitHub API docs: https://docs.github.com/en/rest/search#search-labels
   271  func (s *SearchService) Labels(ctx context.Context, repoID int64, query string, opts *SearchOptions) (*LabelsSearchResult, *Response, error) {
   272  	result := new(LabelsSearchResult)
   273  	resp, err := s.search(ctx, "labels", &searchParameters{RepositoryID: &repoID, Query: query}, opts, result)
   274  	if err != nil {
   275  		return nil, resp, err
   276  	}
   277  
   278  	return result, resp, nil
   279  }
   280  
   281  // Helper function that executes search queries against different
   282  // GitHub search types (repositories, commits, code, issues, users, labels)
   283  //
   284  // If searchParameters.Query includes multiple condition, it MUST NOT include "+" as condition separator.
   285  // For example, querying with "language:c++" and "leveldb", then searchParameters.Query should be "language:c++ leveldb" but not "language:c+++leveldb".
   286  func (s *SearchService) search(ctx context.Context, searchType string, parameters *searchParameters, opts *SearchOptions, result interface{}) (*Response, error) {
   287  	params, err := qs.Values(opts)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	if parameters.RepositoryID != nil {
   293  		params.Set("repository_id", strconv.FormatInt(*parameters.RepositoryID, 10))
   294  	}
   295  	params.Set("q", parameters.Query)
   296  	u := fmt.Sprintf("search/%s?%s", searchType, params.Encode())
   297  
   298  	req, err := s.client.NewRequest("GET", u, nil)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	switch {
   304  	case searchType == "commits":
   305  		// Accept header for search commits preview endpoint
   306  		// TODO: remove custom Accept header when this API fully launches.
   307  		req.Header.Set("Accept", mediaTypeCommitSearchPreview)
   308  	case searchType == "topics":
   309  		// Accept header for search repositories based on topics preview endpoint
   310  		// TODO: remove custom Accept header when this API fully launches.
   311  		req.Header.Set("Accept", mediaTypeTopicsPreview)
   312  	case searchType == "repositories":
   313  		// Accept header for search repositories based on topics preview endpoint
   314  		// TODO: remove custom Accept header when this API fully launches.
   315  		req.Header.Set("Accept", mediaTypeTopicsPreview)
   316  	case searchType == "issues":
   317  		// Accept header for search issues based on reactions preview endpoint
   318  		// TODO: remove custom Accept header when this API fully launches.
   319  		req.Header.Set("Accept", mediaTypeReactionsPreview)
   320  	case opts != nil && opts.TextMatch:
   321  		// Accept header defaults to "application/vnd.github.v3+json"
   322  		// We change it here to fetch back text-match metadata
   323  		req.Header.Set("Accept", "application/vnd.github.v3.text-match+json")
   324  	}
   325  
   326  	return s.client.Do(ctx, req, result)
   327  }
   328  

View as plain text