From 629632182d82ab346a86139abfb6588093b6f88d Mon Sep 17 00:00:00 2001 From: touchmarine Date: Thu, 18 Jan 2024 16:00:40 +0100 Subject: [PATCH] fix parse type: field must be exported Request/response struct fields must still be exported, but deeper fields in them can now be unexported. This allows the use of types such as big.Int which contains unexported fields. --- parser/parser.go | 24 +++++++++++-------- parser/parser_test.go | 21 ++++++++++++++++ .../unexported-deeper-fields.go | 14 +++++++++++ .../unexported-fields/unexported-fields.go | 11 +++++++++ 4 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 parser/testdata/unexported-deeper-fields/unexported-deeper-fields.go create mode 100644 parser/testdata/unexported-fields/unexported-fields.go diff --git a/parser/parser.go b/parser/parser.go index 4a577c6..288baad 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -226,7 +226,8 @@ func (p *Parser) Parse() (Definition, error) { } p.def.Services = append(p.def.Services, s) case *types.Struct: - p.parseObject(pkg, obj, item) + // depth=1 to be consistent with parseMethod param objects + p.parseObject(pkg, obj, item, 1) } } } @@ -299,7 +300,7 @@ func (p *Parser) parseMethod(pkg *packages.Package, serviceName string, methodTy if inputParams.Len() != 1 { return m, p.wrapErr(errors.New("invalid method signature: expected Method(MethodRequest) MethodResponse"), pkg, methodType.Pos()) } - m.InputObject, err = p.parseFieldType(pkg, inputParams.At(0)) + m.InputObject, err = p.parseFieldType(pkg, inputParams.At(0), 0) if err != nil { return m, errors.Wrap(err, "parse input object type") } @@ -307,7 +308,7 @@ func (p *Parser) parseMethod(pkg *packages.Package, serviceName string, methodTy if outputParams.Len() != 1 { return m, p.wrapErr(errors.New("invalid method signature: expected Method(MethodRequest) MethodResponse"), pkg, methodType.Pos()) } - m.OutputObject, err = p.parseFieldType(pkg, outputParams.At(0)) + m.OutputObject, err = p.parseFieldType(pkg, outputParams.At(0), 0) if err != nil { return m, errors.Wrap(err, "parse output object type") } @@ -316,7 +317,9 @@ func (p *Parser) parseMethod(pkg *packages.Package, serviceName string, methodTy } // parseObject parses a struct type and adds it to the Definition. -func (p *Parser) parseObject(pkg *packages.Package, o types.Object, v *types.Struct) error { +// +// depth is 0-indexed. +func (p *Parser) parseObject(pkg *packages.Package, o types.Object, v *types.Struct, depth int) error { var obj Object obj.Name = o.Name() obj.Comment = p.commentForType(obj.Name) @@ -344,7 +347,7 @@ func (p *Parser) parseObject(pkg *packages.Package, o types.Object, v *types.Str obj.Fields = []Field{} for i := 0; i < st.NumFields(); i++ { - field, err := p.parseField(pkg, obj.Name, st.Field(i), st.Tag(i)) + field, err := p.parseField(pkg, obj.Name, st.Field(i), st.Tag(i), depth) if err != nil { return err } @@ -375,7 +378,7 @@ func (p *Parser) parseTags(tag string) (map[string]FieldTag, error) { return fieldTags, nil } -func (p *Parser) parseField(pkg *packages.Package, objectName string, v *types.Var, tag string) (Field, error) { +func (p *Parser) parseField(pkg *packages.Package, objectName string, v *types.Var, tag string, depth int) (Field, error) { var f Field f.Name = v.Name() f.NameLowerCamel = camelizeDown(f.Name) @@ -389,7 +392,8 @@ func (p *Parser) parseField(pkg *packages.Package, objectName string, v *types.V } f.Comment = p.commentForField(objectName, f.Name) f.Metadata = map[string]interface{}{} - if !v.Exported() { + if depth <= 1 && !v.Exported() { + // request/response struct field return f, p.wrapErr(errors.New(f.Name+" must be exported"), pkg, v.Pos()) } var err error @@ -400,14 +404,14 @@ func (p *Parser) parseField(pkg *packages.Package, objectName string, v *types.V if example, ok := f.Metadata["example"]; ok { f.Example = example } - f.Type, err = p.parseFieldType(pkg, v) + f.Type, err = p.parseFieldType(pkg, v, depth) if err != nil { return f, errors.Wrap(err, "parse type") } return f, nil } -func (p *Parser) parseFieldType(pkg *packages.Package, obj types.Object) (FieldType, error) { +func (p *Parser) parseFieldType(pkg *packages.Package, obj types.Object, depth int) (FieldType, error) { var ftype FieldType pkgPath := pkg.PkgPath resolver := func(other *types.Package) string { @@ -437,7 +441,7 @@ func (p *Parser) parseFieldType(pkg *packages.Package, obj types.Object) (FieldT } if named, ok := typ.(*types.Named); ok { if structure, ok := named.Underlying().(*types.Struct); ok { - if err := p.parseObject(pkg, named.Obj(), structure); err != nil { + if err := p.parseObject(pkg, named.Obj(), structure, depth+1); err != nil { return ftype, err } ftype.IsObject = true diff --git a/parser/parser_test.go b/parser/parser_test.go index 15422ca..471b6e8 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -211,6 +211,27 @@ You will love it.`) // log.Println(string(b)) } +func TestParseUnexportedFields(t *testing.T) { + t.Run("response field", func(t *testing.T) { + is := is.New(t) + patterns := []string{"./testdata/unexported-fields"} + p := New(patterns...) + p.Verbose = testing.Verbose() + _, err := p.Parse() + is.True(err != nil) + is.True(strings.Contains(err.Error(), "result must be exported")) + }) + + t.Run("deeper field", func(t *testing.T) { + is := is.New(t) + patterns := []string{"./testdata/unexported-deeper-fields"} + p := New(patterns...) + p.Verbose = testing.Verbose() + _, err := p.Parse() + is.NoErr(err) + }) +} + func TestFieldTypeIsOptional(t *testing.T) { is := is.New(t) diff --git a/parser/testdata/unexported-deeper-fields/unexported-deeper-fields.go b/parser/testdata/unexported-deeper-fields/unexported-deeper-fields.go new file mode 100644 index 0000000..e8cd5ec --- /dev/null +++ b/parser/testdata/unexported-deeper-fields/unexported-deeper-fields.go @@ -0,0 +1,14 @@ +package unexporteddeeper + +import "math/big" + +type CalculatorService interface { + Calculate(CalculateRequest) CalculateResponse +} + +type CalculateRequest struct{} + +type CalculateResponse struct { + // big.Int has unexported fields + Result big.Int +} diff --git a/parser/testdata/unexported-fields/unexported-fields.go b/parser/testdata/unexported-fields/unexported-fields.go new file mode 100644 index 0000000..8544c88 --- /dev/null +++ b/parser/testdata/unexported-fields/unexported-fields.go @@ -0,0 +1,11 @@ +package unexported + +type CalculatorService interface { + Calculate(CalculateRequest) CalculateResponse +} + +type CalculateRequest struct{} + +type CalculateResponse struct { + result int +}