Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
37 changes: 37 additions & 0 deletions docs/src/pages/reference/configuration/input.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,43 @@ module.exports = {
};
```

### paths

Type: `(String | RegExp)[] | Array<[String | RegExp, String[]]>`.

Only endpoints that match the specified `String` or `RegExp` will be automatically generated.
For instance, the example below only generates the endpoints that match the path `/pets` or regular expression `/health/`.

```js
module.exports = {
petstore: {
input: {
filters: {
paths: ['/pets', /health/],
},
},
},
};
```

You can also filter by specific HTTP methods for each path using the tuple format `[path, [methods]]`:

```js
module.exports = {
petstore: {
input: {
filters: {
paths: [
['/pets', ['get', 'post']],
['/pets/{petId}', ['get', 'put']],
[/health/, ['get']],
],
},
},
},
};
```

## converterOptions

Type: `Object`.
Expand Down
150 changes: 140 additions & 10 deletions packages/core/src/generators/verbs-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export const generateVerbsOptions = ({
context: ContextSpecs;
}): Promise<GeneratorVerbsOptions> =>
asyncReduce(
_filteredVerbs(verbs, input.filters),
_filteredVerbs(verbs, input.filters, pathRoute),
async (acc, [verb, operation]: [string, OperationObject]) => {
if (isVerb(verb)) {
const verbOptions = await generateVerbOptions({
Expand All @@ -264,25 +264,155 @@ export const generateVerbsOptions = ({
export const _filteredVerbs = (
verbs: PathItemObject,
filters: NormalizedInputOptions['filters'],
pathRoute?: string,
) => {
if (filters?.tags === undefined) {
if (
filters === undefined ||
(filters.tags === undefined && filters.paths === undefined)
) {
return Object.entries(verbs);
}

const filterTags = filters.tags || [];
const filterPaths = filters.paths || [];
const filterMode = filters.mode || 'include';

return Object.entries(verbs).filter(
([_verb, operation]: [string, OperationObject]) => {
const operationTags = operation.tags || [];
([verb, operation]: [string, OperationObject]) => {
// Check for method-specific path filtering
if (filters.paths && pathRoute) {
const isMethodSpecificFilter =
Array.isArray(filterPaths) &&
filterPaths.length > 0 &&
Array.isArray(filterPaths[0]);

const isMatch = operationTags.some((tag) =>
filterTags.some((filterTag) =>
filterTag instanceof RegExp ? filterTag.test(tag) : filterTag === tag,
),
);
if (isMethodSpecificFilter) {
// Method-specific filtering: [path, [methods]]
const pathMatch = filterPaths.some((filterItem) => {
if (!Array.isArray(filterItem) || filterItem.length !== 2)
return false;

return filterMode === 'exclude' ? !isMatch : isMatch;
const [filterPath, methods] = filterItem as [
string | RegExp,
string[],
];
const pathMatches =
filterPath instanceof RegExp
? filterPath.test(pathRoute)
: filterPath === pathRoute;

if (!pathMatches) return false;

// Check if this specific verb is in the allowed methods
return methods.includes(verb.toLowerCase());
});

if (filterMode === 'exclude') {
return !pathMatch;
} else {
return pathMatch;
}
}
}

// Regular tag filtering
if (filters.tags) {
const operationTags = operation.tags || [];

const isMatch = operationTags.some((tag) =>
filterTags.some((filterTag) =>
filterTag instanceof RegExp
? filterTag.test(tag)
: filterTag === tag,
),
);

return filterMode === 'exclude' ? !isMatch : isMatch;
}

// If only path filters are specified (not method-specific),
// we need to check if the path matches the filter
if (filters.paths && pathRoute) {
const isMatch = filterPaths.some((filterPath) =>
filterPath instanceof RegExp
? filterPath.test(pathRoute)
: filterPath === pathRoute,
);

return filterMode === 'exclude' ? !isMatch : isMatch;
}

return true;
},
);
};

export const _filteredPaths = (
paths: Record<string, PathItemObject>,
filters: NormalizedInputOptions['filters'],
) => {
if (filters === undefined || filters.paths === undefined) {
return Object.entries(paths);
}

const filterPaths = filters.paths || [];
const filterMode = filters.mode || 'include';

return Object.entries(paths).filter(
([pathRoute, verbs]: [string, PathItemObject]) => {
// Check if this is method-specific filtering (array of tuples)
const isMethodSpecificFilter =
Array.isArray(filterPaths) &&
filterPaths.length > 0 &&
Array.isArray(filterPaths[0]);

if (isMethodSpecificFilter) {
// Method-specific filtering: [path, [methods]]
const isMatch = filterPaths.some((filterItem) => {
if (!Array.isArray(filterItem) || filterItem.length !== 2)
return false;

const [filterPath, methods] = filterItem as [
string | RegExp,
string[],
];
const pathMatches =
filterPath instanceof RegExp
? filterPath.test(pathRoute)
: filterPath === pathRoute;

if (!pathMatches) return false;

// Check if any of the specified methods exist in this path
return methods.some((method: string) => {
const lowerMethod = method.toLowerCase();
return lowerMethod === 'get'
? !!verbs.get
: lowerMethod === 'post'
? !!verbs.post
: lowerMethod === 'put'
? !!verbs.put
: lowerMethod === 'delete'
? !!verbs.delete
: lowerMethod === 'patch'
? !!verbs.patch
: lowerMethod === 'head'
? !!verbs.head
: false;
});
});

return filterMode === 'exclude' ? !isMatch : isMatch;
} else {
// Simple path filtering (backward compatibility)
const isMatch = filterPaths.some((filterPath) =>
filterPath instanceof RegExp
? filterPath.test(pathRoute)
: filterPath === pathRoute,
);

return filterMode === 'exclude' ? !isMatch : isMatch;
}
},
);
};
11 changes: 11 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,21 @@ export type SwaggerParserOptions = Omit<SwaggerParser.Options, 'validate'> & {
validate?: boolean;
};

export type HttpMethod =
| 'delete'
| 'get'
| 'head'
| 'options'
| 'patch'
| 'post'
| 'put';

export type InputFiltersOption = {
mode?: 'include' | 'exclude';
tags?: (string | RegExp)[];
schemas?: (string | RegExp)[];
paths?: (string | RegExp)[] | Array<[string | RegExp, HttpMethod[]]>;
schemaDependencyAnalysis?: boolean;
};

export type InputOptions = {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './merge-deep';
export * from './occurrence';
export * from './open-api-converter';
export * as upath from './path';
export * from './schema-dependency-analyzer';
export * from './sort';
export * from './string';
export * from './tsconfig';
Expand Down
Loading
Loading