Skip to content

Commit c903e89

Browse files
committed
feat: implement root security application to services in OpenAPI generation
1 parent b5ae3a6 commit c903e89

6 files changed

Lines changed: 48 additions & 148 deletions

File tree

packages/openapi-plugin/src/lib/QraftCommand.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ export class QraftCommand extends QraftCommandBase<OpenAPIQraftCommandActionOpti
147147
let services = getServices(schema as OpenAPISchemaType, {
148148
postfixServices: args.postfixServices,
149149
serviceNameBase: args.serviceNameBase,
150-
rootSecurity: args.rootSecurity,
151150
});
152151

153152
if (args.operationNameModifier) {

packages/openapi-plugin/src/lib/open-api/OpenAPISchemaType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type OpenAPISchemaType = {
2424
required?: boolean;
2525
};
2626
responses: {
27-
[statusCode in number | 'default']?: {
27+
[statusCode in number | 'default']: {
2828
$ref?: string;
2929
description?: string;
3030
content?: {
Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { OpenAPISchemaType } from './OpenAPISchemaType.js';
21
import openAPI from '@openapi-qraft/test-fixtures/openapi.json' with { type: 'json' };
32
import { describe, expect, it } from 'vitest';
43
import { filterDocumentPaths } from '../filterDocumentPaths.js';
@@ -32,126 +31,4 @@ describe('getServices', () => {
3231
it('matches snapshot with "serviceNameBase: tags"', () => {
3332
expect(getServices(openAPI, { serviceNameBase: 'tags' })).toMatchSnapshot();
3433
});
35-
36-
it('inherits root-level security when `rootSecurity` is enabled and operation-level security is omitted', () => {
37-
const document: OpenAPISchemaType = {
38-
openapi: '3.1.0',
39-
info: {
40-
title: 'Fixture API',
41-
version: '1.0.0',
42-
},
43-
security: [{ jwtUserToken: [] }],
44-
paths: {
45-
'/accounts': {
46-
get: {
47-
operationId: 'getAccounts',
48-
responses: {
49-
200: {
50-
description: 'Successful response',
51-
},
52-
},
53-
},
54-
},
55-
},
56-
components: {
57-
parameters: undefined,
58-
},
59-
};
60-
61-
expect(
62-
getServices(document, { rootSecurity: true })[0]?.operations[0]?.security
63-
).toEqual([{ jwtUserToken: [] }]);
64-
});
65-
66-
it('does not inherit root-level security when `rootSecurity` is disabled', () => {
67-
const document: OpenAPISchemaType = {
68-
openapi: '3.1.0',
69-
info: {
70-
title: 'Fixture API',
71-
version: '1.0.0',
72-
},
73-
security: [{ jwtUserToken: [] }],
74-
paths: {
75-
'/accounts': {
76-
get: {
77-
operationId: 'getAccounts',
78-
responses: {
79-
200: {
80-
description: 'Successful response',
81-
},
82-
},
83-
},
84-
},
85-
},
86-
components: {
87-
parameters: undefined,
88-
},
89-
};
90-
91-
expect(
92-
getServices(document, { rootSecurity: false })[0]?.operations[0]?.security
93-
).toBeUndefined();
94-
});
95-
96-
it('prefers operation-level security over root-level security when `rootSecurity` is enabled', () => {
97-
const document: OpenAPISchemaType = {
98-
openapi: '3.1.0',
99-
info: {
100-
title: 'Fixture API',
101-
version: '1.0.0',
102-
},
103-
security: [{ jwtUserToken: [] }],
104-
paths: {
105-
'/accounts': {
106-
get: {
107-
operationId: 'getAccounts',
108-
security: [{ apiKey: [] }],
109-
responses: {
110-
200: {
111-
description: 'Successful response',
112-
},
113-
},
114-
},
115-
},
116-
},
117-
components: {
118-
parameters: undefined,
119-
},
120-
};
121-
122-
expect(
123-
getServices(document, { rootSecurity: true })[0]?.operations[0]?.security
124-
).toEqual([{ apiKey: [] }]);
125-
});
126-
127-
it('treats empty operation-level security as an explicit override when `rootSecurity` is enabled', () => {
128-
const document: OpenAPISchemaType = {
129-
openapi: '3.1.0',
130-
info: {
131-
title: 'Fixture API',
132-
version: '1.0.0',
133-
},
134-
security: [{ jwtUserToken: [] }],
135-
paths: {
136-
'/health': {
137-
get: {
138-
operationId: 'getHealth',
139-
security: [],
140-
responses: {
141-
200: {
142-
description: 'Successful response',
143-
},
144-
},
145-
},
146-
},
147-
},
148-
components: {
149-
parameters: undefined,
150-
},
151-
};
152-
153-
expect(
154-
getServices(document, { rootSecurity: true })[0]?.operations[0]?.security
155-
).toEqual([]);
156-
});
15734
});

packages/openapi-plugin/src/lib/open-api/getServices.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ export type ServiceOperation = ServiceOperationStable;
2929
export interface ServiceOutputOptions {
3030
postfixServices?: string; // todo::rename to `postfixService`
3131
serviceNameBase?: ServiceBaseName;
32-
rootSecurity?: boolean;
3332
}
3433

3534
export const getServices = (
3635
openApiJson: OpenAPISchemaType,
3736
{
3837
postfixServices = 'Service',
3938
serviceNameBase = 'endpoint[0]',
40-
rootSecurity,
4139
}: ServiceOutputOptions = {}
4240
) => {
4341
const paths = openApiJson.paths;
@@ -58,11 +56,6 @@ export const getServices = (
5856
}
5957

6058
const methodOperation = paths[path][method];
61-
const operationSecurity = resolveOperationSecurity(
62-
openApiJson.security,
63-
methodOperation,
64-
rootSecurity
65-
);
6659

6760
const { success, errors } = Object.entries(
6861
methodOperation.responses
@@ -73,8 +66,6 @@ export const getServices = (
7366
>
7467
>(
7568
(acc, [statusCode, response]) => {
76-
if (!response) return acc;
77-
7869
if (response.$ref) {
7970
response = resolveDocumentLocalRef(
8071
response.$ref,
@@ -154,7 +145,7 @@ export const getServices = (
154145
)
155146
: methodOperation.requestBody) ?? undefined,
156147
success: success,
157-
security: operationSecurity,
148+
security: methodOperation.security,
158149
});
159150
}
160151
}
@@ -163,18 +154,6 @@ export const getServices = (
163154
return Array.from(services.values());
164155
};
165156

166-
const resolveOperationSecurity = (
167-
rootSecurity: OpenAPISchemaType['security'],
168-
methodOperation: OpenAPISchemaType['paths'][string][string],
169-
shouldUseRootSecurity?: boolean
170-
) => {
171-
if (shouldUseRootSecurity && !('security' in methodOperation)) {
172-
return rootSecurity;
173-
}
174-
175-
return methodOperation.security;
176-
};
177-
178157
export const supportedMethod = (
179158
method: unknown
180159
): method is (typeof supportedHTTPMethods)[number] =>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { OpenAPISchemaType } from '@openapi-qraft/plugin/lib/open-api/OpenAPISchemaType';
2+
import type { OpenAPIService } from '@openapi-qraft/plugin/lib/open-api/OpenAPIService';
3+
4+
export function applyRootSecurityToServices({
5+
services,
6+
schema,
7+
}: {
8+
services: OpenAPIService[];
9+
schema: OpenAPISchemaType;
10+
}) {
11+
return services.map((service) => ({
12+
...service,
13+
operations: service.operations.map((operation) => ({
14+
...operation,
15+
security: resolveOperationSecurity(
16+
schema,
17+
operation.path,
18+
operation.method
19+
),
20+
})),
21+
}));
22+
}
23+
24+
function resolveOperationSecurity(
25+
schema: OpenAPISchemaType,
26+
path: OpenAPIService['operations'][number]['path'],
27+
method: OpenAPIService['operations'][number]['method']
28+
) {
29+
const methodOperation = schema.paths[path]?.[method];
30+
31+
if (!methodOperation) {
32+
return undefined;
33+
}
34+
35+
if ('security' in methodOperation) {
36+
return methodOperation.security;
37+
}
38+
39+
return schema.security;
40+
}

packages/tanstack-query-react-plugin/src/plugin.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { QraftCommand } from '@openapi-qraft/plugin/lib/QraftCommand';
77
import { QraftCommandPlugin } from '@openapi-qraft/plugin/lib/QraftCommandPlugin';
88
import { CommanderError, Option } from 'commander';
99
import { generateCode } from './generateCode.js';
10+
import { applyRootSecurityToServices } from './lib/applyRootSecurityToServices.js';
1011
import { parseOptionSubValues } from './lib/parseOptionSubValues.js';
1112
import { getAllAvailableCallbackNames } from './ts-factory/getCallbackNames.js';
1213
import { CreateAPIClientFnOptions } from './ts-factory/getIndexFactory.js';
@@ -70,9 +71,13 @@ export const plugin: QraftCommandPlugin = {
7071
).argParser(parseOperationParametersType)
7172
)
7273
.action(async ({ spinner, output, args, services, schema }, resolve) => {
74+
const resolvedServices = args.rootSecurity
75+
? applyRootSecurityToServices({ services, schema })
76+
: services;
77+
7378
return void (await generateCode({
7479
spinner,
75-
services,
80+
services: resolvedServices,
7681
serviceOptions: {
7782
openapiTypesImportPath: args.openapiTypesImportPath,
7883
queryableWriteOperations: args.queryableWriteOperations,

0 commit comments

Comments
 (0)