···44import { sync } from 'cross-spawn';
5566import { generateOutput } from './generate/output';
77+import type { IRContext } from './ir/context';
78import { parse, parseExperimental } from './openApi';
99+import type { ParserConfig } from './openApi/config';
1010+import {
1111+ operationFilterFn,
1212+ operationNameFn,
1313+ operationParameterFilterFn,
1414+ operationParameterNameFn,
1515+} from './openApi/config';
816import { defaultPluginConfigs } from './plugins';
917import type { Client } from './types/client';
1018import type { ClientConfig, Config, UserConfig } from './types/config';
···1220import { getConfig, isLegacyClient, setConfig } from './utils/config';
1321import { getOpenApiSpec } from './utils/getOpenApiSpec';
1422import { registerHandlebarTemplates } from './utils/handlebars';
1515-import {
1616- operationFilterFn,
1717- operationNameFn,
1818- operationParameterFilterFn,
1919- operationParameterNameFn,
2020-} from './utils/parse';
2123import { Performance, PerformanceReport } from './utils/performance';
2224import { postProcessClient } from './utils/postprocess';
2325···339341 >);
340342 Performance.end('openapi');
341343342342- if (config.experimental_parser) {
343343- Performance.start('experimental_parser');
344344- parseExperimental({
344344+ let client: Client | undefined;
345345+ let context: IRContext | undefined;
346346+347347+ Performance.start('parser');
348348+ const parserConfig: ParserConfig = {
349349+ filterFn: {
350350+ operation: operationFilterFn,
351351+ operationParameter: operationParameterFilterFn,
352352+ },
353353+ nameFn: {
354354+ operation: operationNameFn,
355355+ operationParameter: operationParameterNameFn,
356356+ },
357357+ };
358358+ if (config.experimental_parser && !isLegacyClient(config)) {
359359+ context = parseExperimental({
360360+ config,
361361+ parserConfig,
345362 spec: openApi,
346363 });
347347- Performance.end('experimental_parser');
348348- } else {
349349- Performance.start('parser');
364364+ }
365365+366366+ if (!context) {
350367 const parsed = parse({
351351- config: {
352352- filterFn: {
353353- operation: operationFilterFn,
354354- operationParameter: operationParameterFilterFn,
355355- },
356356- nameFn: {
357357- operation: operationNameFn,
358358- operationParameter: operationParameterNameFn,
359359- },
360360- },
361368 openApi,
369369+ parserConfig,
362370 });
363363- const client = postProcessClient(parsed);
364364- Performance.end('parser');
371371+ client = postProcessClient(parsed);
372372+ }
373373+ Performance.end('parser');
365374366366- logClientMessage();
375375+ logClientMessage();
367376368368- Performance.start('generator');
369369- await generateOutput(openApi, client, templates);
370370- Performance.end('generator');
377377+ Performance.start('generator');
378378+ await generateOutput({
379379+ client,
380380+ context,
381381+ openApi,
382382+ templates,
383383+ });
384384+ Performance.end('generator');
371385372372- Performance.start('postprocess');
373373- if (!config.dryRun) {
374374- processOutput();
386386+ Performance.start('postprocess');
387387+ if (!config.dryRun) {
388388+ processOutput();
375389376376- console.log('✨ Done! Your client is located in:', config.output.path);
377377- }
378378- Performance.end('postprocess');
390390+ console.log('✨ Done! Your client is located in:', config.output.path);
391391+ }
392392+ Performance.end('postprocess');
379393380380- return client;
381381- }
394394+ return context || client;
382395 };
383396384397 const clients: Array<Client> = [];
···386399 const pClients = configs.map((config) => pCreateClient(config));
387400 for (const pClient of pClients) {
388401 const client = await pClient();
389389- if (client) {
402402+ if (client && 'version' in client) {
390403 clients.push(client);
391404 }
392405 }
···423436};
424437425438export type { OpenApiV3_0_3 } from './openApi/3.0.3';
426426-export type { OpenApiV3_1 } from './openApi/3.1';
439439+export type { OpenApiV3_1_0 } from './openApi/3.1.0';
427440export type { UserConfig } from './types/config';
+74
packages/openapi-ts/src/ir/context.ts
···11+import path from 'node:path';
22+33+import { TypeScriptFile } from '../generate/files';
44+import type { ParserConfig } from '../openApi/config';
55+import type { Config } from '../types/config';
66+import type { Files } from '../types/utils';
77+import { resolveRef } from '../utils/ref';
88+import type { IR } from './ir';
99+1010+interface ContextFile {
1111+ /**
1212+ * Unique file identifier.
1313+ */
1414+ id: string;
1515+ /**
1616+ * Relative file path to the output path.
1717+ * @example
1818+ * 'bar/foo.ts'
1919+ */
2020+ path: string;
2121+}
2222+2323+export class IRContext<Spec extends Record<string, any> = any> {
2424+ public config: Config;
2525+ public files: Files;
2626+ public ir: IR;
2727+ public parserConfig: ParserConfig;
2828+ public spec: Spec;
2929+3030+ constructor({
3131+ config,
3232+ parserConfig,
3333+ spec,
3434+ }: {
3535+ config: Config;
3636+ parserConfig: ParserConfig;
3737+ spec: Spec;
3838+ }) {
3939+ this.config = config;
4040+ this.files = {};
4141+ this.ir = {};
4242+ this.parserConfig = parserConfig;
4343+ this.spec = spec;
4444+ }
4545+4646+ /**
4747+ * Create and return a new TypeScript file. Also set the current file context
4848+ * to the newly created file.
4949+ */
5050+ public createFile(file: ContextFile): TypeScriptFile {
5151+ const outputParts = file.path.split('/');
5252+ const outputDir = path.resolve(
5353+ this.config.output.path,
5454+ ...outputParts.slice(0, outputParts.length - 1),
5555+ );
5656+ const createdFile = new TypeScriptFile({
5757+ dir: outputDir,
5858+ name: `${outputParts[outputParts.length - 1]}.ts`,
5959+ });
6060+ this.files[file.id] = createdFile;
6161+ return createdFile;
6262+ }
6363+6464+ public file({ id }: Pick<ContextFile, 'id'>): TypeScriptFile | undefined {
6565+ return this.files[id];
6666+ }
6767+6868+ public resolveRef<T>($ref: string) {
6969+ return resolveRef<T>({
7070+ $ref,
7171+ spec: this.spec,
7272+ });
7373+ }
7474+}
+129
packages/openapi-ts/src/ir/ir.d.ts
···11+import type { JsonSchemaDraft2020_12 } from '../openApi/3.1.0/types/json-schema-draft-2020-12';
22+33+export interface IR {
44+ components?: IRComponentsObject;
55+ paths?: IRPathsObject;
66+}
77+88+interface IRComponentsObject {
99+ parameters?: Record<string, IRParameterObject>;
1010+ schemas?: Record<string, IRSchemaObject>;
1111+}
1212+1313+interface IRPathsObject {
1414+ [path: `/${string}`]: IRPathItemObject;
1515+}
1616+1717+interface IRPathItemObject {
1818+ delete?: IROperationObject;
1919+ get?: IROperationObject;
2020+ head?: IROperationObject;
2121+ options?: IROperationObject;
2222+ patch?: IROperationObject;
2323+ post?: IROperationObject;
2424+ put?: IROperationObject;
2525+ trace?: IROperationObject;
2626+}
2727+2828+export interface IROperationObject {
2929+ body?: IRBodyObject;
3030+ deprecated?: boolean;
3131+ description?: string;
3232+ id: string;
3333+ parameters?: IRParametersObject;
3434+ responses?: IRResponsesObject;
3535+ // TODO: parser - add more properties
3636+ // security?: ReadonlyArray<SecurityRequirementObject>;
3737+ // servers?: ReadonlyArray<ServerObject>;
3838+ summary?: string;
3939+ tags?: ReadonlyArray<string>;
4040+}
4141+4242+export interface IRBodyObject {
4343+ required?: boolean;
4444+ schema: IRSchemaObject;
4545+}
4646+4747+export interface IRParametersObject {
4848+ cookie?: Record<string, IRParameterObject>;
4949+ header?: Record<string, IRParameterObject>;
5050+ path?: Record<string, IRParameterObject>;
5151+ query?: Record<string, IRParameterObject>;
5252+}
5353+5454+export interface IRParameterObject {
5555+ /**
5656+ * Endpoint parameters must specify their location.
5757+ */
5858+ location: 'cookie' | 'header' | 'path' | 'query';
5959+ name: string;
6060+ required?: boolean;
6161+ schema: IRSchemaObject;
6262+}
6363+6464+export interface IRResponsesObject {
6565+ /**
6666+ * Any {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#http-status-codes HTTP status code} can be used as the property name, but only one property per code, to describe the expected response for that HTTP status code. This field MUST be enclosed in quotation marks (for example, "200") for compatibility between JSON and YAML. To define a range of response codes, this field MAY contain the uppercase wildcard character `X`. For example, `2XX` represents all response codes between `[200-299]`. Only the following range definitions are allowed: `1XX`, `2XX`, `3XX`, `4XX`, and `5XX`. If a response is defined using an explicit code, the explicit code definition takes precedence over the range definition for that code.
6767+ */
6868+ [statusCode: string]: IRResponseObject | undefined;
6969+ /**
7070+ * The documentation of responses other than the ones declared for specific HTTP response codes. Use this field to cover undeclared responses.
7171+ */
7272+ default?: IRResponseObject;
7373+}
7474+7575+export interface IRResponseObject {
7676+ // TODO: parser - handle headers, links, and possibly other media types?
7777+ schema: IRSchemaObject;
7878+}
7979+8080+export interface IRSchemaObject
8181+ extends Pick<
8282+ JsonSchemaDraft2020_12,
8383+ '$ref' | 'const' | 'deprecated' | 'description' | 'required' | 'title'
8484+ > {
8585+ /**
8686+ * If the schema is intended to be used as an object property, it can be
8787+ * marked as read-only or write-only.
8888+ */
8989+ accessScope?: 'read' | 'write';
9090+ /**
9191+ * If type is `object`, `additionalProperties` can be used to either define
9292+ * a schema for properties not included in `properties` or disallow such
9393+ * properties altogether.
9494+ */
9595+ additionalProperties?: IRSchemaObject | false;
9696+ /**
9797+ * Any string value is accepted as `format`.
9898+ */
9999+ format?: JsonSchemaDraft2020_12['format'] | 'binary' | 'integer';
100100+ /**
101101+ * If schema resolves into multiple items instead of a simple `type`, they
102102+ * will be included in `items` array.
103103+ */
104104+ items?: ReadonlyArray<IRSchemaObject>;
105105+ /**
106106+ * When resolving a list of items, we need to know the relationship between
107107+ * them. `logicalOperator` specifies this logical relationship.
108108+ * @default 'or'
109109+ */
110110+ logicalOperator?: 'and' | 'or';
111111+ /**
112112+ * When type is `object`, `properties` will contain a map of its properties.
113113+ */
114114+ properties?: Record<string, IRSchemaObject>;
115115+ /**
116116+ * Each schema eventually resolves into `type`.
117117+ */
118118+ type?:
119119+ | 'array'
120120+ | 'boolean'
121121+ | 'enum'
122122+ | 'null'
123123+ | 'number'
124124+ | 'object'
125125+ | 'string'
126126+ | 'tuple'
127127+ | 'unknown'
128128+ | 'void';
129129+}
+35
packages/openapi-ts/src/ir/parameter.ts
···11+import type { IRParametersObject } from './ir';
22+33+export const hasParametersObjectRequired = (
44+ parameters: IRParametersObject | undefined,
55+): boolean => {
66+ if (!parameters) {
77+ return false;
88+ }
99+1010+ for (const name in parameters.cookie) {
1111+ if (parameters.cookie[name].required) {
1212+ return true;
1313+ }
1414+ }
1515+1616+ for (const name in parameters.header) {
1717+ if (parameters.header[name].required) {
1818+ return true;
1919+ }
2020+ }
2121+2222+ for (const name in parameters.path) {
2323+ if (parameters.path[name].required) {
2424+ return true;
2525+ }
2626+ }
2727+2828+ for (const name in parameters.query) {
2929+ if (parameters.query[name].required) {
3030+ return true;
3131+ }
3232+ }
3333+3434+ return false;
3535+};
+25
packages/openapi-ts/src/ir/utils.ts
···11+import type { IRSchemaObject } from './ir';
22+33+/**
44+ * Simply adds `items` to the schema. Also handles setting the logical operator
55+ * and avoids setting it for a single item or tuples.
66+ */
77+export const addItemsToSchema = ({
88+ items,
99+ schema,
1010+}: {
1111+ items: Array<IRSchemaObject>;
1212+ schema: IRSchemaObject;
1313+}) => {
1414+ if (!items.length) {
1515+ return;
1616+ }
1717+1818+ schema.items = items;
1919+2020+ if (items.length === 1 || schema.type === 'tuple') {
2121+ return;
2222+ }
2323+2424+ schema.logicalOperator = 'or';
2525+};
···11+import type {
22+ EnumExtensions,
33+ OpenApiSchemaExtensions,
44+} from './spec-extensions';
55+66+// TODO: left out some keywords related to structuring a complex schema and declaring a dialect
77+export interface JsonSchemaDraft2020_12
88+ extends ArrayKeywords,
99+ NumberKeywords,
1010+ ObjectKeywords,
1111+ StringKeywords,
1212+ EnumExtensions,
1313+ OpenApiSchemaExtensions {
1414+ /**
1515+ * The `$comment` {@link https://json-schema.org/learn/glossary#keyword keyword} is strictly intended for adding comments to a schema. Its value must always be a string. Unlike the annotations `title`, `description`, and `examples`, JSON schema {@link https://json-schema.org/learn/glossary#implementation implementations} aren't allowed to attach any meaning or behavior to it whatsoever, and may even strip them at any time. Therefore, they are useful for leaving notes to future editors of a JSON schema, but should not be used to communicate to users of the schema.
1616+ */
1717+ $comment?: string;
1818+ /**
1919+ * A schema can reference another schema using the `$ref` keyword. The value of `$ref` is a URI-reference that is resolved against the schema's {@link https://json-schema.org/understanding-json-schema/structuring#base-uri Base URI}. When evaluating a `$ref`, an implementation uses the resolved identifier to retrieve the referenced schema and applies that schema to the {@link https://json-schema.org/learn/glossary#instance instance}.
2020+ *
2121+ * The `$ref` keyword may be used to create recursive schemas that refer to themselves.
2222+ */
2323+ $ref?: string;
2424+ /**
2525+ * `allOf`: (AND) Must be valid against _all_ of the {@link https://json-schema.org/learn/glossary#subschema subschemas}
2626+ *
2727+ * To validate against `allOf`, the given data must be valid against all of the given subschemas.
2828+ *
2929+ * {@link https://json-schema.org/understanding-json-schema/reference/combining#allof allOf} can not be used to "extend" a schema to add more details to it in the sense of object-oriented inheritance. {@link https://json-schema.org/learn/glossary#instance Instances} must independently be valid against "all of" the schemas in the `allOf`. See the section on {@link https://json-schema.org/understanding-json-schema/reference/object#extending Extending Closed Schemas} for more information.
3030+ */
3131+ allOf?: ReadonlyArray<JsonSchemaDraft2020_12>;
3232+ /**
3333+ * `anyOf`: (OR) Must be valid against _any_ of the subschemas
3434+ *
3535+ * To validate against `anyOf`, the given data must be valid against any (one or more) of the given subschemas.
3636+ */
3737+ anyOf?: ReadonlyArray<JsonSchemaDraft2020_12>;
3838+ /**
3939+ * The `const` keyword is used to restrict a value to a single value.
4040+ */
4141+ const?: unknown;
4242+ /**
4343+ * The `contentEncoding` keyword specifies the encoding used to store the contents, as specified in {@link https://tools.ietf.org/html/rfc2045 RFC 2054, part 6.1} and {@link https://datatracker.ietf.org/doc/html/rfc4648 RFC 4648}.
4444+ *
4545+ * The acceptable values are `quoted-printable`, `base16`, `base32`, and `base64`. If not specified, the encoding is the same as the containing JSON document.
4646+ *
4747+ * Without getting into the low-level details of each of these encodings, there are really only two options useful for modern usage:
4848+ * - If the content is encoded in the same encoding as the enclosing JSON document (which for practical purposes, is almost always UTF-8), leave `contentEncoding` unspecified, and include the content in a string as-is. This includes text-based content types, such as `text/html` or `application/xml`.
4949+ * - If the content is binary data, set `contentEncoding` to `base64` and encode the contents using {@link https://tools.ietf.org/html/rfc4648 Base64}. This would include many image types, such as `image/png` or audio types, such as `audio/mpeg`.
5050+ */
5151+ contentEncoding?: 'base16' | 'base32' | 'base64' | 'quoted-printable';
5252+ /**
5353+ * The `contentMediaType` keyword specifies the MIME type of the contents of a string, as described in {@link https://tools.ietf.org/html/rfc2046 RFC 2046}. There is a list of {@link http://www.iana.org/assignments/media-types/media-types.xhtml MIME types officially registered by the IANA}, but the set of types supported will be application and operating system dependent. Mozilla Developer Network also maintains a {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types shorter list of MIME types that are important for the web}
5454+ */
5555+ contentMediaType?: string;
5656+ /**
5757+ * The `default` keyword specifies a default value. This value is not used to fill in missing values during the validation process. Non-validation tools such as documentation generators or form generators may use this value to give hints to users about how to use a value. However, `default` is typically used to express that if a value is missing, then the value is semantically the same as if the value was present with the default value. The value of `default` should validate against the schema in which it resides, but that isn't required.
5858+ */
5959+ default?: unknown;
6060+ /**
6161+ * The `dependentRequired` {@link https://json-schema.org/learn/glossary#keyword keyword} conditionally requires that certain properties must be present if a given property is present in an object. For example, suppose we have a {@link https://json-schema.org/learn/glossary#schema schema} representing a customer. If you have their credit card number, you also want to ensure you have a billing address. If you don't have their credit card number, a billing address would not be required. We represent this dependency of one property on another using the `dependentRequired` keyword. The value of the `dependentRequired` keyword is an object. Each entry in the object maps from the name of a property, _p_, to an array of strings listing properties that are required if _p_ is present.
6262+ */
6363+ dependentRequired?: Record<string, ReadonlyArray<string>>;
6464+ /**
6565+ * The `dependentSchemas` keyword conditionally applies a {@link https://json-schema.org/learn/glossary#subschema subschema} when a given property is present. This schema is applied in the same way {@link https://json-schema.org/understanding-json-schema/reference/combining#allof allOf} applies schemas. Nothing is merged or extended. Both schemas apply independently.
6666+ */
6767+ dependentSchemas?: Record<string, JsonSchemaDraft2020_12>;
6868+ /**
6969+ * The `deprecated` keyword is a boolean that indicates that the {@link https://json-schema.org/learn/glossary#instance instance} value the keyword applies to should not be used and may be removed in the future.
7070+ */
7171+ deprecated?: boolean;
7272+ /**
7373+ * The `title` and `description` keywords must be strings. A "title" will preferably be short, whereas a "description" will provide a more lengthy explanation about the purpose of the data described by the schema.
7474+ */
7575+ description?: string;
7676+ /**
7777+ * The `if`, `then` and `else` keywords allow the application of a subschema based on the outcome of another schema, much like the `if`/`then`/`else` constructs you've probably seen in traditional programming languages.
7878+ *
7979+ * If `if` is valid, `then` must also be valid (and `else` is ignored.) If `if` is invalid, `else` must also be valid (and `then` is ignored).
8080+ *
8181+ * If `then` or `else` is not defined, `if` behaves as if they have a value of `true`.
8282+ *
8383+ * If `then` and/or `else` appear in a schema without `if`, `then` and `else` are ignored.
8484+ */
8585+ else?: JsonSchemaDraft2020_12;
8686+ /**
8787+ * The `enum` {@link https://json-schema.org/learn/glossary#keyword keyword} is used to restrict a value to a fixed set of values. It must be an array with at least one element, where each element is unique.
8888+ *
8989+ * You can use `enum` even without a type, to accept values of different types.
9090+ */
9191+ enum?: ReadonlyArray<unknown>;
9292+ /**
9393+ * The `examples` keyword is a place to provide an array of examples that validate against the schema. This isn't used for validation, but may help with explaining the effect and purpose of the schema to a reader. Each entry should validate against the schema in which it resides, but that isn't strictly required. There is no need to duplicate the `default` value in the `examples` array, since `default` will be treated as another example.
9494+ */
9595+ examples?: ReadonlyArray<unknown>;
9696+ /**
9797+ * The `format` keyword allows for basic semantic identification of certain kinds of string values that are commonly used. For example, because JSON doesn't have a "DateTime" type, dates need to be encoded as strings. `format` allows the schema author to indicate that the string value should be interpreted as a date. By default, `format` is just an annotation and does not effect validation.
9898+ *
9999+ * Optionally, validator {@link https://json-schema.org/learn/glossary#implementation implementations} can provide a configuration option to enable `format` to function as an assertion rather than just an annotation. That means that validation will fail if, for example, a value with a `date` format isn't in a form that can be parsed as a date. This can allow values to be constrained beyond what the other tools in JSON Schema, including {@link https://json-schema.org/understanding-json-schema/reference/regular_expressions Regular Expressions} can do.
100100+ *
101101+ * There is a bias toward networking-related formats in the JSON Schema specification, most likely due to its heritage in web technologies. However, custom formats may also be used, as long as the parties exchanging the JSON documents also exchange information about the custom format types. A JSON Schema validator will ignore any format type that it does not understand.
102102+ */
103103+ format?: JsonSchemaFormats;
104104+ /**
105105+ * The `if`, `then` and `else` keywords allow the application of a subschema based on the outcome of another schema, much like the `if`/`then`/`else` constructs you've probably seen in traditional programming languages.
106106+ *
107107+ * If `if` is valid, `then` must also be valid (and `else` is ignored.) If `if` is invalid, `else` must also be valid (and `then` is ignored).
108108+ *
109109+ * If `then` or `else` is not defined, `if` behaves as if they have a value of `true`.
110110+ *
111111+ * If `then` and/or `else` appear in a schema without `if`, `then` and `else` are ignored.
112112+ */
113113+ if?: JsonSchemaDraft2020_12;
114114+ /**
115115+ * `not`: (NOT) Must _not_ be valid against the given schema
116116+ *
117117+ * The `not` keyword declares that an instance validates if it doesn't validate against the given subschema.
118118+ */
119119+ not?: JsonSchemaDraft2020_12;
120120+ /**
121121+ * `oneOf`: (XOR) Must be valid against _exactly one_ of the subschemas
122122+ *
123123+ * To validate against `oneOf`, the given data must be valid against exactly one of the given subschemas.
124124+ *
125125+ * Careful consideration should be taken when using `oneOf` entries as the nature of it requires verification of _every_ sub-schema which can lead to increased processing times. Prefer `anyOf` where possible.
126126+ */
127127+ oneOf?: ReadonlyArray<JsonSchemaDraft2020_12>;
128128+ /**
129129+ * The boolean keywords `readOnly` and `writeOnly` are typically used in an API context. `readOnly` indicates that a value should not be modified. It could be used to indicate that a `PUT` request that changes a value would result in a `400 Bad Request` response. `writeOnly` indicates that a value may be set, but will remain hidden. In could be used to indicate you can set a value with a `PUT` request, but it would not be included when retrieving that record with a `GET` request.
130130+ */
131131+ readOnly?: boolean;
132132+ /**
133133+ * The `if`, `then` and `else` keywords allow the application of a subschema based on the outcome of another schema, much like the `if`/`then`/`else` constructs you've probably seen in traditional programming languages.
134134+ *
135135+ * If `if` is valid, `then` must also be valid (and `else` is ignored.) If `if` is invalid, `else` must also be valid (and `then` is ignored).
136136+ *
137137+ * If `then` or `else` is not defined, `if` behaves as if they have a value of `true`.
138138+ *
139139+ * If `then` and/or `else` appear in a schema without `if`, `then` and `else` are ignored.
140140+ */
141141+ then?: JsonSchemaDraft2020_12;
142142+ /**
143143+ * The `title` and `description` keywords must be strings. A "title" will preferably be short, whereas a "description" will provide a more lengthy explanation about the purpose of the data described by the schema.
144144+ */
145145+ title?: string;
146146+ /**
147147+ * If it is an array, it must be an array of strings, where each string is the name of one of the basic types, and each element is unique. In this case, the JSON snippet is valid if it matches any of the given types.
148148+ */
149149+ type?: JsonSchemaTypes | ReadonlyArray<JsonSchemaTypes>;
150150+ /**
151151+ * The boolean keywords `readOnly` and `writeOnly` are typically used in an API context. `readOnly` indicates that a value should not be modified. It could be used to indicate that a `PUT` request that changes a value would result in a `400 Bad Request` response. `writeOnly` indicates that a value may be set, but will remain hidden. In could be used to indicate you can set a value with a `PUT` request, but it would not be included when retrieving that record with a `GET` request.
152152+ */
153153+ writeOnly?: boolean;
154154+}
155155+156156+interface ArrayKeywords {
157157+ /**
158158+ * While the `items` schema must be valid for every item in the array, the `contains` schema only needs to validate against one or more items in the array.
159159+ */
160160+ contains?: JsonSchemaDraft2020_12;
161161+ /**
162162+ * List validation is useful for arrays of arbitrary length where each item matches the same schema. For this kind of array, set the `items` {@link https://json-schema.org/learn/glossary#keyword keyword} to a single schema that will be used to validate all of the items in the array.
163163+ *
164164+ * The `items` keyword can be used to control whether it's valid to have additional items in a tuple beyond what is defined in `prefixItems`. The value of the `items` keyword is a schema that all additional items must pass in order for the keyword to validate.
165165+ *
166166+ * Note that `items` doesn't "see inside" any {@link https://json-schema.org/learn/glossary#instance instances} of `allOf`, `anyOf`, or `oneOf` in the same {@link https://json-schema.org/learn/glossary#subschema subschema}.
167167+ */
168168+ items?: JsonSchemaDraft2020_12 | false;
169169+ /**
170170+ * `minContains` and `maxContains` can be used with `contains` to further specify how many times a schema matches a `contains` constraint. These keywords can be any non-negative number including zero.
171171+ */
172172+ maxContains?: number;
173173+ /**
174174+ * The length of the array can be specified using the `minItems` and `maxItems` keywords. The value of each keyword must be a non-negative number. These keywords work whether doing {@link https://json-schema.org/understanding-json-schema/reference/array#items list validation} or {@link https://json-schema.org/understanding-json-schema/reference/array#tupleValidation tuple-validation}.
175175+ */
176176+ maxItems?: number;
177177+ /**
178178+ * `minContains` and `maxContains` can be used with `contains` to further specify how many times a schema matches a `contains` constraint. These keywords can be any non-negative number including zero.
179179+ */
180180+ minContains?: number;
181181+ /**
182182+ * The length of the array can be specified using the `minItems` and `maxItems` keywords. The value of each keyword must be a non-negative number. These keywords work whether doing {@link https://json-schema.org/understanding-json-schema/reference/array#items list validation} or {@link https://json-schema.org/understanding-json-schema/reference/array#tupleValidation tuple-validation}.
183183+ */
184184+ minItems?: number;
185185+ /**
186186+ * `prefixItems` is an array, where each item is a schema that corresponds to each index of the document's array. That is, an array where the first element validates the first element of the input array, the second element validates the second element of the input array, etc.
187187+ */
188188+ prefixItems?: ReadonlyArray<JsonSchemaDraft2020_12>;
189189+ /**
190190+ * The `unevaluatedItems` keyword is useful mainly when you want to add or disallow extra items to an array.
191191+ *
192192+ * `unevaluatedItems` applies to any values not evaluated by an `items`, `prefixItems`, or `contains` keyword. Just as `unevaluatedProperties` affects only properties in an object, `unevaluatedItems` affects only items in an array.
193193+ *
194194+ * Watch out! The word "unevaluated" _does not mean_ "not evaluated by `items`, `prefixItems`, or `contains`." "Unevaluated" means "not successfully evaluated", or "does not evaluate to true".
195195+ *
196196+ * Like with `items`, if you set `unevaluatedItems` to false, you can disallow extra items in the array.
197197+ */
198198+ unevaluatedItems?: JsonSchemaDraft2020_12 | false;
199199+ /**
200200+ * A schema can ensure that each of the items in an array is unique. Simply set the `uniqueItems` keyword to `true`.
201201+ */
202202+ uniqueItems?: boolean;
203203+}
204204+205205+interface NumberKeywords {
206206+ /**
207207+ * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range).
208208+ *
209209+ * If _x_ is the value being validated, the following must hold true:
210210+ *
211211+ * ```
212212+ * x ≥ minimum
213213+ * x > exclusiveMinimum
214214+ * x ≤ maximum
215215+ * x < exclusiveMaximum
216216+ * ```
217217+ *
218218+ * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so.
219219+ */
220220+ exclusiveMaximum?: number;
221221+ /**
222222+ * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range).
223223+ *
224224+ * If _x_ is the value being validated, the following must hold true:
225225+ *
226226+ * ```
227227+ * x ≥ minimum
228228+ * x > exclusiveMinimum
229229+ * x ≤ maximum
230230+ * x < exclusiveMaximum
231231+ * ```
232232+ *
233233+ * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so.
234234+ */
235235+ exclusiveMinimum?: number;
236236+ /**
237237+ * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range).
238238+ *
239239+ * If _x_ is the value being validated, the following must hold true:
240240+ *
241241+ * ```
242242+ * x ≥ minimum
243243+ * x > exclusiveMinimum
244244+ * x ≤ maximum
245245+ * x < exclusiveMaximum
246246+ * ```
247247+ *
248248+ * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so.
249249+ */
250250+ maximum?: number;
251251+ /**
252252+ * Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range).
253253+ *
254254+ * If _x_ is the value being validated, the following must hold true:
255255+ *
256256+ * ```
257257+ * x ≥ minimum
258258+ * x > exclusiveMinimum
259259+ * x ≤ maximum
260260+ * x < exclusiveMaximum
261261+ * ```
262262+ *
263263+ * While you can specify both of `minimum` and `exclusiveMinimum` or both of `maximum` and `exclusiveMaximum`, it doesn't really make sense to do so.
264264+ */
265265+ minimum?: number;
266266+ /**
267267+ * Numbers can be restricted to a multiple of a given number, using the `multipleOf` keyword. It may be set to any positive number. The multiple can be a floating point number.
268268+ */
269269+ multipleOf?: number;
270270+}
271271+272272+interface ObjectKeywords {
273273+ /**
274274+ * The `additionalProperties` keyword is used to control the handling of extra stuff, that is, properties whose names are not listed in the `properties` keyword or match any of the regular expressions in the `patternProperties` keyword. By default any additional properties are allowed.
275275+ *
276276+ * The value of the `additionalProperties` keyword is a schema that will be used to validate any properties in the {@link https://json-schema.org/learn/glossary#instance instance} that are not matched by `properties` or `patternProperties`. Setting the `additionalProperties` schema to `false` means no additional properties will be allowed.
277277+ *
278278+ * It's important to note that `additionalProperties` only recognizes properties declared in the same {@link https://json-schema.org/learn/glossary#subschema subschema} as itself. So, `additionalProperties` can restrict you from "extending" a schema using {@link https://json-schema.org/understanding-json-schema/reference/combining combining} keywords such as {@link https://json-schema.org/understanding-json-schema/reference/combining#allof allOf}.
279279+ */
280280+ additionalProperties?: JsonSchemaDraft2020_12 | false;
281281+ /**
282282+ * The number of properties on an object can be restricted using the `minProperties` and `maxProperties` keywords. Each of these must be a non-negative integer.
283283+ */
284284+ maxProperties?: number;
285285+ /**
286286+ * The number of properties on an object can be restricted using the `minProperties` and `maxProperties` keywords. Each of these must be a non-negative integer.
287287+ */
288288+ minProperties?: number;
289289+ /**
290290+ * Sometimes you want to say that, given a particular kind of property name, the value should match a particular schema. That's where `patternProperties` comes in: it maps regular expressions to schemas. If a property name matches the given regular expression, the property value must validate against the corresponding schema.
291291+ */
292292+ patternProperties?: Record<string, JsonSchemaDraft2020_12>;
293293+ /**
294294+ * The properties (key-value pairs) on an object are defined using the `properties` {@link https://json-schema.org/learn/glossary#keyword keyword}. The value of `properties` is an object, where each key is the name of a property and each value is a {@link https://json-schema.org/learn/glossary#schema schema} used to validate that property. Any property that doesn't match any of the property names in the `properties` keyword is ignored by this keyword.
295295+ */
296296+ properties?: Record<string, JsonSchemaDraft2020_12 | true>;
297297+ /**
298298+ * The names of properties can be validated against a schema, irrespective of their values. This can be useful if you don't want to enforce specific properties, but you want to make sure that the names of those properties follow a specific convention. You might, for example, want to enforce that all names are valid ASCII tokens so they can be used as attributes in a particular programming language.
299299+ *
300300+ * Since object keys must always be strings anyway, it is implied that the schema given to `propertyNames` is always at least:
301301+ *
302302+ * ```json
303303+ * { "type": "string" }
304304+ * ```
305305+ */
306306+ propertyNames?: JsonSchemaDraft2020_12;
307307+ /**
308308+ * By default, the properties defined by the `properties` keyword are not required. However, one can provide a list of required properties using the `required` keyword.
309309+ *
310310+ * The `required` keyword takes an array of zero or more strings. Each of these strings must be unique.
311311+ */
312312+ required?: ReadonlyArray<string>;
313313+ /**
314314+ * The `unevaluatedProperties` keyword is similar to `additionalProperties` except that it can recognize properties declared in subschemas. So, the example from the previous section can be rewritten without the need to redeclare properties.
315315+ *
316316+ * `unevaluatedProperties` works by collecting any properties that are successfully validated when processing the schemas and using those as the allowed list of properties. This allows you to do more complex things like conditionally adding properties.
317317+ */
318318+ unevaluatedProperties?: JsonSchemaDraft2020_12 | false;
319319+}
320320+321321+interface StringKeywords {
322322+ /**
323323+ * The length of a string can be constrained using the `minLength` and `maxLength` {@link https://json-schema.org/learn/glossary#keyword keywords}. For both keywords, the value must be a non-negative number.
324324+ */
325325+ maxLength?: number;
326326+ /**
327327+ * The length of a string can be constrained using the `minLength` and `maxLength` {@link https://json-schema.org/learn/glossary#keyword keywords}. For both keywords, the value must be a non-negative number.
328328+ */
329329+ minLength?: number;
330330+ /**
331331+ * The `pattern` keyword is used to restrict a string to a particular regular expression. The regular expression syntax is the one defined in JavaScript ({@link https://www.ecma-international.org/publications-and-standards/standards/ecma-262/ ECMA 262} specifically) with Unicode support. See {@link https://json-schema.org/understanding-json-schema/reference/regular_expressions Regular Expressions} for more information.
332332+ */
333333+ pattern?: string;
334334+}
335335+336336+type JsonSchemaFormats =
337337+ | 'date'
338338+ | 'date-time'
339339+ | 'duration'
340340+ | 'email'
341341+ | 'hostname'
342342+ | 'idn-email'
343343+ | 'idn-hostname'
344344+ | 'ipv4'
345345+ | 'ipv6'
346346+ | 'iri'
347347+ | 'iri-reference'
348348+ | 'json-pointer'
349349+ | 'regex'
350350+ | 'relative-json-pointer'
351351+ | 'time'
352352+ | 'uri'
353353+ | 'uri-reference'
354354+ | 'uri-template'
355355+ | 'uuid'
356356+ // eslint-disable-next-line @typescript-eslint/ban-types
357357+ | (string & {});
358358+359359+type JsonSchemaTypes =
360360+ | 'array'
361361+ | 'boolean'
362362+ | 'integer'
363363+ | 'null'
364364+ | 'number'
365365+ | 'object'
366366+ | 'string';
···11+import type {
22+ DiscriminatorObject,
33+ ExternalDocumentationObject,
44+ XMLObject,
55+} from './spec';
66+77+export interface EnumExtensions {
88+ /**
99+ * `x-enum-descriptions` are {@link https://stackoverflow.com/a/66471626 supported} by OpenAPI Generator.
1010+ */
1111+ 'x-enum-descriptions'?: ReadonlyArray<string>;
1212+ /**
1313+ * `x-enum-varnames` are {@link https://stackoverflow.com/a/66471626 supported} by OpenAPI Generator.
1414+ */
1515+ 'x-enum-varnames'?: ReadonlyArray<string>;
1616+ /**
1717+ * {@link https://github.com/RicoSuter/NSwag NSwag} generates `x-enumNames` field containing custom enum names.
1818+ */
1919+ 'x-enumNames'?: ReadonlyArray<string>;
2020+}
2121+2222+export interface OpenApiSchemaExtensions {
2323+ /**
2424+ * Adds support for polymorphism. The discriminator is an object name that is used to differentiate between other schemas which may satisfy the payload description. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism Composition and Inheritance} for more details.
2525+ */
2626+ discriminator?: DiscriminatorObject;
2727+ /**
2828+ * A free-form property to include an example of an instance for this schema. To represent examples that cannot be naturally represented in JSON or YAML, a string value can be used to contain the example with escaping where necessary.
2929+ *
3030+ * **Deprecated**: The `example` property has been deprecated in favor of the JSON Schema `examples` keyword. Use of `example` is discouraged, and later versions of this specification may remove it.
3131+ */
3232+ example?: unknown;
3333+ /**
3434+ * Additional external documentation for this schema.
3535+ */
3636+ externalDocs?: ExternalDocumentationObject;
3737+ /**
3838+ * This MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property.
3939+ */
4040+ xml?: XMLObject;
4141+}
-2
packages/openapi-ts/src/openApi/3.1/index.ts
···11-export { parseV3_1 } from './parser';
22-export type { OpenApiV3_1 } from './types/spec';
···11+import type { JsonSchemaDraft2020_12 } from './json-schema-draft-2020-12';
22+13/**
24 * This is the root object of the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-document OpenAPI document}.
35 *
46 * This object MAY be extended with {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions Specification Extensions}.
57 */
66-export interface OpenApiV3_1 {
88+export interface OpenApiV3_1_0 {
79 /**
810 * An element to hold various schemas for the document.
911 */
···16511653 *
16521654 * This object MAY be extended with {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions Specification Extensions}, though as noted, additional properties MAY omit the `x-` prefix within this object.
16531655 */
16541654-export interface SchemaObject {
16551655- /**
16561656- * Adds support for polymorphism. The discriminator is an object name that is used to differentiate between other schemas which may satisfy the payload description. See {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#composition-and-inheritance-polymorphism Composition and Inheritance} for more details.
16571657- */
16581658- discriminator?: DiscriminatorObject;
16591659- /**
16601660- * A free-form property to include an example of an instance for this schema. To represent examples that cannot be naturally represented in JSON or YAML, a string value can be used to contain the example with escaping where necessary.
16611661- *
16621662- * **Deprecated**: The `example` property has been deprecated in favor of the JSON Schema `examples` keyword. Use of `example` is discouraged, and later versions of this specification may remove it.
16631663- */
16641664- example?: unknown;
16651665- /**
16661666- * Additional external documentation for this schema.
16671667- */
16681668- externalDocs?: ExternalDocumentationObject;
16691669- /**
16701670- * This MAY be used only on properties schemas. It has no effect on root schemas. Adds additional metadata to describe the XML representation of this property.
16711671- */
16721672- xml?: XMLObject;
16731673-}
16561656+export interface SchemaObject extends JsonSchemaDraft2020_12 {}
1674165716751658/**
16761659 * Lists the required security schemes to execute this operation. The name used for each property MUST correspond to a security scheme declared in the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#componentsSecuritySchemes Security Schemes} under the {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#components-object Components Object}.
···11import { illegalStartCharactersRegExp } from '../../../utils/regexp';
2233-/**
44- * Sanitizes names of types, so they are valid TypeScript identifiers of a certain form.
55- *
66- * 1: Remove any leading characters that are illegal as starting character of a TypeScript identifier.
77- * 2: Replace illegal characters in remaining part of type name with underscore (_).
88- *
99- * Step 1 should perhaps instead also replace illegal characters with underscore, or prefix with it, like sanitizeEnumName
1010- * does. The way this is now one could perhaps end up removing all characters, if all are illegal start characters. It
1111- * would be sort of a breaking change to do so, though, previously generated code might change then.
1212- *
1313- * JavaScript identifier regexp pattern retrieved from https://developer.mozilla.org/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers
1414- */
1515-const replaceInvalidTypeScriptJavaScriptIdentifier = (name: string) =>
1616- name
1717- .replace(illegalStartCharactersRegExp, '')
1818- .replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_');
1919-203export const ensureValidTypeScriptJavaScriptIdentifier = (name: string) => {
214 illegalStartCharactersRegExp.lastIndex = 0;
2222- const startsWithIllegalCharacter = illegalStartCharactersRegExp.test(name);
2323- // avoid removing all characters in case they're all illegal
2424- const input = startsWithIllegalCharacter ? `_${name}` : name;
2525- const cleaned = replaceInvalidTypeScriptJavaScriptIdentifier(input);
2626- return cleaned;
55+ const replaced = name.replace(/[^$\u200c\u200d\p{ID_Continue}]/gu, '_');
66+ const startsWithIllegalCharacter =
77+ illegalStartCharactersRegExp.test(replaced);
88+ const valid = startsWithIllegalCharacter ? `_${replaced}` : replaced;
99+ return valid;
2710};
28112912/**
+98-5
packages/openapi-ts/src/openApi/config.ts
···11-import type { Config } from './common/interfaces/config';
11+import type { Config } from '../types/config';
22+import { camelCase } from '../utils/camelCase';
33+import { getConfig, isLegacyClient } from '../utils/config';
44+import { transformTypeKeyName } from '../utils/type';
55+import type { OperationParameter } from './common/interfaces/client';
66+import { sanitizeNamespaceIdentifier } from './common/parser/sanitize';
2733-let _config: Config;
88+export interface ParserConfig {
99+ debug?: boolean;
1010+ filterFn: {
1111+ operation: typeof operationFilterFn;
1212+ operationParameter: typeof operationParameterFilterFn;
1313+ };
1414+ nameFn: {
1515+ operation: typeof operationNameFn;
1616+ operationParameter: typeof operationParameterNameFn;
1717+ };
1818+}
41955-export const getConfig = () => _config;
2020+let _config: ParserConfig;
62177-export const setConfig = (config: Config) => {
2222+export const getParserConfig = () => _config;
2323+2424+export const setParserConfig = (config: ParserConfig) => {
825 _config = config;
99- return getConfig();
2626+ return getParserConfig();
2727+};
2828+2929+export const operationFilterFn = ({
3030+ config,
3131+ operationKey,
3232+}: {
3333+ config: Config;
3434+ operationKey: string;
3535+}): boolean => {
3636+ const regexp = config.services.filter
3737+ ? new RegExp(config.services.filter)
3838+ : undefined;
3939+ return !regexp || regexp.test(operationKey);
4040+};
4141+4242+export const operationParameterFilterFn = (
4343+ parameter: OperationParameter,
4444+): boolean => {
4545+ const config = getConfig();
4646+4747+ // legacy clients ignore the "api-version" param since we do not want to
4848+ // add it as the first/default parameter for each of the service calls
4949+ return !isLegacyClient(config) || parameter.prop !== 'api-version';
5050+};
5151+5252+/**
5353+ * Convert the input value to a correct operation (method) class name.
5454+ * This will use the operation ID - if available - and otherwise fallback
5555+ * on a generated name from the URL
5656+ */
5757+export const operationNameFn = ({
5858+ config,
5959+ method,
6060+ operationId,
6161+ path,
6262+}: {
6363+ config: Config;
6464+ method: string;
6565+ operationId: string | undefined;
6666+ path: string;
6767+}): string => {
6868+ if (config.services.operationId && operationId) {
6969+ return camelCase({
7070+ input: sanitizeNamespaceIdentifier(operationId),
7171+ });
7272+ }
7373+7474+ let urlWithoutPlaceholders = path;
7575+7676+ // legacy clients ignore the "api-version" param since we do not want to
7777+ // add it as the first/default parameter for each of the service calls
7878+ if (isLegacyClient(config)) {
7979+ urlWithoutPlaceholders = urlWithoutPlaceholders.replace(
8080+ /[^/]*?{api-version}.*?\//g,
8181+ '',
8282+ );
8383+ }
8484+8585+ urlWithoutPlaceholders = urlWithoutPlaceholders
8686+ .replace(/{(.*?)}/g, 'by-$1')
8787+ // replace slashes with hyphens for camelcase method at the end
8888+ .replace(/[/:]/g, '-');
8989+9090+ return camelCase({
9191+ input: `${method}-${urlWithoutPlaceholders}`,
9292+ });
9393+};
9494+9595+export const operationParameterNameFn = (
9696+ parameter: Omit<OperationParameter, 'name'>,
9797+): string => {
9898+ const config = getConfig();
9999+100100+ return !isLegacyClient(config)
101101+ ? parameter.prop
102102+ : transformTypeKeyName(parameter.prop);
10103};
+35-13
packages/openapi-ts/src/openApi/index.ts
···11+import { IRContext } from '../ir/context';
22+import type { Config } from '../types/config';
13import { type OpenApiV3_0_3, parseV3_0_3 } from './3.0.3';
22-import { type OpenApiV3_1, parseV3_1 } from './3.1';
44+import { type OpenApiV3_1_0, parseV3_1_0 } from './3.1.0';
35import type { Client } from './common/interfaces/client';
44-import type { Config } from './common/interfaces/config';
56import type { OpenApi } from './common/interfaces/OpenApi';
66-import { setConfig } from './config';
77+import type { ParserConfig } from './config';
88+import { setParserConfig } from './config';
79import { parse as parseV2 } from './v2';
810import { parse as parseV3 } from './v3';
911···1719 OperationParameter,
1820 OperationResponse,
1921} from './common/interfaces/client';
2020-export type { Config } from './common/interfaces/config';
2122export type { OpenApi } from './common/interfaces/OpenApi';
2223export { isOperationParameterRequired } from './common/parser/operation';
2324export {
···3637 */
3738export function parse({
3839 openApi,
3939- config,
4040+ parserConfig,
4041}: {
4141- config: Config;
4242 openApi: OpenApi;
4343+ parserConfig: ParserConfig;
4344}): Client {
4444- setConfig(config);
4545+ setParserConfig(parserConfig);
45464647 if ('openapi' in openApi) {
4748 return parseV3(openApi);
···5657 );
5758}
58595959-export const parseExperimental = ({ spec }: { spec: unknown }) => {
6060- const s = spec as OpenApiV3_0_3 | OpenApiV3_1;
6060+// TODO: parser - add JSDoc comment
6161+export const parseExperimental = ({
6262+ config,
6363+ parserConfig,
6464+ spec,
6565+}: {
6666+ config: Config;
6767+ parserConfig: ParserConfig;
6868+ spec: unknown;
6969+}) => {
7070+ const context = new IRContext({
7171+ config,
7272+ parserConfig,
7373+ spec: spec as OpenApiV3_0_3 | OpenApiV3_1_0,
7474+ });
61756262- switch (s.openapi) {
7676+ switch (context.spec.openapi) {
6377 case '3.0.3':
6464- return parseV3_0_3(s);
7878+ parseV3_0_3(context as IRContext<OpenApiV3_0_3>);
7979+ break;
6580 case '3.1.0':
6666- return parseV3_1(s);
8181+ parseV3_1_0(context as IRContext<OpenApiV3_1_0>);
8282+ break;
6783 default:
6868- throw new Error('Unsupported OpenAPI specification');
8484+ // TODO: parser - uncomment after removing legacy parser.
8585+ // For now, we fall back to legacy parser if spec version
8686+ // is not supported
8787+ break;
8888+ // throw new Error('Unsupported OpenAPI specification');
6989 }
9090+9191+ return context;
7092};
···11-import type { PluginHandler } from '../types';
11+import type { PluginHandler, PluginHandlerExperimental } from '../types';
2233interface Config {
44 /**
···14141515export interface PluginConfig extends Config {
1616 handler: PluginHandler<Config>;
1717+ handler_experimental?: PluginHandlerExperimental<Config>;
1718}
18191920export interface UserConfig extends Omit<Config, 'output'> {}
+6-2
packages/openapi-ts/src/types/config.ts
···11+import type { IROperationObject } from '../ir/ir';
12import type { OpenApiV2Schema, OpenApiV3Schema } from '../openApi';
23import type { ClientPlugins, UserPlugins } from '../plugins/';
34import type { Operation } from '../types/client';
···175176 */
176177 include?: string;
177178 /**
178178- * Customise the name of methods within the service. By default, {@link Operation.name} is used.
179179+ * Customise the name of methods within the service. By default, {@link IROperationObject.id} or {@link Operation.name} is used.
179180 */
180180- methodNameBuilder?: (operation: Operation) => string;
181181+ methodNameBuilder?: (
182182+ operation: IROperationObject | Operation,
183183+ ) => string;
181184 /**
182185 * Customize the generated service class names. The name variable is
183186 * obtained from your OpenAPI specification tags.
···186189 * @default '{{name}}Service'
187190 */
188191 name?: string;
192192+ // TODO: parser - rename operationId option to something like inferId?: boolean
189193 /**
190194 * Use operation ID to generate operation names?
191195 * @default true
+1-1
packages/openapi-ts/src/types/utils.ts
···11-import type { TypeScriptFile } from '../compiler';
11+import type { TypeScriptFile } from '../generate/files';
2233type ExtractFromArray<T, Discriminator> = T extends Discriminator
44 ? Required<T>
···11-import type { Operation, OperationParameter } from '../openApi';
22-import { sanitizeNamespaceIdentifier } from '../openApi';
33-import { camelCase } from './camelCase';
44-import { getConfig, isLegacyClient } from './config';
55-import { transformTypeKeyName } from './type';
66-77-export const operationFilterFn = (operationKey: string): boolean => {
88- const config = getConfig();
99- const regexp = config.services.filter
1010- ? new RegExp(config.services.filter)
1111- : undefined;
1212- return !regexp || regexp.test(operationKey);
1313-};
1414-1515-export const operationParameterFilterFn = (
1616- parameter: OperationParameter,
1717-): boolean => {
1818- const config = getConfig();
1919-2020- // legacy clients ignore the "api-version" param since we do not want to
2121- // add it as the first/default parameter for each of the service calls
2222- return !isLegacyClient(config) || parameter.prop !== 'api-version';
2323-};
2424-2525-/**
2626- * Convert the input value to a correct operation (method) class name.
2727- * This will use the operation ID - if available - and otherwise fallback
2828- * on a generated name from the URL
2929- */
3030-export const operationNameFn = (operation: Omit<Operation, 'name'>): string => {
3131- const config = getConfig();
3232-3333- if (config.services.operationId && operation.id) {
3434- return camelCase({
3535- input: sanitizeNamespaceIdentifier(operation.id),
3636- });
3737- }
3838-3939- let urlWithoutPlaceholders = operation.path;
4040-4141- // legacy clients ignore the "api-version" param since we do not want to
4242- // add it as the first/default parameter for each of the service calls
4343- if (isLegacyClient(config)) {
4444- urlWithoutPlaceholders = urlWithoutPlaceholders.replace(
4545- /[^/]*?{api-version}.*?\//g,
4646- '',
4747- );
4848- }
4949-5050- urlWithoutPlaceholders = urlWithoutPlaceholders
5151- .replace(/{(.*?)}/g, 'by-$1')
5252- // replace slashes with hyphens for camelcase method at the end
5353- .replace(/[/:]/g, '-');
5454-5555- return camelCase({
5656- input: `${operation.method}-${urlWithoutPlaceholders}`,
5757- });
5858-};
5959-6060-export const operationParameterNameFn = (
6161- parameter: Omit<OperationParameter, 'name'>,
6262-): string => {
6363- const config = getConfig();
6464-6565- return !isLegacyClient(config)
6666- ? parameter.prop
6767- : transformTypeKeyName(parameter.prop);
6868-};
+35
packages/openapi-ts/src/utils/ref.ts
···11+export const irRef = '#/ir/';
22+33+export const isRefOpenApiComponent = ($ref: string): boolean => {
44+ const parts = refToParts($ref);
55+ // reusable components are nested within components/<namespace>/<name>
66+ return parts.length === 3 && parts[0] === 'components';
77+};
88+99+const refToParts = ($ref: string): string[] => {
1010+ // Remove the leading `#` and split by `/` to traverse the object
1111+ const parts = $ref.replace(/^#\//, '').split('/');
1212+ return parts;
1313+};
1414+1515+export const resolveRef = <T>({
1616+ $ref,
1717+ spec,
1818+}: {
1919+ $ref: string;
2020+ spec: Record<string, any>;
2121+}): T => {
2222+ const parts = refToParts($ref);
2323+2424+ let current = spec;
2525+2626+ for (const part of parts) {
2727+ const p = part as keyof typeof current;
2828+ if (current[p] === undefined) {
2929+ throw new Error(`Reference not found: ${$ref}`);
3030+ }
3131+ current = current[p];
3232+ }
3333+3434+ return current as T;
3535+};