UNPKG

@deployport/specular-runtime

Version:

Runtime for Specular API clients with support for Node.js and Browser

162 lines (161 loc) 5.46 kB
import { CreateContentFromObject } from "../runtime/content.js"; import Struct from "./struct.js"; export function normalizeMapEntry(key) { return key.toLowerCase(); } export const moduleSuperType = "application/spec"; export const mediaTypeSeparator = "."; /** * PackagePath contains the scope of the package */ export class PackagePath { namespace; name; /** * mediaTypeSubType returns the media type sub type for the package * as in application/spec.<namespace>.<module> */ mediaTypeSubType; constructor(namespace, name) { this.namespace = normalizeMapEntry(namespace); this.name = normalizeMapEntry(name); this.mediaTypeSubType = `${moduleSuperType}.${this.namespace}.${this.name}`; } toString() { return this.mediaTypeSubType; } } /** * Package */ export default class Package { path; resources = []; types = []; annotations = []; /** * allPackages is a list of all packages including this and all imported */ allPackages = []; structsByFQTN = new Map(); constructor(namespace, module) { this.path = new PackagePath(namespace, module); this.allPackages.push(this); } importPackage(pkg) { this.allPackages.push(pkg); } _addAnnotation(annotation, config) { config?.(annotation); this.annotations = this.annotations || []; this.annotations.push(annotation); } /** * Resource by name returns the resource with the given name * or undefined if not found * @param name name of the resource * @returns */ resourceByName(name) { name = normalizeMapEntry(name); return this.resources.find(resource => normalizeMapEntry(resource.name) === name) || null; } _addResource(resource) { if (this.resourceByName(resource.name)) { throw new Error(`duplicate resource name ${resource.name}`); } this.resources.push(resource); } typeByName(name) { name = normalizeMapEntry(name); return this.types.find(struct => normalizeMapEntry(struct.name) === name) || null; } requireTypeByName(name) { const st = this.typeByName(name); if (!st) { throw new Error(`failed to find user defined type ${name}`); } return st; } _addType(tp) { if (this.typeByName(tp.name)) { throw new Error(`duplicate type name ${tp.name}`); } this.types.push(tp); if (tp instanceof Struct) { this.structsByFQTN.set(normalizeMapEntry(tp.path.mediaType), tp); } } /** * Returns the Struct with the given FQTN, otherwise throws an error @TypeNotFoundError * @param fqtn * @returns */ structByFQTN(structPath) { const mediaType = normalizeMapEntry(structPath.mediaType); for (const pkg of this.allPackages) { const struct = pkg.structsByFQTN.get(mediaType); if (struct) { return struct; } } throw new TypeNotFoundError(structPath.mediaType); } buildByFQTN(structPath, content) { const structMeta = this.structByFQTN(structPath); // Preserve the deserialized struct's prototype. Spreading it into a new // object ({ ...struct }) would flatten the instance into a plain object, // stripping the Error/RpcError prototype of error structs (so they would // be returned as results instead of thrown) and dropping non-enumerable // fields like Error.message. Error/builtin classes expose __structPath // via a getter; only plain output structs need it injected. const struct = structMeta.deserialize(content); if (struct.__structPath === undefined) { struct.__structPath = structMeta.path; } return struct; } requireBuildFromJSON(structPath, structJSON) { const responseContent = CreateContentFromObject(structJSON); if (!responseContent) { throw new Error("failed to create content from malformed JSON"); } return this.requireBuildByFQTN(structPath, responseContent); } requireBuildByFQTN(structPath, content) { const st = this.buildByFQTN(structPath, content); if (!st) { throw new Error(`failed to build struct ${structPath}`); } return st; } requireBuildArrayNullableItems(structPath, contentArray) { const stArray = []; for (const content of contentArray) { if (content == null) { stArray.push(null); continue; } const st = this.requireBuildByFQTN(structPath, content); stArray.push(st); } return stArray; } requireBuildArrayNonNullableItems(structPath, contentArray) { const stArray = []; for (const [index, value] of contentArray.entries()) { const content = value; if (content == null) { throw new Error(`failed to build struct at index ${index}, unexpected null item`); } const st = this.requireBuildByFQTN(structPath, content); stArray.push(st); } return stArray; } } export class TypeNotFoundError extends Error { constructor(typeName) { super(`failed to find type ${typeName}`); } }