@let-aurn/swagger-client 0.3.2 → 0.3.3

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.
package/README.md CHANGED
@@ -24,6 +24,23 @@ npx swagger-client generate --config=swagger-client.json
24
24
 
25
25
  `client` can be `angular` or `react`.
26
26
 
27
+ `serverUrlIndex` is optional and selects a zero-based index from each document's
28
+ top-level `servers` array to prefix generated endpoint paths:
29
+
30
+ ```json
31
+ {
32
+ "serverUrlIndex": 0
33
+ }
34
+ ```
35
+
36
+ For `servers: [{ "url": "/some-api/V01/xxx" }]`, the endpoint `/posts/{id}`
37
+ gets the path `/some-api/V01/xxx/posts/{id}` in `api-endpoints.ts`. Relative
38
+ server prefixes are appended to the client's configured `baseUrl` when making
39
+ requests. Slashes at the join are normalized; an empty operation path uses the
40
+ selected server URL exactly, without adding a trailing slash. Omitting this option keeps the
41
+ original endpoint paths. Invalid indexes or missing, empty, or non-string
42
+ selected URLs fail generation; URL errors identify the source file.
43
+
27
44
  `endpoint-name-strategy` is optional:
28
45
 
29
46
  - `operationId` uses the OpenAPI `operationId` as-is. This is the default.
@@ -13,7 +13,7 @@ import { emitReactClient } from "../generators/react-client.js";
13
13
  * @throws Error when OpenAPI files cannot be read or generated files cannot be written.
14
14
  */
15
15
  export async function generate(config) {
16
- const project = await readOpenApiProject(config["swaggers-directory"], config["endpoint-name-strategy"], config["endpoint-namespace"]);
16
+ const project = await readOpenApiProject(config["swaggers-directory"], config["endpoint-name-strategy"], config["endpoint-namespace"], config.serverUrlIndex);
17
17
  const modelsPath = config.output["api-models.ts"];
18
18
  const endpointsPath = config.output["api-endpoints.ts"];
19
19
  const clientPath = config.output["client.ts"];
@@ -1,6 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { validateEndpointNamespace } from "./endpoint-namespace.js";
4
+ import { validateServerUrlIndex } from "./server-url.js";
4
5
  /**
5
6
  * Loads and validates a swagger-client configuration file.
6
7
  *
@@ -24,6 +25,7 @@ export async function loadConfig(configPath) {
24
25
  };
25
26
  }
26
27
  function validateConfig(config, configPath) {
28
+ validateServerUrlIndex(config.serverUrlIndex, configPath);
27
29
  if (config.client !== "angular" && config.client !== "react") {
28
30
  throw new Error(`${configPath}: "client" must be "angular" or "react".`);
29
31
  }
@@ -5,7 +5,8 @@ import type { EndpointNameStrategy, EndpointNamespaceConfig, ProjectModel } from
5
5
  * @param swaggerDirectory The directory containing one or more OpenAPI JSON files.
6
6
  * @param endpointNameStrategy The strategy used to name generated endpoint constants.
7
7
  * @param endpointNamespace Configurable document field and optional namespace extraction regex.
8
+ * @param serverUrlIndex Optional document server URL index to prefix endpoint paths with.
8
9
  * @returns The normalized schemas and endpoints discovered in the OpenAPI files.
9
10
  * @throws Error when a JSON file cannot be read or parsed.
10
11
  */
11
- export declare function readOpenApiProject(swaggerDirectory: string, endpointNameStrategy?: EndpointNameStrategy, endpointNamespace?: EndpointNamespaceConfig): Promise<ProjectModel>;
12
+ export declare function readOpenApiProject(swaggerDirectory: string, endpointNameStrategy?: EndpointNameStrategy, endpointNamespace?: EndpointNamespaceConfig, serverUrlIndex?: number): Promise<ProjectModel>;
@@ -3,6 +3,7 @@ import { join, relative } from "node:path";
3
3
  import { schemaToType } from "./schema-to-typescript.js";
4
4
  import { ensureIdentifier, toPascalCase, uniqueName } from "./names.js";
5
5
  import { extractEndpointNamespace, validateEndpointNamespace } from "./endpoint-namespace.js";
6
+ import { selectServerUrl, validateServerUrlIndex } from "./server-url.js";
6
7
  const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "options"]);
7
8
  /**
8
9
  * Reads OpenAPI JSON files from a directory and converts them to the generator project model.
@@ -10,10 +11,12 @@ const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete", "head", "
10
11
  * @param swaggerDirectory The directory containing one or more OpenAPI JSON files.
11
12
  * @param endpointNameStrategy The strategy used to name generated endpoint constants.
12
13
  * @param endpointNamespace Configurable document field and optional namespace extraction regex.
14
+ * @param serverUrlIndex Optional document server URL index to prefix endpoint paths with.
13
15
  * @returns The normalized schemas and endpoints discovered in the OpenAPI files.
14
16
  * @throws Error when a JSON file cannot be read or parsed.
15
17
  */
16
- export async function readOpenApiProject(swaggerDirectory, endpointNameStrategy = "operationId", endpointNamespace) {
18
+ export async function readOpenApiProject(swaggerDirectory, endpointNameStrategy = "operationId", endpointNamespace, serverUrlIndex) {
19
+ validateServerUrlIndex(serverUrlIndex, swaggerDirectory);
17
20
  if (endpointNameStrategy === "namespaceTagAndOperationId") {
18
21
  validateEndpointNamespace(endpointNamespace, swaggerDirectory);
19
22
  }
@@ -25,11 +28,12 @@ export async function readOpenApiProject(swaggerDirectory, endpointNameStrategy
25
28
  for (const file of files) {
26
29
  const document = JSON.parse(await readFile(file, "utf8"));
27
30
  const sourceFile = relative(swaggerDirectory, file);
31
+ const serverUrl = selectServerUrl(document, serverUrlIndex, sourceFile);
28
32
  const schemaNameMap = collectSchemas(document, schemas, usedSchemaNames);
29
33
  const namespace = endpointNameStrategy === "namespaceTagAndOperationId"
30
34
  ? extractEndpointNamespace(document, endpointNamespace, sourceFile)
31
35
  : undefined;
32
- endpoints.push(...collectEndpoints(document, sourceFile, schemaNameMap, usedEndpointNames, endpointNameStrategy, namespace));
36
+ endpoints.push(...collectEndpoints(document, sourceFile, schemaNameMap, usedEndpointNames, endpointNameStrategy, namespace, serverUrl));
33
37
  }
34
38
  return { schemas, endpoints };
35
39
  }
@@ -66,7 +70,7 @@ function collectSchemas(document, schemas, usedSchemaNames) {
66
70
  }
67
71
  return schemaNameMap;
68
72
  }
69
- function collectEndpoints(document, sourceFile, schemaNameMap, usedEndpointNames, endpointNameStrategy, namespace) {
73
+ function collectEndpoints(document, sourceFile, schemaNameMap, usedEndpointNames, endpointNameStrategy, namespace, serverUrl = "") {
70
74
  const endpoints = [];
71
75
  for (const [path, pathItem] of Object.entries(document.paths ?? {})) {
72
76
  if (!isRecord(pathItem)) {
@@ -82,7 +86,7 @@ function collectEndpoints(document, sourceFile, schemaNameMap, usedEndpointNames
82
86
  endpoints.push({
83
87
  name: endpointName,
84
88
  method: method.toUpperCase(),
85
- path,
89
+ path: serverUrl && path ? `${serverUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}` : serverUrl || path,
86
90
  operationId,
87
91
  sourceFile,
88
92
  pathVariables: variablesFor(parameters, "path", schemaNameMap),
@@ -0,0 +1,3 @@
1
+ import type { OpenApiDocument } from "./types.js";
2
+ export declare function validateServerUrlIndex(index: number | undefined, source: string): void;
3
+ export declare function selectServerUrl(document: OpenApiDocument, index: number | undefined, sourceFile: string): string;
@@ -0,0 +1,15 @@
1
+ export function validateServerUrlIndex(index, source) {
2
+ if (index !== undefined && (!Number.isSafeInteger(index) || index < 0)) {
3
+ throw new Error(`${source}: "serverUrlIndex" must be a non-negative safe integer.`);
4
+ }
5
+ }
6
+ export function selectServerUrl(document, index, sourceFile) {
7
+ if (index === undefined) {
8
+ return "";
9
+ }
10
+ const url = Array.isArray(document.servers) ? document.servers[index]?.url : undefined;
11
+ if (typeof url !== "string" || url.trim() === "") {
12
+ throw new Error(`${sourceFile}: servers[${index}].url must be a non-empty string when "serverUrlIndex" is provided.`);
13
+ }
14
+ return url;
15
+ }
@@ -11,6 +11,8 @@ export interface SwaggerClientConfig {
11
11
  "swaggers-directory": string;
12
12
  "endpoint-name-strategy"?: EndpointNameStrategy;
13
13
  "endpoint-namespace"?: EndpointNamespaceConfig;
14
+ /** Index of the document server URL to prefix endpoint paths with. */
15
+ serverUrlIndex?: number;
14
16
  output: {
15
17
  "api-models.ts": string;
16
18
  "api-endpoints.ts": string;
@@ -46,6 +48,9 @@ export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" |
46
48
  export interface OpenApiDocument {
47
49
  openapi?: string;
48
50
  swagger?: string;
51
+ servers?: {
52
+ url: string;
53
+ }[];
49
54
  info?: {
50
55
  title?: string;
51
56
  version?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@let-aurn/swagger-client",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Generate typed Angular and React clients from OpenAPI JSON files.",
5
5
  "type": "module",
6
6
  "bin": {