@autorest/openapi-to-cadl
Version:
Autorest plugin to scaffold a Typespec definition from an OpenAPI document
219 lines (183 loc) • 7.09 kB
text/typescript
import {
CodeModel,
DictionarySchema,
isObjectSchema,
ObjectSchema,
Property,
Schema,
SchemaType,
} from "@autorest/codemodel";
import { getDataTypes } from "../data-types";
import { CadlObject, CadlObjectProperty } from "../interfaces";
import { addCorePageAlias } from "../utils/alias";
import { getModelDecorators, getPropertyDecorators } from "../utils/decorators";
import { getDiscriminator, getOwnDiscriminator } from "../utils/discriminator";
import { getLogger } from "../utils/logger";
import {
isAnySchema,
isArraySchema,
isChoiceSchema,
isConstantSchema,
isDictionarySchema,
isSealedChoiceSchema,
} from "../utils/schemas";
import { transformValue } from "../utils/values";
const cadlTypes = new Map<SchemaType, string>([
[SchemaType.Date, "plainDate"],
[SchemaType.DateTime, "utcDateTime"],
[SchemaType.UnixTime, "plainTime"],
[SchemaType.String, "string"],
[SchemaType.Time, "plainTime"],
[SchemaType.Uuid, "string"],
[SchemaType.Uri, "string"],
[SchemaType.ByteArray, "bytes"],
[SchemaType.Binary, "bytes"],
[SchemaType.Number, "float32"],
[SchemaType.Integer, "int32"],
[SchemaType.Boolean, "boolean"],
[SchemaType.Credential, "@secret string"],
[SchemaType.Duration, "duration"],
[SchemaType.AnyObject, "object"],
]);
export function transformObject(schema: ObjectSchema, codeModel: CodeModel): CadlObject {
const cadlTypes = getDataTypes(codeModel);
let visited: Partial<CadlObject> = cadlTypes.get(schema) as CadlObject;
if (visited) {
return visited as CadlObject;
}
const logger = getLogger("transformOperationGroup");
logger.info(`Transforming object ${schema.language.default.name}`);
const name = schema.language.default.name.replace(/-/g, "_");
const doc = schema.language.default.description;
// Marking as visited before processing properties to avoid infinite recursion
// when transforming properties that reference this object.
visited = { name, doc };
cadlTypes.set(schema, visited as any);
const properties = (schema.properties ?? [])
.filter((p) => !p.isDiscriminator)
.map((p) => transformObjectProperty(p, codeModel));
const ownDiscriminator = getOwnDiscriminator(schema);
if (!ownDiscriminator) {
const discriminatorProperty = getDiscriminator(schema);
discriminatorProperty && properties.push(discriminatorProperty);
}
const updatedVisited: CadlObject = {
name,
doc,
kind: "object",
properties,
parents: getParents(schema),
extendedParents: getExtendedParents(schema),
spreadParents: getSpreadParents(schema, codeModel),
decorators: getModelDecorators(schema),
};
addCorePageAlias(updatedVisited);
addFixmes(updatedVisited);
cadlTypes.set(schema, updatedVisited);
return updatedVisited;
}
function addFixmes(cadlObject: CadlObject): void {
cadlObject.fixMe = cadlObject.fixMe ?? [];
if ((cadlObject.extendedParents ?? []).length > 1) {
cadlObject.fixMe
.push(`// FIXME: (multiple-inheritance) Multiple inheritance is not supported in CADL, so this type will only inherit from one parent.
// this may happen because of multiple parents having discriminator properties.
// Parents not included ${cadlObject.extendedParents?.join(", ")}`);
}
}
export function transformObjectProperty(propertySchema: Property, codeModel: CodeModel): CadlObjectProperty {
const name = propertySchema.language.default.name;
const doc = propertySchema.language.default.description;
if (isObjectSchema(propertySchema.schema)) {
const dataTypes = getDataTypes(codeModel);
let visited = dataTypes.get(propertySchema.schema) as CadlObject;
if (!visited) {
visited = transformObject(propertySchema.schema, codeModel);
dataTypes.set(propertySchema.schema, visited);
}
return {
kind: "property",
name: name,
doc: doc,
isOptional: propertySchema.required !== true,
type: visited.name,
decorators: getPropertyDecorators(propertySchema),
...(propertySchema.readOnly === true && { visibility: "read" }),
};
}
const logger = getLogger("getDiscriminatorProperty");
logger.info(`Transforming property ${propertySchema.language.default.name} of type ${propertySchema.schema.type}`);
return {
kind: "property",
doc,
name,
isOptional: propertySchema.required !== true,
type: getCadlType(propertySchema.schema, codeModel),
decorators: getPropertyDecorators(propertySchema),
fixMe: getFixme(propertySchema, codeModel),
};
}
function getFixme(property: Property, codeModel: CodeModel): string[] {
const cadlType = getCadlType(property.schema, codeModel);
if (cadlType === "utcDateTime") {
return ["// FIXME: (utcDateTime) Please double check that this is the correct type for your scenario."];
}
return [];
}
function getParents(schema: ObjectSchema): string[] {
const immediateParents = schema.parents?.immediate ?? [];
return immediateParents
.filter((p) => p.language.default.name !== schema.language.default.name)
.map((p) => p.language.default.name);
}
function getExtendedParents(schema: ObjectSchema): string[] {
const immediateParents = schema.parents?.immediate ?? [];
return immediateParents
.filter((p) => p.language.default.name !== schema.language.default.name)
.filter((p) => getOwnDiscriminator(p as ObjectSchema))
.map((p) => p.language.default.name);
}
function getSpreadParents(schema: ObjectSchema, codeModel: CodeModel): string[] {
const immediateParents = schema.parents?.immediate ?? [];
const spreadingParents = immediateParents
.filter((p) => p.language.default.name !== schema.language.default.name)
.filter((p) => !isDictionarySchema(p))
.filter((p) => !getOwnDiscriminator(p as ObjectSchema))
.map((p) => p.language.default.name);
const dictionaryParent = immediateParents.find((p) => isDictionarySchema(p)) as DictionarySchema | undefined;
if (dictionaryParent) {
spreadingParents.push(`Record<${getCadlType(dictionaryParent.elementType, codeModel)}>`);
}
return spreadingParents;
}
export function getCadlType(schema: Schema, codeModel: CodeModel): string {
const schemaType = schema.type;
const visited = getDataTypes(codeModel).get(schema);
if (visited) {
return visited.name;
}
if (isConstantSchema(schema)) {
return `${transformValue(schema.value.value as any)}`;
}
if (isArraySchema(schema)) {
const elementType = getCadlType(schema.elementType, codeModel);
return `${elementType}[]`;
}
if (isObjectSchema(schema)) {
return schema.language.default.name.replace(/-/g, "_");
}
if (isChoiceSchema(schema) || isSealedChoiceSchema(schema)) {
return schema.language.default.name.replace(/-/g, "_");
}
if (isDictionarySchema(schema)) {
return `Record<${getCadlType(schema.elementType, codeModel)}>`;
}
if (isAnySchema(schema)) {
return `unknown`;
}
const cadlType = cadlTypes.get(schemaType);
if (!cadlType) {
throw new Error(`Unknown type ${(schema as any).type}`);
}
return cadlType;
}