search.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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. package github
  6. import (
  7. "context"
  8. "fmt"
  9. "strconv"
  10. qs "github.com/google/go-querystring/query"
  11. )
  12. // SearchService provides access to the search related functions
  13. // in the GitHub API.
  14. //
  15. // Each method takes a query string defining the search keywords and any search qualifiers.
  16. // For example, when searching issues, the query "gopher is:issue language:go" will search
  17. // for issues containing the word "gopher" in Go repositories. The method call
  18. // opts := &github.SearchOptions{Sort: "created", Order: "asc"}
  19. // cl.Search.Issues(ctx, "gopher is:issue language:go", opts)
  20. // will search for such issues, sorting by creation date in ascending order
  21. // (i.e., oldest first).
  22. //
  23. // GitHub API docs: https://developer.github.com/v3/search/
  24. type SearchService service
  25. // SearchOptions specifies optional parameters to the SearchService methods.
  26. type SearchOptions struct {
  27. // How to sort the search results. Possible values are:
  28. // - for repositories: stars, fork, updated
  29. // - for commits: author-date, committer-date
  30. // - for code: indexed
  31. // - for issues: comments, created, updated
  32. // - for users: followers, repositories, joined
  33. //
  34. // Default is to sort by best match.
  35. Sort string `url:"sort,omitempty"`
  36. // Sort order if sort parameter is provided. Possible values are: asc,
  37. // desc. Default is desc.
  38. Order string `url:"order,omitempty"`
  39. // Whether to retrieve text match metadata with a query
  40. TextMatch bool `url:"-"`
  41. ListOptions
  42. }
  43. // Common search parameters.
  44. type searchParameters struct {
  45. Query string
  46. RepositoryID *int64 // Sent if non-nil.
  47. }
  48. // RepositoriesSearchResult represents the result of a repositories search.
  49. type RepositoriesSearchResult struct {
  50. Total *int `json:"total_count,omitempty"`
  51. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  52. Repositories []Repository `json:"items,omitempty"`
  53. }
  54. // Repositories searches repositories via various criteria.
  55. //
  56. // GitHub API docs: https://developer.github.com/v3/search/#search-repositories
  57. func (s *SearchService) Repositories(ctx context.Context, query string, opt *SearchOptions) (*RepositoriesSearchResult, *Response, error) {
  58. result := new(RepositoriesSearchResult)
  59. resp, err := s.search(ctx, "repositories", &searchParameters{Query: query}, opt, result)
  60. return result, resp, err
  61. }
  62. // CommitsSearchResult represents the result of a commits search.
  63. type CommitsSearchResult struct {
  64. Total *int `json:"total_count,omitempty"`
  65. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  66. Commits []*CommitResult `json:"items,omitempty"`
  67. }
  68. // CommitResult represents a commit object as returned in commit search endpoint response.
  69. type CommitResult struct {
  70. SHA *string `json:"sha,omitempty"`
  71. Commit *Commit `json:"commit,omitempty"`
  72. Author *User `json:"author,omitempty"`
  73. Committer *User `json:"committer,omitempty"`
  74. Parents []*Commit `json:"parents,omitempty"`
  75. HTMLURL *string `json:"html_url,omitempty"`
  76. URL *string `json:"url,omitempty"`
  77. CommentsURL *string `json:"comments_url,omitempty"`
  78. Repository *Repository `json:"repository,omitempty"`
  79. Score *float64 `json:"score,omitempty"`
  80. }
  81. // Commits searches commits via various criteria.
  82. //
  83. // GitHub API docs: https://developer.github.com/v3/search/#search-commits
  84. func (s *SearchService) Commits(ctx context.Context, query string, opt *SearchOptions) (*CommitsSearchResult, *Response, error) {
  85. result := new(CommitsSearchResult)
  86. resp, err := s.search(ctx, "commits", &searchParameters{Query: query}, opt, result)
  87. return result, resp, err
  88. }
  89. // IssuesSearchResult represents the result of an issues search.
  90. type IssuesSearchResult struct {
  91. Total *int `json:"total_count,omitempty"`
  92. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  93. Issues []Issue `json:"items,omitempty"`
  94. }
  95. // Issues searches issues via various criteria.
  96. //
  97. // GitHub API docs: https://developer.github.com/v3/search/#search-issues
  98. func (s *SearchService) Issues(ctx context.Context, query string, opt *SearchOptions) (*IssuesSearchResult, *Response, error) {
  99. result := new(IssuesSearchResult)
  100. resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opt, result)
  101. return result, resp, err
  102. }
  103. // UsersSearchResult represents the result of a users search.
  104. type UsersSearchResult struct {
  105. Total *int `json:"total_count,omitempty"`
  106. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  107. Users []User `json:"items,omitempty"`
  108. }
  109. // Users searches users via various criteria.
  110. //
  111. // GitHub API docs: https://developer.github.com/v3/search/#search-users
  112. func (s *SearchService) Users(ctx context.Context, query string, opt *SearchOptions) (*UsersSearchResult, *Response, error) {
  113. result := new(UsersSearchResult)
  114. resp, err := s.search(ctx, "users", &searchParameters{Query: query}, opt, result)
  115. return result, resp, err
  116. }
  117. // Match represents a single text match.
  118. type Match struct {
  119. Text *string `json:"text,omitempty"`
  120. Indices []int `json:"indices,omitempty"`
  121. }
  122. // TextMatch represents a text match for a SearchResult
  123. type TextMatch struct {
  124. ObjectURL *string `json:"object_url,omitempty"`
  125. ObjectType *string `json:"object_type,omitempty"`
  126. Property *string `json:"property,omitempty"`
  127. Fragment *string `json:"fragment,omitempty"`
  128. Matches []Match `json:"matches,omitempty"`
  129. }
  130. func (tm TextMatch) String() string {
  131. return Stringify(tm)
  132. }
  133. // CodeSearchResult represents the result of a code search.
  134. type CodeSearchResult struct {
  135. Total *int `json:"total_count,omitempty"`
  136. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  137. CodeResults []CodeResult `json:"items,omitempty"`
  138. }
  139. // CodeResult represents a single search result.
  140. type CodeResult struct {
  141. Name *string `json:"name,omitempty"`
  142. Path *string `json:"path,omitempty"`
  143. SHA *string `json:"sha,omitempty"`
  144. HTMLURL *string `json:"html_url,omitempty"`
  145. Repository *Repository `json:"repository,omitempty"`
  146. TextMatches []TextMatch `json:"text_matches,omitempty"`
  147. }
  148. func (c CodeResult) String() string {
  149. return Stringify(c)
  150. }
  151. // Code searches code via various criteria.
  152. //
  153. // GitHub API docs: https://developer.github.com/v3/search/#search-code
  154. func (s *SearchService) Code(ctx context.Context, query string, opt *SearchOptions) (*CodeSearchResult, *Response, error) {
  155. result := new(CodeSearchResult)
  156. resp, err := s.search(ctx, "code", &searchParameters{Query: query}, opt, result)
  157. return result, resp, err
  158. }
  159. // LabelsSearchResult represents the result of a code search.
  160. type LabelsSearchResult struct {
  161. Total *int `json:"total_count,omitempty"`
  162. IncompleteResults *bool `json:"incomplete_results,omitempty"`
  163. Labels []*LabelResult `json:"items,omitempty"`
  164. }
  165. // LabelResult represents a single search result.
  166. type LabelResult struct {
  167. ID *int64 `json:"id,omitempty"`
  168. URL *string `json:"url,omitempty"`
  169. Name *string `json:"name,omitempty"`
  170. Color *string `json:"color,omitempty"`
  171. Default *bool `json:"default,omitempty"`
  172. Description *string `json:"description,omitempty"`
  173. Score *float64 `json:"score,omitempty"`
  174. }
  175. func (l LabelResult) String() string {
  176. return Stringify(l)
  177. }
  178. // Labels searches labels in the repository with ID repoID via various criteria.
  179. //
  180. // GitHub API docs: https://developer.github.com/v3/search/#search-labels
  181. func (s *SearchService) Labels(ctx context.Context, repoID int64, query string, opt *SearchOptions) (*LabelsSearchResult, *Response, error) {
  182. result := new(LabelsSearchResult)
  183. resp, err := s.search(ctx, "labels", &searchParameters{RepositoryID: &repoID, Query: query}, opt, result)
  184. return result, resp, err
  185. }
  186. // Helper function that executes search queries against different
  187. // GitHub search types (repositories, commits, code, issues, users, labels)
  188. func (s *SearchService) search(ctx context.Context, searchType string, parameters *searchParameters, opt *SearchOptions, result interface{}) (*Response, error) {
  189. params, err := qs.Values(opt)
  190. if err != nil {
  191. return nil, err
  192. }
  193. params.Set("q", parameters.Query)
  194. if parameters.RepositoryID != nil {
  195. params.Set("repository_id", strconv.FormatInt(*parameters.RepositoryID, 10))
  196. }
  197. u := fmt.Sprintf("search/%s?%s", searchType, params.Encode())
  198. req, err := s.client.NewRequest("GET", u, nil)
  199. if err != nil {
  200. return nil, err
  201. }
  202. switch {
  203. case searchType == "commits":
  204. // Accept header for search commits preview endpoint
  205. // TODO: remove custom Accept header when this API fully launches.
  206. req.Header.Set("Accept", mediaTypeCommitSearchPreview)
  207. case searchType == "repositories":
  208. // Accept header for search repositories based on topics preview endpoint
  209. // TODO: remove custom Accept header when this API fully launches.
  210. req.Header.Set("Accept", mediaTypeTopicsPreview)
  211. case searchType == "labels":
  212. // Accept header for search labels based on label description preview endpoint.
  213. // TODO: remove custom Accept header when this API fully launches.
  214. req.Header.Set("Accept", mediaTypeLabelDescriptionSearchPreview)
  215. case opt != nil && opt.TextMatch:
  216. // Accept header defaults to "application/vnd.github.v3+json"
  217. // We change it here to fetch back text-match metadata
  218. req.Header.Set("Accept", "application/vnd.github.v3.text-match+json")
  219. }
  220. return s.client.Do(ctx, req, result)
  221. }