M api/graph/generated/generated.go => api/graph/generated/generated.go +174 -23
@@ 37,6 37,7 @@ type Config struct {
}
type ResolverRoot interface {
+ ACL() ACLResolver
Mutation() MutationResolver
Query() QueryResolver
Repository() RepositoryResolver
@@ 56,6 57,11 @@ type ComplexityRoot struct {
Repository func(childComplexity int) int
}
+ ACLCursor struct {
+ Cursor func(childComplexity int) int
+ Results func(childComplexity int) int
+ }
+
Artifact struct {
Checksum func(childComplexity int) int
Created func(childComplexity int) int
@@ 199,6 205,10 @@ type ComplexityRoot struct {
}
}
+type ACLResolver interface {
+ Repository(ctx context.Context, obj *model.ACL) (*model.Repository, error)
+ Entity(ctx context.Context, obj *model.ACL) (model.Entity, error)
+}
type MutationResolver interface {
CreateRepository(ctx context.Context, params *model.RepoInput) (*model.Repository, error)
UpdateRepository(ctx context.Context, id string, params *model.RepoInput) (*model.Repository, error)
@@ 220,7 230,7 @@ type QueryResolver interface {
type RepositoryResolver interface {
Owner(ctx context.Context, obj *model.Repository) (model.Entity, error)
- AccessControlList(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.ACL, error)
+ AccessControlList(ctx context.Context, obj *model.Repository, cursor *model.Cursor) (*model.ACLCursor, error)
References(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.Reference, error)
}
type TreeResolver interface {
@@ 280,6 290,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ACL.Repository(childComplexity), true
+ case "ACLCursor.cursor":
+ if e.complexity.ACLCursor.Cursor == nil {
+ break
+ }
+
+ return e.complexity.ACLCursor.Cursor(childComplexity), true
+
+ case "ACLCursor.results":
+ if e.complexity.ACLCursor.Results == nil {
+ break
+ }
+
+ return e.complexity.ACLCursor.Results(childComplexity), true
+
case "Artifact.checksum":
if e.complexity.Artifact.Checksum == nil {
break
@@ 1189,7 1213,7 @@ type Repository {
upstreamUrl: String
# Returns access control list entries for this repository
- accessControlList(cursor: Cursor): [ACL]!
+ accessControlList(cursor: Cursor): ACLCursor
## Plumbing API:
@@ 1239,6 1263,16 @@ type RepositoryCursor {
cursor: Cursor
}
+# A cursor for enumerating access control list entries
+#
+# If there are additional results available, the cursor object may be passed
+# back into the same endpoint to retrieve another page. If the cursor is null,
+# there are no remaining results to return.
+type ACLCursor {
+ results: [ACL]!
+ cursor: Cursor
+}
+
# Access Control List entry
type ACL {
id: Int!
@@ 1958,13 1992,13 @@ func (ec *executionContext) _ACL_repository(ctx context.Context, field graphql.C
Object: "ACL",
Field: field,
Args: nil,
- IsMethod: false,
+ IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
- return obj.Repository, nil
+ return ec.resolvers.ACL().Repository(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@@ 1992,13 2026,13 @@ func (ec *executionContext) _ACL_entity(ctx context.Context, field graphql.Colle
Object: "ACL",
Field: field,
Args: nil,
- IsMethod: false,
+ IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
- return obj.Entity, nil
+ return ec.resolvers.ACL().Entity(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@@ 2046,6 2080,71 @@ func (ec *executionContext) _ACL_mode(ctx context.Context, field graphql.Collect
return ec.marshalOAccessMode2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx, field.Selections, res)
}
+func (ec *executionContext) _ACLCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.ACLCursor) (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ fc := &graphql.FieldContext{
+ Object: "ACLCursor",
+ Field: field,
+ Args: nil,
+ IsMethod: false,
+ }
+
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Results, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.([]*model.ACL)
+ fc.Result = res
+ return ec.marshalNACL2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACL(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _ACLCursor_cursor(ctx context.Context, field graphql.CollectedField, obj *model.ACLCursor) (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ fc := &graphql.FieldContext{
+ Object: "ACLCursor",
+ Field: field,
+ Args: nil,
+ IsMethod: false,
+ }
+
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Cursor, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ return graphql.Null
+ }
+ res := resTmp.(*model.Cursor)
+ fc.Result = res
+ return ec.marshalOCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCursor(ctx, field.Selections, res)
+}
+
func (ec *executionContext) _Artifact_id(ctx context.Context, field graphql.CollectedField, obj *model.Artifact) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ 3835,14 3934,11 @@ func (ec *executionContext) _Repository_accessControlList(ctx context.Context, f
return graphql.Null
}
if resTmp == nil {
- if !graphql.HasFieldError(ctx, fc) {
- ec.Errorf(ctx, "must not be null")
- }
return graphql.Null
}
- res := resTmp.([]*model.ACL)
+ res := resTmp.(*model.ACLCursor)
fc.Result = res
- return ec.marshalNACL2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACL(ctx, field.Selections, res)
+ return ec.marshalOACLCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACLCursor(ctx, field.Selections, res)
}
func (ec *executionContext) _Repository_references(ctx context.Context, field graphql.CollectedField, obj *model.Repository) (ret graphql.Marshaler) {
@@ 6619,25 6715,72 @@ func (ec *executionContext) _ACL(ctx context.Context, sel ast.SelectionSet, obj
case "id":
out.Values[i] = ec._ACL_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
- invalids++
+ atomic.AddUint32(&invalids, 1)
}
case "created":
out.Values[i] = ec._ACL_created(ctx, field, obj)
if out.Values[i] == graphql.Null {
- invalids++
+ atomic.AddUint32(&invalids, 1)
}
case "repository":
- out.Values[i] = ec._ACL_repository(ctx, field, obj)
- if out.Values[i] == graphql.Null {
- invalids++
- }
+ field := field
+ out.Concurrently(i, func() (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._ACL_repository(ctx, field, obj)
+ if res == graphql.Null {
+ atomic.AddUint32(&invalids, 1)
+ }
+ return res
+ })
case "entity":
- out.Values[i] = ec._ACL_entity(ctx, field, obj)
+ field := field
+ out.Concurrently(i, func() (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._ACL_entity(ctx, field, obj)
+ if res == graphql.Null {
+ atomic.AddUint32(&invalids, 1)
+ }
+ return res
+ })
+ case "mode":
+ out.Values[i] = ec._ACL_mode(ctx, field, obj)
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+ out.Dispatch()
+ if invalids > 0 {
+ return graphql.Null
+ }
+ return out
+}
+
+var aCLCursorImplementors = []string{"ACLCursor"}
+
+func (ec *executionContext) _ACLCursor(ctx context.Context, sel ast.SelectionSet, obj *model.ACLCursor) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.OperationContext, sel, aCLCursorImplementors)
+
+ out := graphql.NewFieldSet(fields)
+ var invalids uint32
+ for i, field := range fields {
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("ACLCursor")
+ case "results":
+ out.Values[i] = ec._ACLCursor_results(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
- case "mode":
- out.Values[i] = ec._ACL_mode(ctx, field, obj)
+ case "cursor":
+ out.Values[i] = ec._ACLCursor_cursor(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ 7120,9 7263,6 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe
}
}()
res = ec._Repository_accessControlList(ctx, field, obj)
- if res == graphql.Null {
- atomic.AddUint32(&invalids, 1)
- }
return res
})
case "references":
@@ 8535,6 8675,17 @@ func (ec *executionContext) marshalOACL2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsr
return ec._ACL(ctx, sel, v)
}
+func (ec *executionContext) marshalOACLCursor2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACLCursor(ctx context.Context, sel ast.SelectionSet, v model.ACLCursor) graphql.Marshaler {
+ return ec._ACLCursor(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalOACLCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐACLCursor(ctx context.Context, sel ast.SelectionSet, v *model.ACLCursor) graphql.Marshaler {
+ if v == nil {
+ return graphql.Null
+ }
+ return ec._ACLCursor(ctx, sel, v)
+}
+
func (ec *executionContext) unmarshalOAccessMode2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessMode(ctx context.Context, v interface{}) (model.AccessMode, error) {
var res model.AccessMode
return res, res.UnmarshalGQL(v)
A api/graph/model/acl.go => api/graph/model/acl.go +93 -0
@@ 0,0 1,93 @@
+package model
+
+import (
+ "context"
+ "database/sql"
+ "strconv"
+ "time"
+
+ sq "github.com/Masterminds/squirrel"
+
+ "git.sr.ht/~sircmpwn/git.sr.ht/api/database"
+)
+
+// TODO: Drop updated column from database
+type ACL struct {
+ ID int `json:"id"`
+ Created time.Time `json:"created"`
+ Mode *AccessMode `json:"mode"`
+
+ RepoID int
+ UserID int
+
+ alias string
+}
+
+func (acl *ACL) As(alias string) *ACL {
+ acl.alias = alias
+ return acl
+}
+
+func (acl *ACL) Select(ctx context.Context) []string {
+ cols := database.ColumnsFor(ctx, acl.alias, map[string]string{
+ "id": "id",
+ "created": "created",
+ "mode": "mode",
+ })
+ return append(cols,
+ database.WithAlias(acl.alias, "id"),
+ database.WithAlias(acl.alias, "repo_id"),
+ database.WithAlias(acl.alias, "user_id"))
+}
+
+func (acl *ACL) Fields(ctx context.Context) []interface{} {
+ fields := database.FieldsFor(ctx, map[string]interface{}{
+ "id": &acl.ID,
+ "created": &acl.Created,
+ "mode": &acl.Mode,
+ })
+ return append(fields, &acl.ID, &acl.RepoID, &acl.UserID)
+}
+
+func (acl *ACL) QueryWithCursor(ctx context.Context,
+ db *sql.DB, q sq.SelectBuilder, cur *Cursor) ([]*ACL, *Cursor) {
+ var (
+ err error
+ rows *sql.Rows
+ )
+
+ if cur.Next != "" {
+ next, _ := strconv.Atoi(cur.Next)
+ q = q.Where(database.WithAlias(acl.alias, "id") + "<= ?", next)
+ }
+ q = q.
+ OrderBy(database.WithAlias(acl.alias, "id") + " DESC").
+ Limit(uint64(cur.Count + 1))
+
+ if rows, err = q.RunWith(db).QueryContext(ctx); err != nil {
+ panic(err)
+ }
+ defer rows.Close()
+
+ var acls []*ACL
+ for rows.Next() {
+ var acl ACL
+ if err := rows.Scan(acl.Fields(ctx)...); err != nil {
+ panic(err)
+ }
+ acls = append(acls, &acl)
+ }
+
+ if len(acls) > cur.Count {
+ cur = &Cursor{
+ Count: cur.Count,
+ Next: strconv.Itoa(acls[len(acls)-1].ID),
+ Search: cur.Search,
+ }
+ acls = acls[:cur.Count]
+ } else {
+ cur = nil
+ }
+
+ return acls, cur
+}
M api/graph/model/models_gen.go => api/graph/model/models_gen.go +3 -6
@@ 13,12 13,9 @@ type Entity interface {
IsEntity()
}
-type ACL struct {
- ID int `json:"id"`
- Created time.Time `json:"created"`
- Repository *Repository `json:"repository"`
- Entity Entity `json:"entity"`
- Mode *AccessMode `json:"mode"`
+type ACLCursor struct {
+ Results []*ACL `json:"results"`
+ Cursor *Cursor `json:"cursor"`
}
type Artifact struct {
M api/graph/model/repository.go => api/graph/model/repository.go +5 -5
@@ 54,6 54,11 @@ func (r *Repository) Head() *Reference {
return &Reference{Ref: ref, Repo: r.repo}
}
+func (r *Repository) As(alias string) *Repository {
+ r.alias = alias
+ return r
+}
+
func (r *Repository) Select(ctx context.Context) []string {
cols := database.ColumnsFor(ctx, r.alias, map[string]string{
"id": "id",
@@ 70,11 75,6 @@ func (r *Repository) Select(ctx context.Context) []string {
database.WithAlias(r.alias, "updated"))
}
-func (r *Repository) As(alias string) *Repository {
- r.alias = alias
- return r
-}
-
func (r *Repository) Fields(ctx context.Context) []interface{} {
fields := database.FieldsFor(ctx, map[string]interface{}{
"id": &r.ID,
M api/graph/model/user.go => api/graph/model/user.go +11 -7
@@ 26,8 26,13 @@ func (u *User) CanonicalName() string {
return "~" + u.Username
}
+func (u *User) As(alias string) *User {
+ u.alias = alias
+ return u
+}
+
func (u *User) Select(ctx context.Context) []string {
- return database.ColumnsFor(ctx, u.alias, map[string]string{
+ cols := database.ColumnsFor(ctx, u.alias, map[string]string{
"id": "id",
"created": "created",
"updated": "updated",
@@ 37,15 42,13 @@ func (u *User) Select(ctx context.Context) []string {
"location": "location",
"bio": "bio",
})
-}
-
-func (u *User) As(alias string) database.Selectable {
- u.alias = alias
- return u
+ return append(cols,
+ database.WithAlias(u.alias, "id"),
+ database.WithAlias(u.alias, "username"))
}
func (u *User) Fields(ctx context.Context) []interface{} {
- return database.FieldsFor(ctx, map[string]interface{}{
+ fields := database.FieldsFor(ctx, map[string]interface{}{
"id": &u.ID,
"created": &u.Created,
"updated": &u.Updated,
@@ 55,4 58,5 @@ func (u *User) Fields(ctx context.Context) []interface{} {
"location": &u.Location,
"bio": &u.Bio,
})
+ return append(fields, &u.ID, &u.Username)
}
M api/graph/schema.graphqls => api/graph/schema.graphqls +11 -1
@@ 75,7 75,7 @@ type Repository {
upstreamUrl: String
# Returns access control list entries for this repository
- accessControlList(cursor: Cursor): [ACL]!
+ accessControlList(cursor: Cursor): ACLCursor
## Plumbing API:
@@ 125,6 125,16 @@ type RepositoryCursor {
cursor: Cursor
}
+# A cursor for enumerating access control list entries
+#
+# If there are additional results available, the cursor object may be passed
+# back into the same endpoint to retrieve another page. If the cursor is null,
+# there are no remaining results to return.
+type ACLCursor {
+ results: [ACL]!
+ cursor: Cursor
+}
+
# Access Control List entry
type ACL {
id: Int!
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +61 -10
@@ 19,31 19,31 @@ import (
)
func (r *mutationResolver) CreateRepository(ctx context.Context, params *model.RepoInput) (*model.Repository, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("createRepository: not implemented"))
}
func (r *mutationResolver) UpdateRepository(ctx context.Context, id string, params *model.RepoInput) (*model.Repository, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("updateRepository: not implemented"))
}
func (r *mutationResolver) DeleteRepository(ctx context.Context, id string) (*model.Repository, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("deleteRepository: not implemented"))
}
func (r *mutationResolver) UpdateACL(ctx context.Context, repoID string, mode model.AccessMode, entity string) (*model.ACL, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("updateACL: not implemented"))
}
func (r *mutationResolver) DeleteACL(ctx context.Context, repoID int, entity string) (*model.ACL, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("deleteACL: not implemented"))
}
func (r *mutationResolver) UploadArtifact(ctx context.Context, repoID int, revspec string, file graphql.Upload) (*model.Artifact, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("uploadArtifact: not implemented"))
}
func (r *mutationResolver) DeleteArtifact(ctx context.Context, id int) (*model.Artifact, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("deleteArtifact: not implemented"))
}
func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) {
@@ 110,8 110,55 @@ func (r *repositoryResolver) Owner(ctx context.Context, obj *model.Repository) (
return loaders.ForContext(ctx).UsersByID.Load(obj.OwnerID)
}
-func (r *repositoryResolver) AccessControlList(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.ACL, error) {
- panic(fmt.Errorf("not implemented"))
+func (r *repositoryResolver) AccessControlList(ctx context.Context, obj *model.Repository, cursor *model.Cursor) (*model.ACLCursor, error) {
+ if cursor == nil {
+ cursor = model.NewCursor(nil)
+ }
+
+ acl := (&model.ACL{}).As(`acl`)
+ query := database.
+ Select(ctx, acl).
+ From(`access acl`).
+ Join(`repository repo ON acl.repo_id = repo.id`).
+ Where(`acl.repo_id = ?`, obj.ID).
+ Where(`repo.owner_id = ?`, auth.ForContext(ctx).ID)
+
+ acls, cursor := acl.QueryWithCursor(ctx, r.DB, query, cursor)
+ return &model.ACLCursor{acls, cursor}, nil
+}
+
+func (r *aCLResolver) Repository(ctx context.Context, obj *model.ACL) (*model.Repository, error) {
+ // XXX This could be moved into a loader, but it's unlikely to be a
+ // frequently utilized endpoint, so I'm not especially interested in the
+ // extra work/cruft.
+ repo := (&model.Repository{}).As(`repo`)
+ query := database.
+ Select(ctx, repo).
+ From(`repository repo`).
+ Join(`access acl ON acl.repo_id = repo.id`).
+ Where(`acl.id = ?`, obj.ID)
+ row := query.RunWith(r.DB).QueryRow()
+ if err := row.Scan(repo.Fields(ctx)...); err != nil {
+ panic(err)
+ }
+ return repo, nil
+}
+
+func (r *aCLResolver) Entity(ctx context.Context, obj *model.ACL) (model.Entity, error) {
+ // XXX This could be moved into a loader, but it's unlikely to be a
+ // frequently utilized endpoint, so I'm not especially interested in the
+ // extra work/cruft.
+ user := (&model.User{}).As(`u`)
+ query := database.
+ Select(ctx, user).
+ From(`"user" u`).
+ Join(`access acl ON acl.user_id = u.id`).
+ Where(`acl.id = ?`, obj.ID)
+ row := query.RunWith(r.DB).QueryRow()
+ if err := row.Scan(user.Fields(ctx)...); err != nil {
+ panic(err)
+ }
+ return user, nil
}
func (r *repositoryResolver) References(ctx context.Context, obj *model.Repository, cursor *model.Cursor) ([]*model.Reference, error) {
@@ 136,7 183,7 @@ func (r *repositoryResolver) References(ctx context.Context, obj *model.Reposito
}
func (r *treeResolver) Entries(ctx context.Context, obj *model.Tree, cursor *model.Cursor) ([]*model.TreeEntry, error) {
- panic(fmt.Errorf("not implemented"))
+ panic(fmt.Errorf("tree.entries: not implemented"))
}
func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor *model.Cursor, filter *model.Filter) (*model.RepositoryCursor, error) {
@@ 154,6 201,9 @@ func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor
return &model.RepositoryCursor{repos, cursor}, nil
}
+// ACL returns generated.ACLResolver implementation.
+func (r *Resolver) ACL() generated.ACLResolver { return &aCLResolver{r} }
+
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
@@ 169,6 219,7 @@ func (r *Resolver) Tree() generated.TreeResolver { return &treeResolver{r} }
// User returns generated.UserResolver implementation.
func (r *Resolver) User() generated.UserResolver { return &userResolver{r} }
+type aCLResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type repositoryResolver struct{ *Resolver }