typed-aws
Version:
Helps you write AWS CloudFormation in TypeScript
345 lines (341 loc) • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResourceModel = exports.ModuleModel = exports.getAllResources = exports.createModels = void 0;
const normalize_1 = require("./normalize");
const utils_1 = require("./utils");
function createModels(registry, specs) {
const modules = {};
Object.values(registry).forEach((schema) => {
const module = (modules[schema.module] =
modules[schema.module] || new ModuleModel(schema.module));
module.addSchemaType(schema, specs);
});
return modules;
}
exports.createModels = createModels;
function getAllResources(modules) {
const resourceModels = [];
Object.values(modules).forEach((module) => {
resourceModels.push(...Object.values(module.resources));
});
return resourceModels;
}
exports.getAllResources = getAllResources;
class ModuleModel {
constructor(name) {
this.name = name;
this.resources = {};
}
addSchemaType(schema, specs) {
const module = (this.resources[schema.resource] =
this.resources[schema.resource] ||
new ResourceModel(schema.resource, this));
module.addSchemaType(schema, specs);
}
}
exports.ModuleModel = ModuleModel;
class ResourceModel {
constructor(name, module) {
this.name = name;
this.module = module;
this.types = [];
}
addSchemaType(schema, specs) {
const model = createModel(schema, [schema.name], specs);
this.types.push(model);
if (schema.id === schema.namespace) {
this.mainModel = model;
this.mainSchema = schema;
}
}
toString() {
return `
import { CfnResource, Resolvable } from '../../base';
export type ${this.mainModel.getName()}_Type = '${this.mainSchema.namespace}';
export const ${this.mainModel.getName()}_Type = '${this.mainSchema.namespace}';
${(0, utils_1.formatDoc)(this.mainModel.getTypeDocumentation())}export default function ${this.mainModel.getName()}(props: ${this.mainModel.getName()}_Properties) {
return new CfnResource<${this.mainModel.getName()}_Properties>(${this.mainModel.getName()}_Type, props);
};
${this.types
.map((type) => {
return `${(0, utils_1.formatDoc)(type.getTypeDocumentation())}export type ${type.getName()}${type === this.mainModel ? '_Properties' : ''} = ${type.toString()}`;
})
.join('\n\n')}
`;
}
}
exports.ResourceModel = ResourceModel;
class TypeModel {
constructor(schema, names, specs) {
this.schema = schema;
this.names = names;
this.specs = specs;
this.name = schema.name;
this.type = schema.type; // TODO check ref
if (schema.namespace === schema.id) {
this.resourceType = schema.namespace;
}
this.description = schema.description;
}
getName() {
if (this.name)
return normalizeName(this.name);
return normalizeName(this.names.join('_'));
}
getPropertyDocs(propertyName) {
let seeLink = this.specs.getDocumentation(this.schema.id, propertyName === AnyProperty ? '' : propertyName);
if (seeLink)
seeLink = `{ ${seeLink}}`;
return seeLink;
}
getTypeDocumentation() {
let seeLink = this.specs.getDocumentation(this.schema.id);
if (seeLink)
seeLink = `{ ${seeLink}}`;
if (!this.description)
return (0, utils_1.concatWith)({ prefix: '\n' }, seeLink);
return (0, utils_1.concatWith)({ joiner: '\n' }, this.description, seeLink);
}
}
class StringTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
}
toString() {
if (this.schema.enum) {
const enumTypeLiteral = this.schema.enum
.map((item) => `'${item}'`)
.join(' | ');
return `Resolvable<${enumTypeLiteral}>`;
}
return `Resolvable<string>`;
}
}
class ArrayTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
this.model = createModel(schema.items, this.names, specs);
}
toString() {
return `(${this.model}\n)[]`;
}
}
class NumberTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
}
toString() {
return `Resolvable<number>`;
}
}
class BooleanTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
}
toString() {
return `Resolvable<boolean>`;
}
}
class RefTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
this.refType =
this.schema.$ref.split('/').pop() ||
`unknown // Ref type: ${this.schema.$ref}`;
}
toString() {
return this.refType;
}
}
class IntersectionTypeModel extends TypeModel {
constructor(names, specs) {
super({ type: 'intersection' }, names, specs);
this.models = [];
}
addTypeModel(model) {
this.models.push(model);
}
toString() {
return `(${this.models.map((model) => model.toString()).join(' & ')})`;
}
}
class UnionTypeModel extends TypeModel {
constructor(names, specs) {
super({ type: 'union' }, names, specs);
this.models = [];
}
addTypeModel(model) {
this.models.push(model);
}
toString() {
return `(${this.models.map((model) => model.toString()).join(' | ')})`;
}
}
const AnyProperty = Symbol('*');
class ObjectTypeModel extends TypeModel {
constructor(schema, names, specs) {
super(schema, names, specs);
this.members = {};
const { properties, patternProperties, additionalProperties } = schema;
if (properties) {
Object.keys(properties).forEach((key) => {
const valueSchema = properties[key];
const property = (this.members[key] =
this.members[key] ||
new MemberModel(this, key, [...names, key], specs));
property.addSchema(valueSchema);
});
}
if (patternProperties) {
Object.keys(patternProperties).forEach((pattern) => {
const valueSchema = patternProperties[pattern];
const property = (this.members[AnyProperty] =
this.members[AnyProperty] ||
new MemberModel(this, AnyProperty, [...names, 'Any'], specs));
property.addPatternSchema(pattern, valueSchema);
});
}
if (additionalProperties !== false) {
const property = (this.members[AnyProperty] =
this.members[AnyProperty] ||
new MemberModel(this, AnyProperty, [...names, 'Any'], specs));
property.setUnknown();
}
}
isRequired(key) {
return this.schema.required?.includes(key);
}
toStringMember(member) {
return `${(0, utils_1.formatDoc)(member.getTypeDocumentation())}${member.toString(this.isRequired(member.name))};`;
}
toString() {
return `{
${Reflect.ownKeys(this.members)
.map((key) => this.toStringMember(this.members[key]))
.join('\n')}
}`;
}
}
class MemberModel {
constructor(parentModel, name, names, specs) {
this.parentModel = parentModel;
this.name = name;
this.names = names;
this.specs = specs;
}
setUnknown() {
this.isUnknown = true;
}
getTypeDocumentation() {
const propertyDocs = this.type?.getTypeDocumentation();
const parentPropertyDocs = this.parentModel?.getPropertyDocs(this.name);
if (propertyDocs && parentPropertyDocs)
return `${propertyDocs}\n${parentPropertyDocs}`;
if (propertyDocs && !parentPropertyDocs)
return `${propertyDocs}`;
if (parentPropertyDocs)
return `${parentPropertyDocs}`;
}
addTypeModel(model) {
if (!this.type) {
this.type = model;
}
else if (this.type instanceof UnionTypeModel) {
this.type.addTypeModel(model);
}
else {
const typeModel = (this.type = new UnionTypeModel(this.names, this.specs));
typeModel.addTypeModel(model);
return typeModel;
}
return model;
}
addSchema(schema) {
const newModel = createModel(schema, this.names, this.specs);
this.addTypeModel(newModel);
}
addPatternSchema(pattern, schema) {
const newModel = createModel(schema, this.names, this.specs);
newModel.pattern = pattern;
this.addTypeModel(newModel);
}
toString(isRequired) {
return `${this.name === AnyProperty ? '[k: string]' : this.name}${this.name === AnyProperty || isRequired ? '' : '?'}: ${this.isUnknown
? 'unknown'
: normalizeName(this.type?.toString()) || 'unknown // PROPERTY MODEL??'}`;
}
}
function createModel(schema, names, specs) {
(0, normalize_1.normalizeSchema)(schema);
let typeModel = null;
switch (schema.type) {
case 'string':
typeModel = new StringTypeModel(schema, names, specs);
break;
case 'object':
typeModel = new ObjectTypeModel(schema, names, specs);
break;
case 'array':
typeModel = new ArrayTypeModel(schema, names, specs);
break;
case 'number':
typeModel = new NumberTypeModel(schema, names, specs);
break;
case 'boolean':
typeModel = new BooleanTypeModel(schema, names, specs);
break;
case 'reference':
typeModel = new RefTypeModel(schema, names, specs);
break;
}
const conditionalModel = createConditionalModel(schema, names, specs);
if (conditionalModel) {
return conditionalModel;
}
else if (typeModel) {
return typeModel;
}
throw new Error(`Invalid schema without type and conditionals (anyOf, allOf, oneOf): ${JSON.stringify(schema)}`);
}
function hasAnyOf(schema) {
return Array.isArray(schema['anyOf']);
}
function hasAllOf(schema) {
return Array.isArray(schema['allOf']);
}
function hasOneOf(schema) {
return Array.isArray(schema['oneOf']);
}
function createConditionalModel(schema, names, specs) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any
const { anyOf, allOf, oneOf, not, ...parentSchema } = schema;
const addSubSchemas = (subSchemas, typeModel) => {
subSchemas.map((subSchema, i) => {
const newSchema = { ...parentSchema, ...subSchema };
typeModel.addTypeModel(createModel(newSchema, [...names, `${i}`], specs));
});
};
if (hasAnyOf(schema)) {
const unionModel = new UnionTypeModel(names, specs);
addSubSchemas(schema.anyOf, unionModel);
return unionModel;
}
else if (hasAllOf(schema)) {
const intersectionModel = new IntersectionTypeModel(names, specs);
addSubSchemas(schema.allOf, intersectionModel);
return intersectionModel;
}
else if (hasOneOf(schema)) {
const unionModel = new UnionTypeModel(names, specs);
addSubSchemas(schema.oneOf, unionModel);
return unionModel;
}
return null;
}
function normalizeName(name) {
if (!name)
return name;
if (name === 'Object')
return 'ObjectData';
return name;
}