@typespec/openapi 0.41.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.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # TypeSpec OpenAPI Library
2
+
3
+ This package provide [TypeSpec](https://github.com/microsoft/typespec) decorators to specify some OpenAPI specific concepts.
4
+
5
+ ## Install
6
+
7
+ In your typespec project root
8
+
9
+ ```bash
10
+ npm install @typespec/openapi
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typespec
16
+ import "@typespec/openapi";
17
+
18
+ using OpenAPI;
19
+ ```
20
+
21
+ ## References
22
+
23
+ Decorators:
24
+
25
+ - [`@defaultResponse`](#defaultResponse)
26
+ - [`@extension`](#extension)
27
+ - [`@externalDocs`](#externalDocs)
28
+ - [`@operationId`](#operationId)
29
+
30
+ ### `@defaultResponse`
31
+
32
+ **IMPORTANT This is to be used on `NON-ERROR` responses that cover all the other status codes. If you are looking to represent an error use [`@error`](https://microsoft.github.io/typespec/docs/standard-library/built-in-decorators/#error)**
33
+
34
+ Decorator that can be used on a response model to specify the `default` status code that OpenAPI allow.
35
+
36
+ ```typespec
37
+ @defaultResponse
38
+ model MyNonErrorResponse {}
39
+
40
+ op foo(): MyNonErrorResponse;
41
+ ```
42
+
43
+ ### `@extension`
44
+
45
+ This decorator lets you specify custom key(starting with `x-`) value pair that will be added to the OpenAPI document.
46
+ [OpenAPI reference on extensions](https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.3.md#specificationExtensions)
47
+
48
+ Arguments:
49
+
50
+ | Name | foo | Description |
51
+ | ------- | ------------ | ---------------------------------------------------------------- |
52
+ | `key` | **Required** | Extension key. **MUST** start with `x-` |
53
+ | `value` | **Required** | Value of the extension. Can be an primitive, a tuple or a model. |
54
+
55
+ ```typespec
56
+ @extension("x-custom", "MyCustomValue")
57
+ model Foo {}
58
+
59
+ // Value can be an model.
60
+ @extension(
61
+ "x-custom",
62
+ {
63
+ some: "value",
64
+ }
65
+ )
66
+ model Foo {}
67
+ ```
68
+
69
+ ### `@externalDocs`
70
+
71
+ Decorator that can be used to provide the `externalDocs` property on OpenAPI elements.
72
+ [OpenAPI spec for externalDocs](https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/versions/3.0.3.md#externalDocumentationObject)
73
+
74
+ Arguments:
75
+
76
+ | Name | foo | Description |
77
+ | ------------- | ------------ | -------------------------------- |
78
+ | `url` | **Required** | Url for the external docs |
79
+ | `description` | **Optional** | Description of the documentation |
80
+
81
+ ```typespec
82
+ @externalDocs("https://example.com", "More info there")
83
+ model Foo {}
84
+ ```
85
+
86
+ ### `@operationId`
87
+
88
+ Decorator that can be used on an operation to specify the `operationId` field in OpenAPI. If this is not provided the `operationId` will be the typespec operation name.
89
+
90
+ Arguments:
91
+
92
+ | Name | foo | Description |
93
+ | ------ | ------------ | ------------------- |
94
+ | `opId` | **Required** | Id of the operation |
95
+
96
+ ```typespec
97
+ @operationId("custom_Foo")
98
+ op foo(): string;
99
+ ```
100
+
101
+ ## See also
102
+
103
+ - [TypeSpec Getting Started](https://github.com/microsoft/typespec#getting-started)
104
+ - [TypeSpec Website](https://microsoft.github.io/typespec)
105
+ - [TypeSpec for the OpenAPI Developer](https://github.com/microsoft/typespec/blob/main/docs/typespec-for-openapi-dev.md)
@@ -0,0 +1,37 @@
1
+ import { DecoratorContext, Model, Operation, Program, Type, TypeSpecValue } from "@typespec/compiler";
2
+ import { ExtensionKey } from "./types.js";
3
+ export declare const namespace = "OpenAPI";
4
+ /**
5
+ * Set a specific operation ID.
6
+ * @param context Decorator Context
7
+ * @param entity Decorator target
8
+ * @param opId Operation ID.
9
+ */
10
+ export declare function $operationId(context: DecoratorContext, entity: Operation, opId: string): void;
11
+ /**
12
+ * @returns operationId set via the @operationId decorator or `undefined`
13
+ */
14
+ export declare function getOperationId(program: Program, entity: Operation): string | undefined;
15
+ export declare function $extension(context: DecoratorContext, entity: Type, extensionName: string, value: TypeSpecValue): void;
16
+ export declare function setExtension(program: Program, entity: Type, extensionName: ExtensionKey, data: unknown): void;
17
+ export declare function getExtensions(program: Program, entity: Type): ReadonlyMap<ExtensionKey, any>;
18
+ export declare function $defaultResponse(context: DecoratorContext, entity: Model): void;
19
+ /**
20
+ * Check if the given model has been mark as a default response.
21
+ * @param program TypeSpec Program
22
+ * @param entity Model to check.
23
+ * @returns boolean.
24
+ */
25
+ export declare function isDefaultResponse(program: Program, entity: Type): boolean;
26
+ export interface ExternalDocs {
27
+ url: string;
28
+ description?: string;
29
+ }
30
+ /**
31
+ * Allows referencing an external resource for extended documentation.
32
+ * @param url The URL for the target documentation. Value MUST be in the format of a URL.
33
+ * @param @optional description A short description of the target documentation.
34
+ */
35
+ export declare function $externalDocs(context: DecoratorContext, target: Type, url: string, description?: string): void;
36
+ export declare function getExternalDocs(program: Program, entity: Type): ExternalDocs | undefined;
37
+ //# sourceMappingURL=decorators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,KAAK,EACL,SAAS,EACT,OAAO,EACP,IAAI,EAEJ,aAAa,EACd,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,SAAS,YAAY,CAAC;AAGnC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,QAEtF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAEtF;AAID,wBAAgB,UAAU,CACxB,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,IAAI,EACZ,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,aAAa,QAerB;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,IAAI,EACZ,aAAa,EAAE,YAAY,EAC3B,IAAI,EAAE,OAAO,QAOd;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,WAAW,CAAC,YAAY,EAAE,GAAG,CAAC,CAE5F;AAYD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,QAGxE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,OAAO,CAEzE;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,IAAI,EACZ,GAAG,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,MAAM,QAOrB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,YAAY,GAAG,SAAS,CAExF"}
@@ -0,0 +1,85 @@
1
+ import { typespecTypeToJson, } from "@typespec/compiler";
2
+ import { setStatusCode } from "@typespec/http";
3
+ import { createStateSymbol, reportDiagnostic } from "./lib.js";
4
+ export const namespace = "OpenAPI";
5
+ const operationIdsKey = createStateSymbol("operationIds");
6
+ /**
7
+ * Set a specific operation ID.
8
+ * @param context Decorator Context
9
+ * @param entity Decorator target
10
+ * @param opId Operation ID.
11
+ */
12
+ export function $operationId(context, entity, opId) {
13
+ context.program.stateMap(operationIdsKey).set(entity, opId);
14
+ }
15
+ /**
16
+ * @returns operationId set via the @operationId decorator or `undefined`
17
+ */
18
+ export function getOperationId(program, entity) {
19
+ return program.stateMap(operationIdsKey).get(entity);
20
+ }
21
+ const openApiExtensionKey = createStateSymbol("openApiExtension");
22
+ export function $extension(context, entity, extensionName, value) {
23
+ if (!isOpenAPIExtensionKey(extensionName)) {
24
+ reportDiagnostic(context.program, {
25
+ code: "invalid-extension-key",
26
+ format: { value: extensionName },
27
+ target: entity,
28
+ });
29
+ }
30
+ const [data, diagnostics] = typespecTypeToJson(value, entity);
31
+ if (diagnostics.length > 0) {
32
+ context.program.reportDiagnostics(diagnostics);
33
+ }
34
+ setExtension(context.program, entity, extensionName, data);
35
+ }
36
+ export function setExtension(program, entity, extensionName, data) {
37
+ var _a;
38
+ const openApiExtensions = program.stateMap(openApiExtensionKey);
39
+ const typeExtensions = (_a = openApiExtensions.get(entity)) !== null && _a !== void 0 ? _a : new Map();
40
+ typeExtensions.set(extensionName, data);
41
+ openApiExtensions.set(entity, typeExtensions);
42
+ }
43
+ export function getExtensions(program, entity) {
44
+ var _a;
45
+ return (_a = program.stateMap(openApiExtensionKey).get(entity)) !== null && _a !== void 0 ? _a : new Map();
46
+ }
47
+ function isOpenAPIExtensionKey(key) {
48
+ return key.startsWith("x-");
49
+ }
50
+ /**
51
+ * The @defaultResponse decorator can be applied to a model. When that model is used
52
+ * as the return type of an operation, this return type will be the default response.
53
+ *
54
+ */
55
+ const defaultResponseKey = createStateSymbol("defaultResponse");
56
+ export function $defaultResponse(context, entity) {
57
+ setStatusCode(context.program, entity, ["*"]);
58
+ context.program.stateSet(defaultResponseKey).add(entity);
59
+ }
60
+ /**
61
+ * Check if the given model has been mark as a default response.
62
+ * @param program TypeSpec Program
63
+ * @param entity Model to check.
64
+ * @returns boolean.
65
+ */
66
+ export function isDefaultResponse(program, entity) {
67
+ return program.stateSet(defaultResponseKey).has(entity);
68
+ }
69
+ const externalDocsKey = createStateSymbol("externalDocs");
70
+ /**
71
+ * Allows referencing an external resource for extended documentation.
72
+ * @param url The URL for the target documentation. Value MUST be in the format of a URL.
73
+ * @param @optional description A short description of the target documentation.
74
+ */
75
+ export function $externalDocs(context, target, url, description) {
76
+ const doc = { url };
77
+ if (description) {
78
+ doc.description = description;
79
+ }
80
+ context.program.stateMap(externalDocsKey).set(target, doc);
81
+ }
82
+ export function getExternalDocs(program, entity) {
83
+ return program.stateMap(externalDocsKey).get(entity);
84
+ }
85
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.js","sourceRoot":"","sources":["../../src/decorators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { ModelProperty, Operation, Program, Type, TypeNameOptions } from "@typespec/compiler";
2
+ /**
3
+ * Determines whether a type will be inlined in OpenAPI rather than defined
4
+ * as a schema and referenced.
5
+ *
6
+ * All anonymous types (anonymous models, arrays, tuples, etc.) are inlined.
7
+ *
8
+ * Template instantiations are inlined unless they have a friendly name.
9
+ *
10
+ * A friendly name can be provided by the user using `@friendlyName`
11
+ * decorator, or chosen by default in simple cases.
12
+ */
13
+ export declare function shouldInline(program: Program, type: Type): boolean;
14
+ /**
15
+ * Gets the name of a type to be used in OpenAPI.
16
+ *
17
+ * For inlined types: this is the TypeSpec-native name written to `x-typespec-name`.
18
+ *
19
+ * For non-inlined types: this is either the friendly name or the TypeSpec-native name.
20
+ *
21
+ * TypeSpec-native names are shortened to exclude root `TypeSpec` namespace and service
22
+ * namespace using the provided `TypeNameOptions`.
23
+ */
24
+ export declare function getOpenAPITypeName(program: Program, type: Type, options: TypeNameOptions, existing?: Record<string, any>): string;
25
+ export declare function checkDuplicateTypeName(program: Program, type: Type, name: string, existing: Record<string, unknown> | undefined): void;
26
+ /**
27
+ * Gets the key that is used to define a parameter in OpenAPI.
28
+ */
29
+ export declare function getParameterKey(program: Program, property: ModelProperty, newParam: unknown, existingParams: Record<string, unknown>, options: TypeNameOptions): string;
30
+ /**
31
+ * Resolve the OpenAPI operation ID for the given operation using the following logic:
32
+ * - If @operationId was specified use that value
33
+ * - If operation is defined at the root or under the service namespace return `<operation.name>`
34
+ * - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
35
+ *
36
+ * @param program TypeSpec Program
37
+ * @param operation Operation
38
+ * @returns Operation ID in this format `<name>` or `<group>_<name>`
39
+ */
40
+ export declare function resolveOperationId(program: Program, operation: Operation): string;
41
+ /**
42
+ * Determines if a property is read-only, which is defined as being
43
+ * decorated `@visibility("read")`.
44
+ *
45
+ * If there is more than 1 `@visibility` argument, then the property is not
46
+ * read-only. For example, `@visibility("read", "update")` does not
47
+ * designate a read-only property.
48
+ */
49
+ export declare function isReadonlyProperty(program: Program, property: ModelProperty): boolean;
50
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,aAAa,EACb,SAAS,EACT,OAAO,EACP,IAAI,EACJ,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAI5B;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAelE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,eAAe,EACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC7B,MAAM,CAKR;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,QAW9C;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,aAAa,EACvB,QAAQ,EAAE,OAAO,EACjB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,OAAO,EAAE,eAAe,GACvB,MAAM,CAoBR;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,UAmBxE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,WAK3E"}
@@ -0,0 +1,119 @@
1
+ import { getFriendlyName, getTypeName, getVisibility, isGlobalNamespace, isService, isTemplateInstance, } from "@typespec/compiler";
2
+ import { getOperationId } from "./decorators.js";
3
+ import { reportDiagnostic } from "./lib.js";
4
+ /**
5
+ * Determines whether a type will be inlined in OpenAPI rather than defined
6
+ * as a schema and referenced.
7
+ *
8
+ * All anonymous types (anonymous models, arrays, tuples, etc.) are inlined.
9
+ *
10
+ * Template instantiations are inlined unless they have a friendly name.
11
+ *
12
+ * A friendly name can be provided by the user using `@friendlyName`
13
+ * decorator, or chosen by default in simple cases.
14
+ */
15
+ export function shouldInline(program, type) {
16
+ if (getFriendlyName(program, type)) {
17
+ return false;
18
+ }
19
+ switch (type.kind) {
20
+ case "Model":
21
+ return !type.name || isTemplateInstance(type);
22
+ case "Scalar":
23
+ return program.checker.isStdType(type) || isTemplateInstance(type);
24
+ case "Enum":
25
+ case "Union":
26
+ return !type.name;
27
+ default:
28
+ return true;
29
+ }
30
+ }
31
+ /**
32
+ * Gets the name of a type to be used in OpenAPI.
33
+ *
34
+ * For inlined types: this is the TypeSpec-native name written to `x-typespec-name`.
35
+ *
36
+ * For non-inlined types: this is either the friendly name or the TypeSpec-native name.
37
+ *
38
+ * TypeSpec-native names are shortened to exclude root `TypeSpec` namespace and service
39
+ * namespace using the provided `TypeNameOptions`.
40
+ */
41
+ export function getOpenAPITypeName(program, type, options, existing) {
42
+ var _a;
43
+ const name = (_a = getFriendlyName(program, type)) !== null && _a !== void 0 ? _a : getTypeName(type, options);
44
+ checkDuplicateTypeName(program, type, name, existing);
45
+ return name;
46
+ }
47
+ export function checkDuplicateTypeName(program, type, name, existing) {
48
+ if (existing && existing[name]) {
49
+ reportDiagnostic(program, {
50
+ code: "duplicate-type-name",
51
+ format: {
52
+ value: name,
53
+ },
54
+ target: type,
55
+ });
56
+ }
57
+ }
58
+ /**
59
+ * Gets the key that is used to define a parameter in OpenAPI.
60
+ */
61
+ export function getParameterKey(program, property, newParam, existingParams, options) {
62
+ const parent = property.model;
63
+ let key = getOpenAPITypeName(program, parent, options);
64
+ if (parent.properties.size > 1) {
65
+ key += `.${property.name}`;
66
+ }
67
+ if (existingParams[key]) {
68
+ reportDiagnostic(program, {
69
+ code: "duplicate-type-name",
70
+ messageId: "parameter",
71
+ format: {
72
+ value: key,
73
+ },
74
+ target: property,
75
+ });
76
+ }
77
+ return key;
78
+ }
79
+ /**
80
+ * Resolve the OpenAPI operation ID for the given operation using the following logic:
81
+ * - If @operationId was specified use that value
82
+ * - If operation is defined at the root or under the service namespace return `<operation.name>`
83
+ * - Otherwise(operation is under another namespace or interface) return `<namespace/interface.name>_<operation.name>`
84
+ *
85
+ * @param program TypeSpec Program
86
+ * @param operation Operation
87
+ * @returns Operation ID in this format `<name>` or `<group>_<name>`
88
+ */
89
+ export function resolveOperationId(program, operation) {
90
+ const explicitOperationId = getOperationId(program, operation);
91
+ if (explicitOperationId) {
92
+ return explicitOperationId;
93
+ }
94
+ if (operation.interface) {
95
+ return `${operation.interface.name}_${operation.name}`;
96
+ }
97
+ const namespace = operation.namespace;
98
+ if (namespace === undefined ||
99
+ isGlobalNamespace(program, namespace) ||
100
+ isService(program, namespace)) {
101
+ return operation.name;
102
+ }
103
+ return `${namespace.name}_${operation.name}`;
104
+ }
105
+ /**
106
+ * Determines if a property is read-only, which is defined as being
107
+ * decorated `@visibility("read")`.
108
+ *
109
+ * If there is more than 1 `@visibility` argument, then the property is not
110
+ * read-only. For example, `@visibility("read", "update")` does not
111
+ * designate a read-only property.
112
+ */
113
+ export function isReadonlyProperty(program, property) {
114
+ const visibility = getVisibility(program, property);
115
+ // note: multiple visibilities that include read are not handled using
116
+ // readonly: true, but using separate schemas.
117
+ return (visibility === null || visibility === void 0 ? void 0 : visibility.length) === 1 && visibility[0] === "read";
118
+ }
119
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./decorators.js";
2
+ export * from "./helpers.js";
3
+ export * from "./types.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./decorators.js";
2
+ export * from "./helpers.js";
3
+ export * from "./types.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,36 @@
1
+ export declare const libDef: {
2
+ readonly name: "@typespec/openapi";
3
+ readonly diagnostics: {
4
+ readonly "invalid-extension-key": {
5
+ readonly severity: "error";
6
+ readonly messages: {
7
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
8
+ };
9
+ };
10
+ readonly "duplicate-type-name": {
11
+ readonly severity: "error";
12
+ readonly messages: {
13
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
14
+ readonly parameter: import("@typespec/compiler").CallableMessage<["value"]>;
15
+ };
16
+ };
17
+ };
18
+ };
19
+ export declare const reportDiagnostic: <C extends "invalid-extension-key" | "duplicate-type-name", M extends keyof {
20
+ "invalid-extension-key": {
21
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
22
+ };
23
+ "duplicate-type-name": {
24
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
25
+ readonly parameter: import("@typespec/compiler").CallableMessage<["value"]>;
26
+ };
27
+ }[C]>(program: import("@typespec/compiler").Program, diag: import("@typespec/compiler").DiagnosticReport<{
28
+ "invalid-extension-key": {
29
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
30
+ };
31
+ "duplicate-type-name": {
32
+ readonly default: import("@typespec/compiler").CallableMessage<["value"]>;
33
+ readonly parameter: import("@typespec/compiler").CallableMessage<["value"]>;
34
+ };
35
+ }, C, M>) => void, createStateSymbol: (name: string) => symbol;
36
+ //# sourceMappingURL=lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;CAiBT,CAAC;AACX,eAAO,MAAQ,gBAAgB;;;;;;;;;;;;;;;;mBAAE,iBAAiB,0BAAkC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
2
+ export const libDef = {
3
+ name: "@typespec/openapi",
4
+ diagnostics: {
5
+ "invalid-extension-key": {
6
+ severity: "error",
7
+ messages: {
8
+ default: paramMessage `OpenAPI extension must start with 'x-' but was '${"value"}'`,
9
+ },
10
+ },
11
+ "duplicate-type-name": {
12
+ severity: "error",
13
+ messages: {
14
+ default: paramMessage `Duplicate type name: '${"value"}'. Check @friendlyName decorators and overlap with types in TypeSpec or service namespace.`,
15
+ parameter: paramMessage `Duplicate parameter key: '${"value"}'. Check @friendlyName decorators and overlap with types in TypeSpec or service namespace.`,
16
+ },
17
+ },
18
+ },
19
+ };
20
+ export const { reportDiagnostic, createStateSymbol } = createTypeSpecLibrary(libDef);
21
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../../src/lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEzE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE;QACX,uBAAuB,EAAE;YACvB,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE;gBACR,OAAO,EAAE,YAAY,CAAA,mDAAmD,OAAO,GAAG;aACnF;SACF;QACD,qBAAqB,EAAE;YACrB,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE;gBACR,OAAO,EAAE,YAAY,CAAA,yBAAyB,OAAO,4FAA4F;gBACjJ,SAAS,EAAE,YAAY,CAAA,6BAA6B,OAAO,4FAA4F;aACxJ;SACF;KACF;CACO,CAAC;AACX,MAAM,CAAC,MAAM,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { TypeSpecTestLibrary } from "@typespec/compiler/testing";
2
+ export declare const OpenAPITestLibrary: TypeSpecTestLibrary;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGpF,eAAO,MAAM,kBAAkB,EAAE,mBAG/B,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { resolvePath } from "@typespec/compiler";
2
+ import { createTestLibrary } from "@typespec/compiler/testing";
3
+ import { fileURLToPath } from "url";
4
+ export const OpenAPITestLibrary = createTestLibrary({
5
+ name: "@typespec/openapi",
6
+ packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
7
+ });
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAuB,MAAM,4BAA4B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,CAAC,MAAM,kBAAkB,GAAwB,iBAAiB,CAAC;IACvE,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE,WAAW,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,cAAc,CAAC;CACzE,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export type ExtensionKey = `x-${string}`;
2
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,KAAK,MAAM,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ using TypeSpec.Reflection;
2
+
3
+ namespace OpenAPI;
4
+
5
+ /**
6
+ * Specify the OpenAPI `operationId` property for this operation.
7
+ *
8
+ * @param operationId Operation id value.
9
+ *
10
+ * @example
11
+ *
12
+ * ```typespec
13
+ * @operationId("download")
14
+ * op read(): string;
15
+ * ```
16
+ */
17
+ extern dec operationId(target: Operation, operationId: string);
18
+
19
+ /**
20
+ * Attach some custom data to the OpenAPI element generated from this type.
21
+ *
22
+ * @param key Extension key. Must start with `x-`
23
+ * @param value Extension value.
24
+ *
25
+ * @example
26
+ *
27
+ * ```typespec
28
+ * @extension("x-custom", "My value")
29
+ * @extension("x-pageable", {nextLink: "x-next-link"})
30
+ * op read(): string;
31
+ * ```
32
+ */
33
+ extern dec extension(target: unknown, key: string, value: unknown);
34
+
35
+ /**
36
+ * Specify that this model is to be treated as the OpenAPI `default` response.
37
+ * This differs from the compiler built-in `@error` decorator as this does not necessarily represent an error.
38
+ *
39
+ * @example
40
+ *
41
+ * ```typespec
42
+ * @defaultResponse
43
+ * model PetStoreResponse is object;
44
+ *
45
+ * op listPets(): Pet[] | PetStoreResponse;
46
+ * ```
47
+ */
48
+ extern dec defaultResponse(target: object);
49
+
50
+ /**
51
+ * Specify the OpenAPI `externalDocs` property for this type.
52
+ *
53
+ * @param url Url to the docs
54
+ * @param description Description of the docs
55
+ *
56
+ * @example
57
+ * ```typespec
58
+ * @externalDocs("https://example.com/detailed.md", "Detailed information on how to use this operation")
59
+ * op listPets(): Pet[];
60
+ * ```
61
+ */
62
+ extern dec externalDocs(target: unknown, url: string, description?: string);
package/lib/main.tsp ADDED
@@ -0,0 +1,2 @@
1
+ import "../dist/src/index.js";
2
+ import "./decorators.tsp";
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@typespec/openapi",
3
+ "version": "0.41.0",
4
+ "author": "Microsoft Corporation",
5
+ "description": "TypeSpec library providing OpenAPI concepts",
6
+ "homepage": "https://microsoft.github.io/typespec",
7
+ "readme": "https://github.com/Azure/typespec/blob/master/README.md",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Azure/typespec.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Azure/typespec/issues"
15
+ },
16
+ "keywords": [
17
+ "typespec"
18
+ ],
19
+ "type": "module",
20
+ "main": "dist/src/index.js",
21
+ "tspMain": "lib/main.tsp",
22
+ "exports": {
23
+ ".": "./dist/src/index.js",
24
+ "./testing": "./dist/src/testing/index.js"
25
+ },
26
+ "typesVersions": {
27
+ "*": {
28
+ "*": [
29
+ "./dist/src/index.d.ts"
30
+ ],
31
+ "testing": [
32
+ "./dist/src/testing/index.d.ts"
33
+ ]
34
+ }
35
+ },
36
+ "engines": {
37
+ "node": ">=16.0.0"
38
+ },
39
+ "files": [
40
+ "lib/*.tsp",
41
+ "dist/**",
42
+ "!dist/test/**"
43
+ ],
44
+ "peerDependencies": {
45
+ "@typespec/compiler": "~0.41.0",
46
+ "@typespec/http": "~0.41.0",
47
+ "@typespec/rest": "~0.41.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/mocha": "~10.0.0",
51
+ "@types/node": "~18.11.9",
52
+ "@typespec/compiler": "~0.41.0",
53
+ "@typespec/http": "~0.41.0",
54
+ "@typespec/rest": "~0.41.0",
55
+ "@typespec/eslint-config-typespec": "~0.6.0",
56
+ "@typespec/library-linter": "~0.41.0",
57
+ "@typespec/eslint-plugin": "~0.41.0",
58
+ "eslint": "^8.12.0",
59
+ "mocha": "~10.1.0",
60
+ "mocha-junit-reporter": "~2.2.0",
61
+ "mocha-multi-reporters": "~1.5.1",
62
+ "c8": "~7.12.0",
63
+ "rimraf": "~3.0.2",
64
+ "typescript": "~4.9.3"
65
+ },
66
+ "scripts": {
67
+ "clean": "rimraf ./dist ./temp",
68
+ "build": "tsc -p . && npm run lint-typespec-library",
69
+ "watch": "tsc -p . --watch",
70
+ "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit",
71
+ "test": "mocha",
72
+ "test-official": "c8 mocha --forbid-only --reporter mocha-multi-reporters",
73
+ "lint": "eslint . --ext .ts --max-warnings=0",
74
+ "lint:fix": "eslint . --fix --ext .ts"
75
+ }
76
+ }