...

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

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

View as plain text