From 3fe558bc1767211dfa4d497612859bc944671958 Mon Sep 17 00:00:00 2001 From: rk Date: Sat, 19 Sep 2026 16:05:20 +0500 Subject: [PATCH] feat(typescript-angular): add withHttpResource option for signal-based GET resources Opt-in, Angular 20+. Every GET operation without a request body gets a Resource method next to its Observable method, returning an Angular httpResource (httpResource.text or httpResource.blob when the response is text or binary). Writes keep only their Observable method. The query, header and credential code moves into a partial shared by both paths, so the output of every existing configuration is unchanged. Adds a sample build inside typescript-angular-v22-provided-in-root with an HttpTestingController spec. --- ...script-angular-v22-with-http-resource.yaml | 8 + docs/generators/typescript-angular.md | 1 + .../TypeScriptAngularClientCodegen.java | 94 +++ .../typescript-angular/README.mustache | 44 + .../typescript-angular/api.service.mustache | 104 +-- .../apiHttpResource.mustache | 78 ++ .../typescript-angular/apiInterface.mustache | 6 +- .../apiRequestQueryAndHeaders.mustache | 85 ++ .../httpResourceOptions.mustache | 33 + .../typescript-angular/index.mustache | 3 + ...ypeScriptAngularClientOptionsProvider.java | 1 + .../TypeScriptAngularClientCodegenTest.java | 133 +++ .../3_0/typescript-angular/http-resource.yaml | 247 ++++++ .../builds/with-http-resource/.gitignore | 4 + .../.openapi-generator-ignore | 23 + .../.openapi-generator/FILES | 18 + .../.openapi-generator/VERSION | 1 + .../builds/with-http-resource/README.md | 237 ++++++ .../with-http-resource/api.base.service.ts | 120 +++ .../builds/with-http-resource/api.module.ts | 30 + .../builds/with-http-resource/api/api.ts | 5 + .../with-http-resource/api/pet.service.ts | 791 ++++++++++++++++++ .../with-http-resource/api/store.service.ts | 134 +++ .../with-http-resource/configuration.ts | 216 +++++ .../builds/with-http-resource/encoder.ts | 35 + .../builds/with-http-resource/git_push.sh | 54 ++ .../http.resource.options.ts | 41 + .../builds/with-http-resource/index.ts | 8 + .../builds/with-http-resource/model/models.ts | 1 + .../builds/with-http-resource/model/pet.ts | 26 + .../builds/with-http-resource/param.ts | 69 ++ .../builds/with-http-resource/provide-api.ts | 15 + .../builds/with-http-resource/query.params.ts | 160 ++++ .../builds/with-http-resource/variables.ts | 9 + .../default/src/test/http-resource.spec.ts | 226 +++++ .../tsconfig.json | 3 +- 36 files changed, 2968 insertions(+), 95 deletions(-) create mode 100644 bin/configs/typescript-angular-v22-with-http-resource.yaml create mode 100644 modules/openapi-generator/src/main/resources/typescript-angular/apiHttpResource.mustache create mode 100644 modules/openapi-generator/src/main/resources/typescript-angular/apiRequestQueryAndHeaders.mustache create mode 100644 modules/openapi-generator/src/main/resources/typescript-angular/httpResourceOptions.mustache create mode 100644 modules/openapi-generator/src/test/resources/3_0/typescript-angular/http-resource.yaml create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.gitignore create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator-ignore create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/FILES create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/VERSION create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/README.md create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.base.service.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.module.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/api.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/pet.service.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/store.service.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/configuration.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/encoder.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/git_push.sh create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/http.resource.options.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/index.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/models.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/pet.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/param.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/provide-api.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/query.params.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/variables.ts create mode 100644 samples/client/petstore/typescript-angular-v22-provided-in-root/tests/default/src/test/http-resource.spec.ts diff --git a/bin/configs/typescript-angular-v22-with-http-resource.yaml b/bin/configs/typescript-angular-v22-with-http-resource.yaml new file mode 100644 index 000000000000..2f7e10270400 --- /dev/null +++ b/bin/configs/typescript-angular-v22-with-http-resource.yaml @@ -0,0 +1,8 @@ +generatorName: typescript-angular +outputDir: samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource +inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript-angular/http-resource.yaml +templateDir: modules/openapi-generator/src/main/resources/typescript-angular +additionalProperties: + ngVersion: 22.0.0 + supportsES6: true + withHttpResource: true diff --git a/docs/generators/typescript-angular.md b/docs/generators/typescript-angular.md index 7270a23e5e1f..0f401e3de1b5 100644 --- a/docs/generators/typescript-angular.md +++ b/docs/generators/typescript-angular.md @@ -56,6 +56,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useDotNotationForFormObjects|Use parameter-prefixed dot notation for exploded form query objects instead of the OpenAPI standard unprefixed property names. Enable only for servers requiring this convention.| |false| |useSingleRequestParameter|Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.| |false| |useSquareBracketsInArrayNames|Setting this property to true will add brackets to array attribute names, e.g. my_values[].| |false| +|withHttpResource|Setting this property to true will generate, next to the Observable method of each GET operation without a request body, a method returning a signal-based Angular httpResource. Requires Angular v20+.| |false| |withInterfaces|Setting this property to true will generate interfaces next to the default class implementations.| |false| |zonejsVersion|The version of zone.js compatible with Angular (see ngVersion option).| |null| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java index 41e88007c077..8d331f308d71 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -38,6 +38,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -59,6 +60,9 @@ public static enum QUERY_PARAM_OBJECT_FORMAT_TYPE {dot, json, key} public static enum PROVIDED_IN_LEVEL {none, root, any, platform} + // same expression as Configuration.isJsonMime in configuration.mustache + private static final Pattern JSON_MIME_PATTERN = Pattern.compile("^(application/json|[^;/ \\t]+/[^;/ \\t]+[+]json)[ \\t]*(;.*)?$", Pattern.CASE_INSENSITIVE); + private static final String DEFAULT_IMPORT_PREFIX = "./"; private static final String DEFAULT_MODEL_IMPORT_DIRECTORY_PREFIX = "../"; @@ -87,6 +91,13 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform} public static final String RXJS_VERSION = "rxjsVersion"; public static final String NGPACKAGR_VERSION = "ngPackagrVersion"; public static final String ZONEJS_VERSION = "zonejsVersion"; + public static final String WITH_HTTP_RESOURCE = "withHttpResource"; + + public static final String X_ANGULAR_HTTP_RESOURCE = "x-angular-http-resource"; + public static final String X_ANGULAR_HTTP_RESOURCE_FACTORY = "x-angular-http-resource-factory"; + public static final String X_ANGULAR_HTTP_RESOURCE_TYPE = "x-angular-http-resource-type"; + public static final String X_ANGULAR_HTTP_RESOURCE_ACCEPT = "x-angular-http-resource-accept"; + public static final String X_ANGULAR_REQUEST_PARAMS_INTERFACE = "x-angular-request-params-interface"; protected String ngVersion = "22.0.0"; @Getter @Setter @@ -102,6 +113,7 @@ public static enum PROVIDED_IN_LEVEL {none, root, any, platform} protected PROVIDED_IN_LEVEL providedIn = PROVIDED_IN_LEVEL.root; private boolean taggedUnions = false; + private boolean withHttpResource = false; public TypeScriptAngularClientCodegen() { super(); @@ -132,6 +144,10 @@ public TypeScriptAngularClientCodegen() { this.cliOptions.add(CliOption.newBoolean(USE_SINGLE_REQUEST_PARAMETER, "Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.", false)); + this.cliOptions.add(CliOption.newBoolean(WITH_HTTP_RESOURCE, + "Setting this property to true will generate, next to the Observable method of each GET operation without a request body, " + + "a method returning a signal-based Angular httpResource. Requires Angular v20+.", + false)); this.cliOptions.add(CliOption.newBoolean(TAGGED_UNIONS, "Use discriminators to create tagged unions instead of extending interfaces.", this.taggedUnions)); @@ -223,6 +239,17 @@ public void processOpts() { throw new IllegalArgumentException("Invalid ngVersion: " + ngVersion + ". Only Angular v9+ is supported."); } + if (additionalProperties.containsKey(WITH_HTTP_RESOURCE)) { + withHttpResource = convertPropertyToBoolean(WITH_HTTP_RESOURCE); + } + if (withHttpResource && !ngVersion.atLeast("20.0.0")) { + throw new IllegalArgumentException("Invalid ngVersion: " + ngVersion + ". " + WITH_HTTP_RESOURCE + " requires Angular v20+."); + } + writePropertyBack(WITH_HTTP_RESOURCE, withHttpResource); + if (withHttpResource) { + supportingFiles.add(new SupportingFile("httpResourceOptions.mustache", getIndexDirectory(), "http.resource.options.ts")); + } + if (additionalProperties.containsKey(NPM_NAME)) { addNpmPackageGeneration(ngVersion); } @@ -428,13 +455,24 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L objs.put("apiFilename", getApiFilenameFromClassname(objs.getClassname())); List ops = objs.getOperation(); + Set nicknames = ops.stream().map(op -> op.nickname).collect(Collectors.toSet()); boolean hasSomeFormParams = false; boolean hasSomeEncodableParams = false; + boolean hasSomeHttpResource = false; for (CodegenOperation op : ops) { if (op.getHasFormParams()) { hasSomeFormParams = true; } op.httpMethod = op.httpMethod.toLowerCase(Locale.ENGLISH); + if (markHttpResource(op, nicknames)) { + hasSomeHttpResource = true; + } + // the RequestParams interface: for every operation with useSingleRequestParameter, else for the resource methods only + if (op.getHasParams() && (getUseSingleRequestParameter() || op.vendorExtensions.containsKey(X_ANGULAR_HTTP_RESOURCE))) { + op.vendorExtensions.put(X_ANGULAR_REQUEST_PARAMS_INTERFACE, true); + } else { + op.vendorExtensions.remove(X_ANGULAR_REQUEST_PARAMS_INTERFACE); + } // deduplicate auth methods by name (as they will lead to duplicate code): op.authMethods = op.authMethods != null ? op.authMethods.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(x -> x.name))), ArrayList::new)) @@ -479,6 +517,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L operations.put("hasSomeFormParams", hasSomeFormParams); operations.put("hasSomeEncodableParams", hasSomeEncodableParams); + operations.put("hasSomeHttpResource", hasSomeHttpResource); // Add additional filename information for model imports in the services List> imports = operations.getImports(); @@ -491,6 +530,61 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L return operations; } + /** + * Marks a GET operation without a request body for an additional httpResource method. + * The factory mirrors the responseType the Observable method picks at runtime for the default + * Accept header (Configuration.selectHeaderAccept): JSON (or no produces) uses httpResource, + * text uses httpResource.text, anything else, and file responses, httpResource.blob. + * + * @param op the operation, with its http method already lower-cased + * @param nicknames the nicknames of all operations of the same API class + * @return true if the operation gets a resource method + */ + private boolean markHttpResource(CodegenOperation op, Set nicknames) { + op.vendorExtensions.remove(X_ANGULAR_HTTP_RESOURCE); + if (!withHttpResource || !"get".equals(op.httpMethod) || op.getHasBodyOrFormParams()) { + return false; + } + String resourceName = op.nickname + "Resource"; + if (nicknames.contains(resourceName) || nicknames.contains(resourceName + "Request")) { + LOGGER.warn("Not generating {} for operation {}: the name is taken by another operation.", resourceName, op.operationId); + return false; + } + + List produces = op.produces == null ? Collections.emptyList() : op.produces.stream() + .map(mediaType -> mediaType.get("mediaType")) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String accept = produces.stream() + .filter(mediaType -> JSON_MIME_PATTERN.matcher(mediaType).matches()) + .findFirst() + .orElse(produces.isEmpty() ? null : produces.get(0)); + + String factory; + String type; + if (op.isResponseFile) { + factory = ".blob"; + type = "Blob"; + } else if (accept != null && accept.startsWith("text")) { + factory = ".text"; + type = "string"; + } else if (accept == null || JSON_MIME_PATTERN.matcher(accept).matches()) { + factory = ""; + type = op.returnType != null ? op.returnType : "any"; + } else { + factory = ".blob"; + type = "Blob"; + } + + op.vendorExtensions.put(X_ANGULAR_HTTP_RESOURCE, true); + op.vendorExtensions.put(X_ANGULAR_HTTP_RESOURCE_FACTORY, factory); + op.vendorExtensions.put(X_ANGULAR_HTTP_RESOURCE_TYPE, type); + if (accept != null) { + op.vendorExtensions.put(X_ANGULAR_HTTP_RESOURCE_ACCEPT, accept); + } + return true; + } + @Override public ModelsMap postProcessModels(ModelsMap objs) { ModelsMap result = super.postProcessModels(objs); diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/README.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/README.mustache index ce4a81e940c4..226f6ce148c4 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/README.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/README.mustache @@ -160,6 +160,50 @@ export const appConfig: ApplicationConfig = { }; ``` +{{#withHttpResource}} +### Signal-based resources + +Every `GET` operation without a request body also has a `...Resource` method, next to its Observable method, +which returns an Angular [`HttpResourceRef`](https://angular.dev/api/common/http/HttpResourceRef) +(see [`httpResource`](https://angular.dev/guide/http/http-resource)). The method is skipped, with a warning at +generation time, when its name would collide with another operation: + +```typescript +import { Component, computed, inject, input } from '@angular/core'; +import { PetService } from '{{npmName}}'; + +@Component({ + selector: 'app-pet', + templateUrl: './pet.component.html', +}) +export class PetComponent { + readonly petId = input.required(); + + // requested again whenever petId changes + readonly pet = inject(PetService).getPetByIdResource(() => ({ petId: this.petId() })); + + readonly name = computed(() => this.pet.hasValue() ? this.pet.value().name : ''); +} +``` + +- The first argument returns the request parameters and is called in a reactive context: every signal it reads + sends a new request when it changes. While it returns `undefined`, or a required parameter is `null` or `undefined`, + the resource stays idle. Operations without parameters take no such argument, and it can be left out when all + parameters are optional. +- Call the method in an injection context (a field initializer or a constructor), or pass an `injector` in the options. + The resource belongs to that injector: it is destroyed with it, and it sends its request through the `HttpClient` + of that injector, so the interceptors of the caller apply. +- The credentials of the `{{configurationClassName}}` are read each time the request is built, so a credential + function that reads a signal sends a new request when the signal changes. Other credential changes need `reload()`. +- `transferCache` defaults to `true`, as for the Observable methods. Angular's transfer cache skips requests with an + `Authorization` header unless `includeRequestsWithAuthHeaders` is set in `withHttpTransferCacheOptions`. +- Resources are generated for reads only. A resource cancels its pending request when its parameters change, which + is not safe for a write, so operations that change data keep only their Observable method. + +The `withHttpResource` option requires Angular 20 or later. `httpResource` is stable since Angular 22 and +experimental in Angular 20 and 21. + +{{/withHttpResource}} ### Customizing path parameter encoding Without further customization, only [path-parameters][parameter-locations-url] of [style][style-values-url] 'simple' diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache index f60df119a660..5b39b37fcafd 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache @@ -5,6 +5,9 @@ import { Inject, Injectable, Optional } from '@angular/core import { HttpClient, HttpHeaders, HttpParams, HttpResponse, HttpEvent{{#httpContextInOptions}}, HttpContext {{/httpContextInOptions}} } from '@angular/common/http'; +{{#hasSomeHttpResource}} +import { HttpResourceRef, HttpResourceRequest, httpResource } from '@angular/common/http'; +{{/hasSomeHttpResource}} import { Observable } from 'rxjs'; import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; @@ -17,19 +20,21 @@ import { {{ classname }} } from '{{ filename }}'; import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; import { {{configurationClassName}} } from '../configuration'; import { BaseService } from '../api.base.service'; +{{#hasSomeHttpResource}} +import { ApiHttpResourceOptions } from '../http.resource.options'; +{{/hasSomeHttpResource}} {{#withInterfaces}} import { - {{classname}}Interface{{#useSingleRequestParameter}}{{#operations}}{{#operation}}{{#allParams.0}}, - {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams{{/allParams.0}}{{/operation}}{{/operations}}{{/useSingleRequestParameter}} + {{classname}}Interface{{#operations}}{{#operation}}{{#vendorExtensions.x-angular-request-params-interface}}, + {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams{{/vendorExtensions.x-angular-request-params-interface}}{{/operation}}{{/operations}} } from './{{classFilename}}Interface'; {{/withInterfaces}} {{#operations}} {{^withInterfaces}} -{{#useSingleRequestParameter}} {{#operation}} -{{#allParams.0}} +{{#vendorExtensions.x-angular-request-params-interface}} export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams { {{#allParams}} {{#description}}/** {{.}} */ @@ -37,9 +42,8 @@ export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterIn {{/allParams}} } -{{/allParams.0}} +{{/vendorExtensions.x-angular-request-params-interface}} {{/operation}} -{{/useSingleRequestParameter}} {{/withInterfaces}} {{#description}} @@ -107,90 +111,7 @@ export class {{classname}} extends BaseService { {{/required}} {{/allParams}} -{{#hasQueryParamsOrAuth}} - let localVarQueryParameters = new OpenApiHttpParams(this.encoder); -{{#queryParams}} - - localVarQueryParameters = this.addToHttpParams( - localVarQueryParameters, - '{{baseName}}', - {{paramName}}, - {{#isQueryParamObjectFormatJson}} - QueryParamStyle.Json, - {{/isQueryParamObjectFormatJson}} - {{^isQueryParamObjectFormatJson}} - {{^style}} - {{#queryIsJsonMimeType}} - QueryParamStyle.Json, - {{/queryIsJsonMimeType}} - {{^queryIsJsonMimeType}} - QueryParamStyle.Form, - {{/queryIsJsonMimeType}} - {{/style}} - {{#style}} - {{#isDeepObject}} - QueryParamStyle.DeepObject, - {{/isDeepObject}} - {{#isFormStyle}} - QueryParamStyle.Form, - {{/isFormStyle}} - {{#isSpaceDelimited}} - QueryParamStyle.SpaceDelimited, - {{/isSpaceDelimited}} - {{#isPipeDelimited}} - QueryParamStyle.PipeDelimited, - {{/isPipeDelimited}} - {{#queryIsJsonMimeType}} - QueryParamStyle.Json, - {{/queryIsJsonMimeType}} - {{/style}} - {{/isQueryParamObjectFormatJson}} - {{isExplode}}, - ); - -{{/queryParams}} - -{{/hasQueryParamsOrAuth}} - let localVarHeaders = this.defaultHeaders; -{{#headerParams}} - {{#isArray}} - if ({{paramName}}) { - localVarHeaders = localVarHeaders.set('{{baseName}}', [...{{paramName}}].join(COLLECTION_FORMATS['{{collectionFormat}}'])); - } - {{/isArray}} - {{^isArray}} - if ({{paramName}} !== undefined && {{paramName}} !== null) { - localVarHeaders = localVarHeaders.set('{{baseName}}', String({{paramName}})); - } - {{/isArray}} -{{/headerParams}} - -{{#authMethods}} - // authentication ({{name}}) required -{{#isApiKey}} - {{#isKeyInHeader}} - localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', '{{keyParamName}}', localVarHeaders); - {{/isKeyInHeader}} - {{#isKeyInQuery}} - localVarQueryParameters = this.configuration.addCredentialToQuery('{{name}}', '{{keyParamName}}', localVarQueryParameters); - {{/isKeyInQuery}} -{{/isApiKey}} -{{#isBasic}} - {{#isBasicBasic}} - localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Basic '); - {{/isBasicBasic}} - {{#isBasicBearer}} - localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); - {{/isBasicBearer}} -{{/isBasic}} -{{#isOAuth}} - localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); -{{/isOAuth}} -{{#isOpenId}} - localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); -{{/isOpenId}} - -{{/authMethods}} +{{>apiRequestQueryAndHeaders}} const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ {{#produces}} '{{{mediaType}}}'{{^-last}},{{/-last}} @@ -328,5 +249,8 @@ export class {{classname}} extends BaseService { ); } +{{#vendorExtensions.x-angular-http-resource}} +{{>apiHttpResource}} +{{/vendorExtensions.x-angular-http-resource}} {{/operation}}} {{/operations}} diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/apiHttpResource.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/apiHttpResource.mustache new file mode 100644 index 000000000000..16bb11f6b42b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-angular/apiHttpResource.mustache @@ -0,0 +1,78 @@ + /** +{{#summary}} + * {{.}} +{{/summary}} +{{#notes}} + * {{.}} +{{/notes}} + * @endpoint {{httpMethod}} {{{vendorExtensions.x-path-from-spec}}} +{{#hasRequiredParams}} + * @param params returns the request parameters. The resource stays idle while it returns undefined or a required parameter is null or undefined. +{{/hasRequiredParams}} +{{^hasRequiredParams}} +{{#hasParams}} + * @param params returns the request parameters, all of them optional. The resource stays idle while it returns undefined. +{{/hasParams}} +{{/hasRequiredParams}} + * @param options resource and request options + {{#isDeprecated}} + * @deprecated + {{/isDeprecated}} + */ +{{#hasParams}} +{{#hasRequiredParams}} + public {{nickname}}Resource(params: () => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined, options: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}> & { defaultValue: {{{vendorExtensions.x-angular-http-resource-type}}} }): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}}>; + public {{nickname}}Resource(params: () => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined, options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined>; + public {{nickname}}Resource(params: () => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined, options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined> { +{{/hasRequiredParams}} +{{^hasRequiredParams}} + public {{nickname}}Resource(params: (() => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined) | undefined, options: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}> & { defaultValue: {{{vendorExtensions.x-angular-http-resource-type}}} }): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}}>; + public {{nickname}}Resource(params?: () => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined, options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined>; + public {{nickname}}Resource(params: () => {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams | undefined = () => ({}), options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined> { +{{/hasRequiredParams}} + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource{{vendorExtensions.x-angular-http-resource-factory}}<{{{vendorExtensions.x-angular-http-resource-type}}}>(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.{{nickname}}ResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } +{{/hasParams}} +{{^hasParams}} + public {{nickname}}Resource(options: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}> & { defaultValue: {{{vendorExtensions.x-angular-http-resource-type}}} }): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}}>; + public {{nickname}}Resource(options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined>; + public {{nickname}}Resource(options?: ApiHttpResourceOptions<{{{vendorExtensions.x-angular-http-resource-type}}}>): HttpResourceRef<{{{vendorExtensions.x-angular-http-resource-type}}} | undefined> { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource{{vendorExtensions.x-angular-http-resource-factory}}<{{{vendorExtensions.x-angular-http-resource-type}}}>(() => this.{{nickname}}ResourceRequest(context, transferCache), resourceOptions); + } +{{/hasParams}} + + private {{nickname}}ResourceRequest({{#hasParams}}requestParameters: {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams, {{/hasParams}}localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest{{#hasRequiredParams}} | undefined{{/hasRequiredParams}} { +{{#allParams}} + const {{paramName}} = requestParameters.{{paramName}}; +{{#required}} + if ({{paramName}} === null || {{paramName}} === undefined) { + return undefined; + } +{{/required}} +{{/allParams}} + +{{>apiRequestQueryAndHeaders}} +{{#vendorExtensions.x-angular-http-resource-accept}} + localVarHeaders = localVarHeaders.set('Accept', '{{{.}}}'); +{{/vendorExtensions.x-angular-http-resource-accept}} + + let localVarPath = `{{{path}}}`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', +{{#hasQueryParamsOrAuth}} + params: localVarQueryParameters.toHttpParams(), +{{/hasQueryParamsOrAuth}} + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/apiInterface.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/apiInterface.mustache index 6774b0091ebb..0b886abb087f 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/apiInterface.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/apiInterface.mustache @@ -12,18 +12,16 @@ import { {{configurationClassName}} } from ' {{#operations}} -{{#useSingleRequestParameter}} {{#operation}} -{{#allParams.0}} +{{#vendorExtensions.x-angular-request-params-interface}} export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterInterfaces}}{{operationIdCamelCase}}RequestParams { {{#allParams}} {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}{{#isNullable}} | null{{/isNullable}}; {{/allParams}} } -{{/allParams.0}} +{{/vendorExtensions.x-angular-request-params-interface}} {{/operation}} -{{/useSingleRequestParameter}} {{#description}} /** diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/apiRequestQueryAndHeaders.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/apiRequestQueryAndHeaders.mustache new file mode 100644 index 000000000000..2f94a3a4905c --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-angular/apiRequestQueryAndHeaders.mustache @@ -0,0 +1,85 @@ +{{! query parameters, header parameters and credentials, shared by the Observable methods (api.service) and the resource request builders (apiHttpResource) }} +{{#hasQueryParamsOrAuth}} + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); +{{#queryParams}} + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + '{{baseName}}', + {{paramName}}, + {{#isQueryParamObjectFormatJson}} + QueryParamStyle.Json, + {{/isQueryParamObjectFormatJson}} + {{^isQueryParamObjectFormatJson}} + {{^style}} + {{#queryIsJsonMimeType}} + QueryParamStyle.Json, + {{/queryIsJsonMimeType}} + {{^queryIsJsonMimeType}} + QueryParamStyle.Form, + {{/queryIsJsonMimeType}} + {{/style}} + {{#style}} + {{#isDeepObject}} + QueryParamStyle.DeepObject, + {{/isDeepObject}} + {{#isFormStyle}} + QueryParamStyle.Form, + {{/isFormStyle}} + {{#isSpaceDelimited}} + QueryParamStyle.SpaceDelimited, + {{/isSpaceDelimited}} + {{#isPipeDelimited}} + QueryParamStyle.PipeDelimited, + {{/isPipeDelimited}} + {{#queryIsJsonMimeType}} + QueryParamStyle.Json, + {{/queryIsJsonMimeType}} + {{/style}} + {{/isQueryParamObjectFormatJson}} + {{isExplode}}, + ); + +{{/queryParams}} + +{{/hasQueryParamsOrAuth}} + let localVarHeaders = this.defaultHeaders; +{{#headerParams}} + {{#isArray}} + if ({{paramName}}) { + localVarHeaders = localVarHeaders.set('{{baseName}}', [...{{paramName}}].join(COLLECTION_FORMATS['{{collectionFormat}}'])); + } + {{/isArray}} + {{^isArray}} + if ({{paramName}} !== undefined && {{paramName}} !== null) { + localVarHeaders = localVarHeaders.set('{{baseName}}', String({{paramName}})); + } + {{/isArray}} +{{/headerParams}} + +{{#authMethods}} + // authentication ({{name}}) required +{{#isApiKey}} + {{#isKeyInHeader}} + localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', '{{keyParamName}}', localVarHeaders); + {{/isKeyInHeader}} + {{#isKeyInQuery}} + localVarQueryParameters = this.configuration.addCredentialToQuery('{{name}}', '{{keyParamName}}', localVarQueryParameters); + {{/isKeyInQuery}} +{{/isApiKey}} +{{#isBasic}} + {{#isBasicBasic}} + localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Basic '); + {{/isBasicBasic}} + {{#isBasicBearer}} + localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); + {{/isBasicBearer}} +{{/isBasic}} +{{#isOAuth}} + localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); +{{/isOAuth}} +{{#isOpenId}} + localVarHeaders = this.configuration.addCredentialToHeaders('{{name}}', 'Authorization', localVarHeaders, 'Bearer '); +{{/isOpenId}} + +{{/authMethods}} diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/httpResourceOptions.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/httpResourceOptions.mustache new file mode 100644 index 000000000000..4b2d8a7972c4 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/typescript-angular/httpResourceOptions.mustache @@ -0,0 +1,33 @@ +{{>licenseInfo}} +import { Injector, ValueEqualityFn } from '@angular/core'; +import { HttpContext } from '@angular/common/http'; + +/** + * Options of the generated `...Resource` methods, which return an Angular `httpResource`. + */ +export interface ApiHttpResourceOptions { + /** + * Value of the resource while no response is available, for example while it is idle or loading. + */ + defaultValue?: T; + /** + * Injector the resource is created in. Required when the method is called outside an injection context. + */ + injector?: Injector; + /** + * Equality function used to compare the values of the resource. + */ + equal?: ValueEqualityFn; + /** + * Debug name of the resource. Used by Angular 21.1 and later, ignored before. + */ + debugName?: string; + /** + * Context of the request. + */ + context?: HttpContext; + /** + * Whether the SSR transfer cache may be used for the request. Defaults to true. + */ + transferCache?: boolean; +} diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/index.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/index.mustache index b58aade26080..691e9d01210b 100644 --- a/modules/openapi-generator/src/main/resources/typescript-angular/index.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-angular/index.mustache @@ -8,4 +8,7 @@ export * from './api.module'; {{#ngVersionAtLeast_17}} export * from './provide-api'; {{/ngVersionAtLeast_17}} +{{#withHttpResource}} +export * from './http.resource.options'; +{{/withHttpResource}} export * from './param'; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java index d87bd3364651..9985b918cde6 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java @@ -52,6 +52,7 @@ public Map createOptions() { .put(TypeScriptAngularClientCodegen.STRING_ENUMS, STRING_ENUMS_VALUE) .put(TypeScriptAngularClientCodegen.WITH_INTERFACES, Boolean.FALSE.toString()) .put(TypeScriptAngularClientCodegen.USE_SINGLE_REQUEST_PARAMETER, Boolean.FALSE.toString()) + .put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, Boolean.FALSE.toString()) .put(TypeScriptAngularClientCodegen.PROVIDED_IN, PROVIDED_IN_LEVEL) .put(TypeScriptAngularClientCodegen.TAGGED_UNIONS, Boolean.FALSE.toString()) .put(TypeScriptAngularClientCodegen.NPM_REPOSITORY, NPM_REPOSITORY) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java index 184176e1c177..dfe632184f65 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/typescriptangular/TypeScriptAngularClientCodegenTest.java @@ -18,6 +18,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -549,4 +550,136 @@ public void testOpenIdCredentialsAreSet() throws IOException { String credentialsSet = "localVarHeaders = this.configuration.addCredentialToHeaders('oidc', 'Authorization', localVarHeaders, 'Bearer ');"; assertThat(fileContents).contains(credentialsSet); } + + private static final String HTTP_RESOURCE_SPEC = "src/test/resources/3_0/typescript-angular/http-resource.yaml"; + + private File generateAngular(String specPath, Map properties) throws IOException { + File output = Files.createTempDirectory("test").toFile(); + output.deleteOnExit(); + + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("typescript-angular") + .setInputSpec(specPath) + .setAdditionalProperties(properties) + .setOutputDir(output.getAbsolutePath().replace("\\", "/")); + + new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + return output; + } + + @Test + public void testHttpResourceIsOffByDefault() throws IOException { + File output = generateAngular(HTTP_RESOURCE_SPEC, new HashMap<>()); + + TestUtils.assertFileNotContains(Paths.get(output + "/api/pet.service.ts"), + "HttpResourceRef", "httpResource<", "httpResource.", "Resource(", "RequestParams"); + TestUtils.assertFileNotContains(Paths.get(output + "/index.ts"), "http.resource.options"); + TestUtils.assertFileNotExists(Paths.get(output + "/http.resource.options.ts")); + } + + @Test + public void testHttpResourceOnlyForGetOperationsWithoutBody() throws IOException { + Map properties = new HashMap<>(); + properties.put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, "true"); + File output = generateAngular(HTTP_RESOURCE_SPEC, properties); + + Path petService = Paths.get(output + "/api/pet.service.ts"); + TestUtils.assertFileContains(petService, + "import { HttpResourceRef, HttpResourceRequest, httpResource } from '@angular/common/http';", + "import { ApiHttpResourceOptions } from '../http.resource.options';", + "export interface GetPetByIdRequestParams {", + "public getPetByIdResource(params: () => GetPetByIdRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef;", + "public getPetByIdResource(params: () => GetPetByIdRequestParams | undefined, options: ApiHttpResourceOptions & { defaultValue: Pet }): HttpResourceRef;", + // every parameter optional: the params function may be left out + "public listPetsResource(params?: () => ListPetsRequestParams | undefined, options?: ApiHttpResourceOptions>): HttpResourceRef | undefined>;", + // a missing required parameter keeps the resource idle instead of throwing + "if (petId === null || petId === undefined) { return undefined; }", + "params: localVarQueryParameters.toHttpParams(),", + "transferCache: localVarTransferCache ?? true,"); + // the Observable methods are unchanged, and writes and a GET with a body get no resource + TestUtils.assertFileContains(petService, + "public addPet(pet: Pet, observe?: 'body'", + "public searchPets(pet?: Pet, observe?: 'body'"); + TestUtils.assertFileNotContains(petService, + "addPetResource", "deletePetResource", "searchPetsResource", + "export interface AddPetRequestParams", "export interface SearchPetsRequestParams"); + + // an operation without parameters takes only the options + TestUtils.assertFileContains(Paths.get(output + "/api/store.service.ts"), + "public getInventoryResource(options?: ApiHttpResourceOptions<{ [key: string]: number; }>)", + "localVarQueryParameters = this.configuration.addCredentialToQuery('api_key_query', 'api_key', localVarQueryParameters);"); + + TestUtils.assertFileContains(Paths.get(output + "/http.resource.options.ts"), + "export interface ApiHttpResourceOptions {"); + TestUtils.assertFileContains(Paths.get(output + "/index.ts"), + "export * from './http.resource.options';"); + } + + @Test + public void testHttpResourceFactoryFollowsTheAcceptHeader() throws IOException { + Map properties = new HashMap<>(); + properties.put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, true); + File output = generateAngular(HTTP_RESOURCE_SPEC, properties); + + Path petService = Paths.get(output + "/api/pet.service.ts"); + TestUtils.assertFileContains(petService, + // application/xml and application/json: the JSON type wins, as in Configuration.selectHeaderAccept + "return httpResource>(", + "localVarHeaders = localVarHeaders.set('Accept', 'application/json');", + "return httpResource(", + // text/plain only + "return httpResource.text(", + "localVarHeaders = localVarHeaders.set('Accept', 'text/plain');", + // binary + "return httpResource.blob(", + "localVarHeaders = localVarHeaders.set('Accept', 'application/octet-stream');"); + } + + @Test + public void testHttpResourceRequiresAngular20() { + TypeScriptAngularClientCodegen codegen = new TypeScriptAngularClientCodegen(); + codegen.additionalProperties().put(TypeScriptAngularClientCodegen.NG_VERSION, "19.2.0"); + codegen.additionalProperties().put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, "true"); + IllegalArgumentException error = Assert.expectThrows(IllegalArgumentException.class, codegen::processOpts); + assertThat(error.getMessage()).contains("withHttpResource requires Angular v20+"); + + codegen = new TypeScriptAngularClientCodegen(); + codegen.additionalProperties().put(TypeScriptAngularClientCodegen.NG_VERSION, "20.0.0"); + codegen.additionalProperties().put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, "true"); + codegen.processOpts(); + assertThat(codegen.additionalProperties()).containsEntry(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, true); + + // without the option, older versions are not affected + codegen = new TypeScriptAngularClientCodegen(); + codegen.additionalProperties().put(TypeScriptAngularClientCodegen.NG_VERSION, "19.2.0"); + codegen.processOpts(); + assertThat(codegen.additionalProperties()).containsEntry(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, false); + } + + @Test + public void testHttpResourceSkipsAnOperationWhoseNameIsTaken() throws IOException { + Path spec = Files.createTempFile("http-resource-collision", ".yaml"); + spec.toFile().deleteOnExit(); + Files.writeString(spec, String.join("\n", + "openapi: 3.0.1", + "info: {title: collision, version: 1.0.0}", + "paths:", + " /pet:", + " get:", + " operationId: getPet", + " responses: {'200': {description: ok, content: {application/json: {schema: {type: string}}}}}", + " /pet-resource:", + " get:", + " operationId: getPetResource", + " responses: {'200': {description: ok, content: {application/json: {schema: {type: string}}}}}", + "")); + + Map properties = new HashMap<>(); + properties.put(TypeScriptAngularClientCodegen.WITH_HTTP_RESOURCE, "true"); + File output = generateAngular(spec.toString(), properties); + + Path service = Paths.get(output + "/api/default.service.ts"); + TestUtils.assertFileContains(service, "public getPetResourceResource(options?: ApiHttpResourceOptions)"); + TestUtils.assertFileNotContains(service, "public getPetResource(options?: ApiHttpResourceOptions)"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/typescript-angular/http-resource.yaml b/modules/openapi-generator/src/test/resources/3_0/typescript-angular/http-resource.yaml new file mode 100644 index 000000000000..76aeeee29b84 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/typescript-angular/http-resource.yaml @@ -0,0 +1,247 @@ +openapi: 3.0.1 +info: + title: httpResource petstore + description: Operations covering each shape of the resource methods generated with withHttpResource. + version: 1.0.0 +servers: + - url: http://localhost/api +tags: + - name: pet + - name: store +paths: + /pet: + get: + tags: + - pet + summary: List pets + description: Every parameter is optional. + operationId: listPets + parameters: + - name: limit + in: query + schema: + type: integer + format: int32 + - name: tags + in: query + style: form + explode: true + schema: + type: array + items: + type: string + - name: X-Request-Id + in: header + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + post: + tags: + - pet + summary: Add a pet + operationId: addPet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + /pet/findByStatus: + get: + tags: + - pet + summary: Find pets by status + operationId: findPetsByStatus + parameters: + - name: status + in: query + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + 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' + security: + - bearer_auth: [] + /pet/search: + get: + tags: + - pet + summary: Search pets with a request body + description: A GET with a body keeps only its Observable method. + operationId: searchPets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + operationId: getPetById + parameters: + - name: petId + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + security: + - api_key: [] + delete: + tags: + - pet + summary: Delete a pet + operationId: deletePet + parameters: + - name: petId + in: path + required: true + schema: + type: integer + format: int64 + responses: + '204': + description: deleted + /pet/{petId}/notes: + get: + tags: + - pet + summary: Notes about a pet, as plain text + operationId: getPetNotes + parameters: + - name: petId + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + text/plain: + schema: + type: string + /pet/{petId}/photo: + get: + tags: + - pet + summary: Photo of a pet + operationId: getPetPhoto + parameters: + - name: petId + in: path + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/octet-stream: + schema: + type: string + format: binary + /store/inventory: + get: + tags: + - store + summary: Pet inventories by status + description: No parameters, with the API key sent in the query. + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key_query: [] +components: + securitySchemes: + api_key: + type: apiKey + name: api_key + in: header + api_key_query: + type: apiKey + name: api_key + in: query + bearer_auth: + type: http + scheme: bearer + schemas: + Pet: + type: object + required: + - name + properties: + id: + type: integer + format: int64 + name: + type: string + status: + type: string + enum: + - available + - pending + - sold diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.gitignore b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.gitignore new file mode 100644 index 000000000000..149b57654723 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator-ignore b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/FILES b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/FILES new file mode 100644 index 000000000000..3c805738c685 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/FILES @@ -0,0 +1,18 @@ +.gitignore +README.md +api.base.service.ts +api.module.ts +api/api.ts +api/pet.service.ts +api/store.service.ts +configuration.ts +encoder.ts +git_push.sh +http.resource.options.ts +index.ts +model/models.ts +model/pet.ts +param.ts +provide-api.ts +query.params.ts +variables.ts diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/VERSION b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/VERSION new file mode 100644 index 000000000000..56fe0e8b297f --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.27.0-SNAPSHOT diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/README.md b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/README.md new file mode 100644 index 000000000000..4ad71cc3aeae --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/README.md @@ -0,0 +1,237 @@ +# @ + +Operations covering each shape of the resource methods generated with withHttpResource. + +The version of the OpenAPI document: 1.0.0 + +## Building + +To install the required dependencies and to build the typescript sources run: + +```console +npm install +npm run build +``` + +## Publishing + +First build the package then run `npm publish dist` (don't forget to specify the `dist` folder!) + +## Consuming + +Navigate to the folder of your consuming project and run one of next commands. + +_published:_ + +```console +npm install @ --save +``` + +_without publishing (not recommended):_ + +```console +npm install PATH_TO_GENERATED_PACKAGE/dist.tgz --save +``` + +_It's important to take the tgz file, otherwise you'll get trouble with links on windows_ + +_using `npm link`:_ + +In PATH_TO_GENERATED_PACKAGE/dist: + +```console +npm link +``` + +In your project: + +```console +npm link +``` + +__Note for Windows users:__ The Angular CLI has troubles to use linked npm packages. +Please refer to this issue for a solution / workaround. +Published packages are not effected by this issue. + +### General usage + +In your Angular project: + +```typescript + +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi() + ], +}; +``` + +**NOTE** +If you're still using `AppModule` and haven't [migrated](https://angular.dev/reference/migrations/standalone) yet, you can still import an Angular module: +```typescript +import { ApiModule } from ''; +``` + +If different from the generated base path, during app bootstrap, you can provide the base path to your service. + +```typescript +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi('http://localhost:9999') + ], +}; +``` + +```typescript +// with a custom configuration +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideApi({ + withCredentials: true, + username: 'user', + password: 'password' + }) + ], +}; +``` + +```typescript +// with factory building a custom configuration +import { ApplicationConfig } from '@angular/core'; +import { provideHttpClient } from '@angular/common/http'; +import { provideApi, Configuration } from ''; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + { + provide: Configuration, + useFactory: (authService: AuthService) => new Configuration({ + basePath: 'http://localhost:9999', + withCredentials: true, + username: authService.getUsername(), + password: authService.getPassword(), + }), + deps: [AuthService], + multi: false + } + ], +}; +``` + +### Using multiple OpenAPI files / APIs + +In order to use multiple APIs generated from different OpenAPI files, +you can create an alias name when importing the modules +in order to avoid naming conflicts: + +```typescript +import { provideApi as provideUserApi } from 'my-user-api-path'; +import { provideApi as provideAdminApi } from 'my-admin-api-path'; +import { HttpClientModule } from '@angular/common/http'; +import { environment } from '../environments/environment'; + +export const appConfig: ApplicationConfig = { + providers: [ + // ... + provideHttpClient(), + provideUserApi(environment.basePath), + provideAdminApi(environment.basePath), + ], +}; +``` + +### Signal-based resources + +Every `GET` operation without a request body also has a `...Resource` method, next to its Observable method, +which returns an Angular [`HttpResourceRef`](https://angular.dev/api/common/http/HttpResourceRef) +(see [`httpResource`](https://angular.dev/guide/http/http-resource)). The method is skipped, with a warning at +generation time, when its name would collide with another operation: + +```typescript +import { Component, computed, inject, input } from '@angular/core'; +import { PetService } from ''; + +@Component({ + selector: 'app-pet', + templateUrl: './pet.component.html', +}) +export class PetComponent { + readonly petId = input.required(); + + // requested again whenever petId changes + readonly pet = inject(PetService).getPetByIdResource(() => ({ petId: this.petId() })); + + readonly name = computed(() => this.pet.hasValue() ? this.pet.value().name : ''); +} +``` + +- The first argument returns the request parameters and is called in a reactive context: every signal it reads + sends a new request when it changes. While it returns `undefined`, or a required parameter is `null` or `undefined`, + the resource stays idle. Operations without parameters take no such argument, and it can be left out when all + parameters are optional. +- Call the method in an injection context (a field initializer or a constructor), or pass an `injector` in the options. + The resource belongs to that injector: it is destroyed with it, and it sends its request through the `HttpClient` + of that injector, so the interceptors of the caller apply. +- The credentials of the `Configuration` are read each time the request is built, so a credential + function that reads a signal sends a new request when the signal changes. Other credential changes need `reload()`. +- `transferCache` defaults to `true`, as for the Observable methods. Angular's transfer cache skips requests with an + `Authorization` header unless `includeRequestsWithAuthHeaders` is set in `withHttpTransferCacheOptions`. +- Resources are generated for reads only. A resource cancels its pending request when its parameters change, which + is not safe for a write, so operations that change data keep only their Observable method. + +The `withHttpResource` option requires Angular 20 or later. `httpResource` is stable since Angular 22 and +experimental in Angular 20 and 21. + +### Customizing path parameter encoding + +Without further customization, only [path-parameters][parameter-locations-url] of [style][style-values-url] 'simple' +and Dates for format 'date-time' are encoded correctly. + +Other styles (e.g. "matrix") are not that easy to encode +and thus are best delegated to other libraries (e.g.: [@honoluluhenk/http-param-expander]). + +To implement your own parameter encoding (or call another library), +pass an arrow-function or method-reference to the `encodeParam` property of the Configuration-object +(see [General Usage](#general-usage) above). + +Example value for use in your Configuration-Provider: + +```typescript +new Configuration({ + encodeParam: (param: Param) => myFancyParamEncoder(param), +}) +``` + +[parameter-locations-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#parameter-locations +[style-values-url]: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#style-values +[@honoluluhenk/http-param-expander]: https://www.npmjs.com/package/@honoluluhenk/http-param-expander + +## Deep-object query parameters + +For `style: deepObject`, nested objects are serialized using bracket notation, for example +`filter[name][contains]=Alice`. Arrays and sets use zero-based indices, for example +`filter[sort][0][field]=name`. Dates are serialized as ISO strings; null and undefined +values and empty containers are omitted. + +OpenAPI only specifies deepObject serialization for flat objects. This recursive +encoding is a generator extension and requires a server that accepts bracket notation. diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.base.service.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.base.service.ts new file mode 100644 index 000000000000..f1be6d0471fa --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.base.service.ts @@ -0,0 +1,120 @@ +/** + * httpResource petstore + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { HttpHeaders, HttpParams, HttpParameterCodec } from '@angular/common/http'; +import { CustomHttpParameterCodec } from './encoder'; +import { Configuration } from './configuration'; +import { OpenApiHttpParams, QueryParamStyle, concatHttpParamsObject} from './query.params'; + +export class BaseService { + protected basePath = 'http://localhost/api'; + public defaultHeaders = new HttpHeaders(); + public configuration: Configuration; + public encoder: HttpParameterCodec; + + constructor(basePath?: string|string[], configuration?: Configuration) { + this.configuration = configuration || new Configuration(); + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + protected canConsumeForm(consumes: string[]): boolean { + return consumes.indexOf('multipart/form-data') !== -1; + } + + protected addToHttpParams(httpParams: OpenApiHttpParams, key: string, value: any | null | undefined, paramStyle: QueryParamStyle, explode: boolean): OpenApiHttpParams { + if (value === null || value === undefined) { + return httpParams; + } + + if (paramStyle === QueryParamStyle.DeepObject) { + if (typeof value !== 'object') { + throw Error(`An object must be provided for key ${key} as it is a deep object`); + } + + return Object.keys(value as Record).reduce( + (hp, k) => this.addToHttpParamsDeepObject(hp, `${key}[${k}]`, value[k]), + httpParams, + ); + } else if (paramStyle === QueryParamStyle.Json) { + return httpParams.append(key, JSON.stringify(value)); + } else { + // Form-style, SpaceDelimited or PipeDelimited + + if (Object(value) !== value) { + // If it is a primitive type, add its string representation + return httpParams.append(key, value.toString()); + } else if (value instanceof Date) { + return httpParams.append(key, value.toISOString()); + } else if (Array.isArray(value) || value instanceof Set) { + // Otherwise, if it's an array or set, add each element. + const array = Array.isArray(value) ? value : Array.from(value); + if (paramStyle === QueryParamStyle.Form) { + return httpParams.set(key, array, {explode: explode, delimiter: ','}); + } else if (paramStyle === QueryParamStyle.SpaceDelimited) { + return httpParams.set(key, array, {explode: explode, delimiter: ' '}); + } else { + // PipeDelimited + return httpParams.set(key, array, {explode: explode, delimiter: '|'}); + } + } else { + // Otherwise, if it's an object, add each field. + if (paramStyle === QueryParamStyle.Form) { + if (explode) { + Object.keys(value).forEach(k => { + httpParams = this.addToHttpParams(httpParams, k, value[k], paramStyle, explode); + }); + return httpParams; + } else { + return concatHttpParamsObject(httpParams, key, value, ','); + } + } else if (paramStyle === QueryParamStyle.SpaceDelimited) { + return concatHttpParamsObject(httpParams, key, value, ' '); + } else { + // PipeDelimited + return concatHttpParamsObject(httpParams, key, value, '|'); + } + } + } + } + + /** + * Nested deepObject values are an extension to OpenAPI's flat-object serialization. + * Object properties use bracket notation; array and set elements use numeric indices. + */ + private addToHttpParamsDeepObject(httpParams: OpenApiHttpParams, key: string, value: any): OpenApiHttpParams { + if (value === null || value === undefined) { + return httpParams; + } else if (value instanceof Date) { + return httpParams.append(key, value.toISOString()); + } else if (Array.isArray(value) || value instanceof Set) { + const array = Array.isArray(value) ? value : Array.from(value); + return array.reduce( + (hp, item, index) => this.addToHttpParamsDeepObject(hp, `${key}[${index}]`, item), + httpParams, + ); + } else if (typeof value === 'object') { + return Object.keys(value).reduce( + (hp, k) => this.addToHttpParamsDeepObject(hp, `${key}[${k}]`, value[k]), + httpParams, + ); + } + return httpParams.append(key, value.toString()); + } +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.module.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.module.ts new file mode 100644 index 000000000000..58d341fbd2e5 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api.module.ts @@ -0,0 +1,30 @@ +import { NgModule, ModuleWithProviders, SkipSelf, Optional } from '@angular/core'; +import { Configuration } from './configuration'; +import { HttpClient } from '@angular/common/http'; + + +@NgModule({ + imports: [], + declarations: [], + exports: [], + providers: [] +}) +export class ApiModule { + public static forRoot(configurationFactory: () => Configuration): ModuleWithProviders { + return { + ngModule: ApiModule, + providers: [ { provide: Configuration, useFactory: configurationFactory } ] + }; + } + + constructor( @Optional() @SkipSelf() parentModule: ApiModule, + @Optional() http: HttpClient) { + if (parentModule) { + throw new Error('ApiModule is already loaded. Import in your base AppModule only.'); + } + if (!http) { + throw new Error('You need to import the HttpClientModule in your AppModule! \n' + + 'See also https://github.com/angular/angular/issues/20575'); + } + } +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/api.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/api.ts new file mode 100644 index 000000000000..aca343ee9981 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/api.ts @@ -0,0 +1,5 @@ +export * from './pet.service'; +import { PetService } from './pet.service'; +export * from './store.service'; +import { StoreService } from './store.service'; +export const APIS = [PetService, StoreService]; diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/pet.service.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/pet.service.ts new file mode 100644 index 000000000000..c2c3e549ff8b --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/pet.service.ts @@ -0,0 +1,791 @@ +/** + * httpResource petstore + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { HttpResourceRef, HttpResourceRequest, httpResource } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + +// @ts-ignore +import { Pet } from '../model/pet'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; +import { ApiHttpResourceOptions } from '../http.resource.options'; + + +export interface FindPetsByStatusRequestParams { + status: Array<'available' | 'pending' | 'sold'>; +} + +export interface GetPetByIdRequestParams { + petId: number; +} + +export interface GetPetNotesRequestParams { + petId: number; +} + +export interface GetPetPhotoRequestParams { + petId: number; +} + +export interface ListPetsRequestParams { + limit?: number; + tags?: Array; + xRequestId?: string; +} + + +@Injectable({ + providedIn: 'root' +}) +export class PetService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * Add a pet + * @endpoint post /pet + * @param pet + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public addPet(pet: Pet, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public addPet(pet: Pet, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public addPet(pet: Pet, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public addPet(pet: Pet, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (pet === null || pet === undefined) { + throw new Error('Required parameter pet was null or undefined when calling addPet.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('post', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: pet, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Delete a pet + * @endpoint delete /pet/{petId} + * @param petId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public deletePet(petId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable; + public deletePet(petId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public deletePet(petId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable>; + public deletePet(petId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: undefined, context?: HttpContext, transferCache?: boolean}): Observable { + if (petId === null || petId === undefined) { + throw new Error('Required parameter petId was null or undefined when calling deletePet.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('delete', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Find pets by status + * @endpoint get /pet/findByStatus + * @param status + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public findPetsByStatus(status: Array<'available' | 'pending' | 'sold'>, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/xml' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public findPetsByStatus(status: Array<'available' | 'pending' | 'sold'>, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/xml' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public findPetsByStatus(status: Array<'available' | 'pending' | 'sold'>, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/xml' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public findPetsByStatus(status: Array<'available' | 'pending' | 'sold'>, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/xml' | 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (status === null || status === undefined) { + throw new Error('Required parameter status was null or undefined when calling findPetsByStatus.'); + } + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'status', + status, + QueryParamStyle.Form, + false, + ); + + + let localVarHeaders = this.defaultHeaders; + + // authentication (bearer_auth) required + localVarHeaders = this.configuration.addCredentialToHeaders('bearer_auth', 'Authorization', localVarHeaders, 'Bearer '); + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/xml', + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet/findByStatus`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + params: localVarQueryParameters.toHttpParams(), + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Find pets by status + * @endpoint get /pet/findByStatus + * @param params returns the request parameters. The resource stays idle while it returns undefined or a required parameter is null or undefined. + * @param options resource and request options + */ + public findPetsByStatusResource(params: () => FindPetsByStatusRequestParams | undefined, options: ApiHttpResourceOptions> & { defaultValue: Array }): HttpResourceRef>; + public findPetsByStatusResource(params: () => FindPetsByStatusRequestParams | undefined, options?: ApiHttpResourceOptions>): HttpResourceRef | undefined>; + public findPetsByStatusResource(params: () => FindPetsByStatusRequestParams | undefined, options?: ApiHttpResourceOptions>): HttpResourceRef | undefined> { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource>(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.findPetsByStatusResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } + + private findPetsByStatusResourceRequest(requestParameters: FindPetsByStatusRequestParams, localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest | undefined { + const status = requestParameters.status; + if (status === null || status === undefined) { + return undefined; + } + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'status', + status, + QueryParamStyle.Form, + false, + ); + + + let localVarHeaders = this.defaultHeaders; + + // authentication (bearer_auth) required + localVarHeaders = this.configuration.addCredentialToHeaders('bearer_auth', 'Authorization', localVarHeaders, 'Bearer '); + + localVarHeaders = localVarHeaders.set('Accept', 'application/json'); + + let localVarPath = `/pet/findByStatus`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + params: localVarQueryParameters.toHttpParams(), + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + + /** + * Find pet by ID + * @endpoint get /pet/{petId} + * @param petId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getPetById(petId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getPetById(petId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetById(petId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetById(petId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (petId === null || petId === undefined) { + throw new Error('Required parameter petId was null or undefined when calling getPetById.'); + } + + let localVarHeaders = this.defaultHeaders; + + // authentication (api_key) required + localVarHeaders = this.configuration.addCredentialToHeaders('api_key', 'api_key', localVarHeaders); + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Find pet by ID + * @endpoint get /pet/{petId} + * @param params returns the request parameters. The resource stays idle while it returns undefined or a required parameter is null or undefined. + * @param options resource and request options + */ + public getPetByIdResource(params: () => GetPetByIdRequestParams | undefined, options: ApiHttpResourceOptions & { defaultValue: Pet }): HttpResourceRef; + public getPetByIdResource(params: () => GetPetByIdRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef; + public getPetByIdResource(params: () => GetPetByIdRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.getPetByIdResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } + + private getPetByIdResourceRequest(requestParameters: GetPetByIdRequestParams, localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest | undefined { + const petId = requestParameters.petId; + if (petId === null || petId === undefined) { + return undefined; + } + + let localVarHeaders = this.defaultHeaders; + + // authentication (api_key) required + localVarHeaders = this.configuration.addCredentialToHeaders('api_key', 'api_key', localVarHeaders); + + localVarHeaders = localVarHeaders.set('Accept', 'application/json'); + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + + /** + * Notes about a pet, as plain text + * @endpoint get /pet/{petId}/notes + * @param petId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getPetNotes(petId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'text/plain', context?: HttpContext, transferCache?: boolean}): Observable; + public getPetNotes(petId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'text/plain', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetNotes(petId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'text/plain', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetNotes(petId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'text/plain', context?: HttpContext, transferCache?: boolean}): Observable { + if (petId === null || petId === undefined) { + throw new Error('Required parameter petId was null or undefined when calling getPetNotes.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'text/plain' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/notes`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Notes about a pet, as plain text + * @endpoint get /pet/{petId}/notes + * @param params returns the request parameters. The resource stays idle while it returns undefined or a required parameter is null or undefined. + * @param options resource and request options + */ + public getPetNotesResource(params: () => GetPetNotesRequestParams | undefined, options: ApiHttpResourceOptions & { defaultValue: string }): HttpResourceRef; + public getPetNotesResource(params: () => GetPetNotesRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef; + public getPetNotesResource(params: () => GetPetNotesRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource.text(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.getPetNotesResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } + + private getPetNotesResourceRequest(requestParameters: GetPetNotesRequestParams, localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest | undefined { + const petId = requestParameters.petId; + if (petId === null || petId === undefined) { + return undefined; + } + + let localVarHeaders = this.defaultHeaders; + + localVarHeaders = localVarHeaders.set('Accept', 'text/plain'); + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/notes`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + + /** + * Photo of a pet + * @endpoint get /pet/{petId}/photo + * @param petId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getPetPhoto(petId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/octet-stream', context?: HttpContext, transferCache?: boolean}): Observable; + public getPetPhoto(petId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/octet-stream', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetPhoto(petId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/octet-stream', context?: HttpContext, transferCache?: boolean}): Observable>; + public getPetPhoto(petId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/octet-stream', context?: HttpContext, transferCache?: boolean}): Observable { + if (petId === null || petId === undefined) { + throw new Error('Required parameter petId was null or undefined when calling getPetPhoto.'); + } + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/octet-stream' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/photo`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: "blob", + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Photo of a pet + * @endpoint get /pet/{petId}/photo + * @param params returns the request parameters. The resource stays idle while it returns undefined or a required parameter is null or undefined. + * @param options resource and request options + */ + public getPetPhotoResource(params: () => GetPetPhotoRequestParams | undefined, options: ApiHttpResourceOptions & { defaultValue: Blob }): HttpResourceRef; + public getPetPhotoResource(params: () => GetPetPhotoRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef; + public getPetPhotoResource(params: () => GetPetPhotoRequestParams | undefined, options?: ApiHttpResourceOptions): HttpResourceRef { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource.blob(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.getPetPhotoResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } + + private getPetPhotoResourceRequest(requestParameters: GetPetPhotoRequestParams, localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest | undefined { + const petId = requestParameters.petId; + if (petId === null || petId === undefined) { + return undefined; + } + + let localVarHeaders = this.defaultHeaders; + + localVarHeaders = localVarHeaders.set('Accept', 'application/octet-stream'); + + let localVarPath = `/pet/${this.configuration.encodeParam({name: "petId", value: petId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/photo`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + + /** + * List pets + * Every parameter is optional. + * @endpoint get /pet + * @param limit + * @param tags + * @param xRequestId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public listPets(limit?: number, tags?: Array, xRequestId?: string, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public listPets(limit?: number, tags?: Array, xRequestId?: string, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public listPets(limit?: number, tags?: Array, xRequestId?: string, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public listPets(limit?: number, tags?: Array, xRequestId?: string, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'limit', + limit, + QueryParamStyle.Form, + true, + ); + + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'tags', + tags, + QueryParamStyle.Form, + true, + ); + + + let localVarHeaders = this.defaultHeaders; + if (xRequestId !== undefined && xRequestId !== null) { + localVarHeaders = localVarHeaders.set('X-Request-Id', String(xRequestId)); + } + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + params: localVarQueryParameters.toHttpParams(), + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * List pets + * Every parameter is optional. + * @endpoint get /pet + * @param params returns the request parameters, all of them optional. The resource stays idle while it returns undefined. + * @param options resource and request options + */ + public listPetsResource(params: (() => ListPetsRequestParams | undefined) | undefined, options: ApiHttpResourceOptions> & { defaultValue: Array }): HttpResourceRef>; + public listPetsResource(params?: () => ListPetsRequestParams | undefined, options?: ApiHttpResourceOptions>): HttpResourceRef | undefined>; + public listPetsResource(params: () => ListPetsRequestParams | undefined = () => ({}), options?: ApiHttpResourceOptions>): HttpResourceRef | undefined> { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource>(() => { + const requestParameters = params(); + return requestParameters === undefined ? undefined : this.listPetsResourceRequest(requestParameters, context, transferCache); + }, resourceOptions); + } + + private listPetsResourceRequest(requestParameters: ListPetsRequestParams, localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest { + const limit = requestParameters.limit; + const tags = requestParameters.tags; + const xRequestId = requestParameters.xRequestId; + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'limit', + limit, + QueryParamStyle.Form, + true, + ); + + + localVarQueryParameters = this.addToHttpParams( + localVarQueryParameters, + 'tags', + tags, + QueryParamStyle.Form, + true, + ); + + + let localVarHeaders = this.defaultHeaders; + if (xRequestId !== undefined && xRequestId !== null) { + localVarHeaders = localVarHeaders.set('X-Request-Id', String(xRequestId)); + } + + localVarHeaders = localVarHeaders.set('Accept', 'application/json'); + + let localVarPath = `/pet`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + params: localVarQueryParameters.toHttpParams(), + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + + /** + * Search pets with a request body + * A GET with a body keeps only its Observable method. + * @endpoint get /pet/search + * @param pet + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public searchPets(pet?: Pet, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public searchPets(pet?: Pet, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public searchPets(pet?: Pet, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public searchPets(pet?: Pet, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/pet/search`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: pet, + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/store.service.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/store.service.ts new file mode 100644 index 000000000000..dc4240668762 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/api/store.service.ts @@ -0,0 +1,134 @@ +/** + * httpResource petstore + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpContext + } from '@angular/common/http'; +import { HttpResourceRef, HttpResourceRequest, httpResource } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { OpenApiHttpParams, QueryParamStyle } from '../query.params'; + + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; +import { BaseService } from '../api.base.service'; +import { ApiHttpResourceOptions } from '../http.resource.options'; + + + +@Injectable({ + providedIn: 'root' +}) +export class StoreService extends BaseService { + + constructor(protected httpClient: HttpClient, @Optional() @Inject(BASE_PATH) basePath: string|string[], @Optional() configuration?: Configuration) { + super(basePath, configuration); + } + + /** + * Pet inventories by status + * No parameters, with the API key sent in the query. + * @endpoint get /store/inventory + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + * @param options additional options + */ + public getInventory(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<{ [key: string]: number; }>; + public getInventory(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getInventory(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getInventory(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + let localVarHeaders = this.defaultHeaders; + + // authentication (api_key_query) required + localVarQueryParameters = this.configuration.addCredentialToQuery('api_key_query', 'api_key', localVarQueryParameters); + + const localVarHttpHeaderAcceptSelected: string | undefined = options?.httpHeaderAccept ?? this.configuration.selectHeaderAccept([ + 'application/json' + ]); + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + const localVarHttpContext: HttpContext = options?.context ?? new HttpContext(); + + const localVarTransferCache: boolean = options?.transferCache ?? true; + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/store/inventory`; + const { basePath, withCredentials } = this.configuration; + return this.httpClient.request<{ [key: string]: number; }>('get', `${basePath}${localVarPath}`, + { + context: localVarHttpContext, + params: localVarQueryParameters.toHttpParams(), + responseType: responseType_, + ...(withCredentials ? { withCredentials } : {}), + headers: localVarHeaders, + observe: observe, + ...(localVarTransferCache !== undefined ? { transferCache: localVarTransferCache } : {}), + reportProgress: reportProgress + } + ); + } + + /** + * Pet inventories by status + * No parameters, with the API key sent in the query. + * @endpoint get /store/inventory + * @param options resource and request options + */ + public getInventoryResource(options: ApiHttpResourceOptions<{ [key: string]: number; }> & { defaultValue: { [key: string]: number; } }): HttpResourceRef<{ [key: string]: number; }>; + public getInventoryResource(options?: ApiHttpResourceOptions<{ [key: string]: number; }>): HttpResourceRef<{ [key: string]: number; } | undefined>; + public getInventoryResource(options?: ApiHttpResourceOptions<{ [key: string]: number; }>): HttpResourceRef<{ [key: string]: number; } | undefined> { + const { context, transferCache, ...resourceOptions } = options ?? {}; + return httpResource<{ [key: string]: number; }>(() => this.getInventoryResourceRequest(context, transferCache), resourceOptions); + } + + private getInventoryResourceRequest(localVarHttpContext: HttpContext | undefined, localVarTransferCache: boolean | undefined): HttpResourceRequest { + + let localVarQueryParameters = new OpenApiHttpParams(this.encoder); + + let localVarHeaders = this.defaultHeaders; + + // authentication (api_key_query) required + localVarQueryParameters = this.configuration.addCredentialToQuery('api_key_query', 'api_key', localVarQueryParameters); + + localVarHeaders = localVarHeaders.set('Accept', 'application/json'); + + let localVarPath = `/store/inventory`; + const { basePath, withCredentials } = this.configuration; + return { + url: `${basePath}${localVarPath}`, + method: 'GET', + params: localVarQueryParameters.toHttpParams(), + headers: localVarHeaders, + context: localVarHttpContext ?? new HttpContext(), + transferCache: localVarTransferCache ?? true, + ...(withCredentials ? { withCredentials } : {}), + }; + } + +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/configuration.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/configuration.ts new file mode 100644 index 000000000000..5647b55b87a3 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/configuration.ts @@ -0,0 +1,216 @@ +import { HttpHeaders, HttpParameterCodec } from '@angular/common/http'; +import { Param } from './param'; +import { OpenApiHttpParams } from './query.params'; + +export interface ConfigurationParameters { + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: {[ key: string ]: string}; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Override the default method for encoding path parameters in various + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam?: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials?: {[ key: string ]: string | (() => string | undefined)}; +} + +export class Configuration { + /** + * @deprecated Since 5.0. Use credentials instead + */ + apiKeys?: {[ key: string ]: string}; + username?: string; + password?: string; + /** + * @deprecated Since 5.0. Use credentials instead + */ + accessToken?: string | (() => string); + basePath?: string; + withCredentials?: boolean; + /** + * Takes care of encoding query- and form-parameters. + */ + encoder?: HttpParameterCodec; + /** + * Encoding of various path parameter + * styles. + *

+ * See {@link README.md} for more details + *

+ */ + encodeParam: (param: Param) => string; + /** + * The keys are the names in the securitySchemes section of the OpenAPI + * document. They should map to the value used for authentication + * minus any standard prefixes such as 'Basic' or 'Bearer'. + */ + credentials: {[ key: string ]: string | (() => string | undefined)}; + +constructor({ accessToken, apiKeys, basePath, credentials, encodeParam, encoder, password, username, withCredentials }: ConfigurationParameters = {}) { + if (apiKeys) { + this.apiKeys = apiKeys; + } + if (username !== undefined) { + this.username = username; + } + if (password !== undefined) { + this.password = password; + } + if (accessToken !== undefined) { + this.accessToken = accessToken; + } + if (basePath !== undefined) { + this.basePath = basePath; + } + if (withCredentials !== undefined) { + this.withCredentials = withCredentials; + } + if (encoder) { + this.encoder = encoder; + } + this.encodeParam = encodeParam ?? (param => this.defaultEncodeParam(param)); + this.credentials = credentials ?? {}; + + // init default api_key credential + if (!this.credentials['api_key']) { + this.credentials['api_key'] = () => { + if (this.apiKeys === null || this.apiKeys === undefined) { + return undefined; + } else { + return this.apiKeys['api_key'] || this.apiKeys['api_key']; + } + }; + } + + // init default api_key_query credential + if (!this.credentials['api_key_query']) { + this.credentials['api_key_query'] = () => { + if (this.apiKeys === null || this.apiKeys === undefined) { + return undefined; + } else { + return this.apiKeys['api_key_query'] || this.apiKeys['api_key']; + } + }; + } + + // init default bearer_auth credential + if (!this.credentials['bearer_auth']) { + this.credentials['bearer_auth'] = () => { + return typeof this.accessToken === 'function' + ? this.accessToken() + : this.accessToken; + }; + } + } + + /** + * Select the correct content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param contentTypes - the array of content types that are available for selection + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderContentType (contentTypes: string[]): string | undefined { + if (contentTypes.length === 0) { + return undefined; + } + + const type = contentTypes.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return contentTypes[0]; + } + return type; + } + + /** + * Select the correct accept content-type to use for a request. + * Uses {@link Configuration#isJsonMime} to determine the correct accept content-type. + * If no content type is found return the first found type if the contentTypes is not empty + * @param accepts - the array of content types that are available for selection. + * @returns the selected content-type or undefined if no selection could be made. + */ + public selectHeaderAccept(accepts: string[]): string | undefined { + if (accepts.length === 0) { + return undefined; + } + + const type = accepts.find((x: string) => this.isJsonMime(x)); + if (type === undefined) { + return accepts[0]; + } + return type; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = /^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$/i; + return mime !== null && jsonMime.test(mime); + } + + public lookupCredential(key: string): string | undefined { + const value = this.credentials[key]; + return typeof value === 'function' + ? value() + : value; + } + + public addCredentialToHeaders(credentialKey: string, headerName: string, headers: HttpHeaders, prefix?: string): HttpHeaders { + const value = this.lookupCredential(credentialKey); + return value + ? headers.set(headerName, (prefix ?? '') + value) + : headers; + } + + public addCredentialToQuery(credentialKey: string, paramName: string, query: OpenApiHttpParams): OpenApiHttpParams { + const value = this.lookupCredential(credentialKey); + return value + ? query.set(paramName, value) + : query; + } + + private defaultEncodeParam(param: Param): string { + // This implementation exists as fallback for missing configuration + // and for backwards compatibility to older typescript-angular generator versions. + // It only works for the 'simple' parameter style. + // Date-handling only works for the 'date-time' format. + // All other styles and Date-formats are probably handled incorrectly. + // + // But: if that's all you need (i.e.: the most common use-case): no need for customization! + + const value = param.dataFormat === 'date-time' && param.value instanceof Date + ? (param.value as Date).toISOString() + : param.value; + + return encodeURIComponent(String(value)); + } +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/encoder.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/encoder.ts new file mode 100644 index 000000000000..af4523543e6c --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/encoder.ts @@ -0,0 +1,35 @@ +import { HttpParameterCodec } from '@angular/common/http'; + +/** + * Custom HttpParameterCodec + * Workaround for https://github.com/angular/angular/issues/18261 + */ +export class CustomHttpParameterCodec implements HttpParameterCodec { + encodeKey(k: string): string { + return encodeURIComponent(k); + } + encodeValue(v: string): string { + return encodeURIComponent(v); + } + decodeKey(k: string): string { + return decodeURIComponent(k); + } + decodeValue(v: string): string { + return decodeURIComponent(v); + } +} + +export class IdentityHttpParameterCodec implements HttpParameterCodec { + encodeKey(k: string): string { + return k; + } + encodeValue(v: string): string { + return v; + } + decodeKey(k: string): string { + return k; + } + decodeValue(v: string): string { + return v; + } +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/git_push.sh b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/git_push.sh new file mode 100644 index 000000000000..a35991cd51a1 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/git_push.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ -z "${git_host}" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \${git_host} to ${git_host}" +fi + +if [ -z "${git_user_id}" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \${git_user_id} to ${git_user_id}" +fi + +if [ -z "${git_repo_id}" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \${git_repo_id} to ${git_repo_id}" +fi + +if [ -z "${release_note}" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \${release_note} to ${release_note}" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "${release_note}" + +# Sets the new remote +if [ -z "$(git remote)" ]; then # git remote not defined + if [ -z "${GIT_TOKEN:-}" ]; then + echo "[INFO] \${GIT_TOKEN} (environment variable) is not set. Using the git credential in your environment." + git remote add origin "https://${git_host}/${git_user_id}/${git_repo_id}.git" + else + git remote add origin "https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git" + fi +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/http.resource.options.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/http.resource.options.ts new file mode 100644 index 000000000000..0128ade37645 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/http.resource.options.ts @@ -0,0 +1,41 @@ +/** + * httpResource petstore + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +import { Injector, ValueEqualityFn } from '@angular/core'; +import { HttpContext } from '@angular/common/http'; + +/** + * Options of the generated `...Resource` methods, which return an Angular `httpResource`. + */ +export interface ApiHttpResourceOptions { + /** + * Value of the resource while no response is available, for example while it is idle or loading. + */ + defaultValue?: T; + /** + * Injector the resource is created in. Required when the method is called outside an injection context. + */ + injector?: Injector; + /** + * Equality function used to compare the values of the resource. + */ + equal?: ValueEqualityFn; + /** + * Debug name of the resource. Used by Angular 21.1 and later, ignored before. + */ + debugName?: string; + /** + * Context of the request. + */ + context?: HttpContext; + /** + * Whether the SSR transfer cache may be used for the request. Defaults to true. + */ + transferCache?: boolean; +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/index.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/index.ts new file mode 100644 index 000000000000..c984d12d0848 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/index.ts @@ -0,0 +1,8 @@ +export * from './api/api'; +export * from './model/models'; +export * from './variables'; +export * from './configuration'; +export * from './api.module'; +export * from './provide-api'; +export * from './http.resource.options'; +export * from './param'; diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/models.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/models.ts new file mode 100644 index 000000000000..eccf4ec59cbb --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/models.ts @@ -0,0 +1 @@ +export * from './pet'; diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/pet.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/pet.ts new file mode 100644 index 000000000000..40e3bf53975c --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/model/pet.ts @@ -0,0 +1,26 @@ +/** + * httpResource petstore + * + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface Pet { + id?: number; + name: string; + status?: Pet.StatusEnum; +} +export namespace Pet { + export const StatusEnum = { + Available: 'available', + Pending: 'pending', + Sold: 'sold', + } as const; + export type StatusEnum = typeof StatusEnum[keyof typeof StatusEnum]; +} + + diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/param.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/param.ts new file mode 100644 index 000000000000..78a2d20a6433 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/param.ts @@ -0,0 +1,69 @@ +/** + * Standard parameter styles defined by OpenAPI spec + */ +export type StandardParamStyle = + | 'matrix' + | 'label' + | 'form' + | 'simple' + | 'spaceDelimited' + | 'pipeDelimited' + | 'deepObject' + ; + +/** + * The OpenAPI standard {@link StandardParamStyle}s may be extended by custom styles by the user. + */ +export type ParamStyle = StandardParamStyle | string; + +/** + * Standard parameter locations defined by OpenAPI spec + */ +export type ParamLocation = 'query' | 'header' | 'path' | 'cookie'; + +/** + * Standard types as defined in OpenAPI Specification: Data Types + */ +export type StandardDataType = + | "integer" + | "number" + | "boolean" + | "string" + | "object" + | "array" + ; + +/** + * Standard {@link DataType}s plus your own types/classes. + */ +export type DataType = StandardDataType | string; + +/** + * Standard formats as defined in OpenAPI Specification: Data Types + */ +export type StandardDataFormat = + | "int32" + | "int64" + | "float" + | "double" + | "byte" + | "binary" + | "date" + | "date-time" + | "password" + ; + +export type DataFormat = StandardDataFormat | string; + +/** + * The parameter to encode. + */ +export interface Param { + name: string; + value: unknown; + in: ParamLocation; + style: ParamStyle, + explode: boolean; + dataType: DataType; + dataFormat: DataFormat | undefined; +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/provide-api.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/provide-api.ts new file mode 100644 index 000000000000..19c762acdfe0 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/provide-api.ts @@ -0,0 +1,15 @@ +import { EnvironmentProviders, makeEnvironmentProviders } from "@angular/core"; +import { Configuration, ConfigurationParameters } from './configuration'; +import { BASE_PATH } from './variables'; + +// Returns the service class providers, to be used in the [ApplicationConfig](https://angular.dev/api/core/ApplicationConfig). +export function provideApi(configOrBasePath: string | ConfigurationParameters): EnvironmentProviders { + return makeEnvironmentProviders([ + typeof configOrBasePath === "string" + ? { provide: BASE_PATH, useValue: configOrBasePath } + : { + provide: Configuration, + useValue: new Configuration({ ...configOrBasePath }), + }, + ]); +} \ No newline at end of file diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/query.params.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/query.params.ts new file mode 100644 index 000000000000..4076c1140c83 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/query.params.ts @@ -0,0 +1,160 @@ +import { HttpParams, HttpParameterCodec } from '@angular/common/http'; +import { CustomHttpParameterCodec, IdentityHttpParameterCodec } from './encoder'; + +export enum QueryParamStyle { + Json, + Form, + DeepObject, + SpaceDelimited, + PipeDelimited, +} + +export type Delimiter = "," | " " | "|" | "\t"; + +export interface ParamOptions { + /** When true, serialized as multiple repeated key=value pairs. When false, serialized as a single key with joined values using `delimiter`. */ + explode?: boolean; + /** Delimiter used when explode=false. The delimiter itself is inserted unencoded between encoded values. */ + delimiter?: Delimiter; +} + +interface ParamEntry { + values: string[]; + options: Required; +} + +export class OpenApiHttpParams { + private params: Map = new Map(); + private defaults: Required; + private encoder: HttpParameterCodec; + + /** + * @param encoder Parameter serializer + * @param defaults Global defaults used when a specific parameter has no explicit options. + * By OpenAPI default, explode is true for query params with style=form. + */ + constructor(encoder?: HttpParameterCodec, defaults?: { explode?: boolean; delimiter?: Delimiter }) { + this.encoder = encoder || new CustomHttpParameterCodec(); + this.defaults = { + explode: defaults?.explode ?? true, + delimiter: defaults?.delimiter ?? ",", + }; + } + + private resolveOptions(local?: ParamOptions): Required { + return { + explode: local?.explode ?? this.defaults.explode, + delimiter: local?.delimiter ?? this.defaults.delimiter, + }; + } + + /** + * Replace the parameter's values and (optionally) its options. + * Options are stored per-parameter (not global). + */ + set(key: string, values: string[] | string, options?: ParamOptions): this { + const arr = Array.isArray(values) ? values.slice() : [values]; + const opts = this.resolveOptions(options); + this.params.set(key, {values: arr, options: opts}); + return this; + } + + /** + * Append a single value to the parameter. If the parameter didn't exist it will be created + * and use resolved options (global defaults merged with any provided options). + */ + append(key: string, value: string, options?: ParamOptions): this { + const entry = this.params.get(key); + if (entry) { + // If new options provided, override the stored options for subsequent serialization + if (options) { + entry.options = this.resolveOptions({...entry.options, ...options}); + } + entry.values.push(value); + } else { + this.set(key, [value], options); + } + return this; + } + + /** + * Serialize to a query string according to per-parameter OpenAPI options. + * - If explode=true for that parameter → repeated key=value pairs (each value encoded). + * - If explode=false for that parameter → single key=value where values are individually encoded + * and joined using the configured delimiter. The delimiter character is inserted AS-IS + * (not percent-encoded). + */ + toString(): string { + const records = this.toRecord(); + const parts: string[] = []; + + for (const key in records) { + parts.push(`${key}=${records[key]}`); + } + + return parts.join("&"); + } + + /** + * Return parameters as a plain record. + * - If a parameter has exactly one value, returns that value directly. + * - If a parameter has multiple values, returns a readonly array of values. + */ + toRecord(): Record> { + const parts: Record> = {}; + + for (const [key, entry] of this.params.entries()) { + const encodedKey = this.encoder.encodeKey(key); + + if (entry.options.explode) { + parts[encodedKey] = entry.values.map((v) => this.encoder.encodeValue(v)); + } else { + const encodedValues = entry.values.map((v) => this.encoder.encodeValue(v)); + + // join with the delimiter *unencoded* + parts[encodedKey] = encodedValues.join(entry.options.delimiter); + } + } + + return parts; + } + + /** + * Return an Angular's HttpParams with an identity parameter codec as the parameters are already encoded. + */ + toHttpParams(): HttpParams { + const records = this.toRecord(); + + let httpParams = new HttpParams({encoder: new IdentityHttpParameterCodec()}); + + return httpParams.appendAll(records); + } +} + +export function concatHttpParamsObject(httpParams: OpenApiHttpParams, key: string, item: { + [index: string]: any +}, delimiter: Delimiter): OpenApiHttpParams { + let keyAndValues: string[] = []; + + for (const k in item) { + keyAndValues.push(k); + + const value = item[k]; + + if (Array.isArray(value)) { + keyAndValues.push(...value.map(convertToString)); + } else { + keyAndValues.push(convertToString(value)); + } + } + + return httpParams.set(key, keyAndValues, {explode: false, delimiter: delimiter}); +} + +function convertToString(value: any): string { + if (value instanceof Date) { + return value.toISOString(); + } else { + return value.toString(); + } +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/variables.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/variables.ts new file mode 100644 index 000000000000..6fe58549f395 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/builds/with-http-resource/variables.ts @@ -0,0 +1,9 @@ +import { InjectionToken } from '@angular/core'; + +export const BASE_PATH = new InjectionToken('basePath'); +export const COLLECTION_FORMATS = { + 'csv': ',', + 'tsv': ' ', + 'ssv': ' ', + 'pipes': '|' +} diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/tests/default/src/test/http-resource.spec.ts b/samples/client/petstore/typescript-angular-v22-provided-in-root/tests/default/src/test/http-resource.spec.ts new file mode 100644 index 000000000000..468dfd8e7d18 --- /dev/null +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/tests/default/src/test/http-resource.spec.ts @@ -0,0 +1,226 @@ +import { Injector, signal } from '@angular/core' +import { TestBed } from '@angular/core/testing' +import { HttpContext, HttpContextToken, provideHttpClient } from '@angular/common/http' +import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing' +import { + Configuration, + PetService, + StoreService, + type Pet +} from '@swagger/typescript-angular-http-resource' + +const TOKEN = new HttpContextToken(() => 'none') + +describe('API (httpResource)', () => { + let http: HttpTestingController + let petService: PetService + let storeService: StoreService + const accessToken = signal('first-token') + + // lets the resources react to signal changes and deliver the flushed responses + const settle = async (): Promise => { + TestBed.tick() + await new Promise(resolve => setTimeout(resolve)) + TestBed.tick() + } + + beforeEach(() => { + accessToken.set('first-token') + TestBed.configureTestingModule({ + providers: [ + provideHttpClient(), + provideHttpClientTesting(), + { + provide: Configuration, + useValue: new Configuration({ + basePath: 'http://localhost/api', + withCredentials: true, + credentials: { + api_key: 'secret-key', + api_key_query: 'query-key', + bearer_auth: () => accessToken() + } + }) + } + ] + }) + http = TestBed.inject(HttpTestingController) + petService = TestBed.inject(PetService) + storeService = TestBed.inject(StoreService) + }) + + afterEach(() => { + http.verify() + }) + + it('stays idle while the parameters are undefined', async () => { + const petId = signal(undefined) + const pet = TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => { + const id = petId() + return id === undefined ? undefined : { petId: id } + })) + await settle() + + expect(pet.status()).toBe('idle') + expect(pet.value()).toBeUndefined() + http.expectNone(() => true) + + petId.set(1) + await settle() + http.expectOne('http://localhost/api/pet/1').flush({ id: 1, name: 'Rex' }) + await settle() + expect(pet.value()?.name).toBe('Rex') + }) + + it('stays idle while a required parameter is null or undefined', async () => { + const withNull = TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => ({ petId: null as unknown as number }))) + const withUndefined = TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => ({ petId: undefined as unknown as number }))) + await settle() + + expect(withNull.status()).toBe('idle') + expect(withUndefined.status()).toBe('idle') + http.expectNone(() => true) + }) + + it('builds the request from the parameters, the configuration and the options', async () => { + const context = new HttpContext().set(TOKEN, 'from-options') + const pet = TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => ({ petId: 7 }), { context, transferCache: false })) + await settle() + + const req = http.expectOne('http://localhost/api/pet/7') + expect(req.request.method).toBe('GET') + expect(req.request.headers.get('api_key')).toBe('secret-key') + expect(req.request.headers.get('Accept')).toBe('application/json') + expect(req.request.withCredentials).toBe(true) + expect(req.request.context.get(TOKEN)).toBe('from-options') + expect(req.request.transferCache).toBe(false) + expect(req.request.responseType).toBe('json') + + req.flush({ id: 7, name: 'Rex' }) + await settle() + expect(pet.status()).toBe('resolved') + expect(pet.value()).toEqual({ id: 7, name: 'Rex' }) + expect(pet.statusCode()).toBe(200) + }) + + it('uses the transfer cache by default', async () => { + TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => ({ petId: 7 }))) + await settle() + + const req = http.expectOne('http://localhost/api/pet/7') + expect(req.request.transferCache).toBe(true) + req.flush({ id: 7, name: 'Rex' }) + await settle() + }) + + it('sends a new request when a parameter changes', async () => { + const petId = signal(1) + const pet = TestBed.runInInjectionContext(() => petService.getPetByIdResource(() => ({ petId: petId() }))) + await settle() + + const first = http.expectOne('http://localhost/api/pet/1') + petId.set(2) + await settle() + + expect(first.cancelled).toBe(true) + http.expectOne('http://localhost/api/pet/2').flush({ id: 2, name: 'Max' }) + await settle() + expect(pet.value()?.name).toBe('Max') + }) + + it('encodes query parameters with the configured codec', async () => { + const tags = TestBed.runInInjectionContext(() => petService.listPetsResource(() => ({ tags: ['a b&c', 'd'], xRequestId: 'request-1' }))) + await settle() + + const req = http.expectOne(r => r.url === 'http://localhost/api/pet') + expect(req.request.urlWithParams).toBe('http://localhost/api/pet?tags=a%20b%26c&tags=d') + expect(req.request.headers.get('X-Request-Id')).toBe('request-1') + req.flush([]) + await settle() + expect(tags.value()).toEqual([]) + }) + + it('sends the request without arguments when every parameter is optional', async () => { + TestBed.runInInjectionContext(() => petService.listPetsResource()) + await settle() + + const req = http.expectOne('http://localhost/api/pet') + expect(req.request.params.keys()).toEqual([]) + req.flush([]) + await settle() + }) + + it('sends the API key in the query for an operation without parameters', async () => { + const inventory = TestBed.runInInjectionContext(() => storeService.getInventoryResource()) + await settle() + + const req = http.expectOne(r => r.url === 'http://localhost/api/store/inventory') + expect(req.request.urlWithParams).toBe('http://localhost/api/store/inventory?api_key=query-key') + req.flush({ available: 3 }) + await settle() + expect(inventory.value()).toEqual({ available: 3 }) + }) + + it('reads the credentials when the request is built', async () => { + const byStatus = TestBed.runInInjectionContext(() => petService.findPetsByStatusResource(() => ({ status: ['available', 'sold'] }), { defaultValue: [] })) + const initial: Pet[] = byStatus.value() + expect(initial).toEqual([]) + await settle() + + let req = http.expectOne(r => r.url === 'http://localhost/api/pet/findByStatus') + expect(req.request.urlWithParams).toBe('http://localhost/api/pet/findByStatus?status=available,sold') + expect(req.request.headers.get('Authorization')).toBe('Bearer first-token') + req.flush([{ id: 1, name: 'Rex' }]) + await settle() + expect(byStatus.value().length).toBe(1) + + accessToken.set('second-token') + await settle() + req = http.expectOne(r => r.url === 'http://localhost/api/pet/findByStatus') + expect(req.request.headers.get('Authorization')).toBe('Bearer second-token') + req.flush([]) + await settle() + }) + + it('uses httpResource.text for a text response', async () => { + const notes = TestBed.runInInjectionContext(() => petService.getPetNotesResource(() => ({ petId: 3 }))) + await settle() + + const req = http.expectOne('http://localhost/api/pet/3/notes') + expect(req.request.responseType).toBe('text') + expect(req.request.headers.get('Accept')).toBe('text/plain') + req.flush('likes walks') + await settle() + expect(notes.value()).toBe('likes walks') + }) + + it('uses httpResource.blob for a binary response', async () => { + const photo = TestBed.runInInjectionContext(() => petService.getPetPhotoResource(() => ({ petId: 3 }))) + await settle() + + const req = http.expectOne('http://localhost/api/pet/3/photo') + expect(req.request.responseType).toBe('blob') + expect(req.request.headers.get('Accept')).toBe('application/octet-stream') + req.flush(new Blob(['png'])) + await settle() + expect(photo.value() instanceof Blob).toBe(true) + }) + + it('takes an injector when called outside an injection context', async () => { + const pet = petService.getPetByIdResource(() => ({ petId: 5 }), { injector: TestBed.inject(Injector) }) + await settle() + + http.expectOne('http://localhost/api/pet/5').flush({ id: 5, name: 'Bo' }) + await settle() + expect(pet.value()?.name).toBe('Bo') + pet.destroy() + }) + + it('generates resources for reads only', () => { + const methods = petService as unknown as Record + expect(typeof methods['getPetByIdResource']).toBe('function') + expect(methods['addPetResource']).toBeUndefined() + expect(methods['deletePetResource']).toBeUndefined() + expect(methods['searchPetsResource']).toBeUndefined() + }) +}) diff --git a/samples/client/petstore/typescript-angular-v22-provided-in-root/tsconfig.json b/samples/client/petstore/typescript-angular-v22-provided-in-root/tsconfig.json index b32274175413..5afb11a6148f 100644 --- a/samples/client/petstore/typescript-angular-v22-provided-in-root/tsconfig.json +++ b/samples/client/petstore/typescript-angular-v22-provided-in-root/tsconfig.json @@ -13,7 +13,8 @@ "target": "es2022", "typeRoots": ["node_modules/@types"], "paths": { - "@swagger/typescript-angular-petstore": ["./builds/default"] + "@swagger/typescript-angular-petstore": ["./builds/default"], + "@swagger/typescript-angular-http-resource": ["./builds/with-http-resource"] } } }