diff --git a/.github/workflows/samples-java-helidon-v4.yaml b/.github/workflows/samples-java-helidon-v4.yaml index b13977d4691e..2922c81247da 100644 --- a/.github/workflows/samples-java-helidon-v4.yaml +++ b/.github/workflows/samples-java-helidon-v4.yaml @@ -18,7 +18,10 @@ jobs: matrix: sample: - samples/client/petstore/java-helidon-client/v4/mp + - samples/client/petstore/java-helidon-client/v4/se - samples/server/petstore/java-helidon-server/v4/mp + - samples/server/petstore/java-helidon-server/v4/se + - samples/server/petstore/java-helidon-server/v4/se-uac version: [21] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/samples-jdk21.yaml b/.github/workflows/samples-jdk21.yaml index 9910b86a0eb5..bef7496da8c9 100644 --- a/.github/workflows/samples-jdk21.yaml +++ b/.github/workflows/samples-jdk21.yaml @@ -4,14 +4,18 @@ on: paths: # clients - samples/client/petstore/java-helidon-client/v4/mp/** + - samples/client/petstore/java-helidon-client/v4/se/** # servers - samples/server/petstore/java-helidon-server/v4/mp/** + - samples/server/petstore/java-helidon-server/v4/se/** pull_request: paths: # clients - samples/client/petstore/java-helidon-client/v4/mp/** + - samples/client/petstore/java-helidon-client/v4/se/** # servers - samples/server/petstore/java-helidon-server/v4/mp/** + - samples/server/petstore/java-helidon-server/v4/se/** jobs: build: name: Build with JDK21 @@ -22,8 +26,10 @@ jobs: sample: # clients - samples/client/petstore/java-helidon-client/v4/mp/ + - samples/client/petstore/java-helidon-client/v4/se/ # servers - samples/server/petstore/java-helidon-server/v4/mp/ + - samples/server/petstore/java-helidon-server/v4/se/ steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/bin/configs/java-helidon-client-mp_4.yaml b/bin/configs/java-helidon-client-mp_4.yaml index 188afa0e86b5..9d25d15f8230 100644 --- a/bin/configs/java-helidon-client-mp_4.yaml +++ b/bin/configs/java-helidon-client-mp_4.yaml @@ -3,7 +3,7 @@ library: mp outputDir: samples/client/petstore/java-helidon-client/v4/mp inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml additionalProperties: - helidonVersion: 4.0.8 + helidonVersion: 4.0.11 artifactId: petstore-helidon-client-mp hideGenerationTimestamp: "true" configureAuth: "false" diff --git a/bin/configs/java-helidon-client-se_4.yaml b/bin/configs/java-helidon-client-se_4.yaml new file mode 100644 index 000000000000..748b7db894f8 --- /dev/null +++ b/bin/configs/java-helidon-client-se_4.yaml @@ -0,0 +1,14 @@ +generatorName: java-helidon-client +library: se +outputDir: samples/client/petstore/java-helidon-client/v4/se +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +additionalProperties: + helidonVersion: 4.0.11 + artifactId: petstore-helidon-client-se + hideGenerationTimestamp: "true" + configureAuth: "false" + build: "all" + test: "spock" + requiredPropertiesInConstructor: "false" + visitable: "true" + fullProject: "true" diff --git a/bin/configs/java-helidon-server-mp_4.yaml b/bin/configs/java-helidon-server-mp_4.yaml index 40de63bc808f..fbed7514c65c 100644 --- a/bin/configs/java-helidon-server-mp_4.yaml +++ b/bin/configs/java-helidon-server-mp_4.yaml @@ -4,7 +4,7 @@ outputDir: samples/server/petstore/java-helidon-server/v4/mp inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml templateDir: modules/openapi-generator/src/main/resources/java-helidon/server additionalProperties: - helidonVersion: 4.0.8 + helidonVersion: 4.0.11 artifactId: petstore-helidon-server-mp hideGenerationTimestamp: "true" build: "all" diff --git a/bin/configs/java-helidon-server-se_4-uac-group-by-file-path.yaml b/bin/configs/java-helidon-server-se_4-uac-group-by-file-path.yaml new file mode 100644 index 000000000000..db0dc77cefa1 --- /dev/null +++ b/bin/configs/java-helidon-server-se_4-uac-group-by-file-path.yaml @@ -0,0 +1,13 @@ +generatorName: java-helidon-server +library: se +outputDir: samples/server/petstore/java-helidon-server/v4/se-uac-group-by-file-path +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +templateDir: modules/openapi-generator/src/main/resources/java-helidon/server +additionalProperties: + helidonVersion: 4.0.11 + artifactId: petstore-helidon-server-se + hideGenerationTimestamp: "true" + fullProject: "true" + useAbstractClass: "true" + generatorVersion: "stable" + x-helidon-groupBy: first-path-segment diff --git a/bin/configs/java-helidon-server-se_4-uac.yaml b/bin/configs/java-helidon-server-se_4-uac.yaml new file mode 100644 index 000000000000..273d48c350d9 --- /dev/null +++ b/bin/configs/java-helidon-server-se_4-uac.yaml @@ -0,0 +1,12 @@ +generatorName: java-helidon-server +library: se +outputDir: samples/server/petstore/java-helidon-server/v4/se-uac +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +templateDir: modules/openapi-generator/src/main/resources/java-helidon/server +additionalProperties: + helidonVersion: 4.0.11 + artifactId: petstore-helidon-server-se + hideGenerationTimestamp: "true" + fullProject: "true" + useAbstractClass: "true" + generatorVersion: "stable" diff --git a/bin/configs/java-helidon-server-se_4.yaml b/bin/configs/java-helidon-server-se_4.yaml new file mode 100644 index 000000000000..717faad7a235 --- /dev/null +++ b/bin/configs/java-helidon-server-se_4.yaml @@ -0,0 +1,12 @@ +generatorName: java-helidon-server +library: se +outputDir: samples/server/petstore/java-helidon-server/v4/se +inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml +templateDir: modules/openapi-generator/src/main/resources/java-helidon/server +additionalProperties: + helidonVersion: 4.0.11 + artifactId: petstore-helidon-server-se + hideGenerationTimestamp: "true" + fullProject: "true" + x-helidon-useOptional: "true" + generatorVersion: "stable" diff --git a/docs/generators/java-helidon-client.md b/docs/generators/java-helidon-client.md index a274c0e0307f..c9c1e4064469 100644 --- a/docs/generators/java-helidon-client.md +++ b/docs/generators/java-helidon-client.md @@ -65,6 +65,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false| |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |withXml|whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)| |false| +|x-helidon-groupBy|Selects how to group operations into APIs|
+ * Helidon uses its own HTTP status type, and the Helidon code predefines many HTTP status code constants but also allows + * ad hoc creation of other values based on the numeric status value. It's more efficient at runtime to use a constant + * if it exists. + *
+ * This method scans a copy of the Helidon Java file which contains the predefined constants and prepares a map + * from the string representation of the numeric code to the Helidon constant name. This table allows us, when we are + * generating the Response records for an operation, to use the Helidon predefined constant--if it exists--for the + * response code declared for an operation in the OpenAPI document. + *
+ * @return prepared map + */ + private HashMap- * The builder accepts a {@link WebClient.Builder} via the {@code webClientBuilder} method but will provide a default one + * The builder accepts a {@link WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder} via the {@code webClientBuilder} method but will provide a default one * using available configuration (the {@code client} node) and the base URI set in the OpenAPI document. *
*/ public static class Builder { - private WebClient.Builder webClientBuilder; + private WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder webClientBuilder; private Config clientConfig; {{#jsonb}} private JsonbConfig jsonbConfig; @@ -190,19 +196,19 @@ public class ApiClient { } /** - * Sets the {@code WebClient.Builder} which the {@code ApiClient.Builder} uses. Any previous setting is discarded. + * Sets the {@code WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder} which the {@code ApiClient.Builder} uses. Any previous setting is discarded. * - * @param webClientBuilder the {@code WebClient.Builder} to be used going forward + * @param webClientBuilder the {@code WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder} to be used going forward * @return the updated builder */ - public Builder webClientBuilder(WebClient.Builder webClientBuilder) { + public Builder webClientBuilder(WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder webClientBuilder) { this.webClientBuilder = webClientBuilder; return this; } /** - * Sets the client {@code Config} which the {@code ApiClient.Builder} uses in preparing a default {@code WebClient.Builder}. - * The builder ignores this setting if you provide your own {@code WebClient.Builder} by invoking the + * Sets the client {@code Config} which the {@code ApiClient.Builder} uses in preparing a default {@code WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder}. + * The builder ignores this setting if you provide your own {@code WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder} by invoking the * {@code webClientBuilder} method. * * @param clientConfig the {@code Config} node containing client settings @@ -217,7 +223,7 @@ public class ApiClient { * @return the previously-stored web client builder or, if none, a default one using the provided or defaulted * client configuration */ - public WebClient.Builder webClientBuilder() { + public WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder webClientBuilder() { if (webClientBuilder == null) { webClientBuilder = defaultWebClientBuilder(); } @@ -249,8 +255,8 @@ public class ApiClient { } {{/jackson}} - private WebClient.Builder defaultWebClientBuilder() { - WebClient.Builder defaultWebClientBuilder = WebClient.builder() + private WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder defaultWebClientBuilder() { + WebClient{{^x-helidon-v3}}Config{{/x-helidon-v3}}.Builder defaultWebClientBuilder = WebClient.builder() .baseUri("{{basePath}}") .config(clientConfig()); {{#jsonb}} @@ -260,7 +266,7 @@ public class ApiClient { {{/jsonb}} {{#jackson}} defaultWebClientBuilder.addMediaSupport(objectMapper == null - ? JacksonSupport.create() + ? JacksonSupport.create({{^x-helidon-v3}}clientConfig(){{/x-helidon-v3}}) : JacksonSupport.create(objectMapper)); {{/jackson}} return defaultWebClientBuilder; diff --git a/modules/openapi-generator/src/main/resources/java-helidon/client/libraries/se/ApiResponse.mustache b/modules/openapi-generator/src/main/resources/java-helidon/client/libraries/se/ApiResponse.mustache index b651e0b4f251..e0b7cbc84a7d 100644 --- a/modules/openapi-generator/src/main/resources/java-helidon/client/libraries/se/ApiResponse.mustache +++ b/modules/openapi-generator/src/main/resources/java-helidon/client/libraries/se/ApiResponse.mustache @@ -1,11 +1,15 @@ {{>licenseInfo}} package {{invokerPackage}}; -import java.util.concurrent.ExecutionException; +{{#x-helidon-v3}}import java.util.concurrent.ExecutionException; +{{/x-helidon-v3}} import io.helidon.common.GenericType; -import io.helidon.common.reactive.Single; +{{#x-helidon-v3}}import io.helidon.common.reactive.Single; import io.helidon.webclient.WebClientResponse; +{{/x-helidon-v3}}{{! +}}{{^x-helidon-v3}}import io.helidon.webclient.api.HttpClientResponse; +{{/x-helidon-v3}} {{#appName}} /** @@ -18,17 +22,17 @@ import io.helidon.webclient.WebClientResponse; {{/appName}} public interface ApiResponse{{{.}}}
+ {{/appDescription}}
+ */
+{{/appName}}
+public class {{classname}}Impl implements {{classname}} {
+
+ private final ApiClient apiClient;
+
+{{#operations}}
+ {{#operation}}
+ protected static final GenericType<{{>operationResponseTypeDecl}}> RESPONSE_TYPE_{{operationId}} = ResponseType.create({{#isArray}}List.class, {{/isArray}}{{#isMap}}Map.class, String.class, {{/isMap}}{{#returnBaseType}}{{returnBaseType}}{{/returnBaseType}}{{^returnBaseType}}Void{{/returnBaseType}}.class);
+ {{/operation}}
+{{/operations}}
+
+ /**
+ * Creates a new instance of {{classname}}Impl initialized with the specified {@link ApiClient}.
+ *
+ */
+ public static {{classname}}Impl create(ApiClient apiClient) {
+ return new {{classname}}Impl(apiClient);
+ }
+
+ protected {{classname}}Impl(ApiClient apiClient) {
+ this.apiClient = apiClient;
+ }
+
+{{#operations}}
+{{#operation}}
+ {{#isDeprecated}}
+ @Deprecated
+ {{/isDeprecated}}
+ @Override
+ public {{>operationResponseSig}} {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) {
+ {{#requiredParams}}
+ Objects.requireNonNull({{paramName}}, "Required parameter '{{paramName}}' not specified");
+ {{/requiredParams}}
+ HttpClientRequest clientRequest = {{operationId}}RequestBuilder({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}});
+ return {{operationId}}Submit(clientRequest{{#allParams}}, {{paramName}}{{/allParams}});
+ }
+
+ /**
+ * Creates a {@code WebClientRequestBuilder} for the {{operationId}} operation.
+ * Optional customization point for subclasses.
+ *
+ {{#allParams}}
+ * @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{^isContainer}}{{#defaultValue}}, default to {{.}}{{/defaultValue}}{{/isContainer}}){{/required}}
+ {{/allParams}}
+ * @return HttpClientRequest for {{operationId}}
+ */
+ protected HttpClientRequest {{operationId}}HttpClientRequest({{#allParams}}{{{dataType}}} {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) {
+ HttpClientRequest clientRequest = apiClient.webClient()
+ .method("{{httpMethod}}");
+
+ {{#hasQueryParams}}
+ List
+ * Although the constants are instances of this class, they can be compared using instance equality, as the only
+ * way to obtain an instance is through methods {@link #create(int)} {@link #create(int, String)}, which ensures
+ * the same instance is returned for known status codes and reason phrases.
+ *
+ * A good reference is the IANA list of HTTP Status Codes (we may not cover all of them in this type):
+ * IANA HTTP Status Codes
+ */
+public class Status {
+ /**
+ * 100 Continue,
+ * see HTTP/1.1 documentations.
+ */
+ public static final Status CONTINUE_100 = new Status(100, "Continue", true);
+ /**
+ * 101 Switching Protocols,
+ * see HTTP/1.1 documentations.
+ */
+ public static final Status SWITCHING_PROTOCOLS_101 = new Status(101, "Switching Protocols", true);
+ /**
+ * 200 OK, see HTTP/1.1 documentation.
+ */
+ public static final Status OK_200 = new Status(200, "OK", true);
+ /**
+ * 201 Created, see HTTP/1.1 documentation.
+ */
+ public static final Status CREATED_201 = new Status(201, "Created", true);
+ /**
+ * 202 Accepted, see HTTP/1.1 documentation
+ * .
+ */
+ public static final Status ACCEPTED_202 = new Status(202, "Accepted", true);
+ /**
+ * 203 Non-Authoritative Information, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 4.0.6
+ */
+ public static final Status NON_AUTHORITATIVE_INFORMATION_203 = new Status(203, "Non-Authoritative Information", true);
+
+ /**
+ * 204 No Content, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status NO_CONTENT_204 = new Status(204, "No Content", true);
+ /**
+ * 205 Reset Content, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status RESET_CONTENT_205 = new Status(205, "Reset Content", true);
+ /**
+ * 206 Reset Content, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status PARTIAL_CONTENT_206 = new Status(206, "Partial Content", true);
+ /**
+ * 207 Multi-Status, see
+ * RFC 4918 - HTTP Extensions for WebDAV.
+ *
+ * @since 4.0.6
+ */
+ public static final Status MULTI_STATUS_207 = new Status(207, "Multi-Status", true);
+ /**
+ * 301 Moved Permanently, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status MOVED_PERMANENTLY_301 = new Status(301, "Moved Permanently", true);
+ /**
+ * 302 Found, see HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status FOUND_302 = new Status(302, "Found", true);
+ /**
+ * 303 See Other, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status SEE_OTHER_303 = new Status(303, "See Other", true);
+ /**
+ * 304 Not Modified, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status NOT_MODIFIED_304 = new Status(304, "Not Modified", true);
+ /**
+ * 305 Use Proxy, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status USE_PROXY_305 = new Status(305, "Use Proxy", true);
+ /**
+ * 307 Temporary Redirect, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status TEMPORARY_REDIRECT_307 = new Status(307, "Temporary Redirect", true);
+ /**
+ * 308 Permanent Redirect, see
+ * HTTP Status Code 308 documentation.
+ */
+ public static final Status PERMANENT_REDIRECT_308 = new Status(308, "Permanent Redirect", true);
+ /**
+ * 400 Bad Request, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status BAD_REQUEST_400 = new Status(400, "Bad Request", true);
+ /**
+ * 401 Unauthorized, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status UNAUTHORIZED_401 = new Status(401, "Unauthorized", true);
+ /**
+ * 402 Payment Required, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status PAYMENT_REQUIRED_402 = new Status(402, "Payment Required", true);
+ /**
+ * 403 Forbidden, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status FORBIDDEN_403 = new Status(403, "Forbidden", true);
+ /**
+ * 404 Not Found, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status NOT_FOUND_404 = new Status(404, "Not Found", true);
+ /**
+ * 405 Method Not Allowed, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status METHOD_NOT_ALLOWED_405 = new Status(405, "Method Not Allowed", true);
+ /**
+ * 406 Not Acceptable, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status NOT_ACCEPTABLE_406 = new Status(406, "Not Acceptable", true);
+ /**
+ * 407 Proxy Authentication Required, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status PROXY_AUTHENTICATION_REQUIRED_407 = new Status(407, "Proxy Authentication Required", true);
+ /**
+ * 408 Request Timeout, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status REQUEST_TIMEOUT_408 = new Status(408, "Request Timeout", true);
+ /**
+ * 409 Conflict, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status CONFLICT_409 = new Status(409, "Conflict", true);
+ /**
+ * 410 Gone, see HTTP/1.1 documentation.
+ */
+ public static final Status GONE_410 = new Status(410, "Gone", true);
+ /**
+ * 411 Length Required, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status LENGTH_REQUIRED_411 = new Status(411, "Length Required", true);
+ /**
+ * 412 Precondition Failed, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status PRECONDITION_FAILED_412 = new Status(412, "Precondition Failed", true);
+ /**
+ * 413 Request Entity Too Large, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status REQUEST_ENTITY_TOO_LARGE_413 = new Status(413, "Request Entity Too Large", true);
+ /**
+ * 414 Request-URI Too Long, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status REQUEST_URI_TOO_LONG_414 = new Status(414, "Request-URI Too Long", true);
+ /**
+ * 415 Unsupported Media Type, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status UNSUPPORTED_MEDIA_TYPE_415 = new Status(415, "Unsupported Media Type", true);
+ /**
+ * 416 Requested Range Not Satisfiable, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status REQUESTED_RANGE_NOT_SATISFIABLE_416 = new Status(416, "Requested Range Not Satisfiable", true);
+ /**
+ * 417 Expectation Failed, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status EXPECTATION_FAILED_417 = new Status(417, "Expectation Failed", true);
+ /**
+ * 418 I'm a teapot, see
+ * Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0).
+ */
+ public static final Status I_AM_A_TEAPOT_418 = new Status(418, "I'm a teapot", true);
+ /**
+ * Misdirected request, see
+ * RFC 9110 - Http Semantics.
+ */
+ public static final Status MISDIRECTED_REQUEST_421 = new Status(421, "Misdirected Request", true);
+ /**
+ * Unprocessable content, see
+ * RFC 9110 - Http Semantics.
+ */
+ public static final Status UNPROCESSABLE_CONTENT_422 = new Status(422, "Unprocessable Content", true);
+ /**
+ * Locked, see
+ * RFC 4918 - HTTP Extensions for WebDAV.
+ */
+ public static final Status LOCKED_423 = new Status(423, "Locked", true);
+ /**
+ * Failed dependency, see
+ * RFC 4918 - HTTP Extensions for WebDAV.
+ */
+ public static final Status FAILED_DEPENDENCY_424 = new Status(424, "Failed Dependency", true);
+ /**
+ * Upgrade required, see
+ * RFC 9110 - Http Semantics.
+ */
+ public static final Status UPGRADE_REQUIRED_426 = new Status(426, "Upgrade Required", true);
+ /**
+ * Precondition required, see
+ * RFC 6585 - Additional HTTP Status Codes.
+ */
+ public static final Status PRECONDITION_REQUIRED_428 = new Status(428, "Precondition Required", true);
+ /**
+ * Too many requests, see
+ * RFC 6585 - Additional HTTP Status Codes.
+ */
+ public static final Status TOO_MANY_REQUESTS_429 = new Status(429, "Too Many Requests", true);
+ /**
+ * 500 Internal Server Error, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status INTERNAL_SERVER_ERROR_500 = new Status(500, "Internal Server Error", true);
+ /**
+ * 501 Not Implemented, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status NOT_IMPLEMENTED_501 = new Status(501, "Not Implemented", true);
+ /**
+ * 502 Bad Gateway, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status BAD_GATEWAY_502 = new Status(502, "Bad Gateway", true);
+ /**
+ * 503 Service Unavailable, see
+ * HTTP/1.1 documentation.
+ */
+ public static final Status SERVICE_UNAVAILABLE_503 = new Status(503, "Service Unavailable", true);
+ /**
+ * 504 Gateway Timeout, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 2.0
+ */
+ public static final Status GATEWAY_TIMEOUT_504 = new Status(504, "Gateway Timeout", true);
+ /**
+ * 505 HTTP Version Not Supported, see
+ * HTTP/1.1 documentation.
+ *
+ * @since 3.0.3
+ */
+ public static final Status HTTP_VERSION_NOT_SUPPORTED_505 = new Status(505, "HTTP Version Not Supported", true);
+ /**
+ * 507 Insufficient Storage, see
+ * WebDAV documentation.
+ *
+ * @since 4.0.6
+ */
+ public static final Status INSUFFICIENT_STORAGE_507 = new Status(507, "Insufficient Storage", true);
+ /**
+ * 508 Loop Detected, see
+ * RFC 5842 - Bindings for the Constrained Application Protocol (CoAP).
+ *
+ * @since 4.0.6
+ */
+ public static final Status LOOP_DETECTED_508 = new Status(508, "Loop Detected", true);
+
+ /**
+ * 510 Not Extended, see
+ * RFC 2774 - An HTTP Extension Framework.
+ *
+ * @since 4.0.6
+ */
+ public static final Status NOT_EXTENDED_510 = new Status(510, "Not Extended", true);
+
+ /**
+ * 511 Network Authentication Required, see
+ * RFC 6585 - Additional HTTP Status Codes.
+ *
+ * @since 4.0.6
+ */
+ public static final Status NETWORK_AUTHENTICATION_REQUIRED_511 = new Status(511, "Network Authentication Required", true);
+
+
+ static {
+ // THIS MUST BE AFTER THE LAST CONSTANT
+ StatusHelper.statusesDone();
+ }
+
+ private final int code;
+ private final String reason;
+ private final Family family;
+ private final String codeText;
+ private final String stringValue;
+
+ private JavaHelidonHttpStatus(int statusCode, String reasonPhrase, boolean instance) {
+ this.code = statusCode;
+ this.reason = reasonPhrase;
+ this.family = Family.of(statusCode);
+ this.codeText = String.valueOf(code);
+ this.stringValue = code + " " + reason;
+
+ if (instance) {
+ StatusHelper.add(this);
+ }
+ }
+
+ private JavaHelidonHttpStatus(int statusCode, String reasonPhrase, Family family, String codeText) {
+ // for custom codes
+ this.code = statusCode;
+ this.reason = reasonPhrase;
+ this.family = family;
+ this.codeText = codeText;
+ this.stringValue = code + " " + reason;
+ }
+
+ /**
+ * Convert a numerical status code into the corresponding Status.
+ *
+ * For an unknown code, an ad-hoc {@link Status} is created.
+ *
+ * @param statusCode the numerical status code
+ * @return the matching Status; either a constant from this class, or an ad-hoc {@link Status}
+ */
+ public static Status create(int statusCode) {
+ Status found = StatusHelper.find(statusCode);
+
+ if (found == null) {
+ return createNew(Family.of(statusCode), statusCode, "", String.valueOf(statusCode));
+ }
+ return found;
+ }
+
+ /**
+ * Convert a numerical status code into the corresponding Status.
+ *
+ * It either returns an existing {@link Status} constant if possible.
+ * For an unknown code, or code/reason phrase combination it creates
+ * an ad-hoc {@link Status}.
+ *
+ * @param statusCode the numerical status code
+ * @param reasonPhrase the reason phrase; if {@code null} or a known reason phrase, an instance with the default
+ * phrase is returned; otherwise, a new instance is returned
+ * @return the matching Status
+ */
+ public static Status create(int statusCode, String reasonPhrase) {
+ Status found = StatusHelper.find(statusCode);
+ if (found == null) {
+ return createNew(Family.of(statusCode), statusCode, reasonPhrase, String.valueOf(statusCode));
+ }
+ if (reasonPhrase == null) {
+ return found;
+ }
+ if (found.reasonPhrase().equalsIgnoreCase(reasonPhrase)) {
+ return found;
+ }
+ return createNew(found.family(), statusCode, reasonPhrase, found.codeText());
+ }
+
+ private static Status createNew(Family family, int statusCode, String reasonPhrase, String codeText) {
+ return new Status(statusCode, reasonPhrase, family, codeText);
+ }
+
+ /**
+ * Get the associated integer value representing the status code.
+ *
+ * @return the integer value representing the status code.
+ */
+ public int code() {
+ return code;
+ }
+
+ /**
+ * Get the class of status code.
+ *
+ * @return the class of status code.
+ */
+ public Family family() {
+ return family;
+ }
+
+ /**
+ * Get the reason phrase.
+ *
+ * @return the reason phrase.
+ */
+ public String reasonPhrase() {
+ return reason;
+ }
+
+ /**
+ * Text of the {@link #code()}.
+ *
+ * @return code string (number as a string)
+ */
+ public String codeText() {
+ return codeText;
+ }
+
+ /**
+ * Get the response status as string.
+ *
+ * @return the response status string in the form of a partial HTTP response status line,
+ * i.e. {@code "Status-Code SP Reason-Phrase"}.
+ */
+ public String toString() {
+ return stringValue;
+ }
+
+ /**
+ * Text of the status as used in HTTP/1, such as "200 OK".
+ *
+ * @return text of this status
+ */
+ public String text() {
+ return stringValue;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Status status = (Status) o;
+ return code == status.code && reason.equals(status.reason);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, reason);
+ }
+
+ /**
+ * An enumeration representing the class of status code. Family is used
+ * here since class is overloaded in Java.
+ *
+ * Copied from JAX-RS.
+ */
+ public enum Family {
+
+ /**
+ * {@code 1xx} HTTP status codes.
+ */
+ INFORMATIONAL,
+ /**
+ * {@code 2xx} HTTP status codes.
+ */
+ SUCCESSFUL,
+ /**
+ * {@code 3xx} HTTP status codes.
+ */
+ REDIRECTION,
+ /**
+ * {@code 4xx} HTTP status codes.
+ */
+ CLIENT_ERROR,
+ /**
+ * {@code 5xx} HTTP status codes.
+ */
+ SERVER_ERROR,
+ /**
+ * Other, unrecognized HTTP status codes.
+ */
+ OTHER;
+
+ /**
+ * Get the family for the response status code.
+ *
+ * @param statusCode response status code to get the family for.
+ * @return family of the response status code.
+ */
+ public static Family of(int statusCode) {
+ return switch (statusCode / 100) {
+ case 1 -> Family.INFORMATIONAL;
+ case 2 -> Family.SUCCESSFUL;
+ case 3 -> Family.REDIRECTION;
+ case 4 -> Family.CLIENT_ERROR;
+ case 5 -> Family.SERVER_ERROR;
+ default -> Family.OTHER;
+ };
+ }
+ }
+}
diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/api.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/api.mustache
index c44408d778bf..9c88def7bc96 100644
--- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/api.mustache
+++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/api.mustache
@@ -5,25 +5,39 @@ package {{package}};
{{#useAbstractClass}}
import java.util.Optional;
+{{#x-helidon-v3}}
import java.util.logging.Logger;
+{{/x-helidon-v3}}
+{{#x-helidon-v3}}
import io.helidon.common.GenericType;
import io.helidon.common.reactive.Single;
+{{/x-helidon-v3}}
{{/useAbstractClass}}
-import io.helidon.webserver.Routing;
-import io.helidon.webserver.ServerRequest;
-import io.helidon.webserver.ServerResponse;
-import io.helidon.webserver.Service;
+import io.helidon.webserver.{{#x-helidon-v3}}Routing{{/x-helidon-v3}}{{^x-helidon-v3}}http.HttpRules{{/x-helidon-v3}};
+import io.helidon.webserver.{{^x-helidon-v3}}http.{{/x-helidon-v3}}ServerRequest;
+import io.helidon.webserver.{{^x-helidon-v3}}http.{{/x-helidon-v3}}ServerResponse;
+import io.helidon.webserver.{{^x-helidon-v3}}http.Http{{/x-helidon-v3}}Service;
{{#operations}}
-{{^useAbstractClass}}public interface {{classname}} extends Service { {{/useAbstractClass}}
-{{#useAbstractClass}}public abstract class {{classname}} implements Service {
+{{^x-helidon-v3}}
+@io.helidon.common.Generated(value = "{{additionalProperties.generatorClass}}",
+ trigger = "tag = '{{baseName}}'",
+ version = "{{generatorVersion}}")
+{{/x-helidon-v3}}{{!
+}}{{^useAbstractClass}}public interface {{classname}} extends {{^x-helidon-v3}}Http{{/x-helidon-v3}}Service {{=<% %>=}}{<%#x-helidon-v3%> <%/x-helidon-v3%><%={{ }}=%>
+{{/useAbstractClass}}{{!
+}}{{#useAbstractClass}}public abstract class {{classname}} implements {{^x-helidon-v3}}Http{{/x-helidon-v3}}Service {
- protected static final Logger LOGGER = Logger.getLogger({{classname}}.class.getName());
-{{#jackson}}
+{{#x-helidon-v3}} protected static final Logger LOGGER = Logger.getLogger({{classname}}.class.getName());
+{{/x-helidon-v3}}{{!
+}}{{#jackson}}
protected static final ObjectMapper MAPPER = JsonProvider.objectMapper();{{/jackson}}
{{#jsonb}}
- protected static final Jsonb JSONB = JsonbBuilder.create();{{/jsonb}}
+ protected static final Jsonb JSONB = JsonbBuilder.create();{{/jsonb}}{{!
+}}{{^x-helidon-v3}}
+{{#operations}}{{#operation}} protected {{#lambda.titlecase}}{{operationId}}Op{{/lambda.titlecase}} {{operationId}}Op = create{{#lambda.titlecase}}{{operationId}}Op{{/lambda.titlecase}}();
+{{/operation}}{{/operations}}{{/x-helidon-v3}}
{{/useAbstractClass}}
/**
@@ -31,48 +45,87 @@ import io.helidon.webserver.Service;
* @param rules the routing rules.
*/
@Override
- {{#useAbstractClass}}public{{/useAbstractClass}}{{^useAbstractClass}}default{{/useAbstractClass}} void update(Routing.Rules rules) {
+ {{#useAbstractClass}}public{{/useAbstractClass}}{{^useAbstractClass}}default{{/useAbstractClass}} void {{#x-helidon-v3}}update{{/x-helidon-v3}}{{^x-helidon-v3}}routing{{/x-helidon-v3}}({{#x-helidon-v3}}Routing.{{/x-helidon-v3}}{{^x-helidon-v3}}Http{{/x-helidon-v3}}Rules rules) {
{{#operation}}
- rules.{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}("{{{path}}}", {{!
- }}{{#bodyParam}}{{#isModel}}Handler.create({{{dataType}}}.class, {{/isModel}}this::{{{operationId}}}){{#isModel}}){{/isModel}}{{/bodyParam}}{{!
- }}{{^bodyParam}}this::{{{operationId}}}){{/bodyParam}};
+ rules.{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}("{{#x-helidon-v3}}{{{path}}}{{/x-helidon-v3}}{{^x-helidon-v3}}{{{vendorExtensions.x-helidon-pathSuffix}}}{{/x-helidon-v3}}", {{!
+
+ See comment below about the signatures of the generated handler methods for v3 vs. v4.
+
+}}{{#x-helidon-v3}}{{#bodyParam}}{{#isModel}}Handler.create({{{dataType}}}.class, {{/isModel}}this::{{{operationId}}}){{#isModel}}){{/isModel}}{{/bodyParam}}{{!
+}}{{^bodyParam}}this::{{{operationId}}}){{/bodyParam}}{{/x-helidon-v3}}{{!
+}}{{^x-helidon-v3}}this::{{{operationId}}}){{/x-helidon-v3}};
{{/operation}}
}
-{{#useAbstractClass}}{{#isFormParamsFunctions}}
+{{#x-helidon-v3}}{{#useAbstractClass}}{{#isFormParamsFunctions}}
{{!}}{{>formParamsFunctions}}
-{{/isFormParamsFunctions}}{{/useAbstractClass}}
+{{/isFormParamsFunctions}}{{/useAbstractClass}}{{/x-helidon-v3}}
{{#operation}}
/**
* {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}}.
+{{^x-helidon-v3}} *
+{{/x-helidon-v3}}
* @param request the server request
- * @param response the server response{{#allParams}}{{#isBodyParam}}{{#isModel}}
- * @param {{paramName}} {{{description}}}{{^description}}{{paramName}}{{/description}} {{/isModel}}{{/isBodyParam}}{{/allParams}}
+ * @param response the server response{{!
+}}{{!
+ For v3 the interface or abstract class handler methods declare a method parameter for the body parameter--if one is
+ specified in the OpenAPI operation declaration--in addition to the request and response. The routing code extracts the
+ body parameter from the request content and passes it to the handler method.
+
+ For v4 the generated handler methods do not declare the body parameter as a method parameter. Instead body parameters are
+ dealt with, as all other request parameters, by the user-provided code (when an interface is generated) or by the
+ generated code (when an abstract class is generated).
+
+}}{{#x-helidon-v3}}{{#allParams}}{{#isBodyParam}}{{#isModel}}
+ * @param {{paramName}} {{{description}}}{{^description}}{{paramName}}{{/description}} {{/isModel}}{{/isBodyParam}}{{/allParams}}{{/x-helidon-v3}}
*/
- void {{{operationId}}}(ServerRequest request, ServerResponse response{{#allParams}}{{#isBodyParam}}{{#isModel}}, {{{dataType}}} {{paramName}}{{/isModel}}{{/isBodyParam}}{{/allParams}}){{^useAbstractClass}};{{/useAbstractClass}}{{#useAbstractClass}} { {{#formParams}}{{#-first}}
- {{>formParamsInitial}}{{/-first}}{{/formParams}}
- Single.create({{^hasParams}}Single.empty(){{/hasParams}}{{#hasParams}}{{^bodyParam}}{{#formParams}}{{#-first}}formSingle{{/-first}}{{/formParams}}{{^formParams}}Single.empty(){{/formParams}}{{/bodyParam}}{{#bodyParam}}{{^isModel}}request.content().as(new GenericType<{{{dataType}}}>() { }){{/isModel}}{{#isModel}}Single.empty(){{/isModel}}{{/bodyParam}}{{/hasParams}})
+ {{^x-helidon-v3}}{{#useAbstractClass}}protected {{/useAbstractClass}}{{/x-helidon-v3}}void {{{operationId}}}(ServerRequest request, ServerResponse response{{#x-helidon-v3}}{{#allParams}}{{#isBodyParam}}{{#isModel}}, {{{dataType}}} {{paramName}}{{/isModel}}{{/isBodyParam}}{{/allParams}}{{/x-helidon-v3}}){{^useAbstractClass}};{{/useAbstractClass}}{{#useAbstractClass}} { {{> paramsInitial }}{{!
+}}{{#x-helidon-v3}}
+ Single.create({{^hasParams}}Single.empty(){{/hasParams}}{{#hasParams}}{{^bodyParam}}{{#formParams}}{{#-first}}formSingle{{/-first}}{{/formParams}}{{^formParams}}Single.empty(){{/formParams}}{{/bodyParam}}{{#bodyParam}}{{^isModel}}request.content().as(new GenericType<{{#isFile}}InputStream{{/isFile}}{{^isFile}}{{{dataType}}}{{/isFile}}>() { }){{/isModel}}{{#isModel}}Single.empty(){{/isModel}}{{/bodyParam}}{{/hasParams}})
.thenAccept({{#bodyParam}}{{^isModel}}{{paramName}}{{/isModel}}{{#isModel}}val{{/isModel}}{{/bodyParam}}{{^bodyParam}}val{{/bodyParam}} -> {
-{{#allParams}}
- {{> queryParams }}{{> pathParams }}{{> headerParams}}{{> bodyParams}}{{> formParams}}{{> cookieParams}}
+{{/x-helidon-v3}}
+{{#allParams}}{{^x-helidon-v3}}
+ // Parameter: {{baseName}}
+{{/x-helidon-v3}}
+{{#x-helidon-v3}} {{/x-helidon-v3}} {{> queryParams }}{{> pathParams }}{{> headerParams}}{{> bodyParams}}{{> formParams}}{{> cookieParams}}
{{/allParams}}
- handle{{#lambda.titlecase}}{{{operationId}}}{{/lambda.titlecase}}(request, response{{#allParams}}, {{paramName}}{{/allParams}});
- })
+{{#x-helidon-v3}} {{/x-helidon-v3}} {{> paramsFinal }}{{!
+}}{{#x-helidon-v3}} {{/x-helidon-v3}} handle{{#lambda.titlecase}}{{{operationId}}}{{/lambda.titlecase}}(request, response{{#allParams}}, {{^x-helidon-v3}}
+ {{/x-helidon-v3}}{{paramName}}{{/allParams}});
+{{#x-helidon-v3}} })
.exceptionally(throwable -> handleError(request, response, throwable));
+{{/x-helidon-v3}}
}
/**
- * Handle {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}}.
+ * Handle {{httpMethod}} {{{path}}}{{#summary}} : {{.}}{{/summary}}.{{^x-helidon-v3}}
+ *{{/x-helidon-v3}}
* @param request the server request
* @param response the server response{{#allParams}}
* @param {{paramName}} {{{description}}}{{^description}}{{paramName}}{{/description}} {{/allParams}}
*/
- abstract void handle{{#lambda.titlecase}}{{{operationId}}}{{/lambda.titlecase}}(ServerRequest request, ServerResponse response{{#allParams}}, {{>dataType}} {{paramName}}{{/allParams}});
+ {{^x-helidon-v3}}{{#useAbstractClass}}protected {{/useAbstractClass}}{{/x-helidon-v3}}abstract {{> handleMethodSignature }};
{{/useAbstractClass}}
+{{#x-helidon-v3}}
+{{/x-helidon-v3}}
+{{/operation}}
+{{#useAbstractClass}}{{#x-helidon-v3}} abstract Void handleError(ServerRequest request, ServerResponse response, Throwable throwable);{{/x-helidon-v3}}{{!
+}}{{^x-helidon-v3}}
+{{#operation}}
+{{> opHelpers }}
{{/operation}}
-{{#useAbstractClass}} abstract Void handleError(ServerRequest request, ServerResponse response, Throwable throwable);{{!
-}}{{/useAbstractClass}}
-}
+{{/x-helidon-v3}}{{/useAbstractClass}}
+{{#useAbstractClass}}
+{{^x-helidon-v3}}
+ @Override
+ public void afterStop() {
+ System.out.println("Service {{classname}} is down. Goodbye!");
+ }
+
+
+{{/x-helidon-v3}}{{!
+}}{{/useAbstractClass}}{{!
+}}}
{{/operations}}
diff --git a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/apiImpl.mustache b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/apiImpl.mustache
index b6cf71293f52..073412ffa8b2 100644
--- a/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/apiImpl.mustache
+++ b/modules/openapi-generator/src/main/resources/java-helidon/server/libraries/se/apiImpl.mustache
@@ -3,17 +3,22 @@ package {{package}};
{{#imports}}import {{import}};
{{/imports}}
{{^useAbstractClass}}
-import java.util.logging.Logger;{{/useAbstractClass}}
+{{#x-helidon-v3}}import java.util.logging.Logger;
-import io.helidon.webserver.ServerRequest;
-import io.helidon.webserver.ServerResponse;
+{{/x-helidon-v3}}
+{{/useAbstractClass}}
+import io.helidon.webserver.{{^x-helidon-v3}}http.{{/x-helidon-v3}}ServerRequest;
+import io.helidon.webserver.{{^x-helidon-v3}}http.{{/x-helidon-v3}}ServerResponse;
{{#operations}}
public class {{classname}}Impl {{^useAbstractClass}}implements{{/useAbstractClass}}{{#useAbstractClass}}extends{{/useAbstractClass}} {{classname}} {
+{{#x-helidon-v3}}
private static final int HTTP_CODE_NOT_IMPLEMENTED = 501;
+{{/x-helidon-v3}}
{{^useAbstractClass}}
- private static final Logger LOGGER = Logger.getLogger({{classname}}.class.getName());
+{{#x-helidon-v3}} private static final Logger LOGGER = Logger.getLogger({{classname}}.class.getName());
+{{/x-helidon-v3}}
{{#jackson}}
private static final ObjectMapper MAPPER = JsonProvider.objectMapper();{{/jackson}}
{{#jsonb}}
@@ -22,19 +27,47 @@ public class {{classname}}Impl {{^useAbstractClass}}implements{{/useAbstractClas
{{#operation}}
{{#useAbstractClass}}
- public void handle{{#lambda.titlecase}}{{{operationId}}}{{/lambda.titlecase}}(ServerRequest request, ServerResponse response{{#allParams}}, {{>dataType}} {{paramName}}{{/allParams}}) {
+ {{^x-helidon-v3}}@Override
+ protected{{/x-helidon-v3}}{{#x-helidon-v3}}public{{/x-helidon-v3}} {{> handleMethodSignature }} {
{{/useAbstractClass}}
{{^useAbstractClass}}
+{{#x-helidon-v3}}
public void {{{operationId}}}(ServerRequest request, ServerResponse response{{#allParams}}{{#isBodyParam}}{{#isModel}}, {{{dataType}}} {{paramName}}{{/isModel}}{{/isBodyParam}}{{/allParams}}) {
-{{/useAbstractClass}}
- response.status(HTTP_CODE_NOT_IMPLEMENTED).send();
+{{/x-helidon-v3}}
+{{^x-helidon-v3}}
+ @Override
+ public void {{{operationId}}}(ServerRequest request, ServerResponse response) {
+{{!
+ If the user generated an interface instead of an abstract class, add a few conveniences here in the implementation class
+ to suggest how the developer might implement the code.
+
+}}{{^isMultipart}}{{#hasFormParams}} Parameters formParams = request.content().as(Parameters.class);
+{{/hasFormParams}}{{/isMultipart}}{{!
+}}{{#isMultipart}}{{#useAbstractClass}} Map
+ * Inspired hugely by java.net.URLDecoder.
+ *