Skip to content

Commit

Permalink
feat: allow filtering service endpoints with
Browse files Browse the repository at this point in the history
  • Loading branch information
mrlubos committed Jul 17, 2024
1 parent 5c9c654 commit 327c5fb
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .changeset/tiny-owls-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hey-api/openapi-ts': minor
---

feat: allow filtering service endpoints with `services.filter`
14 changes: 14 additions & 0 deletions docs/openapi-ts/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ export default {

:::

### Filtering Endpoints

If you only want to include specific endpoints in the generated services, you can use the `services.filter` config option to filter endpoints. The provided value should be a regular expression to match against endpoints with the `{method} {path}` pattern. For example, the config below will only include all `/api/v1/foo` endpoints.

```js{5}
export default {
input: 'path/to/openapi.json',
output: 'src/client',
services: {
filter: '^\\w+ /api/v1/foo$'
}
}
```

### Output

Below are different outputs depending on your chosen style. No services approach will not generate the `services.gen.ts` file.
Expand Down
19 changes: 11 additions & 8 deletions packages/openapi-ts/src/compiler/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ export const createMethodDeclaration = ({
returnType?: string | ts.TypeNode;
statements?: ts.Statement[];
}) => {
const modifiers = toAccessLevelModifiers(accessLevel);
let modifiers = toAccessLevelModifiers(accessLevel);
if (isStatic) {
modifiers.push(ts.factory.createModifier(ts.SyntaxKind.StaticKeyword));
modifiers = [
...modifiers,
ts.factory.createModifier(ts.SyntaxKind.StaticKeyword),
];
}
const node = ts.factory.createMethodDeclaration(
modifiers,
Expand Down Expand Up @@ -115,11 +118,11 @@ export const createClassDeclaration = ({
members?: ts.ClassElement[];
name: string;
}) => {
const modifiers: ts.ModifierLike[] = [
let modifiers: ts.ModifierLike[] = [
ts.factory.createModifier(ts.SyntaxKind.ExportKeyword),
];
if (decorator) {
modifiers.unshift(
modifiers = [

Check warning on line 125 in packages/openapi-ts/src/compiler/classes.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/classes.ts#L125

Added line #L125 was not covered by tests
ts.factory.createDecorator(
ts.factory.createCallExpression(
ts.factory.createIdentifier(decorator.name),
Expand All @@ -129,14 +132,14 @@ export const createClassDeclaration = ({
.filter(isType<ts.Expression>),
),
),
);
...modifiers,
];

Check warning on line 136 in packages/openapi-ts/src/compiler/classes.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/compiler/classes.ts#L135-L136

Added lines #L135 - L136 were not covered by tests
}
// Add newline between each class member.
const m: ts.ClassElement[] = [];
let m: ts.ClassElement[] = [];
members.forEach((member) => {
m.push(member);
// @ts-ignore
m.push(ts.factory.createIdentifier('\n'));
m = [...m, member, ts.factory.createIdentifier('\n')];
});
return ts.factory.createClassDeclaration(
modifiers,
Expand Down
24 changes: 23 additions & 1 deletion packages/openapi-ts/src/generate/__tests__/services.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,29 @@ describe('generateServices', () => {
$refs: [],
imports: [],
name: 'User',
operations: [],
operations: [
{
$refs: [],
deprecated: false,
description: null,
id: null,
imports: [],
method: 'GET',
name: '',
parameters: [],
parametersBody: null,
parametersCookie: [],
parametersForm: [],
parametersHeader: [],
parametersPath: [],
parametersQuery: [],
path: '/api/v1/foo',
responseHeader: null,
responses: [],
service: '',
summary: null,
},
],
},
],
types: {},
Expand Down
35 changes: 27 additions & 8 deletions packages/openapi-ts/src/generate/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,15 @@ const processService = (

const isStandalone = isStandaloneClient(config);

service.operations.forEach((operation) => {
let filteredOperations = service.operations;
if (config.services.filter) {
const regexp = new RegExp(config.services.filter);
filteredOperations = service.operations.filter((operation) =>
regexp.test(`${operation.method} ${operation.path}`),
);
}

Check warning on line 494 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L490-L494

Added lines #L490 - L494 were not covered by tests

filteredOperations.forEach((operation) => {
if (operation.parameters.length) {
generateImport({
client,
Expand Down Expand Up @@ -533,7 +541,7 @@ const processService = (
});

if (!config.services.asClass && !config.name) {
service.operations.forEach((operation) => {
filteredOperations.forEach((operation) => {
const expression = compiler.types.function({
parameters: toOperationParamType(client, operation),
returnType: isStandalone
Expand All @@ -556,7 +564,7 @@ const processService = (
return;
}

const members: ClassElement[] = service.operations.map((operation) => {
let members: ClassElement[] = filteredOperations.map((operation) => {
const node = compiler.class.method({
accessLevel: 'public',
comment: toOperationComment(operation),
Expand All @@ -576,9 +584,13 @@ const processService = (
return node;
});

if (!members.length) {
return;

Check warning on line 588 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L588

Added line #L588 was not covered by tests
}

// Push to front constructor if needed
if (config.name) {
members.unshift(
members = [

Check warning on line 593 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L593

Added line #L593 was not covered by tests
compiler.class.constructor({
multiLine: false,
parameters: [
Expand All @@ -590,9 +602,10 @@ const processService = (
},
],
}),
);
...members,
];

Check warning on line 606 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L605-L606

Added lines #L605 - L606 were not covered by tests
} else if (config.client === 'angular') {
members.unshift(
members = [

Check warning on line 608 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L608

Added line #L608 was not covered by tests
compiler.class.constructor({
multiLine: false,
parameters: [
Expand All @@ -604,7 +617,8 @@ const processService = (
},
],
}),
);
...members,
];

Check warning on line 621 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L620-L621

Added lines #L620 - L621 were not covered by tests
}

const statement = compiler.class.create({
Expand Down Expand Up @@ -707,6 +721,11 @@ export const generateServices = async ({
asType: !name.endsWith('Transformer'),
name,
}));
files.services?.addImport(importedTypes, `./${files.types.getName(false)}`);
if (importedTypes.length) {
files.services?.addImport(
importedTypes,
`./${files.types.getName(false)}`,
);
}

Check warning on line 729 in packages/openapi-ts/src/generate/services.ts

View check run for this annotation

Codecov / codecov/patch

packages/openapi-ts/src/generate/services.ts#L724-L729

Added lines #L724 - L729 were not covered by tests
}
};
8 changes: 8 additions & 0 deletions packages/openapi-ts/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ export interface ClientConfig {
* @default true
*/
export?: boolean;
/**
* Filter endpoints to be included in the generated services.
* The provided string should be a regular expression where matched
* results will be included in the output. The input pattern this
* string will be tested against is `{method} {path}`. For example,
* you can match `POST /api/v1/foo` with `^POST /api/v1/foo$`.
*/
filter?: string;
/**
* Include only service classes with names matching regular expression
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This file is auto-generated by @hey-api/openapi-ts
export * from './services.gen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// This file is auto-generated by @hey-api/openapi-ts

import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';

/**
* @throws ApiError
*/
export const getCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'GET',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const putCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'PUT',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const postCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'POST',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const deleteCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const optionsCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'OPTIONS',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const headCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'HEAD',
url: '/api/v{api-version}/simple'
}); };

/**
* @throws ApiError
*/
export const patchCallWithoutParametersAndResponse = (): CancelablePromise<void> => { return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v{api-version}/simple'
}); };
40 changes: 26 additions & 14 deletions packages/openapi-ts/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,6 @@ describe('OpenAPI v3', () => {
description: 'generate pascalcase types',
name: 'v3_pascalcase',
},
{
config: createConfig({
exportCore: false,
schemas: false,
services: {
asClass: true,
include: '^(Simple|Parameters)',
name: 'myAwesome{{name}}Api',
},
types: false,
}),
description: 'generate services with custom name',
name: 'v3_services_name',
},
{
config: createConfig({
exportCore: false,
Expand All @@ -255,6 +241,32 @@ describe('OpenAPI v3', () => {
description: 'generate form validation schemas',
name: 'v3_schemas_form',
},
{
config: createConfig({
exportCore: false,
schemas: false,
services: {
asClass: true,
include: '^(Simple|Parameters)',
name: 'myAwesome{{name}}Api',
},
types: false,
}),
description: 'generate services with custom name',
name: 'v3_services_name',
},
{
config: createConfig({
exportCore: false,
schemas: false,
services: {
filter: '^\\w+ /api/v{api-version}/simple$',
},
types: false,
}),
description: 'generate services with specific endpoints',
name: 'v3_services_filter',
},
{
config: createConfig({
exportCore: false,
Expand Down
3 changes: 2 additions & 1 deletion packages/openapi-ts/test/sample.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const main = async () => {
// export: false,
},
services: {
// asClass: true,
asClass: true,
filter: '^\\w+ /api/v{api-version}/simple$',
// export: false,
// name: '^Parameters',
},
Expand Down

0 comments on commit 327c5fb

Please sign in to comment.