github.com/google/go-github/v33@v33.0.0/github/search.go (about) 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/free-pro-team@latest/rest/reference/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/free-pro-team@latest/rest/reference/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 return result, resp, err 77 } 78 79 // TopicsSearchResult represents the result of a topics search. 80 type TopicsSearchResult struct { 81 Total *int `json:"total_count,omitempty"` 82 IncompleteResults *bool `json:"incomplete_results,omitempty"` 83 Topics []*TopicResult `json:"items,omitempty"` 84 } 85 86 type TopicResult struct { 87 Name *string `json:"name,omitempty"` 88 DisplayName *string `json:"display_name,omitempty"` 89 ShortDescription *string `json:"short_description,omitempty"` 90 Description *string `json:"description,omitempty"` 91 CreatedBy *string `json:"created_by,omitempty"` 92 CreatedAt *Timestamp `json:"created_at,omitempty"` 93 UpdatedAt *string `json:"updated_at,omitempty"` 94 Featured *bool `json:"featured,omitempty"` 95 Curated *bool `json:"curated,omitempty"` 96 Score *float64 `json:"score,omitempty"` 97 } 98 99 // Topics finds topics via various criteria. Results are sorted by best match. 100 // Please see https://help.github.com/en/articles/searching-topics for more 101 // information about search qualifiers. 102 // 103 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-topics 104 func (s *SearchService) Topics(ctx context.Context, query string, opts *SearchOptions) (*TopicsSearchResult, *Response, error) { 105 result := new(TopicsSearchResult) 106 resp, err := s.search(ctx, "topics", &searchParameters{Query: query}, opts, result) 107 return result, resp, err 108 } 109 110 // CommitsSearchResult represents the result of a commits search. 111 type CommitsSearchResult struct { 112 Total *int `json:"total_count,omitempty"` 113 IncompleteResults *bool `json:"incomplete_results,omitempty"` 114 Commits []*CommitResult `json:"items,omitempty"` 115 } 116 117 // CommitResult represents a commit object as returned in commit search endpoint response. 118 type CommitResult struct { 119 SHA *string `json:"sha,omitempty"` 120 Commit *Commit `json:"commit,omitempty"` 121 Author *User `json:"author,omitempty"` 122 Committer *User `json:"committer,omitempty"` 123 Parents []*Commit `json:"parents,omitempty"` 124 HTMLURL *string `json:"html_url,omitempty"` 125 URL *string `json:"url,omitempty"` 126 CommentsURL *string `json:"comments_url,omitempty"` 127 128 Repository *Repository `json:"repository,omitempty"` 129 Score *float64 `json:"score,omitempty"` 130 } 131 132 // Commits searches commits via various criteria. 133 // 134 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-commits 135 func (s *SearchService) Commits(ctx context.Context, query string, opts *SearchOptions) (*CommitsSearchResult, *Response, error) { 136 result := new(CommitsSearchResult) 137 resp, err := s.search(ctx, "commits", &searchParameters{Query: query}, opts, result) 138 return result, resp, err 139 } 140 141 // IssuesSearchResult represents the result of an issues search. 142 type IssuesSearchResult struct { 143 Total *int `json:"total_count,omitempty"` 144 IncompleteResults *bool `json:"incomplete_results,omitempty"` 145 Issues []*Issue `json:"items,omitempty"` 146 } 147 148 // Issues searches issues via various criteria. 149 // 150 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-issues-and-pull-requests 151 func (s *SearchService) Issues(ctx context.Context, query string, opts *SearchOptions) (*IssuesSearchResult, *Response, error) { 152 result := new(IssuesSearchResult) 153 resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opts, result) 154 return result, resp, err 155 } 156 157 // UsersSearchResult represents the result of a users search. 158 type UsersSearchResult struct { 159 Total *int `json:"total_count,omitempty"` 160 IncompleteResults *bool `json:"incomplete_results,omitempty"` 161 Users []*User `json:"items,omitempty"` 162 } 163 164 // Users searches users via various criteria. 165 // 166 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-users 167 func (s *SearchService) Users(ctx context.Context, query string, opts *SearchOptions) (*UsersSearchResult, *Response, error) { 168 result := new(UsersSearchResult) 169 resp, err := s.search(ctx, "users", &searchParameters{Query: query}, opts, result) 170 return result, resp, err 171 } 172 173 // Match represents a single text match. 174 type Match struct { 175 Text *string `json:"text,omitempty"` 176 Indices []int `json:"indices,omitempty"` 177 } 178 179 // TextMatch represents a text match for a SearchResult 180 type TextMatch struct { 181 ObjectURL *string `json:"object_url,omitempty"` 182 ObjectType *string `json:"object_type,omitempty"` 183 Property *string `json:"property,omitempty"` 184 Fragment *string `json:"fragment,omitempty"` 185 Matches []*Match `json:"matches,omitempty"` 186 } 187 188 func (tm TextMatch) String() string { 189 return Stringify(tm) 190 } 191 192 // CodeSearchResult represents the result of a code search. 193 type CodeSearchResult struct { 194 Total *int `json:"total_count,omitempty"` 195 IncompleteResults *bool `json:"incomplete_results,omitempty"` 196 CodeResults []*CodeResult `json:"items,omitempty"` 197 } 198 199 // CodeResult represents a single search result. 200 type CodeResult struct { 201 Name *string `json:"name,omitempty"` 202 Path *string `json:"path,omitempty"` 203 SHA *string `json:"sha,omitempty"` 204 HTMLURL *string `json:"html_url,omitempty"` 205 Repository *Repository `json:"repository,omitempty"` 206 TextMatches []*TextMatch `json:"text_matches,omitempty"` 207 } 208 209 func (c CodeResult) String() string { 210 return Stringify(c) 211 } 212 213 // Code searches code via various criteria. 214 // 215 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-code 216 func (s *SearchService) Code(ctx context.Context, query string, opts *SearchOptions) (*CodeSearchResult, *Response, error) { 217 result := new(CodeSearchResult) 218 resp, err := s.search(ctx, "code", &searchParameters{Query: query}, opts, result) 219 return result, resp, err 220 } 221 222 // LabelsSearchResult represents the result of a code search. 223 type LabelsSearchResult struct { 224 Total *int `json:"total_count,omitempty"` 225 IncompleteResults *bool `json:"incomplete_results,omitempty"` 226 Labels []*LabelResult `json:"items,omitempty"` 227 } 228 229 // LabelResult represents a single search result. 230 type LabelResult struct { 231 ID *int64 `json:"id,omitempty"` 232 URL *string `json:"url,omitempty"` 233 Name *string `json:"name,omitempty"` 234 Color *string `json:"color,omitempty"` 235 Default *bool `json:"default,omitempty"` 236 Description *string `json:"description,omitempty"` 237 Score *float64 `json:"score,omitempty"` 238 } 239 240 func (l LabelResult) String() string { 241 return Stringify(l) 242 } 243 244 // Labels searches labels in the repository with ID repoID via various criteria. 245 // 246 // GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/search/#search-labels 247 func (s *SearchService) Labels(ctx context.Context, repoID int64, query string, opts *SearchOptions) (*LabelsSearchResult, *Response, error) { 248 result := new(LabelsSearchResult) 249 resp, err := s.search(ctx, "labels", &searchParameters{RepositoryID: &repoID, Query: query}, opts, result) 250 return result, resp, err 251 } 252 253 // Helper function that executes search queries against different 254 // GitHub search types (repositories, commits, code, issues, users, labels) 255 // 256 // If searchParameters.Query includes multiple condition, it MUST NOT include "+" as condition separator. 257 // For example, querying with "language:c++" and "leveldb", then searchParameters.Query should be "language:c++ leveldb" but not "language:c+++leveldb". 258 func (s *SearchService) search(ctx context.Context, searchType string, parameters *searchParameters, opts *SearchOptions, result interface{}) (*Response, error) { 259 params, err := qs.Values(opts) 260 if err != nil { 261 return nil, err 262 } 263 if parameters.RepositoryID != nil { 264 params.Set("repository_id", strconv.FormatInt(*parameters.RepositoryID, 10)) 265 } 266 params.Set("q", parameters.Query) 267 u := fmt.Sprintf("search/%s?%s", searchType, params.Encode()) 268 269 req, err := s.client.NewRequest("GET", u, nil) 270 if err != nil { 271 return nil, err 272 } 273 274 switch { 275 case searchType == "commits": 276 // Accept header for search commits preview endpoint 277 // TODO: remove custom Accept header when this API fully launches. 278 req.Header.Set("Accept", mediaTypeCommitSearchPreview) 279 case searchType == "topics": 280 // Accept header for search repositories based on topics preview endpoint 281 // TODO: remove custom Accept header when this API fully launches. 282 req.Header.Set("Accept", mediaTypeTopicsPreview) 283 case searchType == "repositories": 284 // Accept header for search repositories based on topics preview endpoint 285 // TODO: remove custom Accept header when this API fully launches. 286 req.Header.Set("Accept", mediaTypeTopicsPreview) 287 case opts != nil && opts.TextMatch: 288 // Accept header defaults to "application/vnd.github.v3+json" 289 // We change it here to fetch back text-match metadata 290 req.Header.Set("Accept", "application/vnd.github.v3.text-match+json") 291 } 292 293 return s.client.Do(ctx, req, result) 294 }