codeintel: Simplify GraphQL resolvers (#10911)

* Remove precise-code-intel-api-server.

* Squash commits.

* WIP.

* Fix unused variable.

* Small cleanup.
This commit is contained in:
Eric Fritz 2020-05-28 08:14:33 -05:00 committed by GitHub
parent d41e56e424
commit b4d12f35f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 324 additions and 1250 deletions

1
.github/CODEOWNERS vendored
View File

@ -233,7 +233,6 @@ Dockerfile @sourcegraph/distribution
/internal/cmd/precise-code-intel-test @sourcegraph/code-intel
/internal/codeintel @sourcegraph/code-intel
/enterprise/internal/codeintel @sourcegraph/code-intel
/internal/lsif @sourcegraph/code-intel
/cmd/frontend/graphqlbackend/codeintel.go @sourcegraph/code-intel
# Development

View File

@ -37,7 +37,6 @@ import (
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
codeinteldb "github.com/sourcegraph/sourcegraph/internal/codeintel/db"
codeintelgitserver "github.com/sourcegraph/sourcegraph/internal/codeintel/gitserver"
lsifserverclient "github.com/sourcegraph/sourcegraph/internal/codeintel/lsifserver/client"
"github.com/sourcegraph/sourcegraph/internal/conf"
"github.com/sourcegraph/sourcegraph/internal/db/dbconn"
"github.com/sourcegraph/sourcegraph/internal/db/globalstatedb"
@ -176,10 +175,13 @@ func initCodeIntel() {
db := codeinteldb.NewObserved(codeinteldb.NewWithHandle(dbconn.Global), observationContext)
bundleManagerClient := bundles.New(bundleManagerURL)
api := codeintelapi.NewObserved(codeintelapi.New(db, bundleManagerClient, codeintelgitserver.DefaultClient), observationContext)
client := lsifserverclient.New(db, bundleManagerClient, api)
graphqlbackend.NewCodeIntelResolver = func() graphqlbackend.CodeIntelResolver {
return codeintelResolvers.NewResolver(client)
return codeintelResolvers.NewResolver(
db,
bundleManagerClient,
api,
)
}
httpapi.NewCodeIntelUploadHandler = func() http.Handler {

View File

@ -0,0 +1,4 @@
package resolvers
const DefaultUploadPageSize = 50
const DefaultReferencesPageSize = 100

View File

@ -0,0 +1,13 @@
package resolvers
import (
"github.com/sourcegraph/go-lsp"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
)
func convertRange(r bundles.Range) lsp.Range {
return lsp.Range{
Start: lsp.Position{Line: r.Start.Line, Character: r.Start.Character},
End: lsp.Position{Line: r.End.Line, Character: r.End.Character},
}
}

View File

@ -0,0 +1,30 @@
package resolvers
import (
"strconv"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
func marshalLSIFUploadGQLID(lsifUploadID int64) graphql.ID {
return relay.MarshalID("LSIFUpload", lsifUploadID)
}
func unmarshalLSIFUploadGQLID(id graphql.ID) (lsifUploadID int64, err error) {
// First, try to unmarshal the ID as a string and then convert it to an
// integer. This is here to maintain backwards compatibility with the
// src-cli lsif upload command, which constructs its own relay identifier
// from a the string payload returned by the upload proxy.
var lsifUploadIDString string
err = relay.UnmarshalSpec(id, &lsifUploadIDString)
if err == nil {
lsifUploadID, err = strconv.ParseInt(lsifUploadIDString, 10, 64)
return
}
// If it wasn't unmarshal-able as a string, it's a new-style int identifier
err = relay.UnmarshalSpec(id, &lsifUploadID)
return
}

View File

@ -3,18 +3,18 @@ package resolvers
import (
"context"
"github.com/sourcegraph/go-langserver/pkg/lsp"
"github.com/sourcegraph/go-lsp"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend/graphqlutil"
"github.com/sourcegraph/sourcegraph/cmd/frontend/types"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/lsif"
codeintelapi "github.com/sourcegraph/sourcegraph/internal/codeintel/api"
)
type locationConnectionResolver struct {
repo *types.Repo
commit api.CommitID
locations []*lsif.LSIFLocation
locations []codeintelapi.ResolvedLocation
endCursor string
}
@ -32,7 +32,7 @@ func (r *locationConnectionResolver) Nodes(ctx context.Context) ([]graphqlbacken
return nil, err
}
treeResolver, err := collectionResolver.resolve(ctx, location.RepositoryID, adjustedCommit, location.Path)
treeResolver, err := collectionResolver.resolve(ctx, api.RepoID(location.Dump.RepositoryID), adjustedCommit, location.Path)
if err != nil {
return nil, err
}
@ -63,21 +63,21 @@ func (r *locationConnectionResolver) PageInfo(ctx context.Context) (*graphqlutil
//
// A non-nil error means the connection resolver was unable to load the diff between
// the requested commit and location's commit.
func (r *locationConnectionResolver) adjustLocation(ctx context.Context, location *lsif.LSIFLocation) (string, lsp.Range, error) {
if location.RepositoryID != r.repo.ID {
return location.Commit, location.Range, nil
func (r *locationConnectionResolver) adjustLocation(ctx context.Context, location codeintelapi.ResolvedLocation) (string, lsp.Range, error) {
if api.RepoID(location.Dump.RepositoryID) != r.repo.ID {
return location.Dump.Commit, convertRange(location.Range), nil
}
adjuster, err := newPositionAdjuster(ctx, r.repo, location.Commit, string(r.commit), location.Path)
adjuster, err := newPositionAdjuster(ctx, r.repo, location.Dump.Commit, string(r.commit), location.Path)
if err != nil {
return "", lsp.Range{}, err
}
if adjustedRange, ok := adjuster.adjustRange(location.Range); ok {
if adjustedRange, ok := adjuster.adjustRange(convertRange(location.Range)); ok {
return string(r.commit), adjustedRange, nil
}
// Couldn't adjust range, return original result which is precise but
// jump the user to another into another commit context on navigation.
return location.Commit, location.Range, nil
return location.Dump.Commit, convertRange(location.Range), nil
}

View File

@ -4,23 +4,27 @@ import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"github.com/sourcegraph/go-lsp"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/codeintel/lsifserver/client"
"github.com/sourcegraph/sourcegraph/internal/lsif"
codeintelapi "github.com/sourcegraph/sourcegraph/internal/codeintel/api"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
type lsifQueryResolver struct {
lsifserverClient *client.Client
db db.DB
bundleManagerClient bundles.BundleManagerClient
codeIntelAPI codeintelapi.CodeIntelAPI
repositoryResolver *graphqlbackend.RepositoryResolver
// commit is the requested target commit
commit api.CommitID
path string
// uploads are ordered by their commit distance from the target commit
uploads []*lsif.LSIFUpload
uploads []db.Dump
}
var _ graphqlbackend.LSIFQueryResolver = &lsifQueryResolver{}
@ -36,23 +40,7 @@ func (r *lsifQueryResolver) Definitions(ctx context.Context, args *graphqlbacken
continue
}
opts := &struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
}{
RepoID: r.repositoryResolver.Type().ID,
Commit: r.commit,
Path: r.path,
Line: int32(adjustedPosition.Line),
Character: int32(adjustedPosition.Character),
UploadID: upload.ID,
}
locations, _, err := r.lsifserverClient.Definitions(ctx, opts)
locations, err := r.codeIntelAPI.Definitions(ctx, r.path, adjustedPosition.Line, adjustedPosition.Character, upload.ID)
if err != nil {
return nil, err
}
@ -82,9 +70,9 @@ func (r *lsifQueryResolver) References(ctx context.Context, args *graphqlbackend
// We need to maintain a symmetric map for the next page
// of results that we can encode into the endCursor of
// this request.
newCursors := map[int64]string{}
newCursors := map[int]string{}
var allLocations []*lsif.LSIFLocation
var allLocations []codeintelapi.ResolvedLocation
for _, upload := range r.uploads {
adjustedPosition, ok, err := r.adjustPosition(ctx, upload.Commit, args.Line, args.Character)
if err != nil {
@ -94,28 +82,18 @@ func (r *lsifQueryResolver) References(ctx context.Context, args *graphqlbackend
continue
}
opts := &struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
Limit *int32
Cursor *string
}{
RepoID: r.repositoryResolver.Type().ID,
Commit: r.commit,
Path: r.path,
Line: int32(adjustedPosition.Line),
Character: int32(adjustedPosition.Character),
UploadID: upload.ID,
}
limit := DefaultReferencesPageSize
if args.First != nil {
opts.Limit = args.First
limit = int(*args.First)
}
if limit <= 0 {
// TODO(efritz) - check on defs too
return nil, errors.New("illegal limit")
}
rawCursor := ""
if nextURL, ok := nextURLs[upload.ID]; ok {
opts.Cursor = &nextURL
rawCursor = nextURL
} else if len(nextURLs) != 0 {
// Result set is exhausted or newer than the first page
// of results. Skip anything from this upload as it will
@ -123,14 +101,31 @@ func (r *lsifQueryResolver) References(ctx context.Context, args *graphqlbackend
continue
}
locations, nextURL, err := r.lsifserverClient.References(ctx, opts)
cursor, err := codeintelapi.DecodeOrCreateCursor(r.path, adjustedPosition.Line, adjustedPosition.Character, upload.ID, rawCursor, r.db, r.bundleManagerClient)
if err != nil {
return nil, err
}
locations, newCursor, hasNewCursor, err := r.codeIntelAPI.References(
ctx,
int(r.repositoryResolver.Type().ID),
string(r.commit),
limit,
cursor,
)
if err != nil {
return nil, err
}
cx := ""
if hasNewCursor {
cx = codeintelapi.EncodeCursor(newCursor)
}
allLocations = append(allLocations, locations...)
if nextURL != "" {
newCursors[upload.ID] = nextURL
if cx != "" {
newCursors[upload.ID] = cx
}
}
@ -157,25 +152,14 @@ func (r *lsifQueryResolver) Hover(ctx context.Context, args *graphqlbackend.LSIF
continue
}
text, lspRange, err := r.lsifserverClient.Hover(ctx, &struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
}{
RepoID: r.repositoryResolver.Type().ID,
Commit: r.commit,
Path: r.path,
Line: int32(adjustedPosition.Line),
Character: int32(adjustedPosition.Character),
UploadID: upload.ID,
})
if err != nil {
// TODO(efritz) - codeintelapi should just return an lsp.Hover
text, rn, exists, err := r.codeIntelAPI.Hover(ctx, r.path, adjustedPosition.Line, adjustedPosition.Character, int(upload.ID))
if err != nil || !exists {
return nil, err
}
lspRange := convertRange(rn)
if text != "" {
adjustedRange, ok, err := r.adjustRange(ctx, upload.Commit, lspRange)
if err != nil {
@ -223,7 +207,7 @@ func (r *lsifQueryResolver) adjustRange(ctx context.Context, uploadCommit string
// readCursor decodes a cursor into a map from upload ids to URLs that
// serves the next page of results.
func readCursor(after *string) (map[int64]string, error) {
func readCursor(after *string) (map[int]string, error) {
if after == nil {
return nil, nil
}
@ -233,7 +217,7 @@ func readCursor(after *string) (map[int64]string, error) {
return nil, err
}
var cursors map[int64]string
var cursors map[int]string
if err := json.Unmarshal(decoded, &cursors); err != nil {
return nil, err
}
@ -243,7 +227,7 @@ func readCursor(after *string) (map[int64]string, error) {
// makeCursor encodes a map from upload ids to URLs that serves the next
// page of results into a single string that can be sent back for use in
// cursor pagination.
func makeCursor(cursors map[int64]string) (string, error) {
func makeCursor(cursors map[int]string) (string, error) {
if len(cursors) == 0 {
return "", nil
}

View File

@ -5,21 +5,28 @@ import (
"encoding/base64"
graphql "github.com/graph-gophers/graphql-go"
"github.com/pkg/errors"
"github.com/sourcegraph/sourcegraph/cmd/frontend/backend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/codeintel/lsifserver/client"
codeintelapi "github.com/sourcegraph/sourcegraph/internal/codeintel/api"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
"github.com/sourcegraph/sourcegraph/internal/codeintel/gitserver"
)
type Resolver struct {
lsifserverClient *client.Client
db db.DB
bundleManagerClient bundles.BundleManagerClient
codeIntelAPI codeintelapi.CodeIntelAPI
}
var _ graphqlbackend.CodeIntelResolver = &Resolver{}
func NewResolver(lsifserverClient *client.Client) graphqlbackend.CodeIntelResolver {
func NewResolver(db db.DB, bundleManagerClient bundles.BundleManagerClient, codeIntelAPI codeintelapi.CodeIntelAPI) graphqlbackend.CodeIntelResolver {
return &Resolver{
lsifserverClient: lsifserverClient,
db: db,
bundleManagerClient: bundleManagerClient,
codeIntelAPI: codeIntelAPI,
}
}
@ -29,16 +36,12 @@ func (r *Resolver) LSIFUploadByID(ctx context.Context, id graphql.ID) (graphqlba
return nil, err
}
lsifUpload, err := r.lsifserverClient.GetUpload(ctx, &struct {
UploadID int64
}{
UploadID: uploadID,
})
if err != nil {
upload, exists, err := r.db.GetUploadByID(ctx, int(uploadID))
if err != nil || !exists {
return nil, err
}
return &lsifUploadResolver{lsifUpload: lsifUpload}, nil
return &lsifUploadResolver{lsifUpload: upload}, nil
}
func (r *Resolver) DeleteLSIFUpload(ctx context.Context, id graphql.ID) (*graphqlbackend.EmptyResponse, error) {
@ -52,10 +55,12 @@ func (r *Resolver) DeleteLSIFUpload(ctx context.Context, id graphql.ID) (*graphq
return nil, err
}
err = r.lsifserverClient.DeleteUpload(ctx, &struct {
UploadID int64
}{
UploadID: uploadID,
_, err = r.db.DeleteUploadByID(ctx, int(uploadID), func(repositoryID int) (string, error) {
tipCommit, err := gitserver.Head(ctx, r.db, repositoryID)
if err != nil {
return "", errors.Wrap(err, "gitserver.Head")
}
return tipCommit, nil
})
if err != nil {
return nil, err
@ -91,33 +96,25 @@ func (r *Resolver) LSIFUploads(ctx context.Context, args *graphqlbackend.LSIFRep
opt.NextURL = &nextURL
}
return &lsifUploadConnectionResolver{lsifserverClient: r.lsifserverClient, opt: opt}, nil
return &lsifUploadConnectionResolver{db: r.db, opt: opt}, nil
}
func (r *Resolver) LSIF(ctx context.Context, args *graphqlbackend.LSIFQueryArgs) (graphqlbackend.LSIFQueryResolver, error) {
uploads, err := r.lsifserverClient.Exists(ctx, &struct {
RepoID api.RepoID
Commit api.CommitID
Path string
}{
RepoID: args.Repository.Type().ID,
Commit: args.Commit,
Path: args.Path,
})
dumps, err := r.codeIntelAPI.FindClosestDumps(ctx, int(args.Repository.Type().ID), string(args.Commit), args.Path)
if err != nil {
return nil, err
}
if len(uploads) == 0 {
if len(dumps) == 0 {
return nil, nil
}
return &lsifQueryResolver{
lsifserverClient: r.lsifserverClient,
repositoryResolver: args.Repository,
commit: args.Commit,
path: args.Path,
uploads: uploads,
db: r.db,
bundleManagerClient: r.bundleManagerClient,
codeIntelAPI: r.codeIntelAPI,
repositoryResolver: args.Repository,
commit: args.Commit,
path: args.Path,
uploads: dumps,
}, nil
}

View File

@ -2,57 +2,35 @@ package resolvers
import (
"context"
"encoding/base64"
"strconv"
"strings"
"sync"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend/graphqlutil"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/codeintel/lsifserver/client"
"github.com/sourcegraph/sourcegraph/internal/lsif"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
type lsifUploadResolver struct {
repositoryResolver *graphqlbackend.RepositoryResolver
lsifUpload *lsif.LSIFUpload
lsifUpload db.Upload
}
var _ graphqlbackend.LSIFUploadResolver = &lsifUploadResolver{}
func (r *lsifUploadResolver) ID() graphql.ID {
return marshalLSIFUploadGQLID(r.lsifUpload.ID)
}
func (r *lsifUploadResolver) ID() graphql.ID { return marshalLSIFUploadGQLID(int64(r.lsifUpload.ID)) }
func (r *lsifUploadResolver) InputCommit() string { return r.lsifUpload.Commit }
func (r *lsifUploadResolver) InputRoot() string { return r.lsifUpload.Root }
func (r *lsifUploadResolver) InputIndexer() string { return r.lsifUpload.Indexer }
func (r *lsifUploadResolver) State() string { return strings.ToUpper(r.lsifUpload.State) }
func (r *lsifUploadResolver) IsLatestForRepo() bool { return r.lsifUpload.VisibleAtTip }
func (r *lsifUploadResolver) ProjectRoot(ctx context.Context) (*graphqlbackend.GitTreeEntryResolver, error) {
return resolvePath(ctx, r.lsifUpload.RepositoryID, r.lsifUpload.Commit, r.lsifUpload.Root)
}
func (r *lsifUploadResolver) InputCommit() string {
return r.lsifUpload.Commit
}
func (r *lsifUploadResolver) InputRoot() string {
return r.lsifUpload.Root
}
func (r *lsifUploadResolver) InputIndexer() string {
return r.lsifUpload.Indexer
}
func (r *lsifUploadResolver) State() string {
return strings.ToUpper(r.lsifUpload.State)
}
func (r *lsifUploadResolver) Failure() graphqlbackend.LSIFUploadFailureReasonResolver {
if r.lsifUpload.FailureSummary == nil {
func (r *lsifUploadResolver) PlaceInQueue() *int32 {
if r.lsifUpload.Rank == nil {
return nil
}
return &lsifUploadFailureReasonResolver{r.lsifUpload}
v := int32(*r.lsifUpload.Rank)
return &v
}
func (r *lsifUploadResolver) UploadedAt() graphqlbackend.DateTime {
@ -67,145 +45,14 @@ func (r *lsifUploadResolver) FinishedAt() *graphqlbackend.DateTime {
return graphqlbackend.DateTimeOrNil(r.lsifUpload.FinishedAt)
}
func (r *lsifUploadResolver) IsLatestForRepo() bool {
return r.lsifUpload.VisibleAtTip
func (r *lsifUploadResolver) ProjectRoot(ctx context.Context) (*graphqlbackend.GitTreeEntryResolver, error) {
return resolvePath(ctx, api.RepoID(r.lsifUpload.RepositoryID), r.lsifUpload.Commit, r.lsifUpload.Root)
}
func (r *lsifUploadResolver) PlaceInQueue() *int32 {
return r.lsifUpload.PlaceInQueue
}
type lsifUploadFailureReasonResolver struct {
lsifUpload *lsif.LSIFUpload
}
var _ graphqlbackend.LSIFUploadFailureReasonResolver = &lsifUploadFailureReasonResolver{}
func (r *lsifUploadFailureReasonResolver) Summary() string {
func (r *lsifUploadResolver) Failure() graphqlbackend.LSIFUploadFailureReasonResolver {
if r.lsifUpload.FailureSummary == nil {
return ""
return nil
}
return *r.lsifUpload.FailureSummary
}
func (r *lsifUploadFailureReasonResolver) Stacktrace() string {
if r.lsifUpload.FailureStacktrace == nil {
return ""
}
return *r.lsifUpload.FailureStacktrace
}
type LSIFUploadsListOptions struct {
RepositoryID graphql.ID
Query *string
State *string
IsLatestForRepo *bool
Limit *int32
NextURL *string
}
type lsifUploadConnectionResolver struct {
lsifserverClient *client.Client
opt LSIFUploadsListOptions
// cache results because they are used by multiple fields
once sync.Once
uploads []*lsif.LSIFUpload
repositoryResolver *graphqlbackend.RepositoryResolver
totalCount *int
nextURL string
err error
}
var _ graphqlbackend.LSIFUploadConnectionResolver = &lsifUploadConnectionResolver{}
func (r *lsifUploadConnectionResolver) Nodes(ctx context.Context) ([]graphqlbackend.LSIFUploadResolver, error) {
uploads, repositoryResolver, _, _, err := r.compute(ctx)
if err != nil {
return nil, err
}
var l []graphqlbackend.LSIFUploadResolver
for _, lsifUpload := range uploads {
l = append(l, &lsifUploadResolver{
repositoryResolver: repositoryResolver,
lsifUpload: lsifUpload,
})
}
return l, nil
}
func (r *lsifUploadConnectionResolver) TotalCount(ctx context.Context) (*int32, error) {
_, _, count, _, err := r.compute(ctx)
if count == nil || err != nil {
return nil, err
}
c := int32(*count)
return &c, nil
}
func (r *lsifUploadConnectionResolver) PageInfo(ctx context.Context) (*graphqlutil.PageInfo, error) {
_, _, _, nextURL, err := r.compute(ctx)
if err != nil {
return nil, err
}
if nextURL != "" {
return graphqlutil.NextPageCursor(base64.StdEncoding.EncodeToString([]byte(nextURL))), nil
}
return graphqlutil.HasNextPage(false), nil
}
func (r *lsifUploadConnectionResolver) compute(ctx context.Context) ([]*lsif.LSIFUpload, *graphqlbackend.RepositoryResolver, *int, string, error) {
r.once.Do(func() {
r.repositoryResolver, r.err = graphqlbackend.RepositoryByID(ctx, r.opt.RepositoryID)
if r.err != nil {
return
}
r.uploads, r.nextURL, r.totalCount, r.err = r.lsifserverClient.GetUploads(ctx, &struct {
RepoID api.RepoID
Query *string
State *string
IsLatestForRepo *bool
Limit *int32
Cursor *string
}{
RepoID: r.repositoryResolver.Type().ID,
Query: r.opt.Query,
State: r.opt.State,
IsLatestForRepo: r.opt.IsLatestForRepo,
Limit: r.opt.Limit,
Cursor: r.opt.NextURL,
})
})
return r.uploads, r.repositoryResolver, r.totalCount, r.nextURL, r.err
}
func marshalLSIFUploadGQLID(lsifUploadID int64) graphql.ID {
return relay.MarshalID("LSIFUpload", lsifUploadID)
}
func unmarshalLSIFUploadGQLID(id graphql.ID) (lsifUploadID int64, err error) {
// First, try to unmarshal the ID as a string and then convert it to an
// integer. This is here to maintain backwards compatibility with the
// src-cli lsif upload command, which constructs its own relay identifier
// from a the string payload returned by the upload proxy.
var lsifUploadIDString string
err = relay.UnmarshalSpec(id, &lsifUploadIDString)
if err == nil {
lsifUploadID, err = strconv.ParseInt(lsifUploadIDString, 10, 64)
return
}
// If it wasn't unmarshal-able as a string, it's a new-style int identifier
err = relay.UnmarshalSpec(id, &lsifUploadID)
return
return &lsifUploadFailureReasonResolver{r.lsifUpload}
}

View File

@ -0,0 +1,137 @@
package resolvers
import (
"context"
"encoding/base64"
"fmt"
"strconv"
"strings"
"sync"
graphql "github.com/graph-gophers/graphql-go"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend/graphqlutil"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
type lsifUploadConnectionResolver struct {
db db.DB
opt LSIFUploadsListOptions
// cache results because they are used by multiple fields
once sync.Once
uploads []db.Upload
repositoryResolver *graphqlbackend.RepositoryResolver
totalCount *int
nextURL string
err error
}
var _ graphqlbackend.LSIFUploadConnectionResolver = &lsifUploadConnectionResolver{}
type LSIFUploadsListOptions struct {
RepositoryID graphql.ID
Query *string
State *string
IsLatestForRepo *bool
Limit *int32
NextURL *string
}
func (r *lsifUploadConnectionResolver) Nodes(ctx context.Context) ([]graphqlbackend.LSIFUploadResolver, error) {
uploads, repositoryResolver, _, _, err := r.compute(ctx)
if err != nil {
return nil, err
}
var l []graphqlbackend.LSIFUploadResolver
for _, lsifUpload := range uploads {
l = append(l, &lsifUploadResolver{
repositoryResolver: repositoryResolver,
lsifUpload: lsifUpload,
})
}
return l, nil
}
func (r *lsifUploadConnectionResolver) TotalCount(ctx context.Context) (*int32, error) {
_, _, count, _, err := r.compute(ctx)
if count == nil || err != nil {
return nil, err
}
c := int32(*count)
return &c, nil
}
func (r *lsifUploadConnectionResolver) PageInfo(ctx context.Context) (*graphqlutil.PageInfo, error) {
_, _, _, nextURL, err := r.compute(ctx)
if err != nil {
return nil, err
}
if nextURL != "" {
return graphqlutil.NextPageCursor(base64.StdEncoding.EncodeToString([]byte(nextURL))), nil
}
return graphqlutil.HasNextPage(false), nil
}
func (r *lsifUploadConnectionResolver) compute(ctx context.Context) ([]db.Upload, *graphqlbackend.RepositoryResolver, *int, string, error) {
r.once.Do(func() {
r.repositoryResolver, r.err = graphqlbackend.RepositoryByID(ctx, r.opt.RepositoryID)
if r.err != nil {
return
}
id := int(r.repositoryResolver.Type().ID)
query := ""
if r.opt.Query != nil {
query = *r.opt.Query
}
visibileAtTip := r.opt.IsLatestForRepo != nil && *r.opt.IsLatestForRepo
state := ""
if r.opt.State != nil {
state = strings.ToLower(*r.opt.State)
}
limit := DefaultUploadPageSize
if r.opt.Limit != nil {
limit = int(*r.opt.Limit)
}
offset := 0
if r.opt.NextURL != nil {
offset, _ = strconv.Atoi(*r.opt.NextURL)
}
uploads, totalCount, err := r.db.GetUploadsByRepo(
ctx,
id,
state,
query,
visibileAtTip,
limit,
offset,
)
if err != nil {
r.err = err
return
}
cursor := ""
if offset+len(uploads) < totalCount {
cursor = fmt.Sprintf("%d", offset+len(uploads))
}
us := uploads
r.uploads = us
r.nextURL = cursor
r.totalCount = &totalCount
})
return r.uploads, r.repositoryResolver, r.totalCount, r.nextURL, r.err
}

View File

@ -0,0 +1,28 @@
package resolvers
import (
"github.com/sourcegraph/sourcegraph/cmd/frontend/graphqlbackend"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
type lsifUploadFailureReasonResolver struct {
lsifUpload db.Upload
}
var _ graphqlbackend.LSIFUploadFailureReasonResolver = &lsifUploadFailureReasonResolver{}
func (r *lsifUploadFailureReasonResolver) Summary() string {
return dereferenceString(r.lsifUpload.FailureSummary)
}
func (r *lsifUploadFailureReasonResolver) Stacktrace() string {
return dereferenceString(r.lsifUpload.FailureStacktrace)
}
func dereferenceString(s *string) string {
if s != nil {
return *s
}
return ""
}

View File

@ -36,7 +36,7 @@ type codeIntelAPI struct {
var _ CodeIntelAPI = &codeIntelAPI{}
var ErrMissingDump = errors.New("no dump")
var ErrMissingDump = errors.New("missing dump")
func New(db db.DB, bundleManagerClient bundles.BundleManagerClient, gitserverClient gitserver.Client) CodeIntelAPI {
return &codeIntelAPI{

View File

@ -1,54 +0,0 @@
package client
import (
"errors"
"net/http"
"strings"
"sync"
"github.com/sourcegraph/sourcegraph/internal/codeintel/api"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
"github.com/sourcegraph/sourcegraph/internal/endpoint"
"github.com/sourcegraph/sourcegraph/internal/env"
"github.com/sourcegraph/sourcegraph/internal/trace/ot"
)
var (
preciseCodeIntelAPIServerURL = env.Get("PRECISE_CODE_INTEL_API_SERVER_URL", "k8s+http://precise-code-intel:3186", "precise-code-intel-api-server URL (or space separated list of precise-code-intel-api-server URLs)")
preciseCodeIntelAPIServerURLsOnce sync.Once
preciseCodeIntelAPIServerURLs *endpoint.Map
)
type Client struct {
endpoint *endpoint.Map
HTTPClient *http.Client
server *Server
}
func New(
db db.DB,
bundleManagerClient bundles.BundleManagerClient,
codeIntelAPI api.CodeIntelAPI,
) *Client {
return &Client{
endpoint: LSIFURLs(),
HTTPClient: &http.Client{
// ot.Transport will propagate opentracing spans
Transport: &ot.Transport{},
},
server: NewServer(db, bundleManagerClient, codeIntelAPI),
}
}
func LSIFURLs() *endpoint.Map {
preciseCodeIntelAPIServerURLsOnce.Do(func() {
if len(strings.Fields(preciseCodeIntelAPIServerURL)) == 0 {
preciseCodeIntelAPIServerURLs = endpoint.Empty(errors.New("an precise-code-intel-api-server has not been configured"))
} else {
preciseCodeIntelAPIServerURLs = endpoint.New(preciseCodeIntelAPIServerURL)
}
})
return preciseCodeIntelAPIServerURLs
}

View File

@ -1,24 +0,0 @@
package client
import (
"net/http"
"github.com/pkg/errors"
)
type lsifError struct {
StatusCode int
Message string
}
func (e *lsifError) Error() string {
return e.Message
}
func IsNotFound(err error) bool {
if e, ok := errors.Cause(err).(*lsifError); ok {
return e.StatusCode == http.StatusNotFound
}
return false
}

View File

@ -1,216 +0,0 @@
package client
import (
"fmt"
"net/http"
"github.com/inconshreveable/log15"
"github.com/pkg/errors"
"github.com/sourcegraph/sourcegraph/internal/codeintel/api"
"github.com/sourcegraph/sourcegraph/internal/codeintel/gitserver"
)
const DefaultUploadPageSize = 50
const DefaultReferencesPageSize = 100
// GET /uploads/{id:[0-9]+}
func (s *Server) handleGetUploadByID(w http.ResponseWriter, r *http.Request) {
upload, exists, err := s.db.GetUploadByID(r.Context(), int(idFromRequest(r)))
if err != nil {
log15.Error("Failed to retrieve upload", "error", err)
http.Error(w, fmt.Sprintf("failed to retrieve upload: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
http.Error(w, "upload not found", http.StatusNotFound)
return
}
writeJSON(w, upload)
}
// DELETE /uploads/{id:[0-9]+}
func (s *Server) handleDeleteUploadByID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
exists, err := s.db.DeleteUploadByID(ctx, int(idFromRequest(r)), func(repositoryID int) (string, error) {
tipCommit, err := gitserver.Head(ctx, s.db, repositoryID)
if err != nil {
return "", errors.Wrap(err, "gitserver.Head")
}
return tipCommit, nil
})
if err != nil {
log15.Error("Failed to delete upload", "error", err)
http.Error(w, fmt.Sprintf("failed to delete upload: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
http.Error(w, "upload not found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
// GET /uploads/repository/{id:[0-9]+}
func (s *Server) handleGetUploadsByRepo(w http.ResponseWriter, r *http.Request) {
id := int(idFromRequest(r))
limit := getQueryIntDefault(r, "limit", DefaultUploadPageSize)
offset := getQueryInt(r, "offset")
uploads, totalCount, err := s.db.GetUploadsByRepo(
r.Context(),
id,
getQuery(r, "state"),
getQuery(r, "query"),
getQueryBool(r, "visibleAtTip"),
limit,
offset,
)
if err != nil {
log15.Error("Failed to list uploads", "error", err)
http.Error(w, fmt.Sprintf("failed to list uploads: %s", err.Error()), http.StatusInternalServerError)
return
}
if offset+len(uploads) < totalCount {
w.Header().Set("Link", makeNextLink(r.URL, map[string]interface{}{
"limit": limit,
"offset": offset + len(uploads),
}))
}
writeJSON(w, map[string]interface{}{"uploads": uploads, "totalCount": totalCount})
}
// GET /exists
func (s *Server) handleExists(w http.ResponseWriter, r *http.Request) {
dumps, err := s.codeIntelAPI.FindClosestDumps(
r.Context(),
getQueryInt(r, "repositoryId"),
getQuery(r, "commit"),
getQuery(r, "path"),
)
if err != nil {
log15.Error("Failed to handle exists request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle exists request: %s", err.Error()), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"uploads": dumps})
}
// GET /definitions
func (s *Server) handleDefinitions(w http.ResponseWriter, r *http.Request) {
defs, err := s.codeIntelAPI.Definitions(
r.Context(),
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to handle definitions request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle definitions request: %s", err.Error()), http.StatusInternalServerError)
return
}
outers, err := serializeLocations(defs)
if err != nil {
log15.Error("Failed to resolve locations", "error", err)
http.Error(w, fmt.Sprintf("failed to resolve locations: %s", err.Error()), http.StatusInternalServerError)
return
}
writeJSON(w, map[string]interface{}{"locations": outers})
}
// GET /references
func (s *Server) handleReferences(w http.ResponseWriter, r *http.Request) {
cursor, err := api.DecodeOrCreateCursor(
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
getQuery(r, "cursor"),
s.db,
s.bundleManagerClient,
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to prepare cursor", "error", err)
http.Error(w, fmt.Sprintf("failed to prepare cursor: %s", err.Error()), http.StatusInternalServerError)
return
}
limit := getQueryIntDefault(r, "limit", DefaultReferencesPageSize)
if limit <= 0 {
http.Error(w, "illegal limit", http.StatusBadRequest)
return
}
locations, newCursor, hasNewCursor, err := s.codeIntelAPI.References(
r.Context(),
getQueryInt(r, "repositoryId"),
getQuery(r, "commit"),
limit,
cursor,
)
if err != nil {
log15.Error("Failed to handle references request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle references request: %s", err.Error()), http.StatusInternalServerError)
return
}
outers, err := serializeLocations(locations)
if err != nil {
log15.Error("Failed to resolve locations", "error", err)
http.Error(w, fmt.Sprintf("failed to resolve locations: %s", err.Error()), http.StatusInternalServerError)
return
}
if hasNewCursor {
w.Header().Set("Link", makeNextLink(r.URL, map[string]interface{}{
"cursor": api.EncodeCursor(newCursor),
}))
}
writeJSON(w, map[string]interface{}{"locations": outers})
}
// GET /hover
func (s *Server) handleHover(w http.ResponseWriter, r *http.Request) {
text, rn, exists, err := s.codeIntelAPI.Hover(
r.Context(),
getQuery(r, "path"),
getQueryInt(r, "line"),
getQueryInt(r, "character"),
getQueryInt(r, "uploadId"),
)
if err != nil {
if err == api.ErrMissingDump {
http.Error(w, "no such dump", http.StatusNotFound)
return
}
log15.Error("Failed to handle hover request", "error", err)
http.Error(w, fmt.Sprintf("failed to handle hover request: %s", err.Error()), http.StatusInternalServerError)
return
}
if !exists {
writeJSON(w, nil)
} else {
writeJSON(w, map[string]interface{}{"text": text, "range": rn})
}
}

View File

@ -1,27 +0,0 @@
package client
import (
"github.com/sourcegraph/sourcegraph/internal/codeintel/api"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
)
type APILocation struct {
RepositoryID int `json:"repositoryId"`
Commit string `json:"commit"`
Path string `json:"path"`
Range bundles.Range `json:"range"`
}
func serializeLocations(resolvedLocations []api.ResolvedLocation) ([]APILocation, error) {
var apiLocations []APILocation
for _, res := range resolvedLocations {
apiLocations = append(apiLocations, APILocation{
RepositoryID: res.Dump.RepositoryID,
Commit: res.Dump.Commit,
Path: res.Path,
Range: res.Range,
})
}
return apiLocations, nil
}

View File

@ -1,92 +0,0 @@
package client
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"github.com/gorilla/mux"
"github.com/opentracing-contrib/go-stdlib/nethttp"
"github.com/opentracing/opentracing-go/ext"
"github.com/pkg/errors"
"github.com/sourcegraph/sourcegraph/internal/trace/ot"
)
func (c *Client) RawRequest(ctx context.Context, req *http.Request) (_ *http.Response, err error) {
router := mux.NewRouter()
router.Path("/uploads/{id:[0-9]+}").Methods("GET").HandlerFunc(c.server.handleGetUploadByID)
router.Path("/uploads/{id:[0-9]+}").Methods("DELETE").HandlerFunc(c.server.handleDeleteUploadByID)
router.Path("/uploads/repository/{id:[0-9]+}").Methods("GET").HandlerFunc(c.server.handleGetUploadsByRepo)
router.Path("/exists").Methods("GET").HandlerFunc(c.server.handleExists)
router.Path("/definitions").Methods("GET").HandlerFunc(c.server.handleDefinitions)
router.Path("/references").Methods("GET").HandlerFunc(c.server.handleReferences)
router.Path("/hover").Methods("GET").HandlerFunc(c.server.handleHover)
router.Path("/upload").Methods("POST").HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp, err := c.rawRequest(ctx, req)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_, _ = w.Write([]byte(err.Error()))
return
}
w.WriteHeader(resp.StatusCode)
_, _ = io.Copy(w, resp.Body)
}))
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
return &http.Response{
StatusCode: rec.Code,
Header: rec.Header(),
Body: ioutil.NopCloser(bytes.NewReader(rec.Body.Bytes())),
}, nil
}
func (c *Client) rawRequest(ctx context.Context, req *http.Request) (_ *http.Response, err error) {
span, ctx := ot.StartSpanFromContext(ctx, "lsifserver.client.do")
defer func() {
if err != nil {
ext.Error.Set(span, true)
span.SetTag("err", err.Error())
}
span.Finish()
}()
req, ht := nethttp.TraceRequest(
span.Tracer(),
req.WithContext(ctx),
nethttp.OperationName("LSIF client"),
nethttp.ClientTrace(false),
)
defer ht.Finish()
// Do not use ctxhttp.Do here as it will re-wrap the request
// with a context and this will causes the ot-headers not to
// propagate correctly.
resp, err := c.HTTPClient.Do(req)
if err != nil {
if ctx.Err() != nil {
err = ctx.Err()
}
return nil, errors.Wrap(err, "lsif request failed")
}
return resp, nil
}
func SelectRandomHost() (string, error) {
endpoints, err := LSIFURLs().Endpoints()
if err != nil {
return "", err
}
for host := range endpoints {
return host, nil
}
return "", fmt.Errorf("no endpoints available")
}

View File

@ -1,174 +0,0 @@
package client
import (
"context"
"fmt"
"github.com/sourcegraph/go-lsp"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/lsif"
)
func (c *Client) Exists(ctx context.Context, args *struct {
RepoID api.RepoID
Commit api.CommitID
Path string
}) ([]*lsif.LSIFUpload, error) {
query := queryValues{}
query.SetInt("repositoryId", int64(args.RepoID))
query.Set("commit", string(args.Commit))
query.Set("path", args.Path)
req := &lsifRequest{
path: "/exists",
query: query,
routingKey: fmt.Sprintf("%d:%s", args.RepoID, args.Commit),
}
payload := struct {
Uploads []*lsif.LSIFUpload `json:"uploads"`
}{}
_, err := c.do(ctx, req, &payload)
if err != nil {
return nil, err
}
return payload.Uploads, nil
}
func (c *Client) Definitions(ctx context.Context, args *struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
}) ([]*lsif.LSIFLocation, string, error) {
return c.locationQuery(ctx, &struct {
Operation string
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
Limit *int32
Cursor *string
}{
Operation: "definitions",
RepoID: args.RepoID,
Commit: args.Commit,
Path: args.Path,
Line: args.Line,
Character: args.Character,
UploadID: args.UploadID,
})
}
func (c *Client) References(ctx context.Context, args *struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
Limit *int32
Cursor *string
}) ([]*lsif.LSIFLocation, string, error) {
return c.locationQuery(ctx, &struct {
Operation string
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
Limit *int32
Cursor *string
}{
Operation: "references",
RepoID: args.RepoID,
Commit: args.Commit,
Path: args.Path,
Line: args.Line,
Character: args.Character,
UploadID: args.UploadID,
Limit: args.Limit,
Cursor: args.Cursor,
})
}
func (c *Client) locationQuery(ctx context.Context, args *struct {
Operation string
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
Limit *int32
Cursor *string
}) ([]*lsif.LSIFLocation, string, error) {
query := queryValues{}
query.SetInt("repositoryId", int64(args.RepoID))
query.Set("commit", string(args.Commit))
query.Set("path", args.Path)
query.SetInt("line", int64(args.Line))
query.SetInt("character", int64(args.Character))
query.SetInt("uploadId", int64(args.UploadID))
query.SetOptionalInt32("limit", args.Limit)
req := &lsifRequest{
path: fmt.Sprintf("/%s", args.Operation),
cursor: args.Cursor,
query: query,
routingKey: fmt.Sprintf("%d:%s", args.RepoID, args.Commit),
}
payload := struct {
Locations []*lsif.LSIFLocation
}{}
meta, err := c.do(ctx, req, &payload)
if err != nil {
return nil, "", err
}
return payload.Locations, meta.nextURL, nil
}
func (c *Client) Hover(ctx context.Context, args *struct {
RepoID api.RepoID
Commit api.CommitID
Path string
Line int32
Character int32
UploadID int64
}) (string, lsp.Range, error) {
query := queryValues{}
query.SetInt("repositoryId", int64(args.RepoID))
query.Set("commit", string(args.Commit))
query.Set("path", args.Path)
query.SetInt("line", int64(args.Line))
query.SetInt("character", int64(args.Character))
query.SetInt("uploadId", int64(args.UploadID))
req := &lsifRequest{
path: "/hover",
query: query,
routingKey: fmt.Sprintf("%d:%s", args.RepoID, args.Commit),
}
payload := struct {
Text string `json:"text"`
Range lsp.Range `json:"range"`
}{}
_, err := c.do(ctx, req, &payload)
if err != nil {
return "", lsp.Range{}, err
}
return payload.Text, payload.Range, nil
}

View File

@ -1,154 +0,0 @@
package client
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"github.com/pkg/errors"
"github.com/sourcegraph/sourcegraph/internal/linkheader"
)
type lsifRequest struct {
method string
path string
cursor *string
query queryValues
body io.ReadCloser
// (Optional) used in routing to select the "hot" precise-code-intel-api-server that
// was used in recent requests for similar data. Requests that are likely to open the
// same dump should hit the same server so that the SQLite database is already in the
// cache.
routingKey string
}
type lsifResponseMeta struct {
statusCode int
nextURL string
}
// do will make a request to LSIF API server. This method will return an error if the
// request cannot be made or the status code is 400 or 500-level. If a non-nil payload
// is given, the request body will be unmarshalled into it.
func (c *Client) do(ctx context.Context, lsifRequest *lsifRequest, payload interface{}) (*lsifResponseMeta, error) {
method := lsifRequest.method
if method == "" {
method = "GET"
}
serverURL, err := c.endpoint.Get(lsifRequest.routingKey, nil)
if err != nil {
return nil, err
}
url, err := buildURL(serverURL, lsifRequest.path, lsifRequest.cursor, lsifRequest.query)
if err != nil {
return nil, err
}
req, err := http.NewRequest(method, url, lsifRequest.body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.RawRequest(ctx, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode >= 400 {
return nil, errors.WithStack(&lsifError{
StatusCode: resp.StatusCode,
Message: string(content),
})
}
if payload != nil {
if err := json.Unmarshal(content, &payload); err != nil {
return nil, err
}
}
nextURL, _ := linkheader.ExtractNextURL(resp)
return &lsifResponseMeta{
statusCode: resp.StatusCode,
nextURL: nextURL,
}, nil
}
// buildURL constructs a URL to the backend LSIF api server with the given path and
// query values. If the provided cursor is non-nil, that will be used instead of the
// given path. If path is relative (indicated by a leading slash), then the configured
// LSIF API server url is prepended. Otherwise, it is treated as an absolute URL. The
// given query values will override any query string that is present in the given path.
//
// This method can be used to construct a LSIF request URL either from a root
// relative path on the first request of a paginated endpoint or from the URL
// provided by the Link header in a previous response.
func buildURL(baseURL, path string, cursor *string, query queryValues) (string, error) {
if cursor != nil {
path = *cursor
}
build := url.Parse
if len(path) > 0 && path[0] == '/' {
build = func(path string) (*url.URL, error) {
return url.Parse(baseURL + path)
}
}
u, err := build(path)
if err != nil {
return "", err
}
q := u.Query()
for key, values := range query {
q.Set(key, values[0])
}
u.RawQuery = q.Encode()
return u.String(), nil
}
type queryValues url.Values
func (qv queryValues) Set(name string, value string) {
qv[name] = []string{value}
}
func (qv queryValues) SetInt(name string, value int64) {
qv.Set(name, strconv.FormatInt(int64(value), 10))
}
func (qv queryValues) SetOptionalString(name string, value *string) {
if value != nil {
qv.Set(name, *value)
}
}
func (qv queryValues) SetOptionalInt32(name string, value *int32) {
if value != nil {
qv.SetInt(name, int64(*value))
}
}
func (qv queryValues) SetOptionalBool(name string, value *bool) {
if value != nil {
qv.Set(name, fmt.Sprintf("%v", *value))
}
}

View File

@ -1,25 +0,0 @@
package client
import (
"github.com/sourcegraph/sourcegraph/internal/codeintel/api"
bundles "github.com/sourcegraph/sourcegraph/internal/codeintel/bundles/client"
"github.com/sourcegraph/sourcegraph/internal/codeintel/db"
)
type Server struct {
db db.DB
bundleManagerClient bundles.BundleManagerClient
codeIntelAPI api.CodeIntelAPI
}
func NewServer(
db db.DB,
bundleManagerClient bundles.BundleManagerClient,
codeIntelAPI api.CodeIntelAPI,
) *Server {
return &Server{
db: db,
bundleManagerClient: bundleManagerClient,
codeIntelAPI: codeIntelAPI,
}
}

View File

@ -1,75 +0,0 @@
package client
import (
"context"
"fmt"
"strings"
"github.com/sourcegraph/sourcegraph/internal/api"
"github.com/sourcegraph/sourcegraph/internal/lsif"
)
func (c *Client) GetUploads(ctx context.Context, args *struct {
RepoID api.RepoID
Query *string
State *string
IsLatestForRepo *bool
Limit *int32
Cursor *string
}) ([]*lsif.LSIFUpload, string, *int, error) {
query := queryValues{}
query.SetOptionalString("query", args.Query)
query.SetOptionalBool("visibleAtTip", args.IsLatestForRepo)
query.SetOptionalInt32("limit", args.Limit)
if args.State != nil {
query.Set("state", strings.ToLower(*args.State))
}
req := &lsifRequest{
path: fmt.Sprintf("/uploads/repository/%d", args.RepoID),
cursor: args.Cursor,
query: query,
}
payload := struct {
Uploads []*lsif.LSIFUpload `json:"uploads"`
TotalCount *int `json:"totalCount"`
}{
Uploads: []*lsif.LSIFUpload{},
}
meta, err := c.do(ctx, req, &payload)
if err != nil {
return nil, "", nil, err
}
return payload.Uploads, meta.nextURL, payload.TotalCount, nil
}
func (c *Client) GetUpload(ctx context.Context, args *struct {
UploadID int64
}) (*lsif.LSIFUpload, error) {
req := &lsifRequest{
path: fmt.Sprintf("/uploads/%d", args.UploadID),
}
payload := &lsif.LSIFUpload{}
_, err := c.do(ctx, req, &payload)
return payload, err
}
func (c *Client) DeleteUpload(ctx context.Context, args *struct {
UploadID int64
}) error {
req := &lsifRequest{
path: fmt.Sprintf("/uploads/%d", args.UploadID),
method: "DELETE",
}
if _, err := c.do(ctx, req, nil); !IsNotFound(err) {
return err
}
return nil
}

View File

@ -1,94 +0,0 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/inconshreveable/log15"
"github.com/tomnomnom/linkheader"
)
func hasQuery(r *http.Request, name string) bool {
return r.URL.Query().Get(name) != ""
}
func getQuery(r *http.Request, name string) string {
return r.URL.Query().Get(name)
}
func getQueryInt(r *http.Request, name string) int {
value, _ := strconv.Atoi(r.URL.Query().Get(name))
return value
}
func getQueryIntDefault(r *http.Request, name string, defaultValue int) int {
value, err := strconv.Atoi(r.URL.Query().Get(name))
if err != nil {
value = defaultValue
}
return value
}
func getQueryBool(r *http.Request, name string) bool {
value, _ := strconv.ParseBool(r.URL.Query().Get(name))
return value
}
func makeNextLink(url *url.URL, newQueryValues map[string]interface{}) string {
q := url.Query()
for k, v := range newQueryValues {
q.Set(k, fmt.Sprintf("%v", v))
}
url.RawQuery = q.Encode()
header := linkheader.Link{
URL: url.String(),
Rel: "next",
}
return header.String()
}
// idFromRequest returns the database id from the request URL's path. This method
// must only be called from routes containing the `id:[0-9]+` pattern, as the error
// return from ParseInt is not checked.
func idFromRequest(r *http.Request) int64 {
id, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
return id
}
// copyAll writes the contents of r to w and logs on write failure.
func copyAll(w http.ResponseWriter, r io.Reader) {
if _, err := io.Copy(w, r); err != nil {
log15.Error("Failed to write payload to client", "error", err)
}
}
// writeJSON writes the JSON-encoded payload to w and logs on write failure.
// If there is an encoding error, then a 500-level status is written to w.
func writeJSON(w http.ResponseWriter, payload interface{}) {
data, err := json.Marshal(payload)
if err != nil {
log15.Error("Failed to serialize result", "error", err)
http.Error(w, fmt.Sprintf("failed to serialize result: %s", err.Error()), http.StatusInternalServerError)
return
}
copyAll(w, bytes.NewReader(data))
}
func sanitizeRoot(s string) string {
if s == "" || s == "/" {
return ""
}
if !strings.HasSuffix(s, "/") {
s += "/"
}
return s
}

View File

@ -1,32 +0,0 @@
package lsif
import (
"time"
"github.com/sourcegraph/go-lsp"
"github.com/sourcegraph/sourcegraph/internal/api"
)
type LSIFUpload struct {
ID int64 `json:"id"`
RepositoryID api.RepoID `json:"repositoryId"`
Commit string `json:"commit"`
Root string `json:"root"`
Indexer string `json:"indexer"`
Filename string `json:"filename"`
State string `json:"state"`
UploadedAt time.Time `json:"uploadedAt"`
StartedAt *time.Time `json:"startedAt"`
FinishedAt *time.Time `json:"finishedAt"`
FailureSummary *string `json:"failureSummary"`
FailureStacktrace *string `json:"failureStacktrace"`
VisibleAtTip bool `json:"visibleAtTip"`
PlaceInQueue *int32 `json:"placeInQueue"`
}
type LSIFLocation struct {
RepositoryID api.RepoID `json:"repositoryId"`
Commit string `json:"commit"`
Path string `json:"path"`
Range lsp.Range `json:"range"`
}