From 22b0d67dcaffb96e1800a0987509754bcc67d84c Mon Sep 17 00:00:00 2001 From: Maxim Krivchun Date: Fri, 11 Mar 2022 15:01:28 +0200 Subject: [PATCH] yaml convertes --- go.mod | 2 +- .../internal/genopenapi/template.go | 6 +- .../internal/genopenapi/template_test.go | 10 +- .../internal/genopenapi/types.go | 61 +++++++--- .../internal/genopenapi/types_test.go | 108 ++++++++++++++++++ 5 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 protoc-gen-openapiv2/internal/genopenapi/types_test.go diff --git a/go.mod b/go.mod index e8dd490b8b0..d9d55293ab6 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.27.1 + gopkg.in/yaml.v2 v2.4.0 sigs.k8s.io/yaml v1.3.0 ) @@ -21,5 +22,4 @@ require ( golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.6 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/protoc-gen-openapiv2/internal/genopenapi/template.go b/protoc-gen-openapiv2/internal/genopenapi/template.go index bc18775eee1..3161d8e2fea 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template.go @@ -1891,7 +1891,7 @@ func processHeaders(inputHdrs map[string]*openapi_options.Header) (openapiHeader if err != nil { return nil, err } - ret.Default = json.RawMessage(v.Default) + ret.Default = RawExample(v.Default) } hdrs[header] = ret } @@ -2396,7 +2396,7 @@ func updateswaggerObjectFromJSONSchema(s *openapiSchemaObject, j *openapi_option s.Type = strings.ToLower(overrideType[0].String()) } if j != nil && j.GetExample() != "" { - s.Example = json.RawMessage(j.GetExample()) + s.Example = RawExample(j.GetExample()) } if j != nil && j.GetFormat() != "" { s.Format = j.GetFormat() @@ -2438,7 +2438,7 @@ func openapiSchemaFromProtoSchema(s *openapi_options.Schema, reg *descriptor.Reg updateswaggerObjectFromJSONSchema(&ret, s.GetJsonSchema(), reg, data) if s != nil && s.Example != "" { - ret.Example = json.RawMessage(s.Example) + ret.Example = RawExample(s.Example) } return ret diff --git a/protoc-gen-openapiv2/internal/genopenapi/template_test.go b/protoc-gen-openapiv2/internal/genopenapi/template_test.go index 6707fbc59d8..e806524cf41 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/template_test.go +++ b/protoc-gen-openapiv2/internal/genopenapi/template_test.go @@ -1807,19 +1807,19 @@ func TestApplyTemplateHeaders(t *testing.T) { "Boolean": openapiHeaderObject{ Description: "boolean header description", Type: "boolean", - Default: json.RawMessage("true"), + Default: RawExample("true"), Pattern: "^true|false$", }, "Integer": openapiHeaderObject{ Description: "integer header description", Type: "integer", - Default: json.RawMessage("0"), + Default: RawExample("0"), Pattern: "^[0-9]$", }, "Number": openapiHeaderObject{ Description: "number header description", Type: "number", - Default: json.RawMessage("1.2"), + Default: RawExample("1.2"), Pattern: "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$", }, }, @@ -4193,7 +4193,7 @@ func TestRenderMessagesAsDefinition(t *testing.T) { defs: map[string]openapiSchemaObject{ "Message": {schemaCore: schemaCore{ Type: "object", - Example: json.RawMessage(`{"foo":"bar"}`), + Example: RawExample(`{"foo":"bar"}`), }}, }, }, @@ -4210,7 +4210,7 @@ func TestRenderMessagesAsDefinition(t *testing.T) { defs: map[string]openapiSchemaObject{ "Message": {schemaCore: schemaCore{ Type: "object", - Example: json.RawMessage(`XXXX anything goes XXXX`), + Example: RawExample(`XXXX anything goes XXXX`), }}, }, }, diff --git a/protoc-gen-openapiv2/internal/genopenapi/types.go b/protoc-gen-openapiv2/internal/genopenapi/types.go index 6e06be4d367..5840211e09e 100644 --- a/protoc-gen-openapiv2/internal/genopenapi/types.go +++ b/protoc-gen-openapiv2/internal/genopenapi/types.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor" + "gopkg.in/yaml.v2" ) type param struct { @@ -53,8 +54,8 @@ type openapiExternalDocumentationObject struct { } type extension struct { - key string - value json.RawMessage + key string `json:"-" yaml:"-"` + value json.RawMessage `json:"-" yaml:"-"` } // http://swagger.io/specification/#swaggerObject @@ -90,7 +91,7 @@ type openapiSecuritySchemeObject struct { TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` Scopes openapiScopesObject `json:"scopes,omitempty" yaml:"scopes,omitempty"` - extensions []extension + extensions []extension `json:"-" yaml:"-"` } // http://swagger.io/specification/#scopesObject @@ -158,11 +159,11 @@ type openapiParameterObject struct { // supported by generation tools such as swagger-codegen and go-swagger. // For protoc-gen-openapiv3, we'd want to add `nullable` instead. type schemaCore struct { - Type string `json:"type,omitempty" yaml:"type,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` - XNullable bool `json:"x-nullable,omitempty" yaml:"x-nullable,omitempty"` - Example json.RawMessage `json:"example,omitempty" yaml:"example,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + XNullable bool `json:"x-nullable,omitempty" yaml:"x-nullable,omitempty"` + Example RawExample `json:"example,omitempty" yaml:"example,omitempty"` Items *openapiItemsObject `json:"items,omitempty" yaml:"items,omitempty"` @@ -173,6 +174,25 @@ type schemaCore struct { Default string `json:"default,omitempty" yaml:"default,omitempty"` } +type RawExample json.RawMessage + +func (m RawExample) MarshalJSON() ([]byte, error) { + return (json.RawMessage)(m).MarshalJSON() +} + +func (m *RawExample) UnmarshalJSON(data []byte) error { + return (*json.RawMessage)(m).UnmarshalJSON(data) +} + +func (e RawExample) MarshalYAML() (interface{}, error) { + var data interface{} + if err := json.Unmarshal(e, &data); err != nil { + return nil, err + } + + return data, nil +} + func (s *schemaCore) setRefFromFQN(ref string, reg *descriptor.Registry) error { name, ok := fullyQualifiedNameToOpenAPIName(ref, reg) if !ok { @@ -201,11 +221,11 @@ type openapiHeadersObject map[string]openapiHeaderObject // http://swagger.io/specification/#headerObject type openapiHeaderObject struct { - Description string `json:"description,omitempty" yaml:"description,omitempty"` - Type string `json:"type,omitempty" yaml:"type,omitempty"` - Format string `json:"format,omitempty" yaml:"format,omitempty"` - Default json.RawMessage `json:"default,omitempty" yaml:"default,omitempty"` - Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Default RawExample `json:"default,omitempty" yaml:"default,omitempty"` + Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"` } type keyVal struct { @@ -215,6 +235,19 @@ type keyVal struct { type openapiSchemaObjectProperties []keyVal +func (p openapiSchemaObjectProperties) MarshalYAML() (interface{}, error) { + ms := make(yaml.MapSlice, len(p)) + + for i, v := range p { + ms[i] = yaml.MapItem{ + Key: v.Key, + Value: v.Value, + } + } + + return ms, nil +} + func (op openapiSchemaObjectProperties) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.WriteString("{") @@ -241,7 +274,7 @@ func (op openapiSchemaObjectProperties) MarshalJSON() ([]byte, error) { // http://swagger.io/specification/#schemaObject type openapiSchemaObject struct { - schemaCore + schemaCore `yaml:",inline"` // Properties can be recursively defined Properties *openapiSchemaObjectProperties `json:"properties,omitempty" yaml:"properties,omitempty"` AdditionalProperties *openapiSchemaObject `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` diff --git a/protoc-gen-openapiv2/internal/genopenapi/types_test.go b/protoc-gen-openapiv2/internal/genopenapi/types_test.go new file mode 100644 index 00000000000..46f5d953278 --- /dev/null +++ b/protoc-gen-openapiv2/internal/genopenapi/types_test.go @@ -0,0 +1,108 @@ +package genopenapi + +import ( + "encoding/json" + "strings" + "testing" + + "gopkg.in/yaml.v2" +) + +func newSpaceReplacer() *strings.Replacer { + return strings.NewReplacer(" ", "", "\n", "", "\t", "") +} + +func TestRawExample(t *testing.T) { + t.Parallel() + + testCases := [...]struct { + In RawExample + Exp string + }{{ + In: RawExample(`1`), + Exp: `1`, + }, { + In: RawExample(`"1"`), + Exp: `"1"`, + }, { + In: RawExample(`{"hello":"worldr"}`), + Exp: "hello:\n" + + " worldr\n", + }} + + sr := newSpaceReplacer() + + for _, tc := range testCases { + tc := tc + + t.Run(string(tc.In), func(t *testing.T) { + t.Parallel() + + ex := RawExample(tc.In) + + out, err := yaml.Marshal(ex) + switch { + case err != nil: + t.Fatalf("expect no yaml marshal error, got: %s", err) + case !json.Valid(tc.In): + t.Fatalf("json is invalid: %#q", tc.In) + case sr.Replace(tc.Exp) != sr.Replace(string(out)): + t.Fatalf("expected: %s, actual: %s", tc.Exp, out) + } + + out, err = json.Marshal(tc.In) + switch { + case err != nil: + t.Fatalf("expect no json marshal error, got: %s", err) + case sr.Replace(string(tc.In)) != sr.Replace(string(out)): + t.Fatalf("expected: %s, actual: %s", tc.In, out) + } + }) + } +} + +func TestOpenapiSchemaObjectProperties(t *testing.T) { + t.Parallel() + + v := map[string]interface{}{ + "example": openapiSchemaObjectProperties{{ + Key: "test1", + Value: 1, + }, { + Key: "test2", + Value: 2, + }}, + } + + t.Run("yaml", func(t *testing.T) { + t.Parallel() + + const exp = "example:\n" + + " test1: 1\n" + + " test2: 2\n" + + sr := newSpaceReplacer() + + out, err := yaml.Marshal(v) + switch { + case err != nil: + t.Fatalf("expect no marshal error, got: %s", err) + case sr.Replace(exp) != sr.Replace(string(out)): + t.Fatalf("expected: %s, actual: %s", exp, out) + } + }) + + t.Run("json", func(t *testing.T) { + t.Parallel() + + const exp = `{"example":{"test1":1,"test2":2}}` + + got, err := json.Marshal(v) + switch { + case err != nil: + t.Fatalf("expect no marshal error, got: %s", err) + case exp != string(got): + t.Fatalf("expected: %s, actual: %s", exp, got) + } + }) +}