@openapi-qraft/plugin 1.13.0 → 1.14.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/lib/QraftCommand.d.ts.map +1 -1
  3. package/dist/lib/QraftCommand.js +27 -8
  4. package/dist/lib/QraftCommand.js.map +1 -1
  5. package/dist/lib/createServicePathMatch.d.ts +0 -9
  6. package/dist/lib/createServicePathMatch.d.ts.map +1 -1
  7. package/dist/lib/createServicePathMatch.js +0 -15
  8. package/dist/lib/createServicePathMatch.js.map +1 -1
  9. package/dist/lib/filterDocumentPaths.d.ts +1 -1
  10. package/dist/lib/filterDocumentPaths.d.ts.map +1 -1
  11. package/dist/lib/filterDocumentPaths.js +1 -1
  12. package/dist/lib/filterDocumentPaths.js.map +1 -1
  13. package/dist/lib/parseOperationGlobs.d.ts +17 -0
  14. package/dist/lib/parseOperationGlobs.d.ts.map +1 -0
  15. package/dist/lib/parseOperationGlobs.js +37 -0
  16. package/dist/lib/parseOperationGlobs.js.map +1 -0
  17. package/dist/lib/predefineSchemaParameters.d.ts +15 -11
  18. package/dist/lib/predefineSchemaParameters.d.ts.map +1 -1
  19. package/dist/lib/predefineSchemaParameters.js +50 -30
  20. package/dist/lib/predefineSchemaParameters.js.map +1 -1
  21. package/dist/lib/processOperationNameModifierOption.d.ts +57 -0
  22. package/dist/lib/processOperationNameModifierOption.d.ts.map +1 -0
  23. package/dist/lib/processOperationNameModifierOption.js +102 -0
  24. package/dist/lib/processOperationNameModifierOption.js.map +1 -0
  25. package/dist/lib/splitCommaSeparatedGlobs.d.ts +7 -0
  26. package/dist/lib/splitCommaSeparatedGlobs.d.ts.map +1 -0
  27. package/dist/lib/splitCommaSeparatedGlobs.js +16 -0
  28. package/dist/lib/splitCommaSeparatedGlobs.js.map +1 -0
  29. package/package.json +3 -3
  30. package/src/lib/QraftCommand.ts +59 -10
  31. package/src/lib/createServicePathMatch.ts +0 -18
  32. package/src/lib/filterDocumentPaths.ts +2 -2
  33. package/src/lib/parseOperationGlobs.ts +54 -0
  34. package/src/lib/predefineSchemaParameters.ts +108 -71
  35. package/src/lib/processOperationNameModifierOption.ts +205 -0
  36. package/src/lib/splitCommaSeparatedGlobs.ts +16 -0
@@ -0,0 +1,102 @@
1
+ import { createServicePathMatch } from './createServicePathMatch.js';
2
+ import { parseOperationGlobs, } from './parseOperationGlobs.js';
3
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
4
+ export const processOperationNameModifierOption = (modifiers, services) => {
5
+ if (!modifiers.length)
6
+ return {
7
+ errors: [],
8
+ services,
9
+ };
10
+ const operationNameModifiers = modifiers.map((modifier) => ({
11
+ ...modifier,
12
+ isPathMatch: createServicePathMatch(splitCommaSeparatedGlobs(modifier.pathGlobs)),
13
+ }));
14
+ const serviceOperationReplacements = new Map();
15
+ const replacedOperationNamesServices = services.map((service) => {
16
+ serviceOperationReplacements.set(service.name, new Map());
17
+ return {
18
+ ...service,
19
+ operations: service.operations.map((operation) => {
20
+ const operationReplacements = serviceOperationReplacements.get(service.name);
21
+ if (!operationReplacements)
22
+ throw new Error('operationReplacements not found');
23
+ const operationModifiers = operationNameModifiers.filter((modifier) => modifier.isPathMatch(operation.path) &&
24
+ (modifier.methods === undefined ||
25
+ modifier.methods.includes(operation.method)) &&
26
+ normalizeOperationNameModifierRegex(modifier.operationNameModifierRegex).test(operation.name));
27
+ const replacedOperationName = operationModifiers.reduce((operationName, modifier) => operationName.replace(normalizeOperationNameModifierRegex(modifier.operationNameModifierRegex), modifier.operationNameModifierReplace), operation.name);
28
+ if (replacedOperationName !== operation.name) {
29
+ if (!operationReplacements.has(replacedOperationName))
30
+ operationReplacements.set(replacedOperationName, new Map());
31
+ operationReplacements.get(replacedOperationName)?.set(operation.name, operationModifiers.map(({ isPathMatch: _, ...operationModifiersRest }) => operationModifiersRest));
32
+ }
33
+ return {
34
+ ...operation,
35
+ name: replacedOperationName,
36
+ };
37
+ }),
38
+ };
39
+ });
40
+ const errors = Array.from(serviceOperationReplacements).flatMap(([serviceName, modifiedOperations]) => Array.from(modifiedOperations).flatMap(([replacedOperationName, operationModifierMap]) => {
41
+ if (operationModifierMap.size > 1) {
42
+ return Array.from(operationModifierMap).map(([originalOperationName, modifiers]) => ({
43
+ serviceName,
44
+ replacedOperationName,
45
+ originalOperationName,
46
+ modifiers,
47
+ }));
48
+ }
49
+ const [originalOperationName, modifiers] = Array.from(operationModifierMap).at(0) ?? [];
50
+ if (modifiers && modifiers.length > 1) {
51
+ return [
52
+ {
53
+ serviceName,
54
+ replacedOperationName,
55
+ originalOperationName,
56
+ modifiers,
57
+ },
58
+ ];
59
+ }
60
+ return [];
61
+ }));
62
+ return {
63
+ errors,
64
+ services: replacedOperationNamesServices,
65
+ };
66
+ };
67
+ function normalizeOperationNameModifierRegex(regex) {
68
+ return new RegExp(regex, 'g');
69
+ }
70
+ /**
71
+ * Parses the given options string into a structured format.
72
+ *
73
+ * @param optionItems - The configuration string to parse. Eg. `/foo,/bar/**:post[A-Z][A-Za-z]+sId[A-Z][A-Za-z]+ ==> createOne` or `post /** ==> createOne`
74
+ * @returns The parsed configuration.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const parsedConfig = parseOperationNameModifier("/foo,/bar/**:/**:post[A-Z][A-Za-z]+sId[A-Z][A-Za-z]+ ==> createOne");
79
+ * ```
80
+ */
81
+ export function parseOperationNameModifier(...optionItems) {
82
+ const globOperationNameModifierMap = [];
83
+ for (const optionItem of optionItems) {
84
+ const operationGlobsRaw = optionItem.slice(0, Math.max(0, optionItem.indexOf(':')));
85
+ if (!operationGlobsRaw.length)
86
+ throw new Error(`Invalid operation name modifier: ${optionItem}. Must be in the format of 'pathGlobs:operationNameModifierRegex ==> operationNameModifierReplace'`);
87
+ const modifierPattern = optionItem.slice(operationGlobsRaw.length + 1);
88
+ const patternRegex = modifierPattern.slice(0, modifierPattern.indexOf('==>'));
89
+ const patternReplace = modifierPattern.slice(modifierPattern.indexOf('==>') + '==>'.length + 1);
90
+ const { methods, pathGlobs } = parseOperationGlobs(operationGlobsRaw);
91
+ if (!splitCommaSeparatedGlobs(pathGlobs).length)
92
+ throw new Error(`Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`);
93
+ globOperationNameModifierMap.push({
94
+ methods,
95
+ pathGlobs,
96
+ operationNameModifierRegex: patternRegex.trim(),
97
+ operationNameModifierReplace: patternReplace.trim(),
98
+ });
99
+ }
100
+ return globOperationNameModifierMap;
101
+ }
102
+ //# sourceMappingURL=processOperationNameModifierOption.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processOperationNameModifierOption.js","sourceRoot":"","sources":["../../src/lib/processOperationNameModifierOption.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Normalize a comma-separated list of globs
3
+ * @param commaSeparatedGlobs - Example: `/foo , /bar/**, ,`
4
+ * @returns Example: `/foo,/bar/**`
5
+ */
6
+ export declare function splitCommaSeparatedGlobs(commaSeparatedGlobs: string | undefined): string[];
7
+ //# sourceMappingURL=splitCommaSeparatedGlobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splitCommaSeparatedGlobs.d.ts","sourceRoot":"","sources":["../../src/lib/splitCommaSeparatedGlobs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,mBAAmB,EAAE,MAAM,GAAG,SAAS,GACtC,MAAM,EAAE,CAQV"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Normalize a comma-separated list of globs
3
+ * @param commaSeparatedGlobs - Example: `/foo , /bar/**, ,`
4
+ * @returns Example: `/foo,/bar/**`
5
+ */
6
+ export function splitCommaSeparatedGlobs(commaSeparatedGlobs) {
7
+ if (commaSeparatedGlobs === undefined)
8
+ return [];
9
+ if (typeof commaSeparatedGlobs !== 'string')
10
+ throw new Error('Invalid input');
11
+ return commaSeparatedGlobs
12
+ .split(',')
13
+ .map((path) => path.trim())
14
+ .filter(Boolean);
15
+ }
16
+ //# sourceMappingURL=splitCommaSeparatedGlobs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splitCommaSeparatedGlobs.js","sourceRoot":"","sources":["../../src/lib/splitCommaSeparatedGlobs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CACtC,mBAAuC;IAEvC,IAAI,mBAAmB,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACjD,IAAI,OAAO,mBAAmB,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IAE9E,OAAO,mBAAmB;SACvB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openapi-qraft/plugin",
3
- "version": "1.13.0",
3
+ "version": "1.14.0-beta.0",
4
4
  "packageManager": "yarn@4.0.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc --project tsconfig.build.json",
8
8
  "dev": "yarn build --watch --noEmitOnError false",
9
- "test": "vitest run",
9
+ "test": "vitest run --coverage",
10
10
  "typecheck": "tsc --noEmit",
11
11
  "lint": "eslint",
12
12
  "clean": "rimraf dist/"
@@ -28,7 +28,7 @@
28
28
  "camelcase": "^8.0.0",
29
29
  "commander": "^11.1.0",
30
30
  "micromatch": "^4.0.5",
31
- "openapi-typescript": "7.0.0-next.11",
31
+ "openapi-typescript": "^7.3.0",
32
32
  "ora": "^8.0.1",
33
33
  "yaml": "^2.3.4"
34
34
  },
@@ -7,7 +7,6 @@ import { pathToFileURL, URL } from 'node:url';
7
7
  import c from 'ansi-colors';
8
8
  import { Command } from 'commander';
9
9
  import ora, { Ora } from 'ora';
10
- import { parsePathGlobs } from './createServicePathMatch.js';
11
10
  import { filterDocumentPaths } from './filterDocumentPaths.js';
12
11
  import { GeneratorFile } from './GeneratorFile.js';
13
12
  import { handleSchemaInput } from './handleSchemaInput.js';
@@ -16,10 +15,15 @@ import { OpenAPISchemaType } from './open-api/OpenAPISchemaType.js';
16
15
  import { readSchema } from './open-api/readSchema.js';
17
16
  import { OutputOptions } from './OutputOptions.js';
18
17
  import {
19
- createPredefinedParametersGlobMap,
18
+ createPredefinedParametersGlobs,
20
19
  parseOperationPredefinedParametersOption,
21
20
  predefineSchemaParameters,
22
21
  } from './predefineSchemaParameters.js';
22
+ import {
23
+ parseOperationNameModifier,
24
+ processOperationNameModifierOption,
25
+ } from './processOperationNameModifierOption.js';
26
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
23
27
  import { writeGeneratorFiles } from './writeGeneratorFiles.js';
24
28
 
25
29
  export class QraftCommand extends Command {
@@ -50,7 +54,11 @@ export class QraftCommand extends Command {
50
54
  )
51
55
  .option(
52
56
  '--operation-predefined-parameters <patterns...>',
53
- 'Predefined parameters for services. The specified services parameters will be optional. Eg: "/**:header.x-monite-version,query.x-api-key;/user/**,/post/**:header.x-monite-entity-id"'
57
+ 'Predefined parameters for services. The specified services parameters will be optional. Eg: "/**:header.x-monite-version,query.x-api-key" or "get /**:header.x-monite-entity-id"'
58
+ )
59
+ .option(
60
+ '--operation-name-modifier <patterns...>',
61
+ 'Modifies operation names using a pattern. Use the `==>` operator to separate the regular expression (left) and the substitution string (right). For example: "post /**:[A-Za-z]+Id ==> createOne"'
54
62
  )
55
63
  .option(
56
64
  '--postfix-services <string>',
@@ -103,20 +111,20 @@ export class QraftCommand extends Command {
103
111
  spinner.text = 'Reading OpenAPI Schema';
104
112
  let schema = filterDocumentPaths(
105
113
  await readSchema(input),
106
- parsePathGlobs(args.filterServices)
114
+ splitCommaSeparatedGlobs(args.filterServices)
107
115
  );
108
116
 
109
117
  if (args.operationPredefinedParameters) {
110
- const predefinedParametersGlobMap = createPredefinedParametersGlobMap(
118
+ const predefinedParametersGlobs = createPredefinedParametersGlobs(
111
119
  schema,
112
120
  parseOperationPredefinedParametersOption(
113
121
  ...args.operationPredefinedParameters
114
122
  )
115
123
  );
116
124
 
117
- const predefinedParametersErrors = Object.values(
118
- predefinedParametersGlobMap
119
- ).flatMap(({ errors }) => errors);
125
+ const predefinedParametersErrors = predefinedParametersGlobs.flatMap(
126
+ ({ errors }) => errors
127
+ );
120
128
 
121
129
  if (predefinedParametersErrors.length) {
122
130
  predefinedParametersErrors.forEach((error) => spinner.warn(error));
@@ -126,15 +134,56 @@ export class QraftCommand extends Command {
126
134
  process.exit(1);
127
135
  }
128
136
 
129
- schema = predefineSchemaParameters(schema, predefinedParametersGlobMap);
137
+ schema = predefineSchemaParameters(schema, predefinedParametersGlobs);
130
138
  }
131
139
 
132
140
  spinner.text = 'Getting OpenAPI Services';
133
- const services = getServices(schema as OpenAPISchemaType, {
141
+ let services = getServices(schema as OpenAPISchemaType, {
134
142
  postfixServices: args.postfixServices,
135
143
  serviceNameBase: args.serviceNameBase,
136
144
  });
137
145
 
146
+ if (args.operationNameModifier) {
147
+ const {
148
+ services: modifiedServicesOperationNames,
149
+ errors: operationNameModifierErrors,
150
+ } = processOperationNameModifierOption(
151
+ parseOperationNameModifier(
152
+ ...(Array.isArray(args.operationNameModifier)
153
+ ? args.operationNameModifier
154
+ : [args.operationNameModifier])
155
+ ),
156
+ services
157
+ );
158
+
159
+ if (operationNameModifierErrors.length) {
160
+ operationNameModifierErrors.forEach((error) => {
161
+ spinner.fail(
162
+ [
163
+ c.red.bold(
164
+ `Error occurred during operation name modifier setup: '${error.serviceName}.${error.originalOperationName}' to '${error.replacedOperationName}'`
165
+ ),
166
+ c.yellow(
167
+ error.modifiers
168
+ .map(
169
+ ({
170
+ pathGlobs,
171
+ operationNameModifierRegex,
172
+ operationNameModifierReplace,
173
+ }) =>
174
+ `Conflicting operation name modifier: '${pathGlobs}': '${operationNameModifierRegex}' => '${operationNameModifierReplace}'`
175
+ )
176
+ .join('\n')
177
+ ),
178
+ ].join('\n')
179
+ );
180
+ });
181
+ process.exit(1);
182
+ }
183
+
184
+ services = modifiedServicesOperationNames;
185
+ }
186
+
138
187
  spinner.text = 'Generating code';
139
188
 
140
189
  const outputDir = normalizeOutputDirPath(args.outputDir);
@@ -26,21 +26,3 @@ export const createServicePathMatch = (servicesGlob: string[]) => {
26
26
  });
27
27
  };
28
28
  };
29
-
30
- /**
31
- * Parse services filter option to array
32
- * @example
33
- * ```ts
34
- * parsePathGlobs('/user/**,/post/**')
35
- * ```
36
- * @param endpointGlobs
37
- */
38
- export function parsePathGlobs(endpointGlobs: string): string[] | undefined {
39
- return endpointGlobs
40
- ? endpointGlobs
41
- .split(',')
42
- .filter((item: unknown): item is string =>
43
- Boolean(typeof item === 'string' && item.trim())
44
- )
45
- : undefined;
46
- }
@@ -13,9 +13,9 @@ import { createServicePathMatch } from './createServicePathMatch.js';
13
13
  */
14
14
  export const filterDocumentPaths = (
15
15
  schema: OpenAPI3,
16
- servicesGlob: string[] | undefined
16
+ servicesGlob: string[]
17
17
  ): OpenAPI3 => {
18
- if (!servicesGlob) return schema;
18
+ if (!servicesGlob.length) return schema;
19
19
 
20
20
  const isPathMatch = createServicePathMatch(servicesGlob);
21
21
  const paths: typeof schema.paths = {};
@@ -0,0 +1,54 @@
1
+ import { ServiceOperation } from './open-api/getServices.js';
2
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
3
+
4
+ /**
5
+ *
6
+ * @param operationFullGlobs - Example: `post /**`, `/**,/foo/bar`
7
+ * @returns `pathGlobs`, and `methods` as an array if methods are specified, or `undefined`
8
+ * @example
9
+ * ```ts
10
+ * parseOperationGlobs('post /**');
11
+ * parseOperationGlobs('patch,put /foo/bar');
12
+ * ```
13
+ */
14
+ export const parseOperationGlobs = (operationFullGlobs: string) => {
15
+ const methodsGlobsRaw = operationFullGlobs.slice(
16
+ 0,
17
+ Math.max(0, operationFullGlobs.indexOf('/'))
18
+ );
19
+
20
+ const methods = methodsGlobsRaw
21
+ .split(',')
22
+ .map((method) => method.trim().toLowerCase())
23
+ .filter(isSupportedOperationMethod)
24
+ .toSorted(sortSupportedMethods);
25
+
26
+ return {
27
+ methods: methods.length ? methods : undefined,
28
+ pathGlobs: splitCommaSeparatedGlobs(
29
+ operationFullGlobs.slice(methodsGlobsRaw.length)
30
+ ).join(','),
31
+ };
32
+ };
33
+
34
+ const supportedOperationMethods = Object.values({
35
+ get: 'get',
36
+ post: 'post',
37
+ put: 'put',
38
+ patch: 'patch',
39
+ delete: 'delete',
40
+ options: 'options',
41
+ head: 'head',
42
+ trace: 'trace',
43
+ } satisfies { [Method in OperationGlobMethods]: Method });
44
+
45
+ const isSupportedOperationMethod = (
46
+ operationRaw: string
47
+ ): operationRaw is OperationGlobMethods =>
48
+ supportedOperationMethods.includes(operationRaw as OperationGlobMethods);
49
+
50
+ const sortSupportedMethods = (a: string, b: string) =>
51
+ supportedOperationMethods.indexOf(a as OperationGlobMethods) -
52
+ supportedOperationMethods.indexOf(b as OperationGlobMethods);
53
+
54
+ export type OperationGlobMethods = ServiceOperation['method'];
@@ -4,14 +4,16 @@ import {
4
4
  PathItemObject,
5
5
  ReferenceObject,
6
6
  } from 'openapi-typescript/src/types.js';
7
+ import { createServicePathMatch } from './createServicePathMatch.js';
7
8
  import {
8
- createServicePathMatch,
9
- parsePathGlobs,
10
- } from './createServicePathMatch.js';
9
+ OperationGlobMethods,
10
+ parseOperationGlobs,
11
+ } from './parseOperationGlobs.js';
12
+ import { splitCommaSeparatedGlobs } from './splitCommaSeparatedGlobs.js';
11
13
 
12
14
  export function predefineSchemaParameters(
13
15
  schema: OpenAPI3,
14
- predefinedParametersGlobMap: PredefinedParametersGlobMap
16
+ predefinedParametersGlobs: PredefinedParametersGlob[]
15
17
  ): OpenAPI3 {
16
18
  const predefinedSchemaPaths: typeof schema.paths = {};
17
19
 
@@ -31,9 +33,7 @@ export function predefineSchemaParameters(
31
33
  const operation = service[method];
32
34
  assertIsOperationObject(operation);
33
35
 
34
- for (const predefinedParametersGlob of Object.values(
35
- predefinedParametersGlobMap
36
- )) {
36
+ for (const predefinedParametersGlob of predefinedParametersGlobs) {
37
37
  if (!predefinedParametersGlob.paths.includes(path)) {
38
38
  continue;
39
39
  }
@@ -82,11 +82,6 @@ export function predefineSchemaParameters(
82
82
  };
83
83
  }
84
84
 
85
- type PredefinedParametersOptionMap = Record<
86
- string,
87
- Pick<ParameterObject, 'in' | 'name'>[]
88
- >;
89
-
90
85
  /**
91
86
  * Parses the given configuration string into a structured format.
92
87
  *
@@ -100,80 +95,90 @@ type PredefinedParametersOptionMap = Record<
100
95
  */
101
96
  function parseOperationPredefinedParametersOption(
102
97
  ...optionItems: string[]
103
- ): PredefinedParametersOptionMap {
104
- const globParametersMap: PredefinedParametersOptionMap = {};
98
+ ): ParsedPredefinedParametersOption[] {
99
+ const globParametersMap = new Map<string, ParsedPredefinedParametersOption>();
105
100
 
106
101
  for (const optionItem of optionItems) {
107
- const [pathGlob, options] = optionItem.split(':').map((s) => s.trim());
102
+ const operationGlobsRaw = optionItem.slice(
103
+ 0,
104
+ Math.max(0, optionItem.indexOf(':')) || optionItem.length // the second part after the `:` is optional for the predefined parameters option
105
+ );
106
+ const parameterGlobs = optionItem.slice(operationGlobsRaw.length + 1);
107
+
108
108
  const parameters: Record<'header' | 'query' | 'cookie', string[]> = {
109
109
  header: [],
110
110
  query: [],
111
111
  cookie: [],
112
112
  };
113
113
 
114
- if (options) {
115
- options
116
- .split(',')
117
- .map((option) => option.trim())
118
- .filter(Boolean)
119
- .forEach((option) => {
120
- const [type, key] = option.split('.').map((s) => s.trim());
121
-
122
- if (type !== 'header' && type !== 'query' && type !== 'cookie')
123
- throw new Error(
124
- `Invalid option type: ${type} in ${option}. Must be one of 'header', 'query', or 'cookie'`
125
- );
114
+ if (parameterGlobs) {
115
+ splitCommaSeparatedGlobs(parameterGlobs).forEach((option) => {
116
+ const [type, key] = option.split('.').map((s) => s.trim());
126
117
 
127
- if (!parameters[type].includes(key)) parameters[type].push(key);
128
- });
118
+ if (type !== 'header' && type !== 'query' && type !== 'cookie')
119
+ throw new Error(
120
+ `Invalid option type: ${type} in ${option}. Must be one of 'header', 'query', or 'cookie'`
121
+ );
122
+
123
+ if (!parameters[type].includes(key)) parameters[type].push(key);
124
+ });
129
125
  }
130
126
 
131
- if (pathGlob in globParametersMap) {
132
- throw new Error(`Duplicate path: ${pathGlob} in config string.`);
127
+ const { pathGlobs, methods: methodList } =
128
+ parseOperationGlobs(operationGlobsRaw);
129
+
130
+ const methods = methodList ? methodList.join(',') : '';
131
+
132
+ const globKey = `${methods}${pathGlobs}`;
133
+
134
+ if (globParametersMap.has(globKey)) {
135
+ throw new Error(
136
+ `Duplicate path: ${methods ? methods + ' ' : ''}${pathGlobs} in config string.`
137
+ );
133
138
  }
134
139
 
135
- globParametersMap[pathGlob] = Object.entries(parameters).flatMap(
136
- ([inLocation, names]) =>
140
+ globParametersMap.set(globKey, {
141
+ parameters: Object.entries(parameters).flatMap(([inLocation, names]) =>
137
142
  names.map((name) => ({ in: inLocation as ParameterObject['in'], name }))
138
- );
143
+ ),
144
+ methods: methodList,
145
+ pathGlobs,
146
+ });
139
147
  }
140
148
 
141
- return globParametersMap;
149
+ return Array.from(globParametersMap.values());
142
150
  }
143
151
 
144
- type PredefinedParametersGlobMap = {
145
- [glob: string]: {
146
- paths: string[];
147
- parameters: ParameterObject[];
148
- errors: string[];
149
- };
150
- };
151
-
152
- export function createPredefinedParametersGlobMap(
152
+ export function createPredefinedParametersGlobs(
153
153
  schema: OpenAPI3,
154
- predefinedParameters: PredefinedParametersOptionMap
155
- ): PredefinedParametersGlobMap {
156
- const predefinedParametersGlobMap: PredefinedParametersGlobMap = {};
157
-
158
- const servicePredefinedParametersList = Object.entries(
159
- predefinedParameters
160
- ).map(([pathGlobs, parameters]) => {
161
- const globs = parsePathGlobs(pathGlobs);
162
- if (!globs)
163
- throw new Error(
164
- `Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`
165
- );
154
+ predefinedParameters: ParsedPredefinedParametersOption[]
155
+ ): Array<PredefinedParametersGlob> {
156
+ const predefinedParametersGlobMap = new Map<
157
+ string,
158
+ PredefinedParametersGlob
159
+ >();
160
+
161
+ const servicePredefinedParametersList = predefinedParameters.map(
162
+ ({ pathGlobs, parameters, methods }) => {
163
+ const globs = splitCommaSeparatedGlobs(pathGlobs);
164
+ if (!globs.length)
165
+ throw new Error(
166
+ `Invalid path glob: ${pathGlobs}. Must be a comma-separated list of globs.`
167
+ );
166
168
 
167
- return {
168
- pathGlobs,
169
- parameters,
170
- isPathMatch: createServicePathMatch(globs),
171
- };
172
- });
169
+ return {
170
+ methods,
171
+ pathGlobs,
172
+ parameters,
173
+ isPathMatch: createServicePathMatch(globs),
174
+ };
175
+ }
176
+ );
173
177
 
174
178
  for (const path in schema.paths) {
175
179
  if (!Object.prototype.hasOwnProperty.call(schema.paths, path)) continue;
176
180
  for (const {
181
+ methods,
177
182
  pathGlobs,
178
183
  parameters: parametersToPredefine,
179
184
  isPathMatch,
@@ -187,6 +192,11 @@ export function createPredefinedParametersGlobMap(
187
192
  for (const _method in service) {
188
193
  const method = _method as keyof typeof service;
189
194
  if (!Object.prototype.hasOwnProperty.call(service, method)) continue;
195
+ if (
196
+ methods !== undefined &&
197
+ !methods.includes(method as OperationGlobMethods)
198
+ )
199
+ continue;
190
200
 
191
201
  const operation = service[method];
192
202
  assertIsOperationObject(operation);
@@ -194,14 +204,18 @@ export function createPredefinedParametersGlobMap(
194
204
  const operationParameters = operation.parameters ?? [];
195
205
  assertIsParameterObjects(operationParameters);
196
206
 
197
- if (!(pathGlobs in predefinedParametersGlobMap))
198
- predefinedParametersGlobMap[pathGlobs] = {
207
+ if (!predefinedParametersGlobMap.has(pathGlobs))
208
+ predefinedParametersGlobMap.set(pathGlobs, {
209
+ methods: [],
199
210
  paths: [],
200
211
  parameters: [],
201
212
  errors: [],
202
- };
213
+ });
203
214
 
204
- const predefinedPathGlob = predefinedParametersGlobMap[pathGlobs];
215
+ const predefinedPathGlob = predefinedParametersGlobMap.get(pathGlobs);
216
+
217
+ if (!predefinedPathGlob)
218
+ throw new Error('predefinedPathGlob not found');
205
219
 
206
220
  for (const parameterSchemaToPredefine of parametersToPredefine) {
207
221
  const parameterToPredefine = findPredefinedOperationParameter(
@@ -209,6 +223,15 @@ export function createPredefinedParametersGlobMap(
209
223
  operationParameters
210
224
  );
211
225
 
226
+ if (
227
+ !predefinedPathGlob.methods?.includes(
228
+ method as OperationGlobMethods
229
+ )
230
+ ) {
231
+ if (!predefinedPathGlob.methods) predefinedPathGlob.methods = [];
232
+ predefinedPathGlob.methods.push(method as OperationGlobMethods);
233
+ }
234
+
212
235
  if (!parameterToPredefine) {
213
236
  predefinedPathGlob.errors.push(
214
237
  `Missing predefined parameter '${parameterSchemaToPredefine.in}' '${parameterSchemaToPredefine.name}' in '${method} ${path}' in '${pathGlobs}'`
@@ -246,17 +269,18 @@ export function createPredefinedParametersGlobMap(
246
269
  }
247
270
  }
248
271
 
249
- Object.entries(predefinedParameters).forEach(([pathGlobs]) => {
250
- if (!predefinedParametersGlobMap[pathGlobs]) {
251
- predefinedParametersGlobMap[pathGlobs] = {
272
+ predefinedParameters.forEach(({ pathGlobs, methods }) => {
273
+ if (!predefinedParametersGlobMap.has(pathGlobs)) {
274
+ predefinedParametersGlobMap.set(pathGlobs, {
275
+ methods,
252
276
  paths: [],
253
277
  parameters: [],
254
278
  errors: [`No matching paths found for '${pathGlobs}'`],
255
- };
279
+ });
256
280
  }
257
281
  });
258
282
 
259
- return predefinedParametersGlobMap;
283
+ return Array.from(predefinedParametersGlobMap.values());
260
284
  }
261
285
 
262
286
  function findPredefinedOperationParameter(
@@ -307,4 +331,17 @@ export function assertIsParameterObjects(
307
331
  });
308
332
  }
309
333
 
334
+ type ParsedPredefinedParametersOption = {
335
+ methods: OperationGlobMethods[] | undefined;
336
+ pathGlobs: string;
337
+ parameters: Pick<ParameterObject, 'in' | 'name'>[];
338
+ };
339
+
340
+ type PredefinedParametersGlob = {
341
+ methods: OperationGlobMethods[] | undefined;
342
+ paths: string[];
343
+ parameters: Pick<ParameterObject, 'in' | 'name'>[];
344
+ errors: string[];
345
+ };
346
+
310
347
  export { parseOperationPredefinedParametersOption };