Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
61dec02
fix(angular): emit filterParams helper for untagged ops in tags-split…
May 15, 2026
df27c8f
refactor(angular): eliminate redundant camel(tag) calls in verb filter
May 15, 2026
5682cd2
Merge branch 'master' into fix/3103-filterparams-default-tag
the-ult May 15, 2026
fea40a8
fix(angular): add regression coverage for untagged default services
May 15, 2026
b8747da
fix(angular): address PR review comments
May 15, 2026
688b046
fix(core): include paramsFilter in generated targets
May 15, 2026
baab3d7
fix(core): export angular params filter plumbing
May 15, 2026
426afc3
fix(orval): normalize paramsFilter overrides
May 15, 2026
044a684
Merge branch 'master' into fix/3103-filterparams-default-tag
the-ult May 15, 2026
e1a283f
test(angular): Refresh sample snapshots for paramsFilter
May 15, 2026
180b194
test(angular-query): Update filterParams snapshots with passthroughKe…
May 15, 2026
9e79d5e
fix(angular): preserve params filter passthrough in generated params
May 15, 2026
fd6b0c5
fix(core): Tighten Angular params passthrough
May 15, 2026
dc0da37
fix(angular): trim 3326 spillover from 3103
May 16, 2026
e15c376
fix(core): restore 3103 branch validation
May 16, 2026
b32ceb3
Merge branch 'master' into fix/3103-filterparams-default-tag
the-ult May 16, 2026
d5841f5
test(snapshots): remove stale generated snapshots
May 16, 2026
0d4eaf7
fix(angular): apply review follow-ups for default tag bucket
May 16, 2026
9518ffa
fix: tighten default tag helpers and doc recursion
May 16, 2026
b719b82
fix(angular): correct default-tag identity check and test housekeeping
May 16, 2026
0e1c20c
fix(angular): resolve lint errors in utils.test.ts
May 17, 2026
06d6c79
Merge branch 'master' into fix/3103-filterparams-default-tag
the-ult May 17, 2026
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
81 changes: 80 additions & 1 deletion packages/angular/src/http-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getHttpClientReturnTypes,
resetHttpClientReturnTypes,
} from './http-client';
import { createQueryParams } from './test-helpers';

// ---------------------------------------------------------------------------
// Test helpers
Expand Down Expand Up @@ -180,7 +181,7 @@ const createVerbOption = (
definition: '',
imports: [],
schemas: [],
originalSchema: {} as never,
originalSchema: { type: 'object' },
contentType: '',
formData: '',
formUrlEncoded: '',
Expand Down Expand Up @@ -225,6 +226,21 @@ const createVerbOption = (
...overrides,
}) as GeneratorVerbOptions;

const createHeaderParams = (
overrides: Partial<Parameters<typeof generateAngularHeader>[0]> = {},
): Parameters<typeof generateAngularHeader>[0] => ({
title: 'PetService',
isRequestOptions: true,
isMutator: false,
isGlobalMutator: false,
provideIn: 'root',
hasAwaitedType: false,
output: createOutput(),
verbOptions: { getPetById: createVerbOption() },
clientImplementation: '',
...overrides,
});

const createContextSpec = (output: NormalizedOutputOptions): ContextSpec => {
const spec = {
openapi: '3.1.0',
Expand Down Expand Up @@ -408,6 +424,69 @@ describe('angular HttpClient generator', () => {

expect(header).not.toContain('type ThirdParameter');
});

it('emits filterParams helper for untagged operations in tags-split default file (#3103)', () => {
const verbOptionWithQueryParams = createVerbOption({
tags: [],
queryParams: createQueryParams({
schema: { name: 'GetApiProductParams', model: '', imports: [] },
}),
});

const header = generateAngularHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: { getApiProduct: verbOptionWithQueryParams },
tag: 'default',
}),
);

expect(header).toContain('function filterParams(');
});

it('includes both explicit default-tagged and untagged operations in the default bucket', () => {
const untaggedVerb = createVerbOption({
operationId: 'getUntaggedProduct',
tags: [],
queryParams: createQueryParams({
schema: { name: 'GetApiProductParams', model: '', imports: [] },
}),
});
const explicitDefaultVerb = createVerbOption({
operationId: 'getTaggedDefaultProduct',
tags: ['default'],
});

const header = generateAngularHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: {
getUntaggedProduct: untaggedVerb,
getTaggedDefaultProduct: explicitDefaultVerb,
},
tag: 'default',
}),
);

expect(header).toContain('function filterParams(');
});

it('does not enable the implicit default bucket when only explicit default tags exist', () => {
const explicitDefaultVerb = createVerbOption({
operationId: 'getTaggedDefaultProduct',
tags: ['default'],
});

const header = generateAngularHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: { getTaggedDefaultProduct: explicitDefaultVerb },
tag: 'default',
}),
);

expect(header).not.toContain('function filterParams(');
});
});

// ── Footer ────────────────────────────────────────────────────────────
Expand Down
10 changes: 2 additions & 8 deletions packages/angular/src/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
camel,
type ClientBuilder,
type ClientDependenciesBuilder,
type ClientFooterBuilder,
Expand Down Expand Up @@ -33,6 +32,7 @@ import {
} from './types';
import {
createReturnTypesRegistry,
getRelevantVerbOptionsForTag,
getSchemaOutputTypeRef,
isPrimitiveType,
isZodSchemaOutput,
Expand Down Expand Up @@ -230,11 +230,7 @@ export const generateAngularHeader: ClientHeaderBuilder = ({
}) => {
returnTypesRegistry.reset();

const relevantVerbs = tag
? Object.values(verbOptions).filter((v) =>
v.tags.some((t) => camel(t) === camel(tag)),
)
: Object.values(verbOptions);
const relevantVerbs = getRelevantVerbOptionsForTag(verbOptions, tag);
const hasQueryParams = relevantVerbs.some((v) => v.queryParams);
const acceptHelpers = buildAcceptHelpers(relevantVerbs, output);

Expand Down Expand Up @@ -471,7 +467,6 @@ export const generateHttpClientImplementation = (
let paramsDeclaration = '';
if (angularParamsRef && queryParams) {
if (isRequestOptions) {
// Uses the shared filterParams helper emitted in the file header
const callExpr = getAngularFilteredParamsCallExpression(
'{...params, ...options?.params}',
queryParams.requiredNullableKeys ?? [],
Expand All @@ -480,7 +475,6 @@ export const generateHttpClientImplementation = (
? `const ${angularParamsRef} = ${paramsSerializer.name}(${callExpr});\n\n `
: `const ${angularParamsRef} = ${callExpr};\n\n `;
} else {
// No shared helper available; use inline IIFE filtering
const iifeExpr = getAngularFilteredParamsExpression(
'params ?? {}',
queryParams.requiredNullableKeys ?? [],
Expand Down
83 changes: 82 additions & 1 deletion packages/angular/src/http-resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
getAngularHttpResourceOnlyDependencies,
routeRegistry,
} from './http-resource';
import { createQueryParams } from './test-helpers';

interface AngularOverride {
provideIn: 'root' | 'any' | boolean;
Expand Down Expand Up @@ -226,7 +227,7 @@ const createVerbOption = (
definition: '',
imports: [],
schemas: [],
originalSchema: {} as never,
originalSchema: { type: 'object' },
contentType: '',
formData: '',
formUrlEncoded: '',
Expand Down Expand Up @@ -273,6 +274,21 @@ const createVerbOption = (
} as GeneratorVerbOptions;
};

const createHeaderParams = (
overrides: Partial<Parameters<typeof generateHttpResourceHeader>[0]> = {},
): Parameters<typeof generateHttpResourceHeader>[0] => ({
title: 'PetService',
isRequestOptions: true,
isMutator: false,
isGlobalMutator: false,
provideIn: 'root',
hasAwaitedType: false,
output: createOutput(),
verbOptions: { getPetById: createVerbOption() },
clientImplementation: '',
...overrides,
});

describe('angular httpResource generator', () => {
beforeEach(() => {
routeRegistry.reset();
Expand Down Expand Up @@ -1075,6 +1091,71 @@ describe('angular httpResource generator', () => {
expect(header).toContain('getPetByIdResource');
expect(header).toContain('healthCheckResource');
});

it('emits filterParams helper for untagged operations in tags-split default file (#3103)', () => {
const verbOptionWithQueryParams = createVerbOption({
tags: [],
queryParams: createQueryParams({
schema: { name: 'GetApiProductParams', model: '', imports: [] },
}),
});
routeRegistry.set('getPetById', '/api/pets/${petId}');

const header = generateHttpResourceHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: { getPetById: verbOptionWithQueryParams },
tag: 'default',
}),
);

expect(header).toContain('function filterParams(');
});

it('includes both explicit default-tagged and untagged operations in the default bucket', () => {
const untaggedVerb = createVerbOption({
operationId: 'getUntaggedProduct',
tags: [],
queryParams: createQueryParams({
schema: { name: 'GetApiProductParams', model: '', imports: [] },
}),
});
const explicitDefaultVerb = createVerbOption({
operationId: 'getTaggedDefaultProduct',
tags: ['default'],
});

const header = generateHttpResourceHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: {
getUntaggedProduct: untaggedVerb,
getTaggedDefaultProduct: explicitDefaultVerb,
},
tag: 'default',
}),
);

expect(header).toContain('function filterParams(');
});

it('does not enable the implicit default bucket when only explicit default tags exist', () => {
const explicitDefaultVerb = createVerbOption({
operationId: 'getTaggedDefaultProduct',
tags: ['default'],
});
routeRegistry.set('getTaggedDefaultProduct', '/api/products/default');

const header = generateHttpResourceHeader(
createHeaderParams({
title: 'DefaultService',
verbOptions: { getTaggedDefaultProduct: explicitDefaultVerb },
tag: 'default',
}),
);

expect(header).not.toContain('function filterParams(');
});
});

// ─── Response type factories ──────────────────────────────────────
Expand Down
16 changes: 3 additions & 13 deletions packages/angular/src/http-resource.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
camel,
type ClientBuilder,
type ClientDependenciesBuilder,
type ClientExtraFilesBuilder,
Expand Down Expand Up @@ -51,6 +50,7 @@ import {
createReturnTypesRegistry,
createRouteRegistry,
getDefaultSuccessType,
getRelevantVerbOptionsForTag,
getSchemaOutputTypeRef,
isMutationVerb,
isPrimitiveType,
Expand Down Expand Up @@ -162,16 +162,6 @@ const resourceReturnTypesRegistry = createReturnTypesRegistry();
/** @internal Exported for testing only */
export const routeRegistry = createRouteRegistry();

const getRelevantVerbOptions = (
verbOptions: Record<string, GeneratorVerbOptions>,
tag?: string,
): GeneratorVerbOptions[] =>
tag
? Object.values(verbOptions).filter((verbOption) =>
verbOption.tags.some((currentTag) => camel(currentTag) === camel(tag)),
)
: Object.values(verbOptions);

const getVerbOptionsRecord = (
verbOptions: readonly GeneratorVerbOptions[],
): Record<string, GeneratorVerbOptions> =>
Expand Down Expand Up @@ -1227,7 +1217,7 @@ export const generateHttpResourceHeader: ClientHeaderBuilder = ({
// the shared header duplicates helpers across every tag file and pulls in
// type names the file-local `imports` filter never sees, producing missing
// schema imports in the generated output.
const relevantVerbOptions = getRelevantVerbOptions(verbOptions, tag);
const relevantVerbOptions = getRelevantVerbOptionsForTag(verbOptions, tag);

const retrievals = relevantVerbOptions.filter((verbOption) =>
isRetrievalVerb(
Expand Down Expand Up @@ -1616,7 +1606,7 @@ export const generateHttpResourceExtraFiles: ClientExtraFilesBuilder = (

return Promise.resolve([
buildHttpResourceExtraFile(
getVerbOptionsRecord(getRelevantVerbOptions(verbOptions)),
getVerbOptionsRecord(getRelevantVerbOptionsForTag(verbOptions)),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accepted — extra files intentionally return all operations and should not filter by tag or default bucket.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accepted — extra resource file intentionally includes all operations from all tags in tags-split mode, mirroring the previous behavior.

getHttpResourceExtraFilePath(output),
output,
context,
Expand Down
17 changes: 17 additions & 0 deletions packages/angular/src/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { GeneratorVerbOptions } from '@orval/core';

/**
* Builds a minimal {@link GeneratorVerbOptions.queryParams} object for use in
* unit tests. Only the fields required by the Angular generators are populated;
* everything else can be overridden via the `overrides` argument.
*/
export const createQueryParams = (
overrides: Partial<NonNullable<GeneratorVerbOptions['queryParams']>> = {},
): NonNullable<GeneratorVerbOptions['queryParams']> => ({
schema: { name: 'GetPetByIdParams', model: '', imports: [] },
deps: [],
isOptional: true,
originalSchema: { type: 'object' },
requiredNullableKeys: [],
...overrides,
});
Loading
Loading