Skip to content
27 changes: 27 additions & 0 deletions modules/structs/indexer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package structs

import (
"time"
)

// IndexerResult a search result to display
type IndexerResult struct {
RepoID int64 `json:"repo_id"`
Filename string `json:"filename"`
CommitID string `json:"commit_id"`
Updated time.Time `json:"updated"`
Language string `json:"language"`
Color string `json:"color"`
LineNumbers []int `json:"line_numbers"`
FormattedLines string `json:"formated_lines"`
}

// IndexerSearchResultLanguages result of top languages count in search results
type IndexerSearchResultLanguages struct {
Language string `json:"language"`
Color string `json:"color"`
Count int `json:"count"`
}
7 changes: 7 additions & 0 deletions modules/structs/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ type RepoTransfer struct {
Teams []*Team `json:"teams"`
}

// RepoCodeSearchAPIResponse represents the resposne of the code_search API endpoint
type RepoCodeSearchAPIResponse struct {
Total int `json:"total"`
SearchResults []*IndexerResult `json:"search_results"`
SearchResultLanguages []*IndexerSearchResultLanguages `json:"search_results_language"`
}

// NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed
type NewIssuePinsAllowed struct {
Issues bool `json:"issues"`
Expand Down
1 change: 1 addition & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ func Routes() *web.Route {
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
m.Get("/code_search", repo.CodeSearch)
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
m.Group("/avatar", func() {
Expand Down
100 changes: 100 additions & 0 deletions routers/api/v1/repo/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import (
"net/http"

"code.gitea.io/gitea/modules/context"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
)

// CodeSearch Performs a code search on a Repo
func CodeSearch(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/code_search repository repoCodeSearch
// ---
// summary: Performs a code search on a Repo
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: keyword
// in: query
// description: the keyword the search for
// type: string
// - name: language
// in: query
// description: filter results by language
// type: string
// - name: match
// in: query
// description: only exact match (defaults to true)
// type: boolean
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: string
// responses:
// "200":
// "$ref": "#/responses/RepoCodeSearch"
// "422":
// description: "The keyword is empty"
// schema:
// type: string
if !setting.Indexer.RepoIndexerEnabled {
ctx.Error(http.StatusInternalServerError, "IndexerNotEnabled", "The Code Indexer is not enabled on this server")
Copy link
Member

@silverwind silverwind Sep 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, 501 seems better here. Imho 500 should be reserved for unexpected errors. Also add it to the docs above.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think 5xx status codes would be correct in such cases. I would say if the feature is disabled and because of that API is not available it should be 4xx status code, I would say 404

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5xx means the problem is on the server side. 4xx on the client side. If code search is not enabled, the problem is on the server side.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem is really on client side for calling API that is disabled (not there) thus 404 just like 403 for calling method you don't have rights

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5xx is for status codes where the server knows it should be able to complete but for unexpected reasons it cannot but it in this case server is not willing to complete the request because it's not just not possible thus it should be 4xx status code

return
}

language := ctx.FormTrim("language")
keyword := ctx.FormTrim("keyword")
isMatch := !ctx.FormOptionalBool("match").IsFalse()

if keyword == "" {
ctx.Error(http.StatusUnprocessableEntity, "KeywordEmpty", "The keyword can't be empty")
return
}

listOptions := utils.GetListOptions(ctx)

if listOptions.Page <= 0 {
listOptions.Page = 1
}

total, searchResults, searchResultLanguages, err := code_indexer.PerformSearch(ctx, []int64{ctx.Repo.Repository.ID},
language, keyword, listOptions.Page, listOptions.PageSize, isMatch)
if err != nil {
ctx.InternalServerError(err)
return
}

response := api.RepoCodeSearchAPIResponse{
Total: total,
SearchResults: convert.ToIndexerSearchResultList(searchResults),
SearchResultLanguages: convert.ToIndexerSearchResultLanguagesList(searchResultLanguages),
}

pager := context.NewPagination(total, listOptions.PageSize, listOptions.Page, 5)

ctx.SetLinkHeader(pager.Paginater.TotalPages(), listOptions.PageSize)
ctx.SetTotalCountHeader(int64(pager.Paginater.TotalPages()))
ctx.JSON(http.StatusOK, response)
}
7 changes: 7 additions & 0 deletions routers/api/v1/swagger/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,13 @@ type swaggerRepoCollaboratorPermission struct {
Body api.RepoCollaboratorPermission `json:"body"`
}

// RepoCodeSearch
// swagger:response RepoCodeSearch
type swaggerRepoCodeSearch struct {
// in:body
Body api.RepoCodeSearchAPIResponse `json:"body"`
}

// RepoIssueConfig
// swagger:response RepoIssueConfig
type swaggerRepoIssueConfig struct {
Expand Down
50 changes: 50 additions & 0 deletions services/convert/indexer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package convert

import (
code_indexer "code.gitea.io/gitea/modules/indexer/code"
api "code.gitea.io/gitea/modules/structs"
)

// ToIndexerSearchResult converts IndexerSearch to API format
func ToIndexerSearchResult(result *code_indexer.Result) *api.IndexerResult {
return &api.IndexerResult{
RepoID: result.RepoID,
Filename: result.Filename,
CommitID: result.CommitID,
Updated: result.UpdatedUnix.AsTime(),
Language: result.Language,
Color: result.Color,
LineNumbers: result.LineNumbers,
FormattedLines: result.FormattedLines,
}
}

// ToIndexerSearchResultLanguages converts IndexerSearch to API format
func ToIndexerSearchResultLanguages(result *code_indexer.SearchResultLanguages) *api.IndexerSearchResultLanguages {
return &api.IndexerSearchResultLanguages{
Language: result.Language,
Color: result.Color,
Count: result.Count,
}
}

// ToIndexerSearchResultList convert list of *code_indexer.Result to list of *api.IndexerResult
func ToIndexerSearchResultList(results []*code_indexer.Result) []*api.IndexerResult {
convertedResults := make([]*api.IndexerResult, len(results))
for i := range results {
convertedResults[i] = ToIndexerSearchResult(results[i])
}
return convertedResults
}

// ToIndexerSearchResultLanguagesList convert list of *code_indexer.SearchResultLanguages to list of *api.IndexerSearchResultLanguages
func ToIndexerSearchResultLanguagesList(results []*code_indexer.SearchResultLanguages) []*api.IndexerSearchResultLanguages {
convertedResults := make([]*api.IndexerSearchResultLanguages, len(results))
for i := range results {
convertedResults[i] = ToIndexerSearchResultLanguages(results[i])
}
return convertedResults
}
Loading