Add new comparators: Exists and NotExists (#33)
This commit is contained in:
parent
482dd095a6
commit
1e0ab5d701
9 changed files with 232 additions and 23 deletions
|
@ -2,6 +2,7 @@ linters:
|
||||||
enable:
|
enable:
|
||||||
- errname
|
- errname
|
||||||
- errorlint
|
- errorlint
|
||||||
|
- goerr113
|
||||||
- lll
|
- lll
|
||||||
- stylecheck
|
- stylecheck
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
// Replace these values with your own connection option. This connection option is hard-coded for easy
|
// Replace these values with your own connection option. This connection option is hard-coded for easy
|
||||||
// demonstration. Make sure not to hard-code the credentials in the production code.
|
// demonstration. Make sure not to hard-code the credentials in the production code.
|
||||||
const (
|
const (
|
||||||
connectionString = "mongodb://lineman:lineman@localhost:27017"
|
connectionString = "mongodb://admin:password@localhost:27017"
|
||||||
databaseName = "repogen_examples"
|
databaseName = "repogen_examples"
|
||||||
collectionName = "gettingstarted_user"
|
collectionName = "gettingstarted_user"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,27 +16,32 @@ type UserModel struct {
|
||||||
|
|
||||||
//go:generate repogen -src=user.go -dest=user_repo.go -model=UserModel -repo=UserRepository
|
//go:generate repogen -src=user.go -dest=user_repo.go -model=UserModel -repo=UserRepository
|
||||||
|
|
||||||
// UserRepository is an interface that describes the specification of querying user data in the database
|
// UserRepository is an interface that describes the specification of querying
|
||||||
|
// user data in the database.
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
// InsertOne stores userModel into the database and returns inserted ID if insertion
|
// InsertOne stores userModel into the database and returns inserted ID
|
||||||
// succeeds and returns error if insertion fails.
|
// if insertion succeeds and returns error if insertion fails.
|
||||||
InsertOne(ctx context.Context, userModel *UserModel) (interface{}, error)
|
InsertOne(ctx context.Context, userModel *UserModel) (interface{}, error)
|
||||||
|
|
||||||
// FindByUsername queries user by username. If a user with specified username exists,
|
// FindByUsername queries user by username. If a user with specified
|
||||||
// the user will be returned. Otherwise, error will be returned.
|
// username exists, the user will be returned. Otherwise, error will be
|
||||||
|
// returned.
|
||||||
FindByUsername(ctx context.Context, username string) (*UserModel, error)
|
FindByUsername(ctx context.Context, username string) (*UserModel, error)
|
||||||
|
|
||||||
// UpdateDisplayNameByID updates a user with the specified ID with a new display name.
|
// UpdateDisplayNameByID updates a user with the specified ID with a new
|
||||||
// If there is a user matches the query, it will return true. Error will be returned
|
// display name. If there is a user matches the query, it will return
|
||||||
// only when error occurs while accessing the database.
|
// true. Error will be returned only when error occurs while accessing
|
||||||
|
// the database.
|
||||||
UpdateDisplayNameByID(ctx context.Context, displayName string, id primitive.ObjectID) (bool, error)
|
UpdateDisplayNameByID(ctx context.Context, displayName string, id primitive.ObjectID) (bool, error)
|
||||||
|
|
||||||
// DeleteByCity deletes users that have `city` value match the parameter and returns
|
// DeleteByCity deletes users that have `city` value match the parameter
|
||||||
// the match count. The error will be returned only when error occurs while accessing
|
// and returns the match count. The error will be returned only when
|
||||||
// the database. This is a MANY mode because the first return type is an integer.
|
// error occurs while accessing the database. This is a MANY mode
|
||||||
|
// because the first return type is an integer.
|
||||||
DeleteByCity(ctx context.Context, city string) (int, error)
|
DeleteByCity(ctx context.Context, city string) (int, error)
|
||||||
|
|
||||||
// CountByCity returns the number of rows that match the given city parameter. If an
|
// CountByCity returns the number of rows that match the given city
|
||||||
// error occurs while accessing the database, error value will be returned.
|
// parameter. If an error occurs while accessing the database, error
|
||||||
|
// value will be returned.
|
||||||
CountByCity(ctx context.Context, city string) (int, error)
|
CountByCity(ctx context.Context, city string) (int, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ var (
|
||||||
Type: code.SimpleType("NameModel"),
|
Type: code.SimpleType("NameModel"),
|
||||||
Tags: map[string][]string{"bson": {"name"}},
|
Tags: map[string][]string{"bson": {"name"}},
|
||||||
}
|
}
|
||||||
|
referrerField = code.StructField{
|
||||||
|
Name: "Referrer",
|
||||||
|
Type: code.PointerType{ContainedType: code.SimpleType("UserModel")},
|
||||||
|
Tags: map[string][]string{"bson": {"referrer"}},
|
||||||
|
}
|
||||||
consentHistoryField = code.StructField{
|
consentHistoryField = code.StructField{
|
||||||
Name: "ConsentHistory",
|
Name: "ConsentHistory",
|
||||||
Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistory")},
|
Type: code.ArrayType{ContainedType: code.SimpleType("ConsentHistory")},
|
||||||
|
@ -68,6 +73,7 @@ var userModel = code.Struct{
|
||||||
genderField,
|
genderField,
|
||||||
ageField,
|
ageField,
|
||||||
nameField,
|
nameField,
|
||||||
|
referrerField,
|
||||||
consentHistoryField,
|
consentHistoryField,
|
||||||
enabledField,
|
enabledField,
|
||||||
accessTokenField,
|
accessTokenField,
|
||||||
|
@ -882,6 +888,80 @@ func TestGenerateMethod_Find(t *testing.T) {
|
||||||
if err := cursor.All(arg0, &entities); err != nil {
|
if err := cursor.All(arg0, &entities); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return entities, nil`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find with Exists comparator",
|
||||||
|
MethodSpec: spec.MethodSpec{
|
||||||
|
Name: "FindByReferrerExists",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
Operation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Query: spec.QuerySpec{
|
||||||
|
Predicates: []spec.Predicate{
|
||||||
|
{
|
||||||
|
Comparator: spec.ComparatorExists,
|
||||||
|
FieldReference: spec.FieldReference{referrerField},
|
||||||
|
ParamIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedBody: ` cursor, err := r.collection.Find(arg0, bson.M{
|
||||||
|
"referrer": bson.M{"$exists": 1},
|
||||||
|
}, options.Find().SetSort(bson.M{
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var entities []*UserModel
|
||||||
|
if err := cursor.All(arg0, &entities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return entities, nil`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "find with NotExists comparator",
|
||||||
|
MethodSpec: spec.MethodSpec{
|
||||||
|
Name: "FindByReferrerNotExists",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Name: "ctx", Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
Operation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Query: spec.QuerySpec{
|
||||||
|
Predicates: []spec.Predicate{
|
||||||
|
{
|
||||||
|
Comparator: spec.ComparatorNotExists,
|
||||||
|
FieldReference: spec.FieldReference{referrerField},
|
||||||
|
ParamIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedBody: ` cursor, err := r.collection.Find(arg0, bson.M{
|
||||||
|
"referrer": bson.M{"$exists": 0},
|
||||||
|
}, options.Find().SetSort(bson.M{
|
||||||
|
}))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var entities []*UserModel
|
||||||
|
if err := cursor.All(arg0, &entities); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return entities, nil`,
|
return entities, nil`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -109,6 +109,10 @@ func (p predicate) Code() string {
|
||||||
return fmt.Sprintf(`"%s": true`, p.Field)
|
return fmt.Sprintf(`"%s": true`, p.Field)
|
||||||
case spec.ComparatorFalse:
|
case spec.ComparatorFalse:
|
||||||
return fmt.Sprintf(`"%s": false`, p.Field)
|
return fmt.Sprintf(`"%s": false`, p.Field)
|
||||||
|
case spec.ComparatorExists:
|
||||||
|
return fmt.Sprintf(`"%s": bson.M{"$exists": 1}`, p.Field)
|
||||||
|
case spec.ComparatorNotExists:
|
||||||
|
return fmt.Sprintf(`"%s": bson.M{"$exists": 0}`, p.Field)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -583,6 +583,52 @@ func TestParseInterfaceMethod_Find(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "FindByArgExists method",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindByReferrerExists",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedOperation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||||
|
{
|
||||||
|
FieldReference: spec.FieldReference{referrerField},
|
||||||
|
Comparator: spec.ComparatorExists,
|
||||||
|
ParamIndex: 1,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FindByArgNotExists method",
|
||||||
|
Method: code.Method{
|
||||||
|
Name: "FindByReferrerNotExists",
|
||||||
|
Params: []code.Param{
|
||||||
|
{Type: code.ExternalType{PackageAlias: "context", Name: "Context"}},
|
||||||
|
},
|
||||||
|
Returns: []code.Type{
|
||||||
|
code.ArrayType{ContainedType: code.PointerType{ContainedType: code.SimpleType("UserModel")}},
|
||||||
|
code.TypeError,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExpectedOperation: spec.FindOperation{
|
||||||
|
Mode: spec.QueryModeMany,
|
||||||
|
Query: spec.QuerySpec{Predicates: []spec.Predicate{
|
||||||
|
{
|
||||||
|
FieldReference: spec.FieldReference{referrerField},
|
||||||
|
Comparator: spec.ComparatorNotExists,
|
||||||
|
ParamIndex: 1,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "FindByArgOrderByArg method",
|
Name: "FindByArgOrderByArg method",
|
||||||
Method: code.Method{
|
Method: code.Method{
|
||||||
|
|
|
@ -44,6 +44,8 @@ const (
|
||||||
ComparatorNotIn Comparator = "NOT_IN"
|
ComparatorNotIn Comparator = "NOT_IN"
|
||||||
ComparatorTrue Comparator = "EQUAL_TRUE"
|
ComparatorTrue Comparator = "EQUAL_TRUE"
|
||||||
ComparatorFalse Comparator = "EQUAL_FALSE"
|
ComparatorFalse Comparator = "EQUAL_FALSE"
|
||||||
|
ComparatorExists Comparator = "EXISTS"
|
||||||
|
ComparatorNotExists Comparator = "NOT_EXISTS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArgumentTypeFromFieldType returns a type of required argument from the given
|
// ArgumentTypeFromFieldType returns a type of required argument from the given
|
||||||
|
@ -63,7 +65,7 @@ func (c Comparator) NumberOfArguments() int {
|
||||||
switch c {
|
switch c {
|
||||||
case ComparatorBetween:
|
case ComparatorBetween:
|
||||||
return 2
|
return 2
|
||||||
case ComparatorTrue, ComparatorFalse:
|
case ComparatorTrue, ComparatorFalse, ComparatorExists, ComparatorNotExists:
|
||||||
return 0
|
return 0
|
||||||
default:
|
default:
|
||||||
return 1
|
return 1
|
||||||
|
@ -82,7 +84,9 @@ type queryParser struct {
|
||||||
StructModel code.Struct
|
StructModel code.Struct
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p queryParser) parseQuery(rawTokens []string, paramIndex int) (QuerySpec, error) {
|
func (p queryParser) parseQuery(rawTokens []string, paramIndex int) (QuerySpec,
|
||||||
|
error) {
|
||||||
|
|
||||||
if len(rawTokens) == 0 {
|
if len(rawTokens) == 0 {
|
||||||
return QuerySpec{}, ErrQueryRequired
|
return QuerySpec{}, ErrQueryRequired
|
||||||
}
|
}
|
||||||
|
@ -154,7 +158,9 @@ func (p queryParser) splitPredicateTokens(tokens []string) (Operator, [][]string
|
||||||
return operator, predicateTokens, nil
|
return operator, predicateTokens, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate, error) {
|
func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate,
|
||||||
|
error) {
|
||||||
|
|
||||||
if len(t) > 1 && t[len(t)-1] == "Not" {
|
if len(t) > 1 && t[len(t)-1] == "Not" {
|
||||||
return p.createPredicate(t[:len(t)-1], ComparatorNot, paramIndex)
|
return p.createPredicate(t[:len(t)-1], ComparatorNot, paramIndex)
|
||||||
}
|
}
|
||||||
|
@ -173,6 +179,9 @@ func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate, erro
|
||||||
if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" {
|
if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "In" {
|
||||||
return p.createPredicate(t[:len(t)-2], ComparatorNotIn, paramIndex)
|
return p.createPredicate(t[:len(t)-2], ComparatorNotIn, paramIndex)
|
||||||
}
|
}
|
||||||
|
if len(t) > 2 && t[len(t)-2] == "Not" && t[len(t)-1] == "Exists" {
|
||||||
|
return p.createPredicate(t[:len(t)-2], ComparatorNotExists, paramIndex)
|
||||||
|
}
|
||||||
if len(t) > 1 && t[len(t)-1] == "In" {
|
if len(t) > 1 && t[len(t)-1] == "In" {
|
||||||
return p.createPredicate(t[:len(t)-1], ComparatorIn, paramIndex)
|
return p.createPredicate(t[:len(t)-1], ComparatorIn, paramIndex)
|
||||||
}
|
}
|
||||||
|
@ -185,10 +194,15 @@ func (p queryParser) parsePredicate(t []string, paramIndex int) (Predicate, erro
|
||||||
if len(t) > 1 && t[len(t)-1] == "False" {
|
if len(t) > 1 && t[len(t)-1] == "False" {
|
||||||
return p.createPredicate(t[:len(t)-1], ComparatorFalse, paramIndex)
|
return p.createPredicate(t[:len(t)-1], ComparatorFalse, paramIndex)
|
||||||
}
|
}
|
||||||
|
if len(t) > 1 && t[len(t)-1] == "Exists" {
|
||||||
|
return p.createPredicate(t[:len(t)-1], ComparatorExists, paramIndex)
|
||||||
|
}
|
||||||
return p.createPredicate(t, ComparatorEqual, paramIndex)
|
return p.createPredicate(t, ComparatorEqual, paramIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p queryParser) createPredicate(t []string, comparator Comparator, paramIndex int) (Predicate, error) {
|
func (p queryParser) createPredicate(t []string, comparator Comparator,
|
||||||
|
paramIndex int) (Predicate, error) {
|
||||||
|
|
||||||
fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
|
fields, ok := p.fieldResolver.ResolveStructField(p.StructModel, t)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Predicate{}, NewStructFieldNotFoundError(t)
|
return Predicate{}, NewStructFieldNotFoundError(t)
|
||||||
|
|
|
@ -5,6 +5,60 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// lineMismatchedError is an error indicating unmatched line when comparing
|
||||||
|
// string using ExpectMultiLineString.
|
||||||
|
type lineMismatchedError struct {
|
||||||
|
LineNumber int
|
||||||
|
Expected string
|
||||||
|
Received string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLineMismatchedError is a constructor for lineMismatchedError.
|
||||||
|
func NewLineMismatchedError(lineNumber int, expected, received string) error {
|
||||||
|
return lineMismatchedError{
|
||||||
|
LineNumber: lineNumber,
|
||||||
|
Expected: expected,
|
||||||
|
Received: received,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err lineMismatchedError) Error() string {
|
||||||
|
return fmt.Sprintf("at line %d\nexpected: %v\nreceived: %v", err.LineNumber,
|
||||||
|
err.Expected, err.Received)
|
||||||
|
}
|
||||||
|
|
||||||
|
type missingLinesError struct {
|
||||||
|
MissingLines []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMissingLinesError is a constructor for missingLinesError.
|
||||||
|
func NewMissingLinesError(missingLines []string) error {
|
||||||
|
return missingLinesError{
|
||||||
|
MissingLines: missingLines,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err missingLinesError) Error() string {
|
||||||
|
return fmt.Sprintf("missing lines:\n%s",
|
||||||
|
strings.Join(err.MissingLines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedLinesError struct {
|
||||||
|
UnexpectedLines []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnexpectedLinesError is a constructor for unexpectedLinesError.
|
||||||
|
func NewUnexpectedLinesError(unexpectedLines []string) error {
|
||||||
|
return unexpectedLinesError{
|
||||||
|
UnexpectedLines: unexpectedLines,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err unexpectedLinesError) Error() string {
|
||||||
|
return fmt.Sprintf("unexpected lines:\n%s",
|
||||||
|
strings.Join(err.UnexpectedLines, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
// ExpectMultiLineString compares two multi-line strings and report the
|
// ExpectMultiLineString compares two multi-line strings and report the
|
||||||
// difference.
|
// difference.
|
||||||
func ExpectMultiLineString(expected, actual string) error {
|
func ExpectMultiLineString(expected, actual string) error {
|
||||||
|
@ -18,14 +72,14 @@ func ExpectMultiLineString(expected, actual string) error {
|
||||||
|
|
||||||
for i := 0; i < numberOfComparableLines; i++ {
|
for i := 0; i < numberOfComparableLines; i++ {
|
||||||
if expectedLines[i] != actualLines[i] {
|
if expectedLines[i] != actualLines[i] {
|
||||||
return fmt.Errorf("at line %d\nexpected: %v\nreceived: %v", i+1, expectedLines[i], actualLines[i])
|
return NewLineMismatchedError(i+1, expectedLines[i], actualLines[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(expectedLines) < len(actualLines) {
|
if len(expectedLines) < len(actualLines) {
|
||||||
return fmt.Errorf("unexpected lines:\n%s", strings.Join(actualLines[len(expectedLines):], "\n"))
|
return NewUnexpectedLinesError(actualLines[len(expectedLines):])
|
||||||
} else if len(expectedLines) > len(actualLines) {
|
} else if len(expectedLines) > len(actualLines) {
|
||||||
return fmt.Errorf("missing lines:\n%s", strings.Join(expectedLines[len(actualLines):], "\n"))
|
return NewMissingLinesError(expectedLines[len(actualLines):])
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
9
main.go
9
main.go
|
@ -96,15 +96,20 @@ func generateFromRequest(pkgDir, structModelName, repositoryInterfaceName string
|
||||||
return generateRepository(pkg, structModelName, repositoryInterfaceName)
|
return generateRepository(pkg, structModelName, repositoryInterfaceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errStructNotFound = errors.New("struct not found")
|
||||||
|
errInterfaceNotFound = errors.New("interface not found")
|
||||||
|
)
|
||||||
|
|
||||||
func generateRepository(pkg code.Package, structModelName, repositoryInterfaceName string) (string, error) {
|
func generateRepository(pkg code.Package, structModelName, repositoryInterfaceName string) (string, error) {
|
||||||
structModel, ok := pkg.Structs[structModelName]
|
structModel, ok := pkg.Structs[structModelName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("struct model not found")
|
return "", errStructNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
intf, ok := pkg.Interfaces[repositoryInterfaceName]
|
intf, ok := pkg.Interfaces[repositoryInterfaceName]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", errors.New("interface model not found")
|
return "", errInterfaceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var methodSpecs []spec.MethodSpec
|
var methodSpecs []spec.MethodSpec
|
||||||
|
|
Loading…
Reference in a new issue