Repository: swaggest/openapi-go
Pull request #67 (2023-07-23 09:46:45)

Fix deepObject support

github-actions[bot]: ### Lines Of Code

| Language  | Files |    Lines    |    Code     | Comments  |  Blanks   | Complexity |     Bytes     |
|-----------|-------|-------------|-------------|-----------|-----------|------------|---------------|
| Go        | 7     | 1322        | 990         | 64        | 268       | 306 (+1)   | 29.9K (+24B)  |
| Go (test) | 7     | 2052 (+205) | 1492 (+124) | 309 (+63) | 251 (+18) | 27 (+2)    | 53.1K (+5.3K) |
<!-- Sticky Pull Request CommentLOC -->
github-actions[bot]: ### Go API Changes

<pre>
# summary
Inferred base version: v0.2.30
Suggested version: v0.2.31
</pre>
<!-- Sticky Pull Request Commentgorelease -->
github-actions[bot]: ### Unit Test Coverage
total:								(statements)				49.8%
changed lines: (statements) 100.0%
<details><summary>Coverage of changed lines</summary>

|          File           |     Function      | Coverage |
|-------------------------|-------------------|----------|
| Total                   |                   | 100.0%   |
| openapi3/reflect.go     |                   | 100.0%   |
| openapi3/reflect.go:250 | parseParametersIn | 100.0%   |

</details>

<details><summary>Coverage diff with base branch</summary>

No changes in coverage.

</details>
<!-- Sticky Pull Request Commentunit-test -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/swaggest/openapi-go/pull/67?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) Report
> Merging [#67](https://app.codecov.io/gh/swaggest/openapi-go/pull/67?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (0e92bc1) into [master](https://app.codecov.io/gh/swaggest/openapi-go/commit/98ec731220f2f34f71ef653f999c6a45e55895be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) (98ec731) will **not change** coverage.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master      #67   +/-   ##
=======================================
  Coverage   42.17%   42.17%           
=======================================
  Files           5        5           
  Lines        3585     3585           
=======================================
  Hits         1512     1512           
  Misses       1784     1784           
  Partials      289      289           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `42.17% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/swaggest/openapi-go/pull/67?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest) | Coverage Δ | |
|---|---|---|
| [openapi3/reflect.go](https://app.codecov.io/gh/swaggest/openapi-go/pull/67?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest#diff-b3BlbmFwaTMvcmVmbGVjdC5nbw==) | `86.91% <100.00%> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=swaggest)


Patch:
diff --git a/openapi3/reflect.go b/openapi3/reflect.go
index ea916f9..73cf2bf 100644
--- a/openapi3/reflect.go
+++ b/openapi3/reflect.go
@@ -268,7 +268,7 @@ func (r *Reflector) parseParametersIn(
 		},
 		jsonschema.SkipEmbeddedMapsSlices,
 		jsonschema.InterceptProp(func(params jsonschema.InterceptPropParams) error {
-			if !params.Processed {
+			if !params.Processed || len(params.Path) > 1 {
 				return nil
 			}
 


Test patch:
diff --git a/openapi3/example_test.go b/openapi3/example_test.go
index 6f57e18..382fe41 100644
--- a/openapi3/example_test.go
+++ b/openapi3/example_test.go
@@ -492,3 +492,106 @@ func ExampleReflector_SetJSONResponse() {
 	//           type: string
 	//       type: object
 }
+
+func ExampleReflector_SetRequest_queryObject() {
+	reflector := openapi3.Reflector{}
+	reflector.Spec = &openapi3.Spec{Openapi: "3.0.3"}
+	reflector.Spec.Info.
+		WithTitle("Things API").
+		WithVersion("1.2.3").
+		WithDescription("Put something here")
+
+	type jsonFilter struct {
+		Foo    string `json:"foo"`
+		Bar    int    `json:"bar"`
+		Deeper struct {
+			Val string `json:"val"`
+		} `json:"deeper"`
+	}
+
+	type deepObjectFilter struct {
+		Baz    bool    `query:"baz"`
+		Quux   float64 `query:"quux"`
+		Deeper struct {
+			Val string `query:"val"`
+		} `json:"deeper"`
+	}
+
+	type req struct {
+		ID     string `path:"id" example:"XXX-XXXXX"`
+		Locale string `query:"locale" pattern:"^[a-z]{2}-[A-Z]{2}$"`
+		// Object values can be serialized in JSON (with json field tags in the value struct).
+		JSONFilter jsonFilter `query:"json_filter"`
+		// Or as deepObject (with same field tag as parent, .e.g query).
+		DeepObjectFilter deepObjectFilter `query:"deep_object_filter"`
+	}
+
+	getOp := openapi3.Operation{}
+
+	_ = reflector.SetRequest(&getOp, new(req), http.MethodGet)
+	_ = reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp)
+
+	schema, err := reflector.Spec.MarshalYAML()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Println(string(schema))
+
+	// Output:
+	// openapi: 3.0.3
+	// info:
+	//   description: Put something here
+	//   title: Things API
+	//   version: 1.2.3
+	// paths:
+	//   /things/{id}:
+	//     get:
+	//       parameters:
+	//       - in: query
+	//         name: locale
+	//         schema:
+	//           pattern: ^[a-z]{2}-[A-Z]{2}$
+	//           type: string
+	//       - content:
+	//           application/json:
+	//             schema:
+	//               $ref: '#/components/schemas/Openapi3TestJsonFilter'
+	//         in: query
+	//         name: json_filter
+	//       - content:
+	//           application/json:
+	//             schema:
+	//               $ref: '#/components/schemas/Openapi3TestDeepObjectFilter'
+	//         in: query
+	//         name: deep_object_filter
+	//       - in: path
+	//         name: id
+	//         required: true
+	//         schema:
+	//           example: XXX-XXXXX
+	//           type: string
+	//       responses:
+	//         "204":
+	//           description: No Content
+	// components:
+	//   schemas:
+	//     Openapi3TestDeepObjectFilter:
+	//       properties:
+	//         deeper:
+	//           nullable: true
+	//           type: object
+	//       type: object
+	//     Openapi3TestJsonFilter:
+	//       properties:
+	//         bar:
+	//           type: integer
+	//         deeper:
+	//           properties:
+	//             val:
+	//               type: string
+	//           type: object
+	//         foo:
+	//           type: string
+	//       type: object
+}
diff --git a/openapi3/reflect_test.go b/openapi3/reflect_test.go
index 720bf45..bcee560 100644
--- a/openapi3/reflect_test.go
+++ b/openapi3/reflect_test.go
@@ -903,3 +903,105 @@ func TestReflector_SetupRequest_form_only(t *testing.T) {
 	  }
 	}`), r.SpecEns())
 }
+
+func TestReflector_SetRequest_queryObject(t *testing.T) {
+	reflector := openapi3.Reflector{}
+
+	type jsonFilter struct {
+		Foo    string `json:"foo"`
+		Bar    int    `json:"bar"`
+		Deeper struct {
+			Val string `json:"val"`
+		} `json:"deeper"`
+	}
+
+	type deepObjectFilter struct {
+		Baz    bool    `query:"baz"`
+		Quux   float64 `query:"quux"`
+		Deeper struct {
+			Val string `query:"val"`
+		} `query:"deeper"`
+	}
+
+	type EmbeddedParams struct {
+		Embedded string `query:"embedded"`
+	}
+
+	type DeeplyEmbedded struct {
+		EmbeddedParams
+	}
+
+	type req struct {
+		// Simple scalar parameters.
+		ID     string `path:"id" example:"XXX-XXXXX"`
+		Locale string `query:"locale" pattern:"^[a-z]{2}-[A-Z]{2}$"`
+		// Embedded types are expanded into top-level scope.
+		DeeplyEmbedded
+		// Object values can be serialized in JSON (with json field tags in the value struct).
+		JSONFilter jsonFilter `query:"json_filter"`
+		// Or as deepObject (with same field tag as parent, .e.g query).
+		DeepObjectFilter deepObjectFilter `query:"deep_object_filter"`
+		// JSON body tags are ignored for GET request by default.
+		Amount uint `json:"amount"`
+	}
+
+	getOp := openapi3.Operation{}
+
+	handleError(reflector.SetRequest(&getOp, new(req), http.MethodGet))
+	handleError(reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp))
+
+	assertjson.EqualMarshal(t, []byte(`{
+	  "openapi":"3.0.3","info":{"title":"","version":""},
+	  "paths":{
+		"/things/{id}":{
+		  "get":{
+			"parameters":[
+			  {
+				"name":"locale","in":"query",
+				"schema":{"pattern":"^[a-z]{2}-[A-Z]{2}$","type":"string"}
+			  },
+			  {"name":"embedded","in":"query","schema":{"type":"string"}},
+			  {
+				"name":"json_filter","in":"query",
+				"content":{
+				  "application/json":{
+					"schema":{"$ref":"#/components/schemas/Openapi3TestJsonFilter"}
+				  }
+				}
+			  },
+			  {
+				"name":"deep_object_filter","in":"query","style":"deepObject",
+				"explode":true,
+				"schema":{"$ref":"#/components/schemas/Openapi3TestDeepObjectFilter"}
+			  },
+			  {
+				"name":"id","in":"path","required":true,
+				"schema":{"type":"string","example":"XXX-XXXXX"}
+			  }
+			],
+			"responses":{"204":{"description":"No Content"}}
+		  }
+		}
+	  },
+	  "components":{
+		"schemas":{
+		  "Openapi3TestDeepObjectFilter":{
+			"type":"object",
+			"properties":{
+			  "baz":{"type":"boolean"},
+			  "deeper":{"type":"object","properties":{"val":{"type":"string"}}},
+			  "quux":{"type":"number"}
+			}
+		  },
+		  "Openapi3TestJsonFilter":{
+			"type":"object",
+			"properties":{
+			  "bar":{"type":"integer"},
+			  "deeper":{"type":"object","properties":{"val":{"type":"string"}}},
+			  "foo":{"type":"string"}
+			}
+		  }
+		}
+	  }
+	}`), reflector.Spec)
+}