Repository: swaggest/openapi-go
Pull request #19 (2020-11-24 08:44:28)

Add support for json encoded parameters

codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=h1) Report
> Merging [#19](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=desc) (c5af538) into [master](https://codecov.io/gh/swaggest/openapi-go/commit/e7b077337930724ed3a0f3dbb7a6fd2b26b52311?el=desc) (e7b0773) will **increase** coverage by `0.25%`.
> The diff coverage is `87.50%`.

[![Impacted file tree graph](https://codecov.io/gh/swaggest/openapi-go/pull/19/graphs/tree.svg?width=650&height=150&src=pr&token=3uETjeijpQ)](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #19      +/-   ##
==========================================
+ Coverage   37.38%   37.63%   +0.25%     
==========================================
  Files           5        5              
  Lines        3483     3494      +11     
==========================================
+ Hits         1302     1315      +13     
+ Misses       1896     1893       -3     
- Partials      285      286       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `37.63% <87.50%> (+0.25%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [openapi3/reflect.go](https://codecov.io/gh/swaggest/openapi-go/pull/19/diff?src=pr&el=tree#diff-b3BlbmFwaTMvcmVmbGVjdC5nbw==) | `84.34% <87.50%> (-0.13%)` | :arrow_down: |
| [openapi3/entities.go](https://codecov.io/gh/swaggest/openapi-go/pull/19/diff?src=pr&el=tree#diff-b3BlbmFwaTMvZW50aXRpZXMuZ28=) | `31.56% <0.00%> (+0.13%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=footer). Last update [e7b0773...c5af538](https://codecov.io/gh/swaggest/openapi-go/pull/19?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 18197a6..997a232 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ This library provides Go structures to marshal/unmarshal and reflect [OpenAPI Sc
 
 ```go
 reflector := openapi3.Reflector{}
-reflector.Spec = &openapi3.Spec{Openapi: "3.0.2"}
+reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
 reflector.Spec.Info.
     WithTitle("Things API").
     WithVersion("1.2.3").
@@ -80,7 +80,7 @@ fmt.Println(string(schema))
 Output:
 
 ```yaml
-openapi: 3.0.2
+openapi: 3.0.3
 info:
   description: Put something here
   title: Things API
diff --git a/openapi3/reflect.go b/openapi3/reflect.go
index 3ed7e01..89db6e1 100644
--- a/openapi3/reflect.go
+++ b/openapi3/reflect.go
@@ -19,6 +19,8 @@ type Reflector struct {
 }
 
 // ResolveJSONSchemaRef builds JSON Schema from OpenAPI Component Schema reference.
+//
+// Can be used in jsonschema.Schema IsTrivial().
 func (r Reflector) ResolveJSONSchemaRef(ref string) (s jsonschema.SchemaOrBool, found bool) {
 	if r.Spec == nil || r.Spec.Components == nil || r.Spec.Components.Schemas == nil ||
 		!strings.HasPrefix(ref, "#/components/schemas/") {
@@ -55,7 +57,7 @@ func joinErrors(errs ...error) error {
 // SpecEns ensures returned Spec is not nil.
 func (r *Reflector) SpecEns() *Spec {
 	if r.Spec == nil {
-		r.Spec = &Spec{Openapi: "3.0.2"}
+		r.Spec = &Spec{Openapi: "3.0.3"}
 	}
 
 	return r.Spec
@@ -240,9 +242,6 @@ func (r *Reflector) parseParametersIn(
 				Description: propertySchema.Description,
 				Schema:      &s,
 				Content:     nil,
-				Example:     nil,
-				Examples:    nil,
-				Location:    nil,
 			}
 
 			swg2CollectionFormat := ""
@@ -258,8 +257,22 @@ func (r *Reflector) parseParametersIn(
 				p.WithStyle(string(QueryParameterStyleForm)).WithExplode(true)
 			}
 
-			// Check if query parameter is an object.
-			if in == ParameterInQuery {
+			// Check if parameter is an JSON encoded object.
+			property := reflect.New(field.Type).Interface()
+			if refl.HasTaggedFields(property, tagJSON) {
+				propertySchema, err := r.Reflect(property,
+					jsonschema.DefinitionsPrefix("#/components/schemas/"),
+					jsonschema.CollectDefinitions(r.collectDefinition),
+					jsonschema.RootRef,
+				)
+				if err != nil {
+					return err
+				}
+
+				openapiSchema := SchemaOrRef{}
+				openapiSchema.FromJSONSchema(propertySchema.ToSchemaOrBool())
+				p.WithContentItem("application/json", MediaType{Schema: &openapiSchema})
+			} else {
 				ps, err := r.Reflect(reflect.New(field.Type).Interface(), jsonschema.InlineRefs)
 				if err != nil {
 					return err
@@ -292,6 +305,10 @@ func (r *Reflector) parseParametersIn(
 }
 
 func (r *Reflector) collectDefinition(name string, schema jsonschema.Schema) {
+	if _, exists := r.SpecEns().ComponentsEns().SchemasEns().MapOfSchemaOrRefValues[name]; exists {
+		return
+	}
+
 	s := SchemaOrRef{}
 	s.FromJSONSchema(schema.ToSchemaOrBool())
 
diff --git a/resources/schema/openapi3.json b/resources/schema/openapi3.json
index 7fa14a0..c5f9176 100644
--- a/resources/schema/openapi3.json
+++ b/resources/schema/openapi3.json
@@ -12,7 +12,7 @@
     "openapi": {
       "type": "string",
       "pattern": "^3\\.0\\.\\d(-.+)?$",
-      "default": "3.0.2"
+      "default": "3.0.3"
     },
     "info": {
       "$ref": "#/definitions/Info"


Test patch:
diff --git a/openapi3/_testdata/openapi.json b/openapi3/_testdata/openapi.json
index e3f3b08..0aed2d4 100644
--- a/openapi3/_testdata/openapi.json
+++ b/openapi3/_testdata/openapi.json
@@ -1,5 +1,5 @@
 {
- "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+ "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
  "paths":{
   "/somewhere/{in_path}":{
    "summary":"Path Summary","description":"Path Description",
diff --git a/openapi3/_testdata/openapi_req.json b/openapi3/_testdata/openapi_req.json
index d2606c0..341e95b 100644
--- a/openapi3/_testdata/openapi_req.json
+++ b/openapi3/_testdata/openapi_req.json
@@ -1,5 +1,5 @@
 {
- "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+ "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
  "paths":{
   "/somewhere/{in_path}":{
    "get":{
diff --git a/openapi3/_testdata/openapi_req_array.json b/openapi3/_testdata/openapi_req_array.json
index 3125b19..3c19036 100644
--- a/openapi3/_testdata/openapi_req_array.json
+++ b/openapi3/_testdata/openapi_req_array.json
@@ -1,5 +1,5 @@
 {
- "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+ "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
  "paths":{
   "/somewhere":{
    "post":{
diff --git a/openapi3/example_test.go b/openapi3/example_test.go
index ee462b4..35ed069 100644
--- a/openapi3/example_test.go
+++ b/openapi3/example_test.go
@@ -17,7 +17,7 @@ func handleError(err error) {
 
 func ExampleReflector_SetJSONResponse() {
 	reflector := openapi3.Reflector{}
-	reflector.Spec = &openapi3.Spec{Openapi: "3.0.2"}
+	reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
 	reflector.Spec.Info.
 		WithTitle("Things API").
 		WithVersion("1.2.3").
@@ -65,7 +65,7 @@ func ExampleReflector_SetJSONResponse() {
 	fmt.Println(string(schema))
 
 	// Output:
-	// openapi: 3.0.2
+	// openapi: 3.0.3
 	// info:
 	//   description: Put something here
 	//   title: Things API
diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go
index fc78281..f47c720 100644
--- a/openapi3/reflect_test.go
+++ b/openapi3/reflect_test.go
@@ -274,7 +274,7 @@ func TestReflector_SetRequest_pathParamAndBody(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere/{id}":{
 	   "post":{
@@ -314,7 +314,7 @@ func TestRequestBodyEnforcer(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere/{id}":{
 	   "get":{
@@ -357,7 +357,7 @@ func TestReflector_SetupResponse(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere":{
 	   "get":{
@@ -421,7 +421,7 @@ func TestReflector_SetupRequest(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere/{value-4}":{
 	   "post":{
@@ -472,7 +472,7 @@ func TestReflector_SetupRequest_queryObject(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere":{
 	   "post":{
@@ -516,7 +516,7 @@ func TestReflector_SetupRequest_queryNamedObject(t *testing.T) {
 	assert.NoError(t, err)
 
 	expected := []byte(`{
-	 "openapi":"3.0.2","info":{"title":"SampleAPI","version":"1.2.3"},
+	 "openapi":"3.0.3","info":{"title":"SampleAPI","version":"1.2.3"},
 	 "paths":{
 	  "/somewhere":{
 	   "post":{
@@ -541,3 +541,67 @@ func TestReflector_SetupRequest_queryNamedObject(t *testing.T) {
 	assert.True(t, found)
 	assertjson.EqualMarshal(t, []byte(`{"type":["object","null"],"additionalProperties":{"type":"number"}}`), js)
 }
+
+func TestReflector_SetupRequest_jsonQuery(t *testing.T) {
+	type filter struct {
+		Labels []string `json:"labels,omitempty"`
+		Type   string   `json:"type"`
+	}
+
+	type req struct {
+		One   filter         `query:"one"`
+		Two   filter         `query:"two"`
+		Three map[string]int `query:"three"`
+	}
+
+	r := openapi3.Reflector{}
+	oc := openapi3.OperationContext{
+		Operation: &openapi3.Operation{},
+		Input:     new(req),
+	}
+
+	require.NoError(t, r.SetupRequest(oc))
+	require.NoError(t, r.SpecEns().AddOperation(http.MethodGet, "/", *oc.Operation))
+
+	assertjson.EqualMarshal(t, []byte(`{
+	  "openapi":"3.0.3","info":{"title":"","version":""},
+	  "paths":{
+		"/":{
+		  "get":{
+			"parameters":[
+			  {
+				"name":"one","in":"query",
+				"schema":{"$ref":"#/components/schemas/Openapi3TestFilter"},
+				"content":{
+				  "application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestFilter"}}
+				}
+			  },
+			  {
+				"name":"two","in":"query",
+				"schema":{"$ref":"#/components/schemas/Openapi3TestFilter"},
+				"content":{
+				  "application/json":{"schema":{"$ref":"#/components/schemas/Openapi3TestFilter"}}
+				}
+			  },
+			  {
+				"name":"three","in":"query","style":"deepObject","explode":true,
+				"schema":{"type":"object","additionalProperties":{"type":"integer"}}
+			  }
+			],
+			"responses":{"204":{"description":"No Content"}}
+		  }
+		}
+	  },
+	  "components":{
+		"schemas":{
+		  "Openapi3TestFilter":{
+			"type":"object",
+			"properties":{
+			  "labels":{"type":"array","items":{"type":"string"}},
+			  "type":{"type":"string"}
+			}
+		  }
+		}
+	  }
+	}`), r.SpecEns())
+}