diff --git a/cmd/protoc-gen-openapi/README.md b/cmd/protoc-gen-openapi/README.md index 80efa37b..27b5f79f 100644 --- a/cmd/protoc-gen-openapi/README.md +++ b/cmd/protoc-gen-openapi/README.md @@ -63,3 +63,77 @@ refers to additional .proto files in the same directory as schema: $ref: '#/components/schemas/google.rpc.Status' ``` +9. `wildcard_body_dedup`: when dealing with wildcard body mapping, remove body parameters which overlap path parameters. + - **default**: false + + Given the following service and message definitions: + ```proto + service MyService { + rpc UpdateItem(Item) returns (Item) { + option (google.api.http) = { + put : "/items/{id}" + body : "*" + }; + } + } + + message Item { + string id = 1; + string name = 2; + } + ``` + - `false`: leave the duplicate body field in the request schema. + ```yaml + paths: + /items/{id}: + put: + parameters: + - name: id + in: path + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Item' + components: + schemas: + Item: + type: object + properties: + id: # field duplicates path parameter + type: string + name: + type: string + ``` + - `true`: generate a new request schema where the path parameters are removed + ```yaml + paths: + /items/{id}: + put: + parameters: + - name: id + in: path + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Item_Body' + components: + schemas: + Item_Body: # new schema for request body + type: object + properties: + name: + type: string + Item: # original retained for response + type: object + properties: + id: + type: string + name: + type: string + ``` \ No newline at end of file diff --git a/cmd/protoc-gen-openapi/examples/google/example/library/v1/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/google/example/library/v1/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..46a2e81f --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/google/example/library/v1/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,541 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: LibraryService API + description: |- + This API represents a simple digital library. It lets you manage Shelf + resources and Book resources in the library. It defines the following + resource model: + + - The API has a collection of [Shelf][google.example.library.v1.Shelf] + resources, named `shelves/*` + + - Each Shelf has a collection of [Book][google.example.library.v1.Book] + resources, named `shelves/*/books/*` + version: 0.0.1 +servers: + - url: https://library-example.googleapis.com +paths: + /v1/shelves: + get: + tags: + - LibraryService + description: |- + Lists shelves. The order is unspecified but deterministic. Newly created + shelves will not necessarily be added to the end of this list. + operationId: LibraryService_ListShelves + parameters: + - name: pageSize + in: query + description: |- + Requested page size. Server may return fewer shelves than requested. + If unspecified, server will pick an appropriate default. + schema: + type: integer + format: int32 + - name: pageToken + in: query + description: |- + A token identifying a page of results the server should return. + Typically, this is the value of + [ListShelvesResponse.next_page_token][google.example.library.v1.ListShelvesResponse.next_page_token] + returned from the previous call to `ListShelves` method. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListShelvesResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - LibraryService + description: Creates a shelf, and returns the new Shelf. + operationId: LibraryService_CreateShelf + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Shelf' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Shelf' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/shelves/{shelf}: + get: + tags: + - LibraryService + description: Gets a shelf. Returns NOT_FOUND if the shelf does not exist. + operationId: LibraryService_GetShelf + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Shelf' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - LibraryService + description: Deletes a shelf. Returns NOT_FOUND if the shelf does not exist. + operationId: LibraryService_DeleteShelf + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/shelves/{shelf}/books: + get: + tags: + - LibraryService + description: |- + Lists books in a shelf. The order is unspecified but deterministic. Newly + created books will not necessarily be added to the end of this list. + Returns NOT_FOUND if the shelf does not exist. + operationId: LibraryService_ListBooks + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + - name: pageSize + in: query + description: |- + Requested page size. Server may return fewer books than requested. + If unspecified, server will pick an appropriate default. + schema: + type: integer + format: int32 + - name: pageToken + in: query + description: |- + A token identifying a page of results the server should return. + Typically, this is the value of + [ListBooksResponse.next_page_token][google.example.library.v1.ListBooksResponse.next_page_token]. + returned from the previous call to `ListBooks` method. + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListBooksResponse' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - LibraryService + description: Creates a book, and returns the new Book. + operationId: LibraryService_CreateBook + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/shelves/{shelf}/books/{book}: + get: + tags: + - LibraryService + description: Gets a book. Returns NOT_FOUND if the book does not exist. + operationId: LibraryService_GetBook + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + - name: book + in: path + description: The book id. + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + put: + tags: + - LibraryService + description: |- + Updates a book. Returns INVALID_ARGUMENT if the name of the book + is non-empty and does not equal the existing name. + operationId: LibraryService_UpdateBook + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + - name: book + in: path + description: The book id. + required: true + schema: + type: string + - name: name + in: query + description: The name of the book to update. + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - LibraryService + description: Deletes a book. Returns NOT_FOUND if the book does not exist. + operationId: LibraryService_DeleteBook + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + - name: book + in: path + description: The book id. + required: true + schema: + type: string + responses: + "200": + description: OK + content: {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/shelves/{shelf}/books/{book}:move: + post: + tags: + - LibraryService + description: |- + Moves a book to another shelf, and returns the new book. The book + id of the new book may not be the same as the original book. + operationId: LibraryService_MoveBook + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + - name: book + in: path + description: The book id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MoveBookRequest_Body' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/shelves/{shelf}:merge: + post: + tags: + - LibraryService + description: |- + Merges two shelves by adding all books from the shelf named + `other_shelf_name` to shelf `name`, and deletes + `other_shelf_name`. Returns the updated shelf. + The book ids of the moved books may not be the same as the original books. + + Returns NOT_FOUND if either shelf does not exist. + This call is a no-op if the specified shelves are the same. + operationId: LibraryService_MergeShelves + parameters: + - name: shelf + in: path + description: The shelf id. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MergeShelvesRequest_Body' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Shelf' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + Book: + required: + - name + type: object + properties: + name: + type: string + description: |- + The resource name of the book. + Book names have the form `shelves/{shelf_id}/books/{book_id}`. + The name is ignored when creating a book. + author: + type: string + description: The name of the book author. + title: + type: string + description: The title of the book. + read: + type: boolean + description: Value indicating whether the book has been read. + borrowTime: + readOnly: true + type: string + description: The previous borrowing timestamp. + format: date-time + createdAt: + readOnly: true + type: string + description: The creation date and time. + format: date-time + updatedAt: + readOnly: true + type: string + description: The last update date and time. + format: date-time + description: A single book in the library. + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + ListBooksResponse: + type: object + properties: + books: + type: array + items: + $ref: '#/components/schemas/Book' + description: The list of books. + nextPageToken: + type: string + description: |- + A token to retrieve next page of results. + Pass this value in the + [ListBooksRequest.page_token][google.example.library.v1.ListBooksRequest.page_token] + field in the subsequent call to `ListBooks` method to retrieve the next + page of results. + description: Response message for LibraryService.ListBooks. + ListShelvesResponse: + type: object + properties: + shelves: + type: array + items: + $ref: '#/components/schemas/Shelf' + description: The list of shelves. + nextPageToken: + type: string + description: |- + A token to retrieve next page of results. + Pass this value in the + [ListShelvesRequest.page_token][google.example.library.v1.ListShelvesRequest.page_token] + field in the subsequent call to `ListShelves` method to retrieve the next + page of results. + description: Response message for LibraryService.ListShelves. + MergeShelvesRequest_Body: + required: + - otherShelfName + type: object + properties: + otherShelfName: + type: string + description: The name of the shelf we're removing books from and deleting. + description: |- + Describes the shelf being removed (other_shelf_name) and updated + (name) in this merge. + MoveBookRequest_Body: + required: + - otherShelfName + type: object + properties: + otherShelfName: + type: string + description: The name of the destination shelf. + description: |- + Describes what book to move (name) and what shelf we're moving it + to (other_shelf_name). + Shelf: + required: + - name + type: object + properties: + name: + type: string + description: |- + The resource name of the shelf. + Shelf names have the form `shelves/{shelf_id}`. + The name is ignored when creating a shelf. + theme: + type: string + description: The theme of the shelf + nextSortAt: + readOnly: true + type: string + description: The next sorting date. + format: date + createdAt: + readOnly: true + type: string + description: The creation date and time. + format: date-time + updatedAt: + readOnly: true + type: string + description: The last update date and time. + format: date-time + description: A Shelf contains a collection of books with a theme. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: LibraryService diff --git a/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_default_response.yaml new file mode 100644 index 00000000..f465262f --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_default_response.yaml @@ -0,0 +1,97 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + text: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_fq_schema_naming.yaml new file mode 100644 index 00000000..f3762491 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_fq_schema_naming.yaml @@ -0,0 +1,97 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/tests.additional_bindings.message.v1.Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/tests.additional_bindings.message.v1.Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/google.rpc.Status' + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/tests.additional_bindings.message.v1.Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/google.rpc.Status' +components: + schemas: + google.protobuf.Any: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + google.rpc.Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/google.protobuf.Any' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + tests.additional_bindings.message.v1.Message: + type: object + properties: + messageId: + type: string + text: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_json.yaml new file mode 100644 index 00000000..9f40f3bf --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_json.yaml @@ -0,0 +1,97 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 1.2.3 +paths: + /v1/messages: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + text: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_string_enum.yaml new file mode 100644 index 00000000..f465262f --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_string_enum.yaml @@ -0,0 +1,97 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + text: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..f465262f --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/additional_bindings/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,97 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + text: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_default_response.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_default_response.yaml new file mode 100644 index 00000000..245ed47d --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_default_response.yaml @@ -0,0 +1,94 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + sub: + $ref: '#/components/schemas/Message_Sub' + subInput: + writeOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subOutput: + readOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subDesc: + allOf: + - $ref: '#/components/schemas/Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/Message_Sub' + description: test repeated, should not allof wrapped + Message_Sub: + type: object + properties: + content: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_fq_schema_naming.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_fq_schema_naming.yaml new file mode 100644 index 00000000..b480b3a2 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_fq_schema_naming.yaml @@ -0,0 +1,94 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/tests.allofwrap.message.v1.Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/tests.allofwrap.message.v1.Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/google.rpc.Status' +components: + schemas: + google.protobuf.Any: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + google.rpc.Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/google.protobuf.Any' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + tests.allofwrap.message.v1.Message: + type: object + properties: + sub: + $ref: '#/components/schemas/tests.allofwrap.message.v1.Message_Sub' + subInput: + writeOnly: true + allOf: + - $ref: '#/components/schemas/tests.allofwrap.message.v1.Message_Sub' + subOutput: + readOnly: true + allOf: + - $ref: '#/components/schemas/tests.allofwrap.message.v1.Message_Sub' + subDesc: + allOf: + - $ref: '#/components/schemas/tests.allofwrap.message.v1.Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/tests.allofwrap.message.v1.Message_Sub' + description: test repeated, should not allof wrapped + tests.allofwrap.message.v1.Message_Sub: + type: object + properties: + content: + type: string +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_json.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_json.yaml new file mode 100644 index 00000000..b7eac986 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_json.yaml @@ -0,0 +1,94 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 1.2.3 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + sub: + $ref: '#/components/schemas/Message_Sub' + subInput: + writeOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subOutput: + readOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subDesc: + allOf: + - $ref: '#/components/schemas/Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/Message_Sub' + description: test repeated, should not allof wrapped + Message_Sub: + type: object + properties: + content: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_string_enum.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_string_enum.yaml new file mode 100644 index 00000000..245ed47d --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_string_enum.yaml @@ -0,0 +1,94 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + sub: + $ref: '#/components/schemas/Message_Sub' + subInput: + writeOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subOutput: + readOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subDesc: + allOf: + - $ref: '#/components/schemas/Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/Message_Sub' + description: test repeated, should not allof wrapped + Message_Sub: + type: object + properties: + content: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..245ed47d --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/allofwrap/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,94 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + sub: + $ref: '#/components/schemas/Message_Sub' + subInput: + writeOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subOutput: + readOnly: true + allOf: + - $ref: '#/components/schemas/Message_Sub' + subDesc: + allOf: + - $ref: '#/components/schemas/Message_Sub' + description: this sub has a description + subs: + readOnly: true + type: array + items: + $ref: '#/components/schemas/Message_Sub' + description: test repeated, should not allof wrapped + Message_Sub: + type: object + properties: + content: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/bodymapping/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/bodymapping/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..1643daff --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/bodymapping/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,73 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + text: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..11073498 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/enumoptions/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,76 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + description: Messaging service + version: 0.0.1 +paths: + /v1/messages/{message_id}: + post: + tags: + - Messaging + operationId: Messaging_CreateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + - name: kind + in: query + schema: + type: integer + format: enum + requestBody: + content: + application/json: {} + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + kind: + type: integer + format: enum + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/jsonoptions/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/jsonoptions/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..d653131a --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/jsonoptions/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,123 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + description: Messaging service + version: 0.0.1 +paths: + /v1/messages/{messageId}: + post: + tags: + - Messaging + operationId: Messaging_CreateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + - name: notUsed + in: query + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{message_id}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + - name: not_used + in: query + schema: + type: string + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message2' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + bodyText: + type: string + notUsed: + type: string + Message2: + type: object + properties: + message_id: + type: string + body_text: + type: string + not_used: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/mapfields/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/mapfields/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..ea9a8b87 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/mapfields/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,144 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message_Body' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + AnotherMessage: + type: object + properties: + id: + type: string + label: + type: string + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + anotherMessage: + $ref: '#/components/schemas/AnotherMessage' + subMessage: + $ref: '#/components/schemas/Message_SubMessage' + stringList: + type: array + items: + type: string + subMessageList: + type: array + items: + $ref: '#/components/schemas/Message_SubMessage' + objectList: + type: array + items: + type: object + stringsMap: + type: object + additionalProperties: + type: string + subMessagesMap: + type: object + additionalProperties: + $ref: '#/components/schemas/Message_SubMessage' + objectsMap: + type: object + additionalProperties: + type: object + Message_Body: + type: object + properties: + anotherMessage: + $ref: '#/components/schemas/AnotherMessage' + subMessage: + $ref: '#/components/schemas/Message_SubMessage' + stringList: + type: array + items: + type: string + subMessageList: + type: array + items: + $ref: '#/components/schemas/Message_SubMessage' + objectList: + type: array + items: + type: object + stringsMap: + type: object + additionalProperties: + type: string + subMessagesMap: + type: object + additionalProperties: + $ref: '#/components/schemas/Message_SubMessage' + objectsMap: + type: object + additionalProperties: + type: object + Message_SubMessage: + type: object + properties: + id: + type: string + label: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/noannotations/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/noannotations/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..d00cbb93 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/noannotations/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,73 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging1 API + version: 0.0.1 +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging1 + operationId: Messaging1_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + id: + type: string + label: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging1 diff --git a/cmd/protoc-gen-openapi/examples/tests/openapiv3annotations/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/openapiv3annotations/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..b1e31f0f --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/openapiv3annotations/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,90 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Title from annotation + description: Description from annotation + contact: + name: Contact Name + url: https://github.com/google/gnostic + email: gnostic@google.com + license: + name: Apache License + url: https://github.com/google/gnostic/blob/master/LICENSE + version: Version from annotation +paths: + /v1/messages/{message_id}: + patch: + tags: + - Messaging1 + operationId: Messaging1_UpdateMessage + parameters: + - name: message_id + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + security: + - BasicAuth: [] +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + title: This is an overridden message schema title + type: object + properties: + id: + type: string + label: + title: this is an overriden field schema title + maxLength: 255 + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + securitySchemes: + BasicAuth: + type: http + scheme: basic +tags: + - name: Messaging1 diff --git a/cmd/protoc-gen-openapi/examples/tests/pathparams/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/pathparams/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..c6a745d5 --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/pathparams/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,142 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +paths: + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + - name: userId + in: query + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - Messaging + operationId: Messaging_CreateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message_Body' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/users/{userId}/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetUserMessage + parameters: + - name: userId + in: path + required: true + schema: + type: string + - name: messageId + in: path + required: true + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Message: + type: object + properties: + messageId: + type: string + userId: + type: string + content: + type: string + maybe: + type: string + Message_Body: + type: object + properties: + userId: + type: string + content: + type: string + maybe: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..fd31f07e --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/protobuftypes/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,588 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Messaging API + version: 0.0.1 +servers: + - url: https://foo.googleapi.com +paths: + /v1/messages: + get: + tags: + - Messaging + operationId: Messaging_ListMessages + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GoogleProtobufValue' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages/{messageId}: + get: + tags: + - Messaging + operationId: Messaging_GetMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + - name: stringType + in: query + schema: + type: string + - name: recursiveType.parentId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.childId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.parent.parentId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.parent.child.childId + in: query + schema: + type: integer + format: int32 + - name: embeddedType.messageId + in: query + schema: + type: string + - name: subType.messageId + in: query + schema: + type: string + - name: subType.subSubMessage.messageId + in: query + schema: + type: string + - name: subType.subSubMessage.integers + in: query + schema: + type: array + items: + type: integer + format: int32 + - name: repeatedType + in: query + schema: + type: array + items: + type: string + - name: valueType + in: query + description: Description of value + schema: + $ref: '#/components/schemas/GoogleProtobufValue' + - name: repeatedValueType + in: query + description: Description of repeated value + schema: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufValue' + - name: boolValueType + in: query + schema: + type: boolean + - name: bytesValueType + in: query + schema: + type: string + format: bytes + - name: int32ValueType + in: query + schema: + type: integer + format: int32 + - name: uint32ValueType + in: query + schema: + type: integer + format: uint32 + - name: stringValueType + in: query + schema: + type: string + - name: int64ValueType + in: query + schema: + type: string + - name: uint64ValueType + in: query + schema: + type: string + - name: floatValueType + in: query + schema: + type: number + format: float + - name: doubleValueType + in: query + schema: + type: number + format: double + - name: timestampType + in: query + schema: + type: string + format: date-time + - name: durationType + in: query + schema: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: Represents a a duration between -315,576,000,000s and 315,576,000,000s (around 10000 years). Precision is in nanoseconds. 1 nanosecond is represented as 0.000000001s + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - Messaging + operationId: Messaging_CreateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Message_Body' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Message' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + patch: + tags: + - Messaging + operationId: Messaging_UpdateMessage + parameters: + - name: messageId + in: path + required: true + schema: + type: string + - name: stringType + in: query + schema: + type: string + - name: recursiveType.parentId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.childId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.parent.parentId + in: query + schema: + type: integer + format: int32 + - name: recursiveType.child.parent.child.childId + in: query + schema: + type: integer + format: int32 + - name: embeddedType.messageId + in: query + schema: + type: string + - name: subType.messageId + in: query + schema: + type: string + - name: subType.subSubMessage.messageId + in: query + schema: + type: string + - name: subType.subSubMessage.integers + in: query + schema: + type: array + items: + type: integer + format: int32 + - name: repeatedType + in: query + schema: + type: array + items: + type: string + - name: valueType + in: query + description: Description of value + schema: + $ref: '#/components/schemas/GoogleProtobufValue' + - name: repeatedValueType + in: query + description: Description of repeated value + schema: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufValue' + - name: boolValueType + in: query + schema: + type: boolean + - name: bytesValueType + in: query + schema: + type: string + format: bytes + - name: int32ValueType + in: query + schema: + type: integer + format: int32 + - name: uint32ValueType + in: query + schema: + type: integer + format: uint32 + - name: stringValueType + in: query + schema: + type: string + - name: int64ValueType + in: query + schema: + type: string + - name: uint64ValueType + in: query + schema: + type: string + - name: floatValueType + in: query + schema: + type: number + format: float + - name: doubleValueType + in: query + schema: + type: number + format: double + - name: timestampType + in: query + schema: + type: string + format: date-time + - name: durationType + in: query + schema: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + description: Represents a a duration between -315,576,000,000s and 315,576,000,000s (around 10000 years). Precision is in nanoseconds. 1 nanosecond is represented as 0.000000001s + requestBody: + content: + application/json: + schema: + type: object + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + type: object + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/messages:csv: + get: + tags: + - Messaging + description: |- + OpenAPI does not allow requestBody in GET operations. + But it should not convert it to query params either. + operationId: Messaging_ListMessagesCSV + responses: + "200": + description: OK + content: + '*/*': {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + post: + tags: + - Messaging + operationId: Messaging_CreateMessagesFromCSV + requestBody: + content: + application/json: + schema: + type: string + required: true + responses: + "200": + description: OK + content: + '*/*': {} + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + GoogleProtobufValue: + description: Represents a dynamically typed value which can be either null, a number, a string, a boolean, a recursive struct value, or a list of values. + Message: + type: object + properties: + messageId: + type: string + stringType: + type: string + recursiveType: + $ref: '#/components/schemas/RecursiveParent' + embeddedType: + $ref: '#/components/schemas/Message_EmbMessage' + subType: + $ref: '#/components/schemas/SubMessage' + repeatedType: + type: array + items: + type: string + repeatedSubType: + type: array + items: + $ref: '#/components/schemas/SubMessage' + repeatedRecursiveType: + type: array + items: + $ref: '#/components/schemas/RecursiveParent' + mapType: + type: object + additionalProperties: + type: string + body: + type: object + media: + type: array + items: + type: object + valueType: + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value + repeatedValueType: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of repeated value + boolValueType: + type: boolean + bytesValueType: + type: string + format: bytes + int32ValueType: + type: integer + format: int32 + uint32ValueType: + type: integer + format: uint32 + stringValueType: + type: string + int64ValueType: + type: string + uint64ValueType: + type: string + floatValueType: + type: number + format: float + doubleValueType: + type: number + format: double + timestampType: + type: string + format: date-time + durationType: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + Message_Body: + type: object + properties: + stringType: + type: string + recursiveType: + $ref: '#/components/schemas/RecursiveParent' + embeddedType: + $ref: '#/components/schemas/Message_EmbMessage' + subType: + $ref: '#/components/schemas/SubMessage' + repeatedType: + type: array + items: + type: string + repeatedSubType: + type: array + items: + $ref: '#/components/schemas/SubMessage' + repeatedRecursiveType: + type: array + items: + $ref: '#/components/schemas/RecursiveParent' + mapType: + type: object + additionalProperties: + type: string + body: + type: object + media: + type: array + items: + type: object + valueType: + allOf: + - $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of value + repeatedValueType: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufValue' + description: Description of repeated value + boolValueType: + type: boolean + bytesValueType: + type: string + format: bytes + int32ValueType: + type: integer + format: int32 + uint32ValueType: + type: integer + format: uint32 + stringValueType: + type: string + int64ValueType: + type: string + uint64ValueType: + type: string + floatValueType: + type: number + format: float + doubleValueType: + type: number + format: double + timestampType: + type: string + format: date-time + durationType: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + Message_EmbMessage: + type: object + properties: + messageId: + type: string + RecursiveChild: + type: object + properties: + childId: + type: integer + format: int32 + parent: + $ref: '#/components/schemas/RecursiveParent' + RecursiveParent: + type: object + properties: + parentId: + type: integer + format: int32 + child: + $ref: '#/components/schemas/RecursiveChild' + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + SubMessage: + type: object + properties: + messageId: + type: string + subSubMessage: + $ref: '#/components/schemas/SubSubMessage' + SubSubMessage: + type: object + properties: + messageId: + type: string + integers: + type: array + items: + type: integer + format: int32 +tags: + - name: Messaging diff --git a/cmd/protoc-gen-openapi/examples/tests/rpctypes/openapi_wildcard_body_dedup.yaml b/cmd/protoc-gen-openapi/examples/tests/rpctypes/openapi_wildcard_body_dedup.yaml new file mode 100644 index 00000000..077d2b5a --- /dev/null +++ b/cmd/protoc-gen-openapi/examples/tests/rpctypes/openapi_wildcard_body_dedup.yaml @@ -0,0 +1,54 @@ +# Generated with protoc-gen-openapi +# https://github.com/google/gnostic/tree/master/cmd/protoc-gen-openapi + +openapi: 3.0.3 +info: + title: Status API + version: 0.0.1 +paths: + /v1/status: + get: + tags: + - Status + operationId: Status_GetStatus + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' +components: + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' +tags: + - name: Status diff --git a/cmd/protoc-gen-openapi/generator/generator.go b/cmd/protoc-gen-openapi/generator/generator.go index e548ab21..2b21fc2f 100644 --- a/cmd/protoc-gen-openapi/generator/generator.go +++ b/cmd/protoc-gen-openapi/generator/generator.go @@ -20,6 +20,7 @@ import ( "log" "net/url" "regexp" + "slices" "sort" "strings" @@ -35,15 +36,16 @@ import ( ) type Configuration struct { - Version *string - Title *string - Description *string - Naming *string - FQSchemaNaming *bool - EnumType *string - CircularDepth *int - DefaultResponse *bool - OutputMode *string + Version *string + Title *string + Description *string + Naming *string + FQSchemaNaming *bool + EnumType *string + CircularDepth *int + DefaultResponse *bool + OutputMode *string + WildcardBodyDedup *bool } const ( @@ -620,7 +622,7 @@ func (g *OpenAPIv3Generator) buildOperationV3( if bodyField == "*" { // Pass the entire request message as the request body. - requestSchema = g.reflect.schemaOrReferenceForMessage(inputMessage.Desc) + requestSchema = g.createWildcardBodyRequestSchema(d, inputMessage, coveredParameters) } else { // If body refers to a message field, use that type. @@ -668,6 +670,175 @@ func (g *OpenAPIv3Generator) buildOperationV3( return op, path } +// buildAndAddSchemaForMessage builds a schema for a message, optionally excluding +// specific fields, adds it to the document, and returns a reference to it. +func (g *OpenAPIv3Generator) buildAndAddSchemaForMessage(d *v3.Document, message *protogen.Message, schemaName, description string, excludedFields []string, ref string) *v3.SchemaOrReference { + definitionProperties := &v3.Properties{ + AdditionalProperties: make([]*v3.NamedSchemaOrReference, 0), + } + + var required []string + for _, field := range message.Fields { + fieldName := string(field.Desc.Name()) + + // Skip fields that should be excluded + if contains(excludedFields, fieldName) { + continue + } + + // Get the field description from the comments + fieldDescription := g.filterCommentString(field.Comments.Leading) + + // Check the field annotations + inputOnly := false + outputOnly := false + extension := proto.GetExtension(field.Desc.Options(), annotations.E_FieldBehavior) + if extension != nil { + switch v := extension.(type) { + case []annotations.FieldBehavior: + for _, vv := range v { + switch vv { + case annotations.FieldBehavior_OUTPUT_ONLY: + outputOnly = true + case annotations.FieldBehavior_INPUT_ONLY: + inputOnly = true + case annotations.FieldBehavior_REQUIRED: + required = append(required, g.reflect.formatFieldName(field.Desc)) + } + } + default: + log.Printf("unsupported extension type %T", extension) + } + } + + fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc) + if fieldSchema == nil { + continue + } + + // If this field has siblings and is a $ref now, create a new schema use `allOf` to wrap it + wrapperNeeded := inputOnly || outputOnly || fieldDescription != "" + if wrapperNeeded { + if _, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Reference); ok { + fieldSchema = &v3.SchemaOrReference{Oneof: &v3.SchemaOrReference_Schema{Schema: &v3.Schema{ + AllOf: []*v3.SchemaOrReference{fieldSchema}, + }}} + } + } + + if schema, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Schema); ok { + schema.Schema.Description = fieldDescription + schema.Schema.ReadOnly = outputOnly + schema.Schema.WriteOnly = inputOnly + + // Merge any `Property` annotations with the current + extProperty := proto.GetExtension(field.Desc.Options(), v3.E_Property) + if extProperty != nil { + proto.Merge(schema.Schema, extProperty.(*v3.Schema)) + } + } + + definitionProperties.AdditionalProperties = append( + definitionProperties.AdditionalProperties, + &v3.NamedSchemaOrReference{ + Name: g.reflect.formatFieldName(field.Desc), + Value: fieldSchema, + }, + ) + } + + schema := &v3.Schema{ + Type: "object", + Description: description, + Properties: definitionProperties, + Required: required, + } + + // Merge any `Schema` annotations from the original message + extSchema := proto.GetExtension(message.Desc.Options(), v3.E_Schema) + if extSchema != nil { + proto.Merge(schema, extSchema.(*v3.Schema)) + } + + // Add the schema to the document + g.addSchemaToDocumentV3(d, &v3.NamedSchemaOrReference{ + Name: schemaName, + Value: &v3.SchemaOrReference{ + Oneof: &v3.SchemaOrReference_Schema{ + Schema: schema, + }, + }, + }) + + // Return reference to the schema + return &v3.SchemaOrReference{ + Oneof: &v3.SchemaOrReference_Reference{ + Reference: &v3.Reference{XRef: ref}, + }, + } +} + +// createWildcardBodyRequestSchema sets or creates the body schema for an +// OpenAPI where the GRPC Method has a wildcard request body (body: "*"). +// +// According to the the rules for [transcoding HTTP/JSON to gRPC] +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. +// +// For OpenAPI, this means that any operation based on a GRPC method with a +// wildcard body needs a request body schema that doesn't overlap fields and +// path parameters. +// +// This function looks for path parameters that correspond to body fields and, +// when it finds them, generates a replacement request body schema of the +// format: +// +// _Body +// +// Where the overlapping body fields are removed. +// +// [transcoding HTTP/JSON to gRPC]: https://docs.cloud.google.com/endpoints/docs/grpc/transcoding#use_wildcard_in_body +func (g *OpenAPIv3Generator) createWildcardBodyRequestSchema(d *v3.Document, message *protogen.Message, pathParameters []string) *v3.SchemaOrReference { + // If dedup is disabled, just return a reference to the full message schema. + if !*g.conf.WildcardBodyDedup { + return g.reflect.schemaOrReferenceForMessage(message.Desc) + } + + messageFields := make(map[string]bool) + for _, field := range message.Fields { + messageFields[string(field.Desc.Name())] = true + } + // filter path parameters which are wildcard or not in the request type. + excludedFields := slices.DeleteFunc(slices.Clone(pathParameters), func(s string) bool { + return s == "*" || !messageFields[s] + }) + + // if there's nothing to remove, return a ref to the request schema, and + // we're done. + if len(excludedFields) == 0 { + return g.reflect.schemaOrReferenceForMessage(message.Desc) + } + + requestTypeName := g.reflect.formatMessageName(message.Desc) + schemaName := requestTypeName + "_Body" + + ref := "#/components/schemas/" + schemaName + + if contains(g.generatedSchemas, schemaName) { + // already generated this schema for another op, so reuse it + return &v3.SchemaOrReference{ + Oneof: &v3.SchemaOrReference_Reference{ + Reference: &v3.Reference{XRef: ref}, + }, + } + } + + messageDescription := g.filterCommentString(message.Comments.Leading) + return g.buildAndAddSchemaForMessage(d, message, schemaName, messageDescription, excludedFields, ref) +} + // addOperationToDocumentV3 adds an operation to the specified path/method. func (g *OpenAPIv3Generator) addOperationToDocumentV3(d *v3.Document, op *v3.Operation, path string, methodName string) { var selectedPathItem *v3.NamedPathItem @@ -814,95 +985,7 @@ func (g *OpenAPIv3Generator) addSchemasForMessagesToDocumentV3(d *v3.Document, m continue } - // Build an array holding the fields of the message. - definitionProperties := &v3.Properties{ - AdditionalProperties: make([]*v3.NamedSchemaOrReference, 0), - } - - var required []string - for _, field := range message.Fields { - // Get the field description from the comments. - description := g.filterCommentString(field.Comments.Leading) - // Check the field annotations to see if this is a readonly or writeonly field. - inputOnly := false - outputOnly := false - extension := proto.GetExtension(field.Desc.Options(), annotations.E_FieldBehavior) - if extension != nil { - switch v := extension.(type) { - case []annotations.FieldBehavior: - for _, vv := range v { - switch vv { - case annotations.FieldBehavior_OUTPUT_ONLY: - outputOnly = true - case annotations.FieldBehavior_INPUT_ONLY: - inputOnly = true - case annotations.FieldBehavior_REQUIRED: - required = append(required, g.reflect.formatFieldName(field.Desc)) - } - } - default: - log.Printf("unsupported extension type %T", extension) - } - } - - // The field is either described by a reference or a schema. - fieldSchema := g.reflect.schemaOrReferenceForField(field.Desc) - if fieldSchema == nil { - continue - } - - // If this field has siblings and is a $ref now, create a new schema use `allOf` to wrap it - wrapperNeeded := inputOnly || outputOnly || description != "" - if wrapperNeeded { - if _, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Reference); ok { - fieldSchema = &v3.SchemaOrReference{Oneof: &v3.SchemaOrReference_Schema{Schema: &v3.Schema{ - AllOf: []*v3.SchemaOrReference{fieldSchema}, - }}} - } - } - - if schema, ok := fieldSchema.Oneof.(*v3.SchemaOrReference_Schema); ok { - schema.Schema.Description = description - schema.Schema.ReadOnly = outputOnly - schema.Schema.WriteOnly = inputOnly - - // Merge any `Property` annotations with the current - extProperty := proto.GetExtension(field.Desc.Options(), v3.E_Property) - if extProperty != nil { - proto.Merge(schema.Schema, extProperty.(*v3.Schema)) - } - } - - definitionProperties.AdditionalProperties = append( - definitionProperties.AdditionalProperties, - &v3.NamedSchemaOrReference{ - Name: g.reflect.formatFieldName(field.Desc), - Value: fieldSchema, - }, - ) - } - - schema := &v3.Schema{ - Type: "object", - Description: messageDescription, - Properties: definitionProperties, - Required: required, - } - - // Merge any `Schema` annotations with the current - extSchema := proto.GetExtension(message.Desc.Options(), v3.E_Schema) - if extSchema != nil { - proto.Merge(schema, extSchema.(*v3.Schema)) - } - - // Add the schema to the components.schema list. - g.addSchemaToDocumentV3(d, &v3.NamedSchemaOrReference{ - Name: schemaName, - Value: &v3.SchemaOrReference{ - Oneof: &v3.SchemaOrReference_Schema{ - Schema: schema, - }, - }, - }) + ref := "#/components/schemas/" + schemaName + g.buildAndAddSchemaForMessage(d, message, schemaName, messageDescription, nil, ref) } } diff --git a/cmd/protoc-gen-openapi/main.go b/cmd/protoc-gen-openapi/main.go index 75405aed..722474c6 100644 --- a/cmd/protoc-gen-openapi/main.go +++ b/cmd/protoc-gen-openapi/main.go @@ -29,15 +29,16 @@ var flags flag.FlagSet func main() { conf := generator.Configuration{ - Version: flags.String("version", "0.0.1", "version number text, e.g. 1.2.3"), - Title: flags.String("title", "", "name of the API"), - Description: flags.String("description", "", "description of the API"), - Naming: flags.String("naming", "json", `naming convention. Use "proto" for passing names directly from the proto files`), - FQSchemaNaming: flags.Bool("fq_schema_naming", false, `schema naming convention. If "true", generates fully-qualified schema names by prefixing them with the proto message package name`), - EnumType: flags.String("enum_type", "integer", `type for enum serialization. Use "string" for string-based serialization`), - CircularDepth: flags.Int("depth", 2, "depth of recursion for circular messages"), - DefaultResponse: flags.Bool("default_response", true, `add default response. If "true", automatically adds a default response to operations which use the google.rpc.Status message. Useful if you use envoy or grpc-gateway to transcode as they use this type for their default error responses.`), - OutputMode: flags.String("output_mode", "merged", `output generation mode. By default, a single openapi.yaml is generated at the out folder. Use "source_relative' to generate a separate '[inputfile].openapi.yaml' next to each '[inputfile].proto'.`), + Version: flags.String("version", "0.0.1", "version number text, e.g. 1.2.3"), + Title: flags.String("title", "", "name of the API"), + Description: flags.String("description", "", "description of the API"), + Naming: flags.String("naming", "json", `naming convention. Use "proto" for passing names directly from the proto files`), + FQSchemaNaming: flags.Bool("fq_schema_naming", false, `schema naming convention. If "true", generates fully-qualified schema names by prefixing them with the proto message package name`), + EnumType: flags.String("enum_type", "integer", `type for enum serialization. Use "string" for string-based serialization`), + CircularDepth: flags.Int("depth", 2, "depth of recursion for circular messages"), + DefaultResponse: flags.Bool("default_response", true, `add default response. If "true", automatically adds a default response to operations which use the google.rpc.Status message. Useful if you use envoy or grpc-gateway to transcode as they use this type for their default error responses.`), + OutputMode: flags.String("output_mode", "merged", `output generation mode. By default, a single openapi.yaml is generated at the out folder. Use "source_relative' to generate a separate '[inputfile].openapi.yaml' next to each '[inputfile].proto'.`), + WildcardBodyDedup: flags.Bool("wildcard_body_dedup", false, `removes path parameter overlap from wildcard body schemas. If "true", generates a separate schema for an operation's request body without the overlapping fields.`), } opts := protogen.Options{ diff --git a/cmd/protoc-gen-openapi/plugin_test.go b/cmd/protoc-gen-openapi/plugin_test.go index 767e9742..b92bba6a 100644 --- a/cmd/protoc-gen-openapi/plugin_test.go +++ b/cmd/protoc-gen-openapi/plugin_test.go @@ -26,6 +26,9 @@ import ( "testing" ) +// Set GNOSTIC_REGEN_FIXTURES to true to regenerate test fixtures +var regenerateFixtures = strings.ToLower(os.Getenv("GNOSTIC_REGEN_FIXTURES")) == "true" + var openapiTests = []struct { name string path string @@ -45,11 +48,7 @@ var openapiTests = []struct { {name: "Additional Bindings", path: "examples/tests/additional_bindings/", protofile: "message.proto"}, } -// Set this to true to generate/overwrite the fixtures. Make sure you set it back -// to false before you commit it. -const GENERATE_FIXTURES = false - -const TEMP_FILE = "openapi.yaml" +const TempFile = "openapi.yaml" func CopyFixture(result, fixture string) error { in, err := os.Open(result) @@ -71,19 +70,12 @@ func CopyFixture(result, fixture string) error { return out.Close() } -func TestGenerateFixturesIsFalse(t *testing.T) { - // This is here to ensure the PR builds fail if someone - // accidentally commits GENERATE_FIXTURES = true - if GENERATE_FIXTURES { - t.Fatalf("GENERATE_FIXTURES is true") - } -} - func TestOpenAPIProtobufNaming(t *testing.T) { for _, tt := range openapiTests { fixture := path.Join(tt.path, "openapi.yaml") if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { - if !GENERATE_FIXTURES { + if !regenerateFixtures { + t.Errorf("Fixture does not exist: %s", fixture) continue } } @@ -98,31 +90,29 @@ func TestOpenAPIProtobufNaming(t *testing.T) { if err != nil { t.Fatalf("protoc failed: %+v", err) } - if GENERATE_FIXTURES { - err := CopyFixture(TEMP_FILE, fixture) + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) if err != nil { t.Fatalf("Can't generate fixture: %+v", err) } } else { // Verify that the generated spec matches our expected version. - err = exec.Command("diff", TEMP_FILE, fixture).Run() + err = exec.Command("diff", TempFile, fixture).Run() if err != nil { t.Fatalf("Diff failed: %+v", err) } } // if the test succeeded, clean up - os.Remove(TEMP_FILE) + os.Remove(TempFile) }) } } func TestOpenAPIFQSchemaNaming(t *testing.T) { - // create temp directory for source_relative outputs - tempDir := "tmp" + tempDir := t.TempDir() if err := os.MkdirAll(path.Join(tempDir, "examples"), os.ModePerm); err != nil { t.Fatalf("create tmp directory %+v", err) } - defer os.RemoveAll(tempDir) // run protoc with source_relative options on all examples args := []string{ "-I", "../../", @@ -141,7 +131,7 @@ func TestOpenAPIFQSchemaNaming(t *testing.T) { for _, tt := range openapiTests { fixture := path.Join(tt.path, "openapi_fq_schema_naming.yaml") if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { - if !GENERATE_FIXTURES { + if !regenerateFixtures { continue } } @@ -156,14 +146,14 @@ func TestOpenAPIFQSchemaNaming(t *testing.T) { if err != nil { t.Fatalf("protoc failed: %+v", err) } - if GENERATE_FIXTURES { - err := CopyFixture(TEMP_FILE, fixture) + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) if err != nil { t.Fatalf("Can't generate fixture: %+v", err) } } else { // Verify that the generated spec matches our expected version. - err = exec.Command("diff", TEMP_FILE, fixture).Run() + err = exec.Command("diff", TempFile, fixture).Run() if err != nil { t.Fatalf("Diff failed: %+v", err) } @@ -176,7 +166,7 @@ func TestOpenAPIFQSchemaNaming(t *testing.T) { } } // if the test succeeded, clean up - os.Remove(TEMP_FILE) + os.Remove(TempFile) }) } } @@ -185,7 +175,7 @@ func TestOpenAPIJSONNaming(t *testing.T) { for _, tt := range openapiTests { fixture := path.Join(tt.path, "openapi_json.yaml") if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { - if !GENERATE_FIXTURES { + if !regenerateFixtures { continue } } @@ -200,20 +190,20 @@ func TestOpenAPIJSONNaming(t *testing.T) { if err != nil { t.Fatalf("protoc failed: %+v", err) } - if GENERATE_FIXTURES { - err := CopyFixture(TEMP_FILE, fixture) + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) if err != nil { t.Fatalf("Can't generate fixture: %+v", err) } } else { // Verify that the generated spec matches our expected version. - err = exec.Command("diff", TEMP_FILE, fixture).Run() + err = exec.Command("diff", TempFile, fixture).Run() if err != nil { t.Fatalf("Diff failed: %+v", err) } } // if the test succeeded, clean up - os.Remove(TEMP_FILE) + os.Remove(TempFile) }) } } @@ -222,7 +212,7 @@ func TestOpenAPIStringEnums(t *testing.T) { for _, tt := range openapiTests { fixture := path.Join(tt.path, "openapi_string_enum.yaml") if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { - if !GENERATE_FIXTURES { + if !regenerateFixtures { continue } } @@ -237,20 +227,20 @@ func TestOpenAPIStringEnums(t *testing.T) { if err != nil { t.Fatalf("protoc failed: %+v", err) } - if GENERATE_FIXTURES { - err := CopyFixture(TEMP_FILE, fixture) + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) if err != nil { t.Fatalf("Can't generate fixture: %+v", err) } } else { // Verify that the generated spec matches our expected version. - err = exec.Command("diff", TEMP_FILE, fixture).Run() + err = exec.Command("diff", TempFile, fixture).Run() if err != nil { t.Fatalf("diff failed: %+v", err) } } // if the test succeeded, clean up - os.Remove(TEMP_FILE) + os.Remove(TempFile) }) } } @@ -259,7 +249,7 @@ func TestOpenAPIDefaultResponse(t *testing.T) { for _, tt := range openapiTests { fixture := path.Join(tt.path, "openapi_default_response.yaml") if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { - if !GENERATE_FIXTURES { + if !regenerateFixtures { continue } } @@ -274,20 +264,57 @@ func TestOpenAPIDefaultResponse(t *testing.T) { if err != nil { t.Fatalf("protoc failed: %+v", err) } - if GENERATE_FIXTURES { - err := CopyFixture(TEMP_FILE, fixture) + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) + if err != nil { + t.Fatalf("Can't generate fixture: %+v", err) + } + } else { + // Verify that the generated spec matches our expected version. + err = exec.Command("diff", TempFile, fixture).Run() + if err != nil { + t.Fatalf("diff failed: %+v", err) + } + } + // if the test succeeded, clean up + os.Remove(TempFile) + }) + } +} + +func TestOpenAPIWildcardBodyDedup(t *testing.T) { + for _, tt := range openapiTests { + fixture := path.Join(tt.path, "openapi_wildcard_body_dedup.yaml") + if _, err := os.Stat(fixture); errors.Is(err, os.ErrNotExist) { + if !regenerateFixtures { + continue + } + } + t.Run(tt.name, func(t *testing.T) { + // Run protoc and the protoc-gen-openapi plugin to generate an OpenAPI spec with wildcard body dedup. + err := exec.Command("protoc", + "-I", "../../", + "-I", "../../third_party", + "-I", "examples", + path.Join(tt.path, tt.protofile), + "--openapi_out=wildcard_body_dedup=true:.").Run() + if err != nil { + t.Fatalf("protoc failed: %+v", err) + } + if regenerateFixtures { + err := CopyFixture(TempFile, fixture) if err != nil { t.Fatalf("Can't generate fixture: %+v", err) } } else { // Verify that the generated spec matches our expected version. - err = exec.Command("diff", TEMP_FILE, fixture).Run() + err = exec.Command("diff", TempFile, fixture).Run() if err != nil { t.Fatalf("diff failed: %+v", err) } } // if the test succeeded, clean up - os.Remove(TEMP_FILE) + os.Remove(TempFile) }) } }