From 3fd0f91aa3de46534a1aef88ffee6bc0b8a06432 Mon Sep 17 00:00:00 2001 From: Tim Quinn Date: Wed, 7 Aug 2024 14:04:38 -0500 Subject: [PATCH] Add back and enhance the page describing OpenAPI generation for Helidon 4 (#9052) --- docs/etc/petstorex.yaml | 794 ++++++++++++++++++ .../main/asciidoc/includes/attributes.adoc | 2 +- .../includes/openapi/openapi-generator.adoc | 51 +- .../mp/openapi/openapi-generator.adoc | 34 +- .../se/openapi/openapi-generator.adoc | 271 +++++- .../mp/openapi/OpenApiGeneratorSnippets.java | 90 ++ .../se/openapi/OpenApiGeneratorSnippets.java | 439 ++++++++++ 7 files changed, 1652 insertions(+), 29 deletions(-) create mode 100644 docs/etc/petstorex.yaml create mode 100644 docs/src/main/java/io/helidon/docs/mp/openapi/OpenApiGeneratorSnippets.java create mode 100644 docs/src/main/java/io/helidon/docs/se/openapi/OpenApiGeneratorSnippets.java diff --git a/docs/etc/petstorex.yaml b/docs/etc/petstorex.yaml new file mode 100644 index 00000000000..a44236ae73c --- /dev/null +++ b/docs/etc/petstorex.yaml @@ -0,0 +1,794 @@ +# +# Copyright (c) 2024 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +openapi: 3.0.0 +servers: + - url: 'https://petstore3.swagger.io/api/v3' +info: + description: >- + This is a sample server Petstore server. For this sample, you can use the api key + `special-key` to test the authorization filters. + version: 1.0.0 + title: OpenAPI Petstore + license: + name: Apache-2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0.html' +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + summary: Add a new pet to the store + description: '' + operationId: addPet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + summary: Update an existing pet + description: '' + operationId: updatePet + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + security: + - petstore_auth: + - 'read:pets' + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: >- + Multiple tags can be provided with comma separated strings. Use tag1, + tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + security: + - petstore_auth: + - 'read:pets' + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + '/pet/{petId}/uploadImage': + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - 'write:pets' + - 'read:pets' + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + additionalMetadata: + description: Additional data to pass to server + type: string + file: + description: file to upload + type: string + format: binary + /pet/info/cookie/{label}: + get: + tags: + - pet + summary: passes params via cookie + description: '' + operationId: petInfoWithCookies + parameters: + - name: petId + in: cookie + description: ID of pet to get + required: true + schema: + type: integer + format: int64 + - name: transactionId + in: cookie + description: ID of transaction + required: true + schema: + type: integer + format: int64 + - name: label + in: path + description: general-purpose label + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: '' + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + '/store/order/{orderId}': + get: + tags: + - store + summary: Find purchase order by ID + description: >- + For valid response try integer IDs with value <= 5 or > 10. Other values + will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of pet that needs to be fetched + required: true + schema: + type: integer + format: int64 + minimum: 1 + maximum: 5 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: >- + For valid response try integer IDs with value < 1000. Anything above + 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithArray: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithArrayInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: '' + operationId: createUsersWithListInput + responses: + default: + description: successful operation + security: + - api_key: [] + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + Set-Cookie: + description: >- + Cookie authentication key for use with the `api_key` + apiKey authentication. + schema: + type: string + example: AUTH_KEY=abcde12345; Path=/; HttpOnly + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + responses: + default: + description: successful operation + security: + - api_key: [] + '/user/{username}': + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Updated user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + security: + - api_key: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found + security: + - api_key: [] +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog' + scopes: + 'write:pets': modify pets in your account + 'read:pets': read your pets + api_key: + type: apiKey + name: api_key + in: header + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + xml: + name: Order + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + xml: + name: Category + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + xml: + name: User + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: Tag + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + xml: + name: photoUrl + wrapped: true + items: + type: string + tags: + type: array + xml: + name: tag + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: Pet + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string diff --git a/docs/src/main/asciidoc/includes/attributes.adoc b/docs/src/main/asciidoc/includes/attributes.adoc index 520f68b7226..e1c80e22389 100644 --- a/docs/src/main/asciidoc/includes/attributes.adoc +++ b/docs/src/main/asciidoc/includes/attributes.adoc @@ -281,7 +281,7 @@ endif::[] :micrometer-javadoc-registry-prometheus-base-url: {micrometer-javadoc-base-url}/micrometer-registry-prometheus/{version-lib-micrometer}/io/micrometer/prometheus // OpenAPI generator -:openapi-generator-version: 6.2.1 +:openapi-generator-version: 7.6.0 :openapi-generator-tool-base-url: https://github.com/OpenAPITools/openapi-generator :openapi-generator-site-version: v{openapi-generator-version} diff --git a/docs/src/main/asciidoc/includes/openapi/openapi-generator.adoc b/docs/src/main/asciidoc/includes/openapi/openapi-generator.adoc index e75a4ee503d..1680dc60707 100644 --- a/docs/src/main/asciidoc/includes/openapi/openapi-generator.adoc +++ b/docs/src/main/asciidoc/includes/openapi/openapi-generator.adoc @@ -19,27 +19,16 @@ :description: Helidon {flavor-uc} OpenAPI Generator :keywords: helidon, {flavor-lc}, openapi, generator :feature-name: Helidon {flavor-uc} OpenAPI Generator -// DO NOT CHANGE THE FOLLOWING - it's used as a minimum release that will not normally change with new releases of the OpenAPI generator -:first-version-with-strong-helidon-support: 6.2.1 -// Update the following when it is convenient to keep pace with the latest releases of the OpenAPITools generator // end::preamble[] // tag::intro[] -== Contents - -- <> -- <> -- <> -- <> -- <> - == Overview The link:{openapi-spec-url}[OpenAPI specification] provides a standard way to express RESTful APIs. Separately, the link:{openapi-generator-tool-site-url}[OpenAPI generator] project has created a powerful code generator tool which accepts an OpenAPI document and generates client and server code for many languages and frameworks. The Helidon team contributes to this tool to ensure that it provides strong support for Helidon {flavor-uc} clients and servers. As a result, you can use the generator to create code that fits smoothly into your Helidon applications. -The OpenAPI generator release {first-version-with-strong-helidon-support} gained particularly strong support for Helidon. -This document applies to that release and later ones. + +Use the OpenAPI generator release {openapi-generator-version} or later which this document describes. In the vocabulary of the tool, there are two _generators_ for Helidon: @@ -81,7 +70,7 @@ The rest of this document walks you through <> each te == Maven Coordinates Your project does not need any dependencies on the OpenAPI generator. -To use the OpenAPI generator plug-in to generate or regenerate files during your project build, add the following to your project's `pom.xml` file to declare the plug-in. Choose whichever version of the generator plug-in meets your needs as long as it is at least {first-version-with-strong-helidon-support}. +To use the OpenAPI generator plug-in to generate or regenerate files during your project build, add the following to your project's `pom.xml` file to declare the plug-in. Choose whichever version of the generator plug-in meets your needs as long as it is at least {openapi-generator-version}. [source,xml,subs="+attributes,+macros"] .Declaring the OpenAPI Generator Plug-in @@ -226,16 +215,16 @@ You must specify the following options: | `java-helidon-server` + `java-helidon-client` -|`--library` + -`` +|`--library` | {nbsp} +| `` |Library you want to use |`mp` + `se` |=== === Recommended Settings for the OpenAPI Generator -Your project might have different needs, but in general we advise developers to use the following settings when using the OpenAPI generator. +Your project might have different needs, but in general we advise developers to use the following settings when using the OpenAPI generator, both from the command line and using the Maven plug-in. .Recommended OpenAPI Generator Additional Properties @@ -270,6 +259,10 @@ Your project might have different needs, but in general we advise developers to | `artifactVersion` | Artifact version in the generated `pom.xml` | `1.0.0` + +| `useAbstractClass` +| Generate server abstract classes instead of interfaces. Setting to `true` generates significantly more helpful code. +| `false` |=== [NOTE] @@ -308,12 +301,13 @@ Among the many configuration settings available to you, some you should particul |Version of Helidon for which to generate the files | {nbsp} -|`2.5.2` +|Latest published Helidon release * a|Affects: * Helidon version for the `` * Dependencies (`javax` vs. `jakarta`) * `java import` statements in generated code (`javax` vs. `jakarta`) +* Which Helidon APIs are used (3.x vs. 4.x, for example) |`fullProject` |Whether to generate all the normal files or only API files @@ -328,11 +322,13 @@ and the model classes. |`jackson` | |=== +* The generator attempts to retrieve the list of released Helidon versions from the Helidon website, falling back to locally-stored Java preferences loaded from the previous generator run, and as a last resort using hard-coded values for each major Helidon release. // end::config[] // tag::usage[] +// tag::usage-basics[] [[usage-section]] == Usage @@ -401,15 +397,24 @@ The generator replaces the generated classes or interfaces but does not touch ot The Helidon _client_ generator always creates concrete classes. Typically, you do not need to customize the behavior in the generated client API classes. If you choose to do so, write your own subclass of the generated client API class; _do not_ modify the generated files. -==== Grouping Operations into "APIs" +// end::usage-basics[] +// tag::usage-grouping-intro[] + +==== Grouping Operations into APIs Each operation in an OpenAPI document can have a `tags` attribute. -The generators group operations with the same `tags` value into the same API. +By default, the generators group operations with the same `tags` value into the same API or service. Alternatively, if you specify the option `x-helidon-groupBy` as `first-path-segment`, the generators use the first segment of the path to group operations together. +// end::usage-grouping-intro[] +// tag::usage-grouping-server[] When you generate a Helidon {flavor-uc} server, the generator creates a separate interface or class for each API your service _exposes_. You implement each interface or extend each class to add your business logic for that API. +// end::usage-grouping-server[] +// tag::usage-grouping-client[] When you generate a Helidon {flavor-uc} client, the generated code contains a separate API class for each distinct API your code might _invoke_. +// end::usage-grouping-client[] +// tag::usage-after-grouping[] [[usage-running]] === Running the OpenAPI Generators @@ -523,6 +528,7 @@ Before you use the online generator, consider whether any of the input you provi This document does not explore further the use of the online generator. +// end::usage-after-grouping[] // end::usage[] @@ -537,9 +543,10 @@ The Helidon generators go a long way in helping you write your client or server. // tag::using-generated-code-server-intro[] === Completing the Server -Recall from earlier how the OpenAPI generator gathers operations into one or more "APIs" and generates either a class or an interface--your choice--for each API. +Recall from earlier how the OpenAPI generator gathers operations into one or more APIs or services and generates either an abstract class or an interface--your choice--for each API. You need to extend each generated API class or implement each generated API interface by writing your own classes. -Any input parameters to the endpoints are expressed as POJO model objects or Java types, as declared in the OpenAPI document. Your server code uses each of the input parameters to accomplish whatever business purpose that endpoint is responsible for, possibly returning a result as a POJO or Java type as indicated for that operation in the OpenAPI document. + +Any input parameters to the operations are expressed as POJO model objects or Java types as declared in the OpenAPI document. You write server code to use each of the input parameters to accomplish whatever business purpose that operation is responsible for, possibly returning a result as a POJO or Java type as indicated for that operation in the OpenAPI document. // end::using-generated-code-server-intro[] In some cases, you might need more control over the response sent to the client. In that case, specify the additional property `returnResponse=true` when you run the Helidon server generator. The return type for the generated methods is diff --git a/docs/src/main/asciidoc/mp/openapi/openapi-generator.adoc b/docs/src/main/asciidoc/mp/openapi/openapi-generator.adoc index 8b16730e1af..4c506b6a736 100644 --- a/docs/src/main/asciidoc/mp/openapi/openapi-generator.adoc +++ b/docs/src/main/asciidoc/mp/openapi/openapi-generator.adoc @@ -24,8 +24,36 @@ include::{rootdir}/includes/mp.adoc[] -== Coming Soon +:helidon-client-xref: {restclient-page} -The support for link:{openapi-generator-tool-site-url}[OpenAPI generator] is not available yet. +== Contents -You can track the progress for this feature here: https://github.com/helidon-io/helidon/issues/7943. +- <> +- <> +- <> +- <> +- <> + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tags=intro;coords;config;usage;using-generated-code-intro;using-generated-code-server;using-generated-code-client-intro] + +The Helidon MP client generator creates a MicroProfile REST client interface for each API. +Each generated API interface is annotated so your code can `@Inject` the API into one of your own beans and then use the interface directly to invoke the remote service. Alternatively, you can also explicitly use the link:{microprofile-rest-client-javadoc-url}/org/eclipse/microprofile/rest/client/RestClientBuilder.html[`RestClientBuilder`] to create an instance programmatically and then invoke its methods to contact the remote service. +The xref:{restclient-page}[Helidon MP REST Client] documentation describes both approaches in more detail. + +In the following example, `ExampleResource` (itself running in a server) invokes a remote Pet service and shows one way to use the generated `PetApi` REST client interface. + + +[source,java] +.Using the generated `PetApi` returned from a separate service +---- +include::{sourcedir}/mp/openapi/OpenApiGeneratorSnippets.java[tag=snippet_1, indent=0] +---- +<1> Uses a bean-defining annotation so CDI can inject into this class. +<2> Requests that CDI inject the following field. +<3> Identifies to Helidon MP that the following field is a REST client. +<4> Declares the field using the generated `PetApi` type. +<5> Invokes the remote service using the injected field and the parameter from the incoming request. + + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tag=common-references] +* link:https://github.com/eclipse/microprofile-rest-client[MicroProfile REST Client specification] diff --git a/docs/src/main/asciidoc/se/openapi/openapi-generator.adoc b/docs/src/main/asciidoc/se/openapi/openapi-generator.adoc index 3773c1dd516..9b4332b2854 100644 --- a/docs/src/main/asciidoc/se/openapi/openapi-generator.adoc +++ b/docs/src/main/asciidoc/se/openapi/openapi-generator.adoc @@ -24,8 +24,273 @@ include::{rootdir}/includes/se.adoc[] -== Coming Soon +:helidon-client-xref: {webclient-page} -The support for link:{openapi-generator-tool-site-url}[OpenAPI generator] is not available yet. +== Contents -You can track the progress for this feature here: https://github.com/helidon-io/helidon/issues/7943. +- <> +- <> +- <> +- <> +- <> + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tags=intro;coords;config;usage-basics;usage-grouping-intro;usage-grouping-server] + +The generator creates Helidon routing logic based on the longest common path prefix shared among the operations that are grouped into each API. + +[NOTE] +==== +If the operations in an API have no common prefix then the generated routing will be inefficient at runtime. +The generator logs a warning and includes a `TODO` comment in the generated routing. + +Review the paths and the `tags` settings in your OpenAPI document and consider revising one or the other so all operations in each API share a common path prefix. If you do not have control over the OpenAPI document or do not want to change it, consider specifying the generator option `x-helidon-groupBy first-path-segment` which groups operations into APIs not by `tags` value but by the first segment of each operation's path. +==== + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tags=usage-grouping-client;usage-after-grouping;using-generated-code-intro;using-generated-code-server-intro] + +If you choose to generate interfaces for the APIs, the generator creates routing rules for the API services it generates but you write virtually all of the logic to process incoming requests by implementing the very short methods generated in the implementation class. + +The rest of this sections focuses on your next steps if, on the other hand, you decide to generate abstract classes. + +==== What you _must_ do: implement your business logic and send the response +The generator creates an implementation class as well as the abstract class for each API. +The implementation class contains a `handle` method for each API operation with a very simple method body that returns a not-yet-implemented HTTP status in the response. The following example shows the generated method for the `addPet` OpenAPI operation. +[source,java] +.The generated `handleAddPet` method in the `PetApiImpl` class +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_1, indent=0] +---- + +Customize the class to manage the pets and revise the method to save the new pet and send the correct response, as shown next. +[source,java] +.The customized `handleAddPet` method in the `PetApiImpl` class +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_2, indent=0] +---- +<1> Business logic: create a very simple data store - a real app would use a database. +<2> Business logic: make sure the pet being added does not already exist. Send the invalid request status code if it does. +<3> Business logic: add the pet to the data store. +<4> Prepare and send the `200` response. + +If a response has any _required_ response parameters you would pass them as parameters to the `builder` method. +Add _optional_ response parameters using other generated builder methods. +The following example illustrates this for the `findPetsByTags` operation and its `response` output parameter. +[source,java] +.The customized `findPetsByTags` method in the `PetApiImpl` class +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_3, indent=0] +---- +<1> Uses the same data store as in the earlier example. +<2> The `tags` parameter conveys the tag values to be matched in selecting pets to report. Other generated code extracts the runtime argument's value from the request and then automatically passes it to the method. +<3> Collects all pets with any tag that matches any of the selection tags passed in. +<4> Uses the generated `Response200` to prepare the response. +<5> Assigns the optional `response` output parameter--the list of matching `Pet` objects. +<6> Send the response using the prepared response information. + +Write each of the `handleXxx` methods appropriately so they implement the business logic you need and send the response. + +The generator creates a `ResponseNNN` Java `record` for each operation response status code `NNN` that is declared in the OpenAPI document. You can return other status values with other output parameters even if they are not declared in the OpenAPI document, but your code must prepare the `ServerResponse` entirely by itself; the generator cannot generate helper records for responses that are absent from the document. + +==== What you _can_ do: override the generated behavior +Generated code takes care of the following work: + +* Route each request to the method which should respond. +* Extract each incoming parameter from the request and convert it to the correct type, applying any validation declared in the OpenAPI document. +* Pass the extracted parameters to the developer-written `handleXxx` method. +* Assemble required and optional response parameters and send the response. + +You can override any of the generated behavior by adding code to the generated API implementation class you are already editing to customize the `handleXxx` methods and by writing new classes which extend some of the generated classes. + +===== Override routing +To change the way routing occurs, simply override the `routing` method in your `PetServiceImpl` class. Make sure your custom routing handles all the paths for which the API is responsible. + +===== Override how to extract one or more parameters from a request +For each operation in an API the generator creates an inner class and, for each incoming parameter for that operation, a method which extracts and validates the parameter. +Override how a parameter is extracted by following these steps, using the `AddPetOp` as an example. + +. Write a class which extends the inner class for the operation. +. In that subclass override the relevant method. ++ +.Customized `AddPetOp` class +[source,java] +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_4, indent=0] +---- +<1> Extracts the parameter from the request. This happens to use the same logic as in the generated method but you can customize that as well if you need to. +<2> Apply any relevant validations. This silly but illustrative example rejects any pet name that starts with a lower-case letter. +<3> Return the extracted value, properly typed. +. In the implementation class for the API (`PetServiceImpl`) override the `createAddPetOp` method so it returns an instance of your new subclass `AddPetOpCustom` of the operation inner class `AddPetOp`. ++ +[source,java] +.Providing your custom implementation of `AddPet` +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_5, indent=0] +---- + +===== Override how an operation is prepared from a request +The generated abstract class contains a method named for each operation declared in the OpenAPI document (`addPet`) which accepts the Helidon request and response as parameters. +The generated code in these methods invokes the code to extract each incoming parameter from the request, perform any declared validation on them, and pass them to the developer-written method (`handleAddPet(request, response, pet)`). + +To completely change this behavior, override the `addPet` method in the `PetServiceImpl` class to do what you need. + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tag=using-generated-code-client-intro, indent=0] + +The generated Helidon SE client includes the class `ApiClient`. This class corresponds to +the Helidon link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/WebClient.html[`WebClient`] and represents the +connection between your code and the remote server. The generator also creates one or more `Api` interfaces and +corresponding implementation classes. The examples below use the `PetApi` interface and the `PetApiImpl` class. + +To invoke the remote service your code must: + +. Create an instance of `ApiClient` using an `ApiClient.Builder`. +. Use that `ApiClient` instance to instantiate a `PetApi` object. +. Invoke the methods on the `PetApi` object to access the remote services and then retrieve the returned result value. + +The following sections explain these steps. + +==== Creating an `ApiClient` Instance +The Helidon SE client generator gives you as much flexibility as you need in connecting to the remote service. + +Internally, the `ApiClient` uses a Helidon `WebClient` object to contact the remote system. +The `ApiClient.Builder` automatically prepares a Helidon +link:{webclient-javadoc-base-url}.api/io/helidon/webclient/api/WebClientConfig.Builder.html[`WebClientConfig.Builder`] +object using information from the OpenAPI document. + +The next sections describe, from simplest to most complicated, the ways your code can create an `ApiClient` instance, each involving increased involvement with the `WebClientConfig.Builder` object. + +===== Accepting the Automatic `WebClientConfig.Builder` +In the simplest case, your code can get an `ApiClient` instance directly. + +[source,java] +.Creating an `ApiClient` instance - simple case +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_6, indent=0] +---- +<1> The same `ApiClient` instance can be reused to invoke multiple APIs handled by the same server. +<2> Creates an `ApiClient` instance using default settings from the OpenAPI document. + +Your code relies fully on the automatic `WebClient`. +In many cases, this approach works very well, especially if the OpenAPI document correctly declares the servers and their URIs. + +===== Influencing the Automatic `WebClientConfig.Builder` +Your code can use the `ApiClient.Builder` to fine-tune the settings for the internal `WebClientConfig.Builder`. +For instance, your code can set an object mapper to be used for Jackson processing or the `JsonbConfig` object to be used for JSON-B processing, depending on which serialization library you chose when you ran the generator. + +Your code does not need to know how the object mapper setting is conveyed to the internal `WebClientConfig.Builder`. The `ApiClient.Builder` knows how to do that. + +[source,java] +.Creating an `ApiClient` instance - influencing the `ApiClient.Builder` +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_7, indent=0] +---- +<1> Stores a reusable `ApiClient`. +<2> A real app would fully set up the `ObjectMapper`. +<3> Sets the object mapper for use in the `ApiClient.Builder` 's internal `WebClientConfig.Builder`. + +===== Adjusting the Automatic `WebClientConfig.Builder` +In more complicated situations, your code can adjust the settings of the `WebClientConfig.Builder` which the `ApiClient.Builder` creates. + +[source,java] +.Creating an `ApiClient` instance - adjusting the `WebClientConfig.Builder` +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_8, indent=0] +---- +<1> Stores a reusable `AppClient`. +<2> Creates a new `AppClient` builder. +<3> Access the `ApiClient.Builder`'s automatic `WebClientConfig.Builder` instance. +<4> Adjusts a setting of the `WebClientConfig.Builder` directly. +<5> Builds the `ApiClient` which implicitly builds the `WebClient` from the now-adjusted internal `WebClientConfig.Builder`. + +The automatic `WebClientConfig.Builder` retains information derived from the OpenAPI document unless your code overrides those specific settings. + +===== Providing a Custom `WebClientConfig.Builder` +Lastly, you can construct the `WebClientConfig.Builder` entirely yourself and have the `ApiClient.Builder` use it instead of its own internal builder. + +[source,java] +.Creating an `ApiClient` instance - using a custom `WebClientConfig.Builder` +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_9, indent=0] +---- +<1> Stores a reusable `AppClient`. +<2> Creates a new `WebClientConfig.Builder`. +<3> Sets the connection timeout directly on the `WebClientConfig.Builder`. +<4> Sets the base URI on the `WebClienConfig.Builder`. +<5> Creates a new `ApiClient.Builder'. +<6> Sets the `WebClientConfig.Builder` which the `ApiClient.Builder` should use (instead of the one it prepares internally). +<7> Builds the `ApiClient` which uses the newly-assigned `WebClientConfig.Builder` in the process. + + +Note that this approach entirely replaces the internal, automatically-prepared `WebClientConfig.Builder` with yours; it _does not_ merge the new builder with the internal one. In particular, any information from the OpenAPI document the generator used to prepare the internal `WebClientConfig.Builder` is lost. + + +==== Creating a `PetApi` Instance +The `ApiClient` represents the connection to the remote server but not the individual RESTful operations. +Each generated `xxxApi` interface exposes a method for each operation declared in the OpenAPI document associated with that API via its `tags` value. +By example, the `PetApi` interface exposes a method for each operation in the OpenAPI document that pertains to pets. + +To invoke an operation defined on the `PetApi` interface, your code instantiates a `PetApi` using an `ApiClient` object: + +[source,java] +.Preparing the PetStore Client API +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_10, indent=0] +---- +<1> Stores a reusable `AppClient`. +<2> Stores a reusable `PetApi` for invoking pet-related operations. +<3> Initializes and saves the `PetApi` instance using the previously-prepared `apiClient`. + + +==== Invoking Remote Endpoints +With the `petApi` object, your code can invoke any of the methods on the `PetApi` interface to contact the remote service. + +The Helidon SE client generator creates an `ApiResponse` interface. +Each generated `PetApi` method returns an `ApiResponse` where the `returnType` is the return type (if any) declared in the OpenAPI document for the corresponding operation. + +The `ApiResponse` interface exposes two methods your code can use to work with the response from the remote service invocation: + +* `T result()` ++ +Provides access to the value returned by the remote service in the response. +This method lets your code fetch the return value directly. +* `HTTPClientResponse webClientResponse()` ++ +Provides access to the Helidon `HTTPClientResponse` object. +Your code can find out the HTTP return status, read headers in the response, and process the content (if any) in the response however it needs to. + +In the Helidon WebClient model, the first part of the response message can arrive (the status and headers are available) before the entity in the body of the response is readable. +So there are two events associated with an incoming HTTP response: + +. when the response _excluding_ the entity content has arrived, and +. when your code can begin consuming the entity content. + +You can adopt different styles of retrieving the results, depending on the specific needs of the code you are writing. + +===== Access only the result +[source,java] +.Access with only result access +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_11, indent=0] +---- +<1> Use the previously-prepared `petApi` to find pets that have the `available` status. +<2> Retrieve the typed result from the `ApiResponse`. + +===== Access with status checking +The Helidon WebClient programming model includes a `HTTPClientResponse` interface which exposes all aspects of the HTTP response returned from the remote service. + +The next example shows how your code can use the `HTTPClientResponse`. + +[source,java] +.Access with status checking +---- +include::{sourcedir}/se/openapi/OpenApiGeneratorSnippets.java/[tags=snippet_12, indent=0] +---- +<1> Start the remote service invocation. +<2> Wait for the HTTP response status and headers to arrive. +<3> Check the status in the HTTP response. +<4> Wait for the content to arrive, extracting the result and converting it to the proper type. + +This code also blocks the current thread, first to wait for the initial response and then to wait for the result content. + +include::{rootdir}/includes/openapi/openapi-generator.adoc[tag=common-references] + +* xref:{helidon-client-xref}[Helidon WebClient documentation] diff --git a/docs/src/main/java/io/helidon/docs/mp/openapi/OpenApiGeneratorSnippets.java b/docs/src/main/java/io/helidon/docs/mp/openapi/OpenApiGeneratorSnippets.java new file mode 100644 index 00000000000..b538784d3cb --- /dev/null +++ b/docs/src/main/java/io/helidon/docs/mp/openapi/OpenApiGeneratorSnippets.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.docs.mp.openapi; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@SuppressWarnings("ALL") +class OpenApiGeneratorSnippets { + + /* + + # The following commands download the generator and run it to generate projects containing the types which need to be + # mocked in the snippets in case of changes in the upstream generators. + # + # See these instructions https://github.com/OpenAPITools/openapi-generator?tab=readme-ov-file#13---download-jar for full + # instructions to download the generator and + # https://github.com/OpenAPITools/openapi-generator?tab=readme-ov-file#11---compatibility to see the latest stable version + # of the generator. + # + # As of this writing use at least 7.8.0. + # + # Revise the generatorVersion and helidonVersion settings below accordingly. + + generatorVersion=7.8.0 + helidonVersion=4 # Uses the latest publicly released version of 4. Specify x.y.z if you want to specify an exact release. + + curl -O \ + --output-dir /tmp \ + https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$generatorVersion/openapi-generator-cli-${generatorVersion}.jar + + rm -rf /tmp/petapi-client + java -jar /tmp/openapi-generator-cli-$generatorVersion.jar generate \ + -o /tmp/petapi-client \ + -g java-helidon-client \ + --library mp \ + -i etc/petstorex.yaml \ + -p helidonVersion=${helidonVersion} + + */ + + interface Pet { + } + + interface PetApi { + Pet getPetById(long petId); + } + + class ApiException extends Exception { + } + + class Snippet1 { + + // tag::snippet_1[] + @Path("/exampleServiceCallingService") // <1> + public class ExampleOpenApiGenClientResource { + @Inject // <2> + @RestClient // <3> + private PetApi petApi; // <4> + + @GET + @Path("/getPet/{petId}") + @Produces(MediaType.APPLICATION_JSON) + public Pet getPetUsingId(@PathParam("petId") Long petId) throws ApiException { + Pet pet = petApi.getPetById(petId); // <5> + return pet; + } + } + // end::snippet_1[] + } +} \ No newline at end of file diff --git a/docs/src/main/java/io/helidon/docs/se/openapi/OpenApiGeneratorSnippets.java b/docs/src/main/java/io/helidon/docs/se/openapi/OpenApiGeneratorSnippets.java new file mode 100644 index 00000000000..d389f53039b --- /dev/null +++ b/docs/src/main/java/io/helidon/docs/se/openapi/OpenApiGeneratorSnippets.java @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.docs.se.openapi; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.helidon.http.Status; +import io.helidon.webclient.api.HttpClientResponse; +import io.helidon.webclient.api.WebClient; +import io.helidon.webclient.api.WebClientConfig; +import io.helidon.webserver.http.ServerRequest; +import io.helidon.webserver.http.ServerResponse; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@SuppressWarnings("ALL") class OpenApiGeneratorSnippets { + + /* + + # The following commands download the generator and run it to generate projects containing the types which need to be + # mocked in the snippets in case of changes in the upstream generators. + # + # See these instructions https://github.com/OpenAPITools/openapi-generator?tab=readme-ov-file#13---download-jar for full + # instructions to download the generator and + # https://github.com/OpenAPITools/openapi-generator?tab=readme-ov-file#11---compatibility to see the latest stable version + # of the generator. + # + # As of this writing use at least 7.8.0. + # + # Revise the generatorVersion and helidonVersion settings below accordingly. + + generatorVersion=7.8.0 + helidonVersion=4 # Uses the latest publicly released version of 4. Specify x.y.z if you want to specify an exact release. + + curl -O \ + --output-dir /tmp \ + https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/$generatorVersion/openapi-generator-cli-${generatorVersion}.jar + + # Generate the server project. + rm -rf /tmp/petapi-server + java -jar /tmp/openapi-generator-cli-$generatorVersion.jar generate \ + -o /tmp/petapi-server \ + -g java-helidon-server \ + --library se \ + -i etc/petstorex.yaml \ + -p useAbstractClass=true \ + -p helidonVersion=${helidonVersion} + + # Generate the client project. + rm -rf /tmp/petapi-client + java -jar /tmp/openapi-generator-cli-$generatorVersion.jar generate \ + -o /tmp/petapi-client \ + -g java-helidon-client \ + --library se \ + -i etc/petstorex.yaml \ + -p helidonVersion=${helidonVersion} + + */ + // stub + public class AddPetOpCustom extends PetService.AddPetOp { + } + + // stub + static class PetService { + + void handleAddPet(ServerRequest request, ServerResponse response, Pet pet) { + } + + void handleFindPetsByTags(ServerRequest request, ServerResponse response, + List tags) { + } + + AddPetOp createAddPetOp() { + return null; + } + + static class AddPetOp { + + Pet pet(ServerRequest request, ValidatorUtils.Validator validator) { + return null; + } + + record Response405() { + + static Response405.Builder builder() { + return new Builder(); + } + + static class Builder { + void send(ServerResponse response) { + } + } + } + + record Response200(Pet response) { + + static Response200.Builder builder() { + return new Builder(); + } + + static class Builder { + void send(ServerResponse response) { + } + } + } + } + + static class FindPetsByTagsOp { + + record Response200() { + + static Response200.Builder builder() { + return new Response200.Builder(); + } + + static class Builder { + public void send(ServerResponse response) { + } + + public Builder response(List result) { + return this; + } + } + } + } + } + + // stub + class ValidatorUtils { + class Validator { + void validatePattern(String itemName, String value, String pattern) { + } + } + } + + // stub + class Pet { + + String getName() { + return null; + } + + long getId() { + return 0L; + } + + enum StatusEnum { + AVAILABLE; + + String value() { + return null; + } + } + + List getTags() { + return List.of(); + } + } + + // stub + class Tag { + + String getName() { + return null; + } + } + + interface PetApi { + ApiResponse> findPetsByStatus(List statusValues); + } + + class PetApiImpl implements PetApi { + + static PetApi create(ApiClient apiClient) { + return null; + } + + @Override + public ApiResponse> findPetsByStatus(List statusValues) { + return null; + } + } + + class ApiClient { + + static Builder builder() { + return null; + } + + class Builder { + + ApiClient build() { + return null; + } + + Builder objectMapper(Object om) { + return this; + } + + Builder webClientBuilder(WebClientConfig.Builder webClientConfigBuilder) { + return this; + } + + WebClientConfig.Builder webClientBuilder() { + return WebClientConfig.builder(); + } + } + } + + class ApiResponse { + + HttpClientResponse webClientResponse() { + return null; + } + + T result() { + return null; + } + } + + class Snippet1 { + // tag::snippet_1[] + public class PetServiceImpl extends PetService { + @Override + protected void handleAddPet(ServerRequest request, ServerResponse response, + Pet pet) { + response.status(Status.NOT_IMPLEMENTED_501).send(); + } + } + // end::snippet_1[] + } + + class Snippet2 { + // tag::snippet_2[] + public class PetServiceImpl extends PetService { + + private final Map pets = new HashMap<>(); // <1> + + @Override + protected void handleAddPet(ServerRequest request, ServerResponse response, + Pet pet) { + if (pets.containsKey(pet.getId())) { // <2> + AddPetOp.Response405.builder().send(response); + } + pets.put(pet.getId(), pet); // <3> + AddPetOp.Response200.builder().send(response); // <4> + } + } + // end::snippet_2[] + } + + class Snippet3 { + // tag::snippet_3[] + public class PetServiceImpl extends PetService { + + private final Map pets = new HashMap<>(); // <1> + + @Override + protected void handleFindPetsByTags(ServerRequest request, ServerResponse response, + List tags) { // <2> + + List result = pets.values().stream() + .filter(pet -> pet.getTags() + .stream() + .anyMatch(petTag -> tags.contains(petTag.getName()))) + .toList(); // <3> + + FindPetsByTagsOp.Response200.builder() // <4> + .response(result) // <5> + .send(response); // <6> + + } + } + // end::snippet_3[] + } + + class Snippet4 { + // tag::snippet_4[] + public class AddPetOpCustom extends PetService.AddPetOp { + @Override + protected Pet pet(ServerRequest request, ValidatorUtils.Validator validator) { + Pet result = request.content().hasEntity() // <1> + ? request.content().as(Pet.class) + : null; + + // Insist that pet names never start with a lower-case letter. + if (result != null) { + validator.validatePattern("pet", result.getName(), "[^a-z].*"); // <2> + } + return result; // <3> + } + } + // end::snippet_4[] + } + + class Snippet5 { + // tag::snippet_5[] + public class PetServiceImpl extends PetService { + @Override + protected AddPetOp createAddPetOp() { + return new AddPetOpCustom(); + } + } + // end::snippet_5[] + } + + class Snippet6 { + // tag::snippet_6[] + public class ExampleClient { + + private ApiClient apiClient; // <1> + + void init() { + ApiClient apiClient = ApiClient.builder().build(); // <2> + } + } + // end::snippet_6[] + } + + class Snippet7 { + // tag::snippet_7[] + public class ExampleClient { + + private ApiClient apiClient; // <1> + + void init() { + ObjectMapper myObjectMapper = new ObjectMapper(); // <2> + apiClient = ApiClient.builder() + .objectMapper(myObjectMapper) // <3> + .build(); + } + } + // end::snippet_7[] + } + + class Snippet8 { + // tag::snippet_8[] + public class ExampleClient { + + private ApiClient apiClient; // <1> + + void init() { + ApiClient.Builder apiClientAdjustedBuilder = ApiClient.builder(); // <2> + + apiClientAdjustedBuilder + .webClientBuilder() // <3> + .connectTimeout(Duration.ofSeconds(4)); // <4> + + apiClient = apiClientAdjustedBuilder.build(); // <5> + } + } + // end::snippet_8[] + } + + class Snippet9 { + // tag::snippet_9[] + public class ExampleClient { + + private ApiClient apiClient; // <1> + + void init() { + WebClientConfig.Builder customWebClientBuilder = WebClient.builder() // <2> + .connectTimeout(Duration.ofSeconds(3)) // <3> + .baseUri("https://myservice.mycompany.com"); // <4> + + apiClient = ApiClient.builder() // <5> + .webClientBuilder(customWebClientBuilder) // <6> + .build(); // <7> + } + } + // end::snippet_9[] + } + + class Snippet10 { + // tag::snippet_10[] + public class ExampleClient { + + private ApiClient apiClient; // <1> + + private PetApi petApi; // <2> + + void preparePetApi() { + petApi = PetApiImpl.create(apiClient); // <3> + } + } + // end::snippet_10[] + } + + class Snippet11 { + PetApi petApi; + + // tag::snippet_11[] + void findAvailablePets() { + ApiResponse> apiResponse = + petApi.findPetsByStatus(List.of(Pet.StatusEnum.AVAILABLE.value())); // <1> + + List availablePets = apiResponse.result(); // <2> + } + // end::snippet_11[] + } + + class Snippet12 { + PetApi petApi; + + // tag::snippet_12[] + void findAvailablePets() { + ApiResponse> apiResponse = + petApi.findPetsByStatus(List.of(Pet.StatusEnum.AVAILABLE.value())); // <1> + + try (HttpClientResponse webClientResponse = apiResponse.webClientResponse()) { // <2> + if (webClientResponse.status().code() != 200) { // <3> + // Handle a non-successful status. + } + } + + List avlPets = apiResponse.result(); // <4> + } + // end::snippet_12[] + } +} + + +