Skip to content
Open
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
8 changes: 6 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ jobs:
components: clippy, rustfmt
- name: Install dependencies
run: bun ci
- name: ESLint
- name: Lint
run: bun run lint
- name: Typecheck
run: bun run typecheck
- name: Snapshot fixture coverage
run: bun run check-fixture-coverage
- name: Cargo fmt
run: cargo fmt -- --check
- name: Clippy
run: cargo clippy
run: cargo clippy --all-targets
- name: Cargo test
run: cargo test --all-targets
test-rust-cross-os:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ jobs:
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
workingDirectory: website
command: deploy
# A pull request from a fork gets no secrets, so wrangler cannot run.
- name: Upload preview version
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Expand Down
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ napi-derive = "=3.5.10"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# Pinned: 0.0.13 swapped its YAML backend and dropped error line/column plus
# duplicate-mapping-key rejection, which the duplicate-schema-name and
# mapping-expansion-exceeded diagnostics both read out of serde_yml errors.
# Pinned: 0.0.13 swapped its YAML backend and dropped the line/column suffix
# from decode errors, which every `E_INPUT_INVALID` message forwards verbatim.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked 0.0.13: it drops the line/column suffix and also the field.path: prefix on decode errors. decode_failure above depends on that prefix to route duplicate-schema-name, so the pin has two reasons now; worth naming both here.

serde_yml = "=0.0.12"

[build-dependencies]
Expand Down
16 changes: 2 additions & 14 deletions __test__/angular-consumer/src/form-non-json-proof.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
// Compile-time proofs for the request bodies and non-JSON responses
// surfaced by Phase 7. Lives next to service-proof.ts (the petstore-rich
// JSON proof) and compiles against a separate combined fixture
// (`consumer-forms-and-non-json.openapi.yaml`) generated into
// `__test__/angular-consumer/generated/` by the matching ava test.
//
// Each block asserts:
// 1. The request type accepts the right field shapes (Blob | File,
// number[], etc.).
// 2. `.observable(...)` and `.resource(...)` carry the right Response
// generic through to `Observable<Response>` / `HttpResourceRef<...>`.
//
// A regression that collapses any of these to `any` or rejects a valid
// call-site shape fails this file under `tsc --noEmit`.
// Compile-time proofs for form request bodies and non-JSON responses,
// against the fixture `consumer-forms-and-non-json.openapi.yaml`.

import type { HttpResourceRef } from '@angular/common/http';
import type { Observable } from 'rxjs';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
// This file is INTENDED TO FAIL TypeScript compilation.
// It exists so the test suite catches type-soundness regressions on the
// multipart form-body surface: the binary field's request-interface
// type MUST stay `Blob | File`, never widen to `string`/`any`. If a
// future change accidentally collapses the binary field type, this
// assignment would succeed and tsc would exit 0 — causing the
// negative-compile test to fail and alerting us.
//
// Expected error: TS2322 — `'string-not-blob'` (a literal string) is not
// assignable to `Blob | File`.
// Must not compile: a multipart binary field stays `Blob | File`.
import type { UpdatePetAvatarParams } from '../../generated/rest/pet.rest.generated';

// Construct an UpdatePetAvatarParams whose `avatar` field is a string,
Expand Down
12 changes: 2 additions & 10 deletions __test__/angular-consumer/src/negative-proof/negative.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
// This file is INTENDED TO FAIL TypeScript compilation.
// It exists so the test suite catches type-soundness regressions
// (e.g. if a future change accidentally collapses a tagged union to `any`).
//
// Expected error: TS2322 — the `kind` literal type 'dog' is not assignable to
// 'cat', so assigning an object with `kind: 'dog'` to a Cat-typed slot fails.
// If the union ever degrades to `any`, this assignment would succeed and tsc
// would exit 0 — causing the negative-compile test to fail and alerting us.
// Must not compile: a discriminated union keeps its `kind` literal types.
import type { Cat } from '../../generated/model.generated';

// Construct an object whose `kind` discriminant is 'dog', not 'cat'.
// This is structurally compatible with Cat except for the literal type on `kind`.
// Structurally a Cat but for the `kind` literal.
const dogKind = { kind: 'dog' as const, lives: 9 };

export const shouldFail: Cat = dogKind;
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
// This file is INTENDED TO FAIL TypeScript compilation.
// It exists so the test suite catches type-soundness regressions on the
// `debounce` option, which `RestValidatorOptions` inherits from Angular's own
// `AsyncValidatorOptions` via `Omit`. That indirection is what keeps the emitted
// template compiling on @angular/forms 21 (where the key is absent), but it
// would also silently swallow a bad value if the inherited type ever widened to
// `any`. A string is not a `DebounceTimer`, so tsc must reject it.
//
// Expected error: TS2322 — `string` is not assignable to
// `DebounceTimer<UpdatePetParams | undefined>` (i.e. `number` or a function).
// Must not compile: `debounce` keeps the `DebounceTimer` type it inherits
// from Angular's `AsyncValidatorOptions`.
import { schema } from '@angular/forms/signals';
import type { PetRest } from '../../generated/rest/pet.rest.generated';
import { validateRest } from '../../generated/rest.validate';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
// This file is INTENDED TO FAIL TypeScript compilation.
// It exists so the test suite catches type-soundness regressions on the
// validateRest surface: the `request` callback's return type MUST stay
// pinned to the endpoint's Request shape (here, UpdatePetParams), never
// widen to `any`/`unknown`. If the typing widened, the
// `{ wrong: 'value' }` literal below would be accepted and tsc would
// exit 0 — causing the negative-compile test to fail and alerting us.
//
// Expected error: TS2322 — `{ wrong: string }` is not assignable to
// `UpdatePetParams | undefined`.
//
// Note: we let `TRequest` be inferred from `service.updatePet` (which
// pins it to `UpdatePetParams`) rather than supplying it explicitly,
// so the type conflict surfaces as a TS2322 assignability error on the
// `request` property of the option-bag — exactly the surface this
// proof is meant to lock down — instead of a TS2345 argument-type
// error on the `service.updatePet` position.
// Must not compile: `validateRest`'s `request` callback stays pinned to
// the endpoint's request shape.
import { schema } from '@angular/forms/signals';
import type { PetRest } from '../../generated/rest/pet.rest.generated';
import { validateRest } from '../../generated/rest.validate';

declare const service: PetRest;

// The mismatched value is annotated with an unrelated interface so the
// failure becomes an unambiguous TS2322 assignability error (named-type
// vs named-type) rather than the more specialised TS2739 "missing
// properties from object literal" diagnostic.
interface WrongRequest {
wrong: string;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
// This file is INTENDED TO FAIL TypeScript compilation.
// It exists so the test suite catches type-soundness regressions on the
// validateRest surface: the `onSuccess` callback's `result` parameter
// MUST be typed as the endpoint's Response (here, Pet), never widen to
// `any`/`unknown`. If `result` were widened, the `result.nonExistentField`
// access below would be accepted and tsc would exit 0 — causing the
// negative-compile test to fail and alerting us.
//
// Expected error: TS2339 — property 'nonExistentField' does not exist on
// type 'Pet'.
// Must not compile: `onSuccess`'s `result` stays typed as the endpoint's
// response.
import { schema } from '@angular/forms/signals';
import type { PetRest, UpdatePetParams } from '../../generated/rest/pet.rest.generated';
import type { Pet } from '../../generated/model.generated.ts';
Expand Down
3 changes: 2 additions & 1 deletion __test__/browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'node:fs';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { GenerateOptions } from '../index.js';

import { generate as nativeGenerate } from '../lib/index.js';

Expand Down Expand Up @@ -44,7 +45,7 @@ const petstoreOptions = {
inputContents: petstore,
displayPath: 'petstore-minimal.openapi.yaml',
emit: ['models', 'angular'],
};
} satisfies GenerateOptions;

wasiTest(
'browser generate through the WASI binding matches the native output',
Expand Down
7 changes: 1 addition & 6 deletions __test__/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,7 @@ test('cli generate writes 3 artifacts for fixture without operations', t => {
// ── Verbose: warnings ──────────────────────────────────────────────────────

test('cli generate suppresses warnings without --verbose', t => {
// cookie-param emits a non-fatal warning (cookies aren't surfaced in the
// generated service contract — browsers manage cookies via the cookie
// store). header-param used to share this behaviour but headers are now
// first-class.
// cookie-param warns: the generated contract does not surface cookies.
const result = runCli(['generate', '--input', fixture('cookie-param.openapi.yaml')]);
t.is(result.status, 0);
t.is(result.stderr, '');
Expand Down Expand Up @@ -321,8 +318,6 @@ test('cli with no args prints help to stdout and exits 2', t => {
});

test('cli --help still exits 0', t => {
// Pin the existing behaviour so the "no args" change does not bleed
// into the explicit-help path.
const result = runCli(['--help']);
t.is(result.status, 0);
});
Expand Down
Loading