Skip to content

Commit 0d4eaf7

Browse files
The UltCopilot
andcommitted
fix(angular): apply review follow-ups for default tag bucket
Tighten the Angular default-tag follow-up by reducing new test-only type assertions, documenting the tag-bucket helper contract, and removing dead shared paramsFilter/nonPrimitiveKeys plumbing that was not wired through the generator pipeline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d5841f5 commit 0d4eaf7

7 files changed

Lines changed: 134 additions & 127 deletions

File tree

packages/angular/src/http-client.test.ts

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,17 @@ const createOutput = (
130130
return output;
131131
};
132132

133+
const createQueryParams = (
134+
overrides: Partial<NonNullable<GeneratorVerbOptions['queryParams']>> = {},
135+
): NonNullable<GeneratorVerbOptions['queryParams']> => ({
136+
schema: { name: 'GetPetByIdParams', model: '', imports: [] },
137+
deps: [],
138+
isOptional: true,
139+
originalSchema: { type: 'object' },
140+
requiredNullableKeys: [],
141+
...overrides,
142+
});
143+
133144
const createSuccessType = (
134145
value: string,
135146
contentType: string,
@@ -180,7 +191,7 @@ const createVerbOption = (
180191
definition: '',
181192
imports: [],
182193
schemas: [],
183-
originalSchema: {} as never,
194+
originalSchema: { type: 'object' },
184195
contentType: '',
185196
formData: '',
186197
formUrlEncoded: '',
@@ -225,6 +236,21 @@ const createVerbOption = (
225236
...overrides,
226237
}) as GeneratorVerbOptions;
227238

239+
const createHeaderParams = (
240+
overrides: Partial<Parameters<typeof generateAngularHeader>[0]> = {},
241+
): Parameters<typeof generateAngularHeader>[0] => ({
242+
title: 'PetService',
243+
isRequestOptions: true,
244+
isMutator: false,
245+
isGlobalMutator: false,
246+
provideIn: 'root',
247+
hasAwaitedType: false,
248+
output: createOutput(),
249+
verbOptions: { getPetById: createVerbOption() },
250+
clientImplementation: '',
251+
...overrides,
252+
});
253+
228254
const createContextSpec = (output: NormalizedOutputOptions): ContextSpec => {
229255
const spec = {
230256
openapi: '3.1.0',
@@ -412,26 +438,19 @@ describe('angular HttpClient generator', () => {
412438
it('emits filterParams helper for untagged operations in tags-split default file (#3103)', () => {
413439
const verbOptionWithQueryParams = createVerbOption({
414440
tags: [],
415-
queryParams: {
441+
queryParams: createQueryParams({
416442
schema: { name: 'GetApiProductParams', model: '', imports: [] },
417-
deps: [],
418-
isOptional: true,
419-
originalSchema: {} as never,
420-
requiredNullableKeys: [],
421-
},
443+
}),
422444
});
423445

424-
const header = generateAngularHeader({
425-
title: 'DefaultService',
426-
isRequestOptions: true,
427-
isMutator: false,
428-
isGlobalMutator: false,
429-
provideIn: 'root',
430-
hasAwaitedType: false,
431-
verbOptions: { getApiProduct: verbOptionWithQueryParams },
432-
tag: 'default',
433-
isDefaultTagBucket: true,
434-
} as never);
446+
const header = generateAngularHeader(
447+
createHeaderParams({
448+
title: 'DefaultService',
449+
verbOptions: { getApiProduct: verbOptionWithQueryParams },
450+
tag: 'default',
451+
isDefaultTagBucket: true,
452+
}),
453+
);
435454

436455
expect(header).toContain('function filterParams(');
437456
});
@@ -440,33 +459,26 @@ describe('angular HttpClient generator', () => {
440459
const untaggedVerb = createVerbOption({
441460
operationId: 'getUntaggedProduct',
442461
tags: [],
443-
queryParams: {
462+
queryParams: createQueryParams({
444463
schema: { name: 'GetApiProductParams', model: '', imports: [] },
445-
deps: [],
446-
isOptional: true,
447-
originalSchema: {} as never,
448-
requiredNullableKeys: [],
449-
},
464+
}),
450465
});
451466
const explicitDefaultVerb = createVerbOption({
452467
operationId: 'getTaggedDefaultProduct',
453468
tags: ['default'],
454469
});
455470

456-
const header = generateAngularHeader({
457-
title: 'DefaultService',
458-
isRequestOptions: true,
459-
isMutator: false,
460-
isGlobalMutator: false,
461-
provideIn: 'root',
462-
hasAwaitedType: false,
463-
verbOptions: {
464-
getUntaggedProduct: untaggedVerb,
465-
getTaggedDefaultProduct: explicitDefaultVerb,
466-
},
467-
tag: 'default',
468-
isDefaultTagBucket: false,
469-
} as never);
471+
const header = generateAngularHeader(
472+
createHeaderParams({
473+
title: 'DefaultService',
474+
verbOptions: {
475+
getUntaggedProduct: untaggedVerb,
476+
getTaggedDefaultProduct: explicitDefaultVerb,
477+
},
478+
tag: 'default',
479+
isDefaultTagBucket: false,
480+
}),
481+
);
470482

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

packages/angular/src/http-resource.test.ts

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,17 @@ const createGeneratorOptions = (
176176
return options;
177177
};
178178

179+
const createQueryParams = (
180+
overrides: Partial<NonNullable<GeneratorVerbOptions['queryParams']>> = {},
181+
): NonNullable<GeneratorVerbOptions['queryParams']> => ({
182+
schema: { name: 'GetPetByIdParams', model: '', imports: [] },
183+
deps: [],
184+
isOptional: true,
185+
originalSchema: { type: 'object' },
186+
requiredNullableKeys: [],
187+
...overrides,
188+
});
189+
179190
const createSuccessType = (
180191
value: string,
181192
contentType: string,
@@ -226,7 +237,7 @@ const createVerbOption = (
226237
definition: '',
227238
imports: [],
228239
schemas: [],
229-
originalSchema: {} as never,
240+
originalSchema: { type: 'object' },
230241
contentType: '',
231242
formData: '',
232243
formUrlEncoded: '',
@@ -273,6 +284,21 @@ const createVerbOption = (
273284
} as GeneratorVerbOptions;
274285
};
275286

287+
const createHeaderParams = (
288+
overrides: Partial<Parameters<typeof generateHttpResourceHeader>[0]> = {},
289+
): Parameters<typeof generateHttpResourceHeader>[0] => ({
290+
title: 'PetService',
291+
isRequestOptions: true,
292+
isMutator: false,
293+
isGlobalMutator: false,
294+
provideIn: 'root',
295+
hasAwaitedType: false,
296+
output: createOutput(),
297+
verbOptions: { getPetById: createVerbOption() },
298+
clientImplementation: '',
299+
...overrides,
300+
});
301+
276302
describe('angular httpResource generator', () => {
277303
beforeEach(() => {
278304
routeRegistry.reset();
@@ -1079,33 +1105,20 @@ describe('angular httpResource generator', () => {
10791105
it('emits filterParams helper for untagged operations in tags-split default file (#3103)', () => {
10801106
const verbOptionWithQueryParams = createVerbOption({
10811107
tags: [],
1082-
queryParams: {
1108+
queryParams: createQueryParams({
10831109
schema: { name: 'GetApiProductParams', model: '', imports: [] },
1084-
deps: [],
1085-
isOptional: true,
1086-
name: 'params',
1087-
definition: 'params: GetApiProductParams',
1088-
implementation: 'params: GetApiProductParams',
1089-
default: false,
1090-
required: false,
1091-
type: GetterPropType.QUERY_PARAM,
1092-
} as never,
1110+
}),
10931111
});
10941112
routeRegistry.set('getPetById', '/api/pets/${petId}');
10951113

1096-
const header = generateHttpResourceHeader({
1097-
title: 'DefaultService',
1098-
isRequestOptions: true,
1099-
isMutator: false,
1100-
isGlobalMutator: false,
1101-
provideIn: 'root',
1102-
hasAwaitedType: false,
1103-
output: createOutput(),
1104-
verbOptions: { getPetById: verbOptionWithQueryParams },
1105-
tag: 'default',
1106-
isDefaultTagBucket: true,
1107-
clientImplementation: '',
1108-
} as never);
1114+
const header = generateHttpResourceHeader(
1115+
createHeaderParams({
1116+
title: 'DefaultService',
1117+
verbOptions: { getPetById: verbOptionWithQueryParams },
1118+
tag: 'default',
1119+
isDefaultTagBucket: true,
1120+
}),
1121+
);
11091122

11101123
expect(header).toContain('function filterParams(');
11111124
});
@@ -1114,39 +1127,26 @@ describe('angular httpResource generator', () => {
11141127
const untaggedVerb = createVerbOption({
11151128
operationId: 'getUntaggedProduct',
11161129
tags: [],
1117-
queryParams: {
1130+
queryParams: createQueryParams({
11181131
schema: { name: 'GetApiProductParams', model: '', imports: [] },
1119-
deps: [],
1120-
isOptional: true,
1121-
name: 'params',
1122-
definition: 'params: GetApiProductParams',
1123-
implementation: 'params: GetApiProductParams',
1124-
default: false,
1125-
required: false,
1126-
type: GetterPropType.QUERY_PARAM,
1127-
} as never,
1132+
}),
11281133
});
11291134
const explicitDefaultVerb = createVerbOption({
11301135
operationId: 'getTaggedDefaultProduct',
11311136
tags: ['default'],
11321137
});
11331138

1134-
const header = generateHttpResourceHeader({
1135-
title: 'DefaultService',
1136-
isRequestOptions: true,
1137-
isMutator: false,
1138-
isGlobalMutator: false,
1139-
provideIn: 'root',
1140-
hasAwaitedType: false,
1141-
output: createOutput(),
1142-
verbOptions: {
1143-
getUntaggedProduct: untaggedVerb,
1144-
getTaggedDefaultProduct: explicitDefaultVerb,
1145-
},
1146-
tag: 'default',
1147-
isDefaultTagBucket: false,
1148-
clientImplementation: '',
1149-
} as never);
1139+
const header = generateHttpResourceHeader(
1140+
createHeaderParams({
1141+
title: 'DefaultService',
1142+
verbOptions: {
1143+
getUntaggedProduct: untaggedVerb,
1144+
getTaggedDefaultProduct: explicitDefaultVerb,
1145+
},
1146+
tag: 'default',
1147+
isDefaultTagBucket: false,
1148+
}),
1149+
);
11501150

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

packages/angular/src/utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,23 @@ const PRIMITIVE_TYPE_LOOKUP = {
4040
unknown: true,
4141
} as const satisfies Record<PrimitiveType, true>;
4242

43+
/**
44+
* Narrows a schema type string to the primitive set supported by the Angular
45+
* generators' query/header helpers.
46+
*/
4347
export const isPrimitiveType = (t: string | undefined): t is PrimitiveType =>
4448
t != undefined &&
4549
Object.prototype.hasOwnProperty.call(PRIMITIVE_TYPE_LOOKUP, t);
4650

51+
/**
52+
* Indicates whether the configured schema output target is Zod-based.
53+
*/
4754
export const isZodSchemaOutput = (output: NormalizedOutputOptions): boolean =>
4855
isObject(output.schemas) && output.schemas.type === 'zod';
4956

57+
/**
58+
* Removes `null` and `undefined` from a value in a type-safe way.
59+
*/
5060
export const isDefined = <T>(v: T | null | undefined): v is T => v != undefined;
5161

5262
/**
@@ -55,6 +65,9 @@ export const isDefined = <T>(v: T | null | undefined): v is T => v != undefined;
5565
export const getSchemaOutputTypeRef = (typeName: string): string =>
5666
`${typeName}Output`;
5767

68+
/**
69+
* Converts an operation/tag title into the generated Angular service class name.
70+
*/
5871
export const generateAngularTitle = (title: string) => {
5972
const sanTitle = sanitize(title);
6073
return `${pascal(sanTitle)}Service`;
@@ -127,6 +140,14 @@ export const createRouteRegistry = () => {
127140
};
128141
};
129142

143+
/**
144+
* Returns only the operations that belong to the current tag output.
145+
*
146+
* In `tags` / `tags-split` mode the writer may route untagged operations into
147+
* the implicit `default` bucket. Callers must pass `isDefaultTagBucket: true`
148+
* only for that synthetic bucket; a literal user-defined `default` tag should
149+
* keep the default `false` value so untagged operations stay excluded.
150+
*/
130151
export const getRelevantVerbOptionsForTag = (
131152
verbOptions: Record<string, GeneratorVerbOptions>,
132153
tag?: string,
@@ -143,6 +164,10 @@ export const getRelevantVerbOptionsForTag = (
143164
);
144165
};
145166

167+
/**
168+
* Tracks deferred `ClientResult` aliases emitted while individual operations
169+
* are rendered, then flushes only the aliases needed by the current file.
170+
*/
146171
export const createReturnTypesRegistry = () => {
147172
const returnTypesToWrite = new Map<string, string>();
148173

@@ -219,6 +244,11 @@ export function isMutationVerb(
219244
return !isRetrievalVerb(verb, operationName, clientOverride);
220245
}
221246

247+
/**
248+
* Selects the preferred success payload type for Angular `httpResource`
249+
* generation, favouring JSON responses and otherwise falling back to the
250+
* generator's default content-type rules.
251+
*/
222252
export function getDefaultSuccessType(
223253
successTypes: ResReqTypesValue[],
224254
fallback: string,

0 commit comments

Comments
 (0)