Родитель
44ffaf7e18
Коммит
44fc030f8d
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
|
||||
changeKind: feature
|
||||
packages:
|
||||
- "@typespec/compiler"
|
||||
- "@typespec/openapi3"
|
||||
- "@typespec/xml"
|
||||
---
|
||||
|
||||
Add support for encoding numeric types as string
|
|
@ -134,3 +134,41 @@ model User {
|
|||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Numeric types ( `int64`, `decimal128`, `float64`, etc.)
|
||||
|
||||
By default numeric types are serialized as a JSON number. However for large types like `int64` or `decimal128` that cannot be represented in certain languages like JavaScript it is recommended to serialize them as string over the wire.
|
||||
|
||||
<table>
|
||||
<tr><td>TypeSpec</td><td>Example payload</td></tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
```tsp
|
||||
model User {
|
||||
id: int64; // JSON number
|
||||
|
||||
@encode(string)
|
||||
idAsString: int64; // JSON string
|
||||
|
||||
viaSalar: decimalString;
|
||||
}
|
||||
|
||||
@encode(string)
|
||||
scalar decimalString extends decimal128;
|
||||
```
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1234567890123456789012345678901234567890,
|
||||
"idAsString": "1234567890123456789012345678901234567890",
|
||||
"viaSalar": "1.3"
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -100,7 +100,7 @@ model Pet {}
|
|||
|
||||
Specify how to encode the target type.
|
||||
```typespec
|
||||
@encode(encoding: string | EnumMember, encodedAs?: Scalar)
|
||||
@encode(encodingOrEncodeAs: Scalar | valueof string | EnumMember, encodedAs?: Scalar)
|
||||
```
|
||||
|
||||
#### Target
|
||||
|
@ -110,7 +110,7 @@ Specify how to encode the target type.
|
|||
#### Parameters
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| encoding | `string \| EnumMember` | Known name of an encoding. |
|
||||
| encodingOrEncodeAs | `Scalar` \| `valueof string \| EnumMember` | Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string). |
|
||||
| encodedAs | `Scalar` | What target type is this being encoded as. Default to string. |
|
||||
|
||||
#### Examples
|
||||
|
@ -130,6 +130,15 @@ scalar myDateTime extends offsetDateTime;
|
|||
scalar myDateTime extends unixTimestamp;
|
||||
```
|
||||
|
||||
##### encode numeric type to string
|
||||
|
||||
|
||||
```tsp
|
||||
model Pet {
|
||||
@encode(string) id: int64;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `@encodedName` {#@encodedName}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type {
|
||||
DecoratorContext,
|
||||
Enum,
|
||||
EnumValue,
|
||||
Interface,
|
||||
Model,
|
||||
ModelProperty,
|
||||
|
@ -26,7 +27,7 @@ export interface OperationExample {
|
|||
/**
|
||||
* Specify how to encode the target type.
|
||||
*
|
||||
* @param encoding Known name of an encoding.
|
||||
* @param encodingOrEncodeAs Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string).
|
||||
* @param encodedAs What target type is this being encoded as. Default to string.
|
||||
* @example offsetDateTime encoded with rfc7231
|
||||
*
|
||||
|
@ -40,11 +41,18 @@ export interface OperationExample {
|
|||
* @encode("unixTimestamp", int32)
|
||||
* scalar myDateTime extends unixTimestamp;
|
||||
* ```
|
||||
* @example encode numeric type to string
|
||||
*
|
||||
* ```tsp
|
||||
* model Pet {
|
||||
* @encode(string) id: int64;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export type EncodeDecorator = (
|
||||
context: DecoratorContext,
|
||||
target: Scalar | ModelProperty,
|
||||
encoding: Type,
|
||||
encodingOrEncodeAs: Scalar | string | EnumValue,
|
||||
encodedAs?: Scalar
|
||||
) => void;
|
||||
|
||||
|
|
|
@ -477,7 +477,7 @@ enum BytesKnownEncoding {
|
|||
|
||||
/**
|
||||
* Specify how to encode the target type.
|
||||
* @param encoding Known name of an encoding.
|
||||
* @param encodingOrEncodeAs Known name of an encoding or a scalar type to encode as(Only for numeric types to encode as string).
|
||||
* @param encodedAs What target type is this being encoded as. Default to string.
|
||||
*
|
||||
* @example offsetDateTime encoded with rfc7231
|
||||
|
@ -493,10 +493,18 @@ enum BytesKnownEncoding {
|
|||
* @encode("unixTimestamp", int32)
|
||||
* scalar myDateTime extends unixTimestamp;
|
||||
* ```
|
||||
*
|
||||
* @example encode numeric type to string
|
||||
*
|
||||
* ```tsp
|
||||
* model Pet {
|
||||
* @encode(string) id: int64;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
extern dec encode(
|
||||
target: Scalar | ModelProperty,
|
||||
encoding: string | EnumMember,
|
||||
encodingOrEncodeAs: (valueof string | EnumMember) | Scalar,
|
||||
encodedAs?: Scalar
|
||||
);
|
||||
|
||||
|
|
|
@ -854,6 +854,7 @@ const diagnostics = {
|
|||
wrongType: paramMessage`Encoding '${"encoding"}' cannot be used on type '${"type"}'. Expected: ${"expected"}.`,
|
||||
wrongEncodingType: paramMessage`Encoding '${"encoding"}' on type '${"type"}' is expected to be serialized as '${"expected"}' but got '${"actual"}'.`,
|
||||
wrongNumericEncodingType: paramMessage`Encoding '${"encoding"}' on type '${"type"}' is expected to be serialized as '${"expected"}' but got '${"actual"}'. Set '@encode' 2nd parameter to be of type ${"expected"}. e.g. '@encode("${"encoding"}", int32)'`,
|
||||
firstArg: `First argument of "@encode" must be the encoding name or the string type when encoding numeric types.`,
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ import {
|
|||
getTypeName,
|
||||
ignoreDiagnostics,
|
||||
isArrayModelType,
|
||||
isValue,
|
||||
reportDeprecated,
|
||||
validateDecoratorUniqueOnNode,
|
||||
} from "../core/index.js";
|
||||
|
@ -90,6 +91,7 @@ import {
|
|||
DiagnosticTarget,
|
||||
Enum,
|
||||
EnumMember,
|
||||
EnumValue,
|
||||
Interface,
|
||||
Model,
|
||||
ModelProperty,
|
||||
|
@ -682,8 +684,13 @@ export function isSecret(program: Program, target: Type): boolean | undefined {
|
|||
export type DateTimeKnownEncoding = "rfc3339" | "rfc7231" | "unixTimestamp";
|
||||
export type DurationKnownEncoding = "ISO8601" | "seconds";
|
||||
export type BytesKnownEncoding = "base64" | "base64url";
|
||||
|
||||
export interface EncodeData {
|
||||
encoding: DateTimeKnownEncoding | DurationKnownEncoding | BytesKnownEncoding | string;
|
||||
/**
|
||||
* Known encoding key.
|
||||
* Can be undefined when `@encode(string)` is used on a numeric type. In that case it just means using the base10 decimal representation of the number.
|
||||
*/
|
||||
encoding?: DateTimeKnownEncoding | DurationKnownEncoding | BytesKnownEncoding | string;
|
||||
type: Scalar;
|
||||
}
|
||||
|
||||
|
@ -691,38 +698,48 @@ const encodeKey = createStateSymbol("encode");
|
|||
export const $encode: EncodeDecorator = (
|
||||
context: DecoratorContext,
|
||||
target: Scalar | ModelProperty,
|
||||
encoding: string | Type,
|
||||
encoding: string | EnumValue | Scalar,
|
||||
encodeAs?: Scalar
|
||||
) => {
|
||||
validateDecoratorUniqueOnNode(context, target, $encode);
|
||||
|
||||
const encodingStr = computeEncoding(encoding);
|
||||
if (encodingStr === undefined) {
|
||||
const encodeData = computeEncoding(context.program, encoding, encodeAs);
|
||||
if (encodeData === undefined) {
|
||||
return;
|
||||
}
|
||||
const encodeData: EncodeData = {
|
||||
encoding: encodingStr,
|
||||
type: encodeAs ?? context.program.checker.getStdType("string"),
|
||||
};
|
||||
const targetType = getPropertyType(target);
|
||||
validateEncodeData(context, targetType, encodeData);
|
||||
context.program.stateMap(encodeKey).set(target, encodeData);
|
||||
};
|
||||
function computeEncoding(encoding: string | Type) {
|
||||
if (typeof encoding === "string") {
|
||||
return encoding;
|
||||
}
|
||||
switch (encoding.kind) {
|
||||
case "String":
|
||||
return encoding.value;
|
||||
case "EnumMember":
|
||||
if (encoding.value && typeof encoding.value === "string") {
|
||||
return encoding.value;
|
||||
} else {
|
||||
return getTypeName(encoding);
|
||||
}
|
||||
default:
|
||||
|
||||
function computeEncoding(
|
||||
program: Program,
|
||||
encodingOrEncodeAs: string | EnumValue | Scalar,
|
||||
encodeAs: Scalar | undefined
|
||||
): EncodeData | undefined {
|
||||
const strType = program.checker.getStdType("string");
|
||||
const resolvedEncodeAs = encodeAs ?? strType;
|
||||
if (typeof encodingOrEncodeAs === "string") {
|
||||
return { encoding: encodingOrEncodeAs, type: resolvedEncodeAs };
|
||||
} else if (isValue(encodingOrEncodeAs)) {
|
||||
const member = encodingOrEncodeAs.value;
|
||||
if (member.value && typeof member.value === "string") {
|
||||
return { encoding: member.value, type: resolvedEncodeAs };
|
||||
} else {
|
||||
return { encoding: getTypeName(member), type: resolvedEncodeAs };
|
||||
}
|
||||
} else {
|
||||
const originalType = encodingOrEncodeAs.projectionBase ?? encodingOrEncodeAs;
|
||||
if (originalType !== strType) {
|
||||
reportDiagnostic(program, {
|
||||
code: "invalid-encode",
|
||||
messageId: "firstArg",
|
||||
target: encodingOrEncodeAs,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { type: encodingOrEncodeAs };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -742,7 +759,7 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData:
|
|||
code: "invalid-encode",
|
||||
messageId: "wrongType",
|
||||
format: {
|
||||
encoding: encodeData.encoding,
|
||||
encoding: encodeData.encoding ?? "string",
|
||||
type: getTypeName(target),
|
||||
expected: validTargets.join(", "),
|
||||
},
|
||||
|
@ -763,11 +780,11 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData:
|
|||
const typeName = getTypeName(encodeData.type.projectionBase ?? encodeData.type);
|
||||
reportDiagnostic(context.program, {
|
||||
code: "invalid-encode",
|
||||
messageId: ["unixTimestamp", "seconds"].includes(encodeData.encoding)
|
||||
messageId: ["unixTimestamp", "seconds"].includes(encodeData.encoding ?? "string")
|
||||
? "wrongNumericEncodingType"
|
||||
: "wrongEncodingType",
|
||||
format: {
|
||||
encoding: encodeData.encoding,
|
||||
encoding: encodeData.encoding!,
|
||||
type: getTypeName(target),
|
||||
expected: validEncodeTypes.join(", "),
|
||||
actual: typeName,
|
||||
|
@ -790,6 +807,8 @@ function validateEncodeData(context: DecoratorContext, target: Type, encodeData:
|
|||
return check(["bytes"], ["string"]);
|
||||
case "base64url":
|
||||
return check(["bytes"], ["string"]);
|
||||
case undefined:
|
||||
return check(["numeric"], ["string"]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -731,6 +731,19 @@ describe("compiler: built-in decorators", () => {
|
|||
strictEqual(encodeData.type.name, encodeAs ?? "string");
|
||||
});
|
||||
});
|
||||
|
||||
it(`@encode(string) on numeric scalar`, async () => {
|
||||
const { s } = (await runner.compile(`
|
||||
@encode(string)
|
||||
@test
|
||||
scalar s extends int64;
|
||||
`)) as { s: Scalar };
|
||||
|
||||
const encodeData = getEncode(runner.program, s);
|
||||
ok(encodeData);
|
||||
strictEqual(encodeData.encoding, undefined);
|
||||
strictEqual(encodeData.type.name, "string");
|
||||
});
|
||||
});
|
||||
describe("invalid", () => {
|
||||
invalidCases.forEach(([target, encoding, encodeAs, expectedCode, expectedMessage]) => {
|
||||
|
@ -750,6 +763,20 @@ describe("compiler: built-in decorators", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`@encode(string) on non-numeric scalar`, async () => {
|
||||
const diagnostics = await runner.diagnose(`
|
||||
@encode(string)
|
||||
@test
|
||||
scalar s extends utcDateTime;
|
||||
`);
|
||||
|
||||
expectDiagnostics(diagnostics, {
|
||||
code: "invalid-encode",
|
||||
severity: "error",
|
||||
message: "Encoding 'string' cannot be used on type 's'. Expected: numeric.",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { type ModelProperty, Program, type Scalar, getEncode } from "@typespec/compiler";
|
||||
import type { ResolvedOpenAPI3EmitterOptions } from "./openapi.js";
|
||||
import { getSchemaForStdScalars } from "./std-scalar-schemas.js";
|
||||
import type { OpenAPI3Schema } from "./types.js";
|
||||
|
||||
export function applyEncoding(
|
||||
program: Program,
|
||||
typespecType: Scalar | ModelProperty,
|
||||
target: OpenAPI3Schema,
|
||||
options: ResolvedOpenAPI3EmitterOptions
|
||||
): OpenAPI3Schema {
|
||||
const encodeData = getEncode(program, typespecType);
|
||||
if (encodeData) {
|
||||
const newTarget = { ...target };
|
||||
const newType = getSchemaForStdScalars(encodeData.type as any, options);
|
||||
newTarget.type = newType.type;
|
||||
// If the target already has a format it takes priority. (e.g. int32)
|
||||
newTarget.format = mergeFormatAndEncoding(
|
||||
newTarget.format,
|
||||
encodeData.encoding,
|
||||
newType.format
|
||||
);
|
||||
return newTarget;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function mergeFormatAndEncoding(
|
||||
format: string | undefined,
|
||||
encoding: string | undefined,
|
||||
encodeAsFormat: string | undefined
|
||||
): string | undefined {
|
||||
switch (format) {
|
||||
case undefined:
|
||||
return encodeAsFormat ?? encoding ?? format;
|
||||
case "date-time":
|
||||
switch (encoding) {
|
||||
case "rfc3339":
|
||||
return "date-time";
|
||||
case "unixTimestamp":
|
||||
return "unixtime";
|
||||
case "rfc7231":
|
||||
return "http-date";
|
||||
default:
|
||||
return encoding;
|
||||
}
|
||||
case "duration":
|
||||
switch (encoding) {
|
||||
case "ISO8601":
|
||||
return "duration";
|
||||
default:
|
||||
return encodeAsFormat ?? encoding;
|
||||
}
|
||||
default:
|
||||
return encodeAsFormat ?? encoding ?? format;
|
||||
}
|
||||
}
|
|
@ -10,7 +10,6 @@ import {
|
|||
getAllTags,
|
||||
getAnyExtensionFromPath,
|
||||
getDoc,
|
||||
getEncode,
|
||||
getFormat,
|
||||
getKnownValues,
|
||||
getMaxItems,
|
||||
|
@ -44,7 +43,6 @@ import {
|
|||
ProjectionApplication,
|
||||
projectProgram,
|
||||
resolvePath,
|
||||
Scalar,
|
||||
serializeValueAsJson,
|
||||
Service,
|
||||
Type,
|
||||
|
@ -97,6 +95,7 @@ import {
|
|||
import { buildVersionProjections, VersionProjections } from "@typespec/versioning";
|
||||
import { stringify } from "yaml";
|
||||
import { getRef } from "./decorators.js";
|
||||
import { applyEncoding } from "./encoding.js";
|
||||
import { createDiagnostic, FileType, OpenAPI3EmitterOptions } from "./lib.js";
|
||||
import { getDefaultValue, isBytesKeptRaw, OpenAPI3SchemaEmitter } from "./schema-emitter.js";
|
||||
import {
|
||||
|
@ -1476,7 +1475,12 @@ function createOAPIEmitter(
|
|||
if (!typeSchema) {
|
||||
return undefined;
|
||||
}
|
||||
const schema = applyEncoding(param, applyIntrinsicDecorators(param, typeSchema));
|
||||
const schema = applyEncoding(
|
||||
program,
|
||||
param,
|
||||
applyIntrinsicDecorators(param, typeSchema),
|
||||
options
|
||||
);
|
||||
if (param.defaultValue) {
|
||||
schema.default = getDefaultValue(program, param.defaultValue);
|
||||
}
|
||||
|
@ -1746,61 +1750,6 @@ function createOAPIEmitter(
|
|||
return newTarget;
|
||||
}
|
||||
|
||||
function applyEncoding(
|
||||
typespecType: Scalar | ModelProperty,
|
||||
target: OpenAPI3Schema
|
||||
): OpenAPI3Schema {
|
||||
const encodeData = getEncode(program, typespecType);
|
||||
if (encodeData) {
|
||||
const newTarget = { ...target };
|
||||
const newType = callSchemaEmitter(
|
||||
encodeData.type,
|
||||
Visibility.Read,
|
||||
false,
|
||||
"application/json"
|
||||
) as OpenAPI3Schema;
|
||||
newTarget.type = newType.type;
|
||||
// If the target already has a format it takes priority. (e.g. int32)
|
||||
newTarget.format = mergeFormatAndEncoding(
|
||||
newTarget.format,
|
||||
encodeData.encoding,
|
||||
newType.format
|
||||
);
|
||||
return newTarget;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
function mergeFormatAndEncoding(
|
||||
format: string | undefined,
|
||||
encoding: string,
|
||||
encodeAsFormat: string | undefined
|
||||
): string {
|
||||
switch (format) {
|
||||
case undefined:
|
||||
return encodeAsFormat ?? encoding;
|
||||
case "date-time":
|
||||
switch (encoding) {
|
||||
case "rfc3339":
|
||||
return "date-time";
|
||||
case "unixTimestamp":
|
||||
return "unixtime";
|
||||
case "rfc7231":
|
||||
return "http-date";
|
||||
default:
|
||||
return encoding;
|
||||
}
|
||||
case "duration":
|
||||
switch (encoding) {
|
||||
case "ISO8601":
|
||||
return "duration";
|
||||
default:
|
||||
return encodeAsFormat ?? encoding;
|
||||
}
|
||||
default:
|
||||
return encodeAsFormat ?? encoding;
|
||||
}
|
||||
}
|
||||
|
||||
function applyExternalDocs(typespecType: Type, target: Record<string, unknown>) {
|
||||
const externalDocs = getExternalDocs(program, typespecType);
|
||||
if (externalDocs) {
|
||||
|
|
|
@ -73,8 +73,10 @@ import {
|
|||
shouldInline,
|
||||
} from "@typespec/openapi";
|
||||
import { getOneOf, getRef } from "./decorators.js";
|
||||
import { applyEncoding } from "./encoding.js";
|
||||
import { OpenAPI3EmitterOptions, reportDiagnostic } from "./lib.js";
|
||||
import { ResolvedOpenAPI3EmitterOptions } from "./openapi.js";
|
||||
import { getSchemaForStdScalars } from "./std-scalar-schemas.js";
|
||||
import { OpenAPI3Discriminator, OpenAPI3Schema, OpenAPI3SchemaProperty } from "./types.js";
|
||||
import { VisibilityUsageTracker } from "./visibility-usage.js";
|
||||
|
||||
|
@ -718,66 +720,7 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
|
|||
}
|
||||
|
||||
#getSchemaForStdScalars(scalar: Scalar & { name: IntrinsicScalarName }): OpenAPI3Schema {
|
||||
switch (scalar.name) {
|
||||
case "bytes":
|
||||
return { type: "string", format: "byte" };
|
||||
case "numeric":
|
||||
return { type: "number" };
|
||||
case "integer":
|
||||
return { type: "integer" };
|
||||
case "int8":
|
||||
return { type: "integer", format: "int8" };
|
||||
case "int16":
|
||||
return { type: "integer", format: "int16" };
|
||||
case "int32":
|
||||
return { type: "integer", format: "int32" };
|
||||
case "int64":
|
||||
return { type: "integer", format: "int64" };
|
||||
case "safeint":
|
||||
switch (this.#options.safeintStrategy) {
|
||||
case "double-int":
|
||||
return { type: "integer", format: "double-int" };
|
||||
case "int64":
|
||||
default:
|
||||
return { type: "integer", format: "int64" };
|
||||
}
|
||||
case "uint8":
|
||||
return { type: "integer", format: "uint8" };
|
||||
case "uint16":
|
||||
return { type: "integer", format: "uint16" };
|
||||
case "uint32":
|
||||
return { type: "integer", format: "uint32" };
|
||||
case "uint64":
|
||||
return { type: "integer", format: "uint64" };
|
||||
case "float":
|
||||
return { type: "number" };
|
||||
case "float64":
|
||||
return { type: "number", format: "double" };
|
||||
case "float32":
|
||||
return { type: "number", format: "float" };
|
||||
case "decimal":
|
||||
return { type: "number", format: "decimal" };
|
||||
case "decimal128":
|
||||
return { type: "number", format: "decimal128" };
|
||||
case "string":
|
||||
return { type: "string" };
|
||||
case "boolean":
|
||||
return { type: "boolean" };
|
||||
case "plainDate":
|
||||
return { type: "string", format: "date" };
|
||||
case "utcDateTime":
|
||||
case "offsetDateTime":
|
||||
return { type: "string", format: "date-time" };
|
||||
case "plainTime":
|
||||
return { type: "string", format: "time" };
|
||||
case "duration":
|
||||
return { type: "string", format: "duration" };
|
||||
case "url":
|
||||
return { type: "string", format: "uri" };
|
||||
default:
|
||||
const _assertNever: never = scalar.name;
|
||||
return {};
|
||||
}
|
||||
return getSchemaForStdScalars(scalar, this.#options);
|
||||
}
|
||||
|
||||
#applySchemaExamples(
|
||||
|
@ -904,31 +847,16 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
|
|||
typespecType: Scalar | ModelProperty,
|
||||
target: OpenAPI3Schema | Placeholder<OpenAPI3Schema>
|
||||
): OpenAPI3Schema {
|
||||
const encodeData = getEncode(this.emitter.getProgram(), typespecType);
|
||||
if (encodeData) {
|
||||
const newTarget = new ObjectBuilder(target);
|
||||
const newType = this.#getSchemaForStdScalars(encodeData.type as any);
|
||||
|
||||
newTarget.type = newType.type;
|
||||
// If the target already has a format it takes priority. (e.g. int32)
|
||||
newTarget.format = this.#mergeFormatAndEncoding(
|
||||
newTarget.format,
|
||||
encodeData.encoding,
|
||||
newType.format
|
||||
);
|
||||
return newTarget;
|
||||
}
|
||||
const result = new ObjectBuilder(target);
|
||||
return result;
|
||||
return applyEncoding(this.emitter.getProgram(), typespecType, target as any, this.#options);
|
||||
}
|
||||
#mergeFormatAndEncoding(
|
||||
format: string | undefined,
|
||||
encoding: string,
|
||||
encoding: string | undefined,
|
||||
encodeAsFormat: string | undefined
|
||||
): string {
|
||||
): string | undefined {
|
||||
switch (format) {
|
||||
case undefined:
|
||||
return encodeAsFormat ?? encoding;
|
||||
return encodeAsFormat ?? encoding ?? format;
|
||||
case "date-time":
|
||||
switch (encoding) {
|
||||
case "rfc3339":
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import type { IntrinsicScalarName, Scalar } from "@typespec/compiler";
|
||||
import type { ResolvedOpenAPI3EmitterOptions } from "./openapi.js";
|
||||
import type { OpenAPI3Schema } from "./types.js";
|
||||
|
||||
export function getSchemaForStdScalars(
|
||||
scalar: Scalar & { name: IntrinsicScalarName },
|
||||
options: ResolvedOpenAPI3EmitterOptions
|
||||
): OpenAPI3Schema {
|
||||
switch (scalar.name) {
|
||||
case "bytes":
|
||||
return { type: "string", format: "byte" };
|
||||
case "numeric":
|
||||
return { type: "number" };
|
||||
case "integer":
|
||||
return { type: "integer" };
|
||||
case "int8":
|
||||
return { type: "integer", format: "int8" };
|
||||
case "int16":
|
||||
return { type: "integer", format: "int16" };
|
||||
case "int32":
|
||||
return { type: "integer", format: "int32" };
|
||||
case "int64":
|
||||
return { type: "integer", format: "int64" };
|
||||
case "safeint":
|
||||
switch (options.safeintStrategy) {
|
||||
case "double-int":
|
||||
return { type: "integer", format: "double-int" };
|
||||
case "int64":
|
||||
default:
|
||||
return { type: "integer", format: "int64" };
|
||||
}
|
||||
case "uint8":
|
||||
return { type: "integer", format: "uint8" };
|
||||
case "uint16":
|
||||
return { type: "integer", format: "uint16" };
|
||||
case "uint32":
|
||||
return { type: "integer", format: "uint32" };
|
||||
case "uint64":
|
||||
return { type: "integer", format: "uint64" };
|
||||
case "float":
|
||||
return { type: "number" };
|
||||
case "float64":
|
||||
return { type: "number", format: "double" };
|
||||
case "float32":
|
||||
return { type: "number", format: "float" };
|
||||
case "decimal":
|
||||
return { type: "number", format: "decimal" };
|
||||
case "decimal128":
|
||||
return { type: "number", format: "decimal128" };
|
||||
case "string":
|
||||
return { type: "string" };
|
||||
case "boolean":
|
||||
return { type: "boolean" };
|
||||
case "plainDate":
|
||||
return { type: "string", format: "date" };
|
||||
case "utcDateTime":
|
||||
case "offsetDateTime":
|
||||
return { type: "string", format: "date-time" };
|
||||
case "plainTime":
|
||||
return { type: "string", format: "time" };
|
||||
case "duration":
|
||||
return { type: "string", format: "duration" };
|
||||
case "url":
|
||||
return { type: "string", format: "uri" };
|
||||
default:
|
||||
const _assertNever: never = scalar.name;
|
||||
return {};
|
||||
}
|
||||
}
|
|
@ -259,11 +259,16 @@ describe("openapi3: primitives", () => {
|
|||
async function testEncode(
|
||||
scalar: string,
|
||||
expectedOpenApi: OpenAPI3Schema,
|
||||
encoding?: string,
|
||||
encoding?: string | null,
|
||||
encodeAs?: string
|
||||
) {
|
||||
const encodeAsParam = encodeAs ? `, ${encodeAs}` : "";
|
||||
const encodeDecorator = encoding ? `@encode("${encoding}"${encodeAsParam})` : "";
|
||||
const encodeDecorator =
|
||||
encoding === null
|
||||
? `@encode(${encodeAs})`
|
||||
: encoding !== undefined
|
||||
? `@encode("${encoding}"${encodeAsParam})`
|
||||
: "";
|
||||
const res1 = await oapiForModel("s", `${encodeDecorator} scalar s extends ${scalar};`);
|
||||
deepStrictEqual(res1.schemas.s, expectedOpenApi);
|
||||
const res2 = await oapiForModel("Test", `model Test {${encodeDecorator} prop: ${scalar}};`);
|
||||
|
@ -309,5 +314,19 @@ describe("openapi3: primitives", () => {
|
|||
it("set format to base64url when encoding bytes as base64url", () =>
|
||||
testEncode("bytes", { type: "string", format: "base64url" }, "base64url"));
|
||||
});
|
||||
|
||||
describe("int64", () => {
|
||||
it("set type: integer and format to 'int64' by default", () =>
|
||||
testEncode("int64", { type: "integer", format: "int64" }));
|
||||
it("set type: string and format to int64 when @encode(string)", () =>
|
||||
testEncode("int64", { type: "string", format: "int64" }, null, "string"));
|
||||
});
|
||||
|
||||
describe("decimal128", () => {
|
||||
it("set type: integer and format to 'int64' by default", () =>
|
||||
testEncode("decimal128", { type: "number", format: "decimal128" }));
|
||||
it("set type: string and format to int64 when @encode(string)", () =>
|
||||
testEncode("decimal128", { type: "string", format: "decimal128" }, null, "string"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,6 @@ export type XmlEncoding =
|
|||
| "TypeSpec.Xml.Encoding.xmlBase64Binary";
|
||||
|
||||
export interface XmlEncodeData extends EncodeData {
|
||||
encoding: XmlEncoding | EncodeData["encoding"];
|
||||
encoding?: XmlEncoding | EncodeData["encoding"];
|
||||
type: Scalar;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче