Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Try it without installing anything: [playground](https://docs.openapi-ng.dev/pla
- **Two layouts.** Per-tag `@Injectable` services by default, or `--layout operations` for one tree-shakeable constant per operation, callable from any injection context or bound once with `withInjector()`.
- **Strict OpenAPI subset.** A focused 3.x slice with clear diagnostics. No silent misgeneration; see [Assumptions & limitations](https://docs.openapi-ng.dev/reference/limitations/) for the accepted shape.
- **Configurable naming.** Tune method names and service grouping with template + regex rules, via YAML, JSON, or TypeScript config.
- **Thin, pass-through helpers.** Generated methods just build the request (method, URL, query, body) and forward every `HttpClient.request` / `httpResource` option through unchanged — `withCredentials`, `transferCache`, `reportProgress`, `equal`, `injector`, and the rest. The response reaches you untouched.
- **Thin, pass-through helpers.** Generated methods just build the request (method, URL, query, body) and forward every `HttpClient.request` / `httpResource` option through unchanged — `withCredentials`, `transferCache`, `reportUploadProgress`, `equal`, `injector`, and the rest. The response reaches you untouched.

## Install

Expand Down
43 changes: 39 additions & 4 deletions __test__/angular-consumer/src/service-proof.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import type { PetRest, UpdatePetParams } from '../generated/rest/pet.rest';
import type { HttpEvent, HttpResourceRef, HttpResponse } from '@angular/common/http';
import type {
HttpClient,
HttpEvent,
HttpResourceRef,
HttpResponse,
} from '@angular/common/http';
import { Pet, PetList } from '../generated/model.ts';
import type { ObservableOptions } from '../generated/rest.model';
import { Observable } from 'rxjs';
import type { ResourceParamsContext } from '@angular/core';
import type {
Expand Down Expand Up @@ -37,7 +43,7 @@ const listPetsObservableResponse = service.listPets.observable({
});
const listPetsObservableEvents = service.listPets.observable({
observe: 'events',
reportProgress: true,
reportDownloadProgress: true,
});

expectType<string>(listPetsRequest.url);
Expand Down Expand Up @@ -99,9 +105,38 @@ const updatePetObservableResponse = service.updatePet.observable(request, {
});
const updatePetObservableEvents = service.updatePet.observable(request, {
observe: 'events',
reportProgress: true,
reportDownloadProgress: true,
});

// Every HttpClient.request option the operation does not supply itself passes
// through, so options added by a later Angular need no regeneration.
type PassThroughOptions = Omit<
NonNullable<Parameters<HttpClient['request']>[2]>,
'body' | 'params' | 'headers' | 'responseType'
>;
// A non-literal argument skips excess-property checks, so compare keys too.
declare const missingOptionKey: Exclude<
keyof PassThroughOptions,
keyof ObservableOptions
>;
expectType<never>(missingOptionKey);
declare const everyRequestOption: Required<PassThroughOptions>;
service.updatePet.observable(request, everyRequestOption);
service.updatePet.observable(request, {
integrity: 'sha256-x',
referrerPolicy: 'no-referrer',
reportUploadProgress: true,
});

// @ts-expect-error — the operation supplies the body
service.updatePet.observable(request, { body: {} });
// @ts-expect-error — the operation supplies the query params
service.updatePet.observable(request, { params: {} });
// @ts-expect-error — the operation supplies the headers
service.updatePet.observable(request, { headers: {} });
// @ts-expect-error — the response type is fixed per operation
service.updatePet.observable(request, { responseType: 'text' });

expectType<string>(updatePetRequest.url);
expectType<Observable<Pet>>(updatePetObservable);
expectType<HttpResourceRef<Pet | undefined>>(updatePetResource);
Expand Down Expand Up @@ -133,6 +168,6 @@ expectType<Observable<HttpResponse<void>>>(
expectType<Observable<HttpEvent<void>>>(
requestVoidFactory.observable(
{ id: 'x' },
{ observe: 'events', reportProgress: true },
{ observe: 'events', reportDownloadProgress: true },
),
);
29 changes: 8 additions & 21 deletions __test__/snapshots/generate-native/static-template/rest.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Injector } from '@angular/core';
import type {
HttpContext,
HttpClient,
HttpHeaders,
HttpParams,
HttpResourceOptions,
Expand Down Expand Up @@ -65,23 +65,10 @@ export interface OperationRequestOptions {
injector?: Injector;
}

// Omits body/params/headers (generator supplies them) and responseType
// (fixed per requestFactory variant). Structurally compatible with
// HttpClient.request(method, url, options) so the runtime spread doesn't
// need a Parameters<…>[2] cast. `injector` is consumed by the runtime and
// never reaches HttpClient; when given it wins over a `withInjector()` binding.
export type ObservableOptions = {
injector?: Injector;
context?: HttpContext;
observe?: 'body' | 'response' | 'events';
reportProgress?: boolean;
transferCache?: { includeHeaders?: string[] } | boolean;
withCredentials?: boolean;
keepalive?: boolean;
redirect?: RequestRedirect;
mode?: RequestMode;
credentials?: RequestCredentials;
priority?: RequestPriority;
cache?: RequestCache;
timeout?: number;
};
// `Parameters` reads request()'s last overload, its catch-all, so options a later
// Angular adds pass through. The operation supplies body/params/headers and fixes
// responseType; `injector` is consumed by the runtime and never reaches HttpClient.
export type ObservableOptions = Omit<
NonNullable<Parameters<HttpClient['request']>[2]>,
'body' | 'params' | 'headers' | 'responseType'
> & { injector?: Injector };
29 changes: 8 additions & 21 deletions templates/angular/rest.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Injector } from '@angular/core';
import type {
HttpContext,
HttpClient,
HttpHeaders,
HttpParams,
HttpResourceOptions,
Expand Down Expand Up @@ -65,23 +65,10 @@ export interface OperationRequestOptions {
injector?: Injector;
}

// Omits body/params/headers (generator supplies them) and responseType
// (fixed per requestFactory variant). Structurally compatible with
// HttpClient.request(method, url, options) so the runtime spread doesn't
// need a Parameters<…>[2] cast. `injector` is consumed by the runtime and
// never reaches HttpClient; when given it wins over a `withInjector()` binding.
export type ObservableOptions = {
injector?: Injector;
context?: HttpContext;
observe?: 'body' | 'response' | 'events';
reportProgress?: boolean;
transferCache?: { includeHeaders?: string[] } | boolean;
withCredentials?: boolean;
keepalive?: boolean;
redirect?: RequestRedirect;
mode?: RequestMode;
credentials?: RequestCredentials;
priority?: RequestPriority;
cache?: RequestCache;
timeout?: number;
};
// `Parameters` reads request()'s last overload, its catch-all, so options a later
// Angular adds pass through. The operation supplies body/params/headers and fixes
// responseType; `injector` is consumed by the runtime and never reaches HttpClient.
export type ObservableOptions = Omit<
NonNullable<Parameters<HttpClient['request']>[2]>,
'body' | 'params' | 'headers' | 'responseType'
> & { injector?: Injector };
21 changes: 13 additions & 8 deletions website/src/content/docs/guides/angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,10 @@ options object to switch observation modes or to forward extra
```ts
this.#petRest.updatePet.observable(req);
this.#petRest.updatePet.observable(req, { observe: 'response' });
this.#petRest.updatePet.observable(req, { observe: 'events', reportProgress: true });
this.#petRest.updatePet.observable(req, {
observe: 'events',
reportUploadProgress: true,
});
```

| Options | Return type |
Expand All @@ -430,13 +433,15 @@ this.#petRest.updatePet.observable(req, { observe: 'events', reportProgress: tru
| `{ observe: 'response' }` | `Observable<HttpResponse<T>>` |
| `{ observe: 'events' }` | `Observable<HttpEvent<T>>` |

The options bag mirrors `HttpClient.request`'s options minus the
fields the generator already supplies — `body`, `params`, `headers`,
and `responseType` are baked in from the operation, so the type
rejects them. Everything else is forwarded: `withCredentials`,
`reportProgress`, `transferCache`, `context`, `keepalive`, and the
Fetch-related options (`redirect`, `mode`, `credentials`, `priority`,
`cache`, `timeout`).
The options bag is `HttpClient.request`'s own options type, read from
your installed Angular, minus the fields the generator already
supplies — `body`, `params`, `headers`, and `responseType` are baked
in from the operation, so the type rejects them. Everything else is
forwarded: `withCredentials`, `transferCache`, `context`, the progress
and Fetch-related options, and any option a later Angular adds,
without regenerating. Deprecations carry over the same way: on
Angular 22 your editor marks `reportProgress` as deprecated, as it
does on `HttpClient` itself.

For void-response operations (204 No Content), the same overloads
still apply — `Observable<HttpResponse<void>>` is meaningful when you
Expand Down
Loading