Repository: swaggest/openapi-go
Pull request #6 (2020-05-08 20:39:24)

Update README

codecov[bot]: # [Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/6?src=pr&el=h1) Report
> Merging [#6](https://codecov.io/gh/swaggest/openapi-go/pull/6?src=pr&el=desc) into [master](https://codecov.io/gh/swaggest/openapi-go/commit/30bd167419aab391e0c0219e6992edc12cb14ce0&el=desc) will **decrease** coverage by `0.05%`.
> The diff coverage is `n/a`.

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

```diff
@@            Coverage Diff             @@
##           master       #6      +/-   ##
==========================================
- Coverage   11.27%   11.22%   -0.06%     
==========================================
  Files           4        4              
  Lines        3992     3992              
==========================================
- Hits          450      448       -2     
- Misses       3496     3498       +2     
  Partials       46       46              
```


| [Impacted Files](https://codecov.io/gh/swaggest/openapi-go/pull/6?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [openapi3/helper.go](https://codecov.io/gh/swaggest/openapi-go/pull/6/diff?src=pr&el=tree#diff-b3BlbmFwaTMvaGVscGVyLmdv) | `85.71% <0.00%> (-4.77%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/swaggest/openapi-go/pull/6?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/6?src=pr&el=footer). Last update [30bd167...7065ca2](https://codecov.io/gh/swaggest/openapi-go/pull/6?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 5d8ddec..052c549 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,27 @@
 
 <img align="right" width="250px" src="/resources/logo.png">
 
+This library provides Go structures to marshal/unmarshal and reflect [OpenAPI Schema](https://swagger.io/resources/open-api/) documents.
+
 [![Build Status](https://github.com/swaggest/openapi-go/workflows/test/badge.svg)](https://github.com/swaggest/openapi-go/actions?query=branch%3Amaster+workflow%3Atest)
 [![Coverage Status](https://codecov.io/gh/swaggest/openapi-go/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/openapi-go)
 [![GoDevDoc](https://img.shields.io/badge/dev-doc-00ADD8?logo=go)](https://pkg.go.dev/github.com/swaggest/openapi-go)
 ![Code lines](https://sloc.xyz/github/swaggest/openapi-go/?category=code)
 ![Comments](https://sloc.xyz/github/swaggest/openapi-go/?category=comments)
 
-This library provides Go structures to marshal/unmarshal and reflect [OpenAPI Schema](https://swagger.io/resources/open-api/) documents.
+## Features
+
+* Type safe mapping of OpenAPI 3 documents with Go structures generated from schema.
+* Type-based reflection of Go structures to OpenAPI 3 schema using
+* Schema control with field tags
+    * `json` for request bodies and responses in JSON
+    * `query`, `path` for parameters in URL
+    * `header`, `cookie`, `formData`, `file` for other parameters
+    * field tags named after JSON Schema/OpenAPI 3 Schema constraints
+* Schema control via interfaces
+    * [`jsonschema.Exposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go?tab=doc#Exposer)
+    * [`jsonschema.Preparer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go?tab=doc#Preparer)
+    * [`jsonschema.RawExposer`](https://pkg.go.dev/github.com/swaggest/jsonschema-go?tab=doc#RawExposer)
 
 ## Example
 
@@ -43,213 +57,128 @@ type resp struct {
 
 putOp := openapi3.Operation{}
 
-err := reflector.SetRequest(&putOp, new(req), http.MethodPut)
-if err != nil {
-    log.Fatal(err)
-}
-
-err = reflector.SetJSONResponse(&putOp, new(resp), http.StatusOK)
-if err != nil {
-    log.Fatal(err)
-}
-
-err = reflector.SetJSONResponse(&putOp, new([]resp), http.StatusConflict)
-if err != nil {
-    log.Fatal(err)
-}
+handleError(reflector.SetRequest(&putOp, new(req), http.MethodPut))
+handleError(reflector.SetJSONResponse(&putOp, new(resp), http.StatusOK))
+handleError(reflector.SetJSONResponse(&putOp, new([]resp), http.StatusConflict))
+handleError(reflector.Spec.AddOperation(http.MethodPut, "/things/{id}", putOp))
 
 getOp := openapi3.Operation{}
 
-err = reflector.SetRequest(&getOp, new(req), http.MethodGet)
-if err != nil {
-    log.Fatal(err)
-}
+handleError(reflector.SetRequest(&getOp, new(req), http.MethodGet))
+handleError(reflector.SetJSONResponse(&getOp, new(resp), http.StatusOK))
+handleError(reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp))
 
-err = reflector.SetJSONResponse(&getOp, new(resp), http.StatusOK)
-if err != nil {
-    log.Fatal(err)
-}
-
-pathItem := reflector.Spec.Paths.MapOfPathItemValues["/things/{id}"]
-pathItem.
-    WithSummary("Path Summary").
-    WithDescription("Path Description")
-
-pathItem.WithOperation(http.MethodPut, putOp).WithOperation(http.MethodGet, getOp)
-
-reflector.Spec.Paths.WithMapOfPathItemValuesItem("/things/{id}", pathItem)
-
-schema, err := json.MarshalIndent(reflector.Spec, "", " ")
+schema, err := reflector.Spec.MarshalYAML()
 if err != nil {
     log.Fatal(err)
 }
 
 fmt.Println(string(schema))
-
-// Output:
-// {
-//  "openapi": "3.0.2",
-//  "info": {
-//   "title": "Things API",
-//   "description": "Put something here",
-//   "version": "1.2.3"
-//  },
-//  "paths": {
-//   "/things/{id}": {
-//    "summary": "Path Summary",
-//    "description": "Path Description",
-//    "get": {
-//     "parameters": [
-//      {
-//       "name": "locale",
-//       "in": "query",
-//       "schema": {
-//        "pattern": "^[a-z]{2}-[A-Z]{2}$",
-//        "type": "string"
-//       }
-//      },
-//      {
-//       "name": "id",
-//       "in": "path",
-//       "required": true,
-//       "schema": {
-//        "type": "string",
-//        "example": "XXX-XXXXX"
-//       }
-//      }
-//     ],
-//     "responses": {
-//      "200": {
-//       "description": "OK",
-//       "content": {
-//        "application/json": {
-//         "schema": {
-//          "$ref": "#/components/schemas/Openapi3TestResp"
-//         }
-//        }
-//       }
-//      }
-//     }
-//    },
-//    "put": {
-//     "parameters": [
-//      {
-//       "name": "locale",
-//       "in": "query",
-//       "schema": {
-//        "pattern": "^[a-z]{2}-[A-Z]{2}$",
-//        "type": "string"
-//       }
-//      },
-//      {
-//       "name": "id",
-//       "in": "path",
-//       "required": true,
-//       "schema": {
-//        "type": "string",
-//        "example": "XXX-XXXXX"
-//       }
-//      }
-//     ],
-//     "requestBody": {
-//      "content": {
-//       "application/json": {
-//        "schema": {
-//         "$ref": "#/components/schemas/Openapi3TestReq"
-//        }
-//       }
-//      }
-//     },
-//     "responses": {
-//      "200": {
-//       "description": "OK",
-//       "content": {
-//        "application/json": {
-//         "schema": {
-//          "$ref": "#/components/schemas/Openapi3TestResp"
-//         }
-//        }
-//       }
-//      },
-//      "409": {
-//       "description": "Conflict",
-//       "content": {
-//        "application/json": {
-//         "schema": {
-//          "type": "array",
-//          "items": {
-//           "$ref": "#/components/schemas/Openapi3TestResp"
-//          }
-//         }
-//        }
-//       }
-//      }
-//     }
-//    }
-//   }
-//  },
-//  "components": {
-//   "schemas": {
-//    "Openapi3TestReq": {
-//     "type": "object",
-//     "properties": {
-//      "amount": {
-//       "minimum": 0,
-//       "type": "integer"
-//      },
-//      "items": {
-//       "type": "array",
-//       "items": {
-//        "type": "object",
-//        "properties": {
-//         "count": {
-//          "minimum": 0,
-//          "type": "integer"
-//         },
-//         "name": {
-//          "type": "string"
-//         }
-//        }
-//       }
-//      },
-//      "string": {
-//       "type": "string"
-//      }
-//     }
-//    },
-//    "Openapi3TestResp": {
-//     "type": "object",
-//     "properties": {
-//      "amount": {
-//       "minimum": 0,
-//       "type": "integer"
-//      },
-//      "id": {
-//       "type": "string",
-//       "example": "XXX-XXXXX"
-//      },
-//      "items": {
-//       "type": "array",
-//       "items": {
-//        "type": "object",
-//        "properties": {
-//         "count": {
-//          "minimum": 0,
-//          "type": "integer"
-//         },
-//         "name": {
-//          "type": "string"
-//         }
-//        }
-//       }
-//      },
-//      "updated_at": {
-//       "type": "string",
-//       "format": "date-time"
-//      }
-//     }
-//    }
-//   }
-//  }
-// }
+```
+
+Output:
+
+```yaml
+openapi: 3.0.2
+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
+      - in: path
+        name: id
+        required: true
+        schema:
+          example: XXX-XXXXX
+          type: string
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Openapi3TestResp'
+          description: OK
+    put:
+      parameters:
+      - in: query
+        name: locale
+        schema:
+          pattern: ^[a-z]{2}-[A-Z]{2}$
+          type: string
+      - in: path
+        name: id
+        required: true
+        schema:
+          example: XXX-XXXXX
+          type: string
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/Openapi3TestReq'
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Openapi3TestResp'
+          description: OK
+        "409":
+          content:
+            application/json:
+              schema:
+                items:
+                  $ref: '#/components/schemas/Openapi3TestResp'
+                type: array
+          description: Conflict
+components:
+  schemas:
+    Openapi3TestReq:
+      properties:
+        amount:
+          minimum: 0
+          type: integer
+        items:
+          items:
+            properties:
+              count:
+                minimum: 0
+                type: integer
+              name:
+                type: string
+            type: object
+          type: array
+        string:
+          type: string
+      type: object
+    Openapi3TestResp:
+      properties:
+        amount:
+          minimum: 0
+          type: integer
+        id:
+          example: XXX-XXXXX
+          type: string
+        items:
+          items:
+            properties:
+              count:
+                minimum: 0
+                type: integer
+              name:
+                type: string
+            type: object
+          type: array
+        updated_at:
+          format: date-time
+          type: string
+      type: object
 ```
\ No newline at end of file
diff --git a/go.mod b/go.mod
index bfa461a..5f59282 100644
--- a/go.mod
+++ b/go.mod
@@ -8,4 +8,5 @@ require (
 	github.com/swaggest/jsonschema-go v0.3.0
 	github.com/swaggest/refl v0.1.2
 	github.com/swaggest/swgen v0.6.22
+	gopkg.in/yaml.v2 v2.2.8
 )
diff --git a/openapi3/yaml.go b/openapi3/yaml.go
new file mode 100644
index 0000000..06429b9
--- /dev/null
+++ b/openapi3/yaml.go
@@ -0,0 +1,174 @@
+package openapi3
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"gopkg.in/yaml.v2"
+)
+
+// UnmarshalYAML reads from YAML bytes.
+func (s *Spec) UnmarshalYAML(data []byte) error {
+	var v interface{}
+
+	err := yaml.Unmarshal(data, &v)
+	if err != nil {
+		return err
+	}
+
+	v = convertMapI2MapS(v)
+
+	data, err = json.Marshal(v)
+	if err != nil {
+		return err
+	}
+
+	return s.UnmarshalJSON(data)
+}
+
+// MarshalYAML produces YAML bytes.
+func (s *Spec) MarshalYAML() ([]byte, error) {
+	//return ya.Marshal(s)
+	jsonData, err := s.MarshalJSON()
+	if err != nil {
+		return nil, err
+	}
+
+	var v orderedMap
+
+	err = json.Unmarshal(jsonData, &v)
+	if err != nil {
+		return nil, err
+	}
+
+	return yaml.Marshal(yaml.MapSlice(v))
+}
+
+type orderedMap []yaml.MapItem
+
+func (om *orderedMap) UnmarshalJSON(data []byte) error {
+	var mapData map[string]interface{}
+
+	err := json.Unmarshal(data, &mapData)
+	if err != nil {
+		return err
+	}
+
+	keys, err := objectKeys(data)
+	if err != nil {
+		return err
+	}
+
+	for _, key := range keys {
+		*om = append(*om, yaml.MapItem{
+			Key:   key,
+			Value: mapData[key],
+		})
+	}
+
+	return nil
+}
+
+func objectKeys(b []byte) ([]string, error) {
+	d := json.NewDecoder(bytes.NewReader(b))
+
+	t, err := d.Token()
+	if err != nil {
+		return nil, err
+	}
+
+	if t != json.Delim('{') {
+		return nil, errors.New("expected start of object")
+	}
+
+	var keys []string
+
+	for {
+		t, err := d.Token()
+		if err != nil {
+			return nil, err
+		}
+
+		if t == json.Delim('}') {
+			return keys, nil
+		}
+
+		keys = append(keys, t.(string))
+
+		if err := skipValue(d); err != nil {
+			return nil, err
+		}
+	}
+}
+
+var errUnterminated = errors.New("unterminated array or object")
+
+func skipValue(d *json.Decoder) error {
+	t, err := d.Token()
+	if err != nil {
+		return err
+	}
+
+	switch t {
+	case json.Delim('['), json.Delim('{'):
+		for {
+			if err := skipValue(d); err != nil {
+				if err == errUnterminated {
+					break
+				}
+
+				return err
+			}
+		}
+	case json.Delim(']'), json.Delim('}'):
+		return errUnterminated
+	}
+
+	return nil
+}
+
+// convertMapI2MapS walks the given dynamic object recursively, and
+// converts maps with interface{} key type to maps with string key type.
+// This function comes handy if you want to marshal a dynamic object into
+// JSON where maps with interface{} key type are not allowed.
+//
+// Recursion is implemented into values of the following types:
+//   -map[interface{}]interface{}
+//   -map[string]interface{}
+//   -[]interface{}
+//
+// When converting map[interface{}]interface{} to map[string]interface{},
+// fmt.Sprint() with default formatting is used to convert the key to a string key.
+//
+// See github.com/icza/dyno.
+func convertMapI2MapS(v interface{}) interface{} {
+	switch x := v.(type) {
+	case map[interface{}]interface{}:
+		m := map[string]interface{}{}
+
+		for k, v2 := range x {
+			switch k2 := k.(type) {
+			case string: // Fast check if it's already a string
+				m[k2] = convertMapI2MapS(v2)
+			default:
+				m[fmt.Sprint(k)] = convertMapI2MapS(v2)
+			}
+		}
+
+		v = m
+
+	case []interface{}:
+		for i, v2 := range x {
+			x[i] = convertMapI2MapS(v2)
+		}
+
+	case map[string]interface{}:
+		for k, v2 := range x {
+			x[k] = convertMapI2MapS(v2)
+		}
+	}
+
+	return v
+}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 802a61f..cd97144 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -32,4 +32,5 @@ jobs:
       - name: Test
         run: go test -mod=vendor -v -cover -race -coverprofile=coverage.txt -covermode=atomic ./...
       - name: Upload code coverage
+        if: matrix.go-version == '1.14.x'
         uses: codecov/codecov-action@v1
\ No newline at end of file
diff --git a/openapi3/example_test.go b/openapi3/example_test.go
index 97308ec..a349fb2 100644
--- a/openapi3/example_test.go
+++ b/openapi3/example_test.go
@@ -1,7 +1,6 @@
 package openapi3_test
 
 import (
-	"encoding/json"
 	"fmt"
 	"log"
 	"net/http"
@@ -10,6 +9,12 @@ import (
 	"github.com/swaggest/openapi-go/openapi3"
 )
 
+func handleError(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
 func ExampleReflector_SetJSONResponse() {
 	reflector := openapi3.Reflector{}
 	reflector.Spec = &openapi3.Spec{Openapi: "3.0.2"}
@@ -41,43 +46,18 @@ func ExampleReflector_SetJSONResponse() {
 
 	putOp := openapi3.Operation{}
 
-	err := reflector.SetRequest(&putOp, new(req), http.MethodPut)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	err = reflector.SetJSONResponse(&putOp, new(resp), http.StatusOK)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	err = reflector.SetJSONResponse(&putOp, new([]resp), http.StatusConflict)
-	if err != nil {
-		log.Fatal(err)
-	}
+	handleError(reflector.SetRequest(&putOp, new(req), http.MethodPut))
+	handleError(reflector.SetJSONResponse(&putOp, new(resp), http.StatusOK))
+	handleError(reflector.SetJSONResponse(&putOp, new([]resp), http.StatusConflict))
+	handleError(reflector.Spec.AddOperation(http.MethodPut, "/things/{id}", putOp))
 
 	getOp := openapi3.Operation{}
 
-	err = reflector.SetRequest(&getOp, new(req), http.MethodGet)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	err = reflector.SetJSONResponse(&getOp, new(resp), http.StatusOK)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	pathItem := reflector.Spec.Paths.MapOfPathItemValues["/things/{id}"]
-	pathItem.
-		WithSummary("Path Summary").
-		WithDescription("Path Description")
-
-	pathItem.WithOperation(http.MethodPut, putOp).WithOperation(http.MethodGet, getOp)
-
-	reflector.Spec.Paths.WithMapOfPathItemValuesItem("/things/{id}", pathItem)
+	handleError(reflector.SetRequest(&getOp, new(req), http.MethodGet))
+	handleError(reflector.SetJSONResponse(&getOp, new(resp), http.StatusOK))
+	handleError(reflector.Spec.AddOperation(http.MethodGet, "/things/{id}", getOp))
 
-	schema, err := json.MarshalIndent(reflector.Spec, "", " ")
+	schema, err := reflector.Spec.MarshalYAML()
 	if err != nil {
 		log.Fatal(err)
 	}
@@ -85,169 +65,106 @@ func ExampleReflector_SetJSONResponse() {
 	fmt.Println(string(schema))
 
 	// Output:
-	// {
-	//  "openapi": "3.0.2",
-	//  "info": {
-	//   "title": "Things API",
-	//   "description": "Put something here",
-	//   "version": "1.2.3"
-	//  },
-	//  "paths": {
-	//   "/things/{id}": {
-	//    "summary": "Path Summary",
-	//    "description": "Path Description",
-	//    "get": {
-	//     "parameters": [
-	//      {
-	//       "name": "locale",
-	//       "in": "query",
-	//       "schema": {
-	//        "pattern": "^[a-z]{2}-[A-Z]{2}$",
-	//        "type": "string"
-	//       }
-	//      },
-	//      {
-	//       "name": "id",
-	//       "in": "path",
-	//       "required": true,
-	//       "schema": {
-	//        "type": "string",
-	//        "example": "XXX-XXXXX"
-	//       }
-	//      }
-	//     ],
-	//     "responses": {
-	//      "200": {
-	//       "description": "OK",
-	//       "content": {
-	//        "application/json": {
-	//         "schema": {
-	//          "$ref": "#/components/schemas/Openapi3TestResp"
-	//         }
-	//        }
-	//       }
-	//      }
-	//     }
-	//    },
-	//    "put": {
-	//     "parameters": [
-	//      {
-	//       "name": "locale",
-	//       "in": "query",
-	//       "schema": {
-	//        "pattern": "^[a-z]{2}-[A-Z]{2}$",
-	//        "type": "string"
-	//       }
-	//      },
-	//      {
-	//       "name": "id",
-	//       "in": "path",
-	//       "required": true,
-	//       "schema": {
-	//        "type": "string",
-	//        "example": "XXX-XXXXX"
-	//       }
-	//      }
-	//     ],
-	//     "requestBody": {
-	//      "content": {
-	//       "application/json": {
-	//        "schema": {
-	//         "$ref": "#/components/schemas/Openapi3TestReq"
-	//        }
-	//       }
-	//      }
-	//     },
-	//     "responses": {
-	//      "200": {
-	//       "description": "OK",
-	//       "content": {
-	//        "application/json": {
-	//         "schema": {
-	//          "$ref": "#/components/schemas/Openapi3TestResp"
-	//         }
-	//        }
-	//       }
-	//      },
-	//      "409": {
-	//       "description": "Conflict",
-	//       "content": {
-	//        "application/json": {
-	//         "schema": {
-	//          "type": "array",
-	//          "items": {
-	//           "$ref": "#/components/schemas/Openapi3TestResp"
-	//          }
-	//         }
-	//        }
-	//       }
-	//      }
-	//     }
-	//    }
-	//   }
-	//  },
-	//  "components": {
-	//   "schemas": {
-	//    "Openapi3TestReq": {
-	//     "type": "object",
-	//     "properties": {
-	//      "amount": {
-	//       "minimum": 0,
-	//       "type": "integer"
-	//      },
-	//      "items": {
-	//       "type": "array",
-	//       "items": {
-	//        "type": "object",
-	//        "properties": {
-	//         "count": {
-	//          "minimum": 0,
-	//          "type": "integer"
-	//         },
-	//         "name": {
-	//          "type": "string"
-	//         }
-	//        }
-	//       }
-	//      },
-	//      "string": {
-	//       "type": "string"
-	//      }
-	//     }
-	//    },
-	//    "Openapi3TestResp": {
-	//     "type": "object",
-	//     "properties": {
-	//      "amount": {
-	//       "minimum": 0,
-	//       "type": "integer"
-	//      },
-	//      "id": {
-	//       "type": "string",
-	//       "example": "XXX-XXXXX"
-	//      },
-	//      "items": {
-	//       "type": "array",
-	//       "items": {
-	//        "type": "object",
-	//        "properties": {
-	//         "count": {
-	//          "minimum": 0,
-	//          "type": "integer"
-	//         },
-	//         "name": {
-	//          "type": "string"
-	//         }
-	//        }
-	//       }
-	//      },
-	//      "updated_at": {
-	//       "type": "string",
-	//       "format": "date-time"
-	//      }
-	//     }
-	//    }
-	//   }
-	//  }
-	// }
+	// openapi: 3.0.2
+	// 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
+	//       - in: path
+	//         name: id
+	//         required: true
+	//         schema:
+	//           example: XXX-XXXXX
+	//           type: string
+	//       responses:
+	//         "200":
+	//           content:
+	//             application/json:
+	//               schema:
+	//                 $ref: '#/components/schemas/Openapi3TestResp'
+	//           description: OK
+	//     put:
+	//       parameters:
+	//       - in: query
+	//         name: locale
+	//         schema:
+	//           pattern: ^[a-z]{2}-[A-Z]{2}$
+	//           type: string
+	//       - in: path
+	//         name: id
+	//         required: true
+	//         schema:
+	//           example: XXX-XXXXX
+	//           type: string
+	//       requestBody:
+	//         content:
+	//           application/json:
+	//             schema:
+	//               $ref: '#/components/schemas/Openapi3TestReq'
+	//       responses:
+	//         "200":
+	//           content:
+	//             application/json:
+	//               schema:
+	//                 $ref: '#/components/schemas/Openapi3TestResp'
+	//           description: OK
+	//         "409":
+	//           content:
+	//             application/json:
+	//               schema:
+	//                 items:
+	//                   $ref: '#/components/schemas/Openapi3TestResp'
+	//                 type: array
+	//           description: Conflict
+	// components:
+	//   schemas:
+	//     Openapi3TestReq:
+	//       properties:
+	//         amount:
+	//           minimum: 0
+	//           type: integer
+	//         items:
+	//           items:
+	//             properties:
+	//               count:
+	//                 minimum: 0
+	//                 type: integer
+	//               name:
+	//                 type: string
+	//             type: object
+	//           type: array
+	//         string:
+	//           type: string
+	//       type: object
+	//     Openapi3TestResp:
+	//       properties:
+	//         amount:
+	//           minimum: 0
+	//           type: integer
+	//         id:
+	//           example: XXX-XXXXX
+	//           type: string
+	//         items:
+	//           items:
+	//             properties:
+	//               count:
+	//                 minimum: 0
+	//                 type: integer
+	//               name:
+	//                 type: string
+	//             type: object
+	//           type: array
+	//         updated_at:
+	//           format: date-time
+	//           type: string
+	//       type: object
 }