@luvio/model
Advanced tools
Comparing version 5.5.0 to 5.6.0
@@ -9,10 +9,2 @@ /** | ||
const DiscriminatorValueTypes = new Set([ | ||
'string', | ||
'number', | ||
'integer', | ||
'double', | ||
'boolean', | ||
]); | ||
/** | ||
@@ -31,2 +23,44 @@ * Base implementation of TypeRegistry. | ||
/** | ||
* Copyright (c) 2022, Salesforce, Inc., | ||
* All rights reserved. | ||
* For full license text, see the LICENSE.txt file | ||
*/ | ||
/** | ||
* Indicates if a given instance of a service satisfies a request. Note that this function | ||
* assumes the version numbers are valid strings. | ||
* | ||
* @param provided ServiceVersion of the service instance to be provided | ||
* @param requested ServiceVersion requested | ||
* @returns true if the service instance to be provided satisfies the request | ||
*/ | ||
function satisfies(provided, requested) { | ||
const providedN = provided.split('.').map((s) => parseInt(s)); | ||
const requestedN = requested.split('.').map((s) => parseInt(s)); | ||
return providedN[0] === requestedN[0] && providedN[1] >= requestedN[1]; | ||
} | ||
function stringIsVersion(s) { | ||
const versionParts = s.split('.'); | ||
return ((versionParts.length === 2 || versionParts.length === 3) && | ||
versionParts.every((part) => !isNaN(parseInt(part)))); | ||
} | ||
/** | ||
* x-onestore extension paths of interest. | ||
*/ | ||
const EXTENSIONS_ONESTORE = 'onestore'; | ||
// Api - extensions version | ||
const ANNOTATIONS_VERSION = `${EXTENSIONS_ONESTORE}/version`; | ||
// EndPoint - Command type. 'aura' | 'http' | ||
const EXTENSION_ONESTORE_ENDPOINT_TYPE = `${EXTENSIONS_ONESTORE}/endpoint-type`; | ||
// EndPoint - Aura controller name | ||
const EXTENSION_ONESTORE_AURA_CONTROLLER = `${EXTENSIONS_ONESTORE}/config/aura/controller`; | ||
// Operation - Aura method name | ||
const EXTENSION_ONESTORE_AURA_METHOD = `${EXTENSIONS_ONESTORE}/config/aura/method`; | ||
// Operation - Aura method input payload parameter name. | ||
const EXTENSION_ONESTORE_AURA_BODY_PARAM = `${EXTENSIONS_ONESTORE}/config/aura/body-param`; | ||
// Operation - Operation type. 'mutation' | 'query' | ||
const EXTENSION_ONESTORE_OPERATION_TYPE = `${EXTENSIONS_ONESTORE}/config/operation-type`; | ||
const STRING_DATA_TYPE = 'http://www.w3.org/2001/XMLSchema#string'; | ||
@@ -115,4 +149,2 @@ const BOOLEAN_DATA_TYPE = 'http://www.w3.org/2001/XMLSchema#boolean'; | ||
return Number(value); | ||
case 'nil': | ||
return null; | ||
default: | ||
@@ -123,2 +155,3 @@ return value; | ||
/* eslint no-redeclare: "off" */ | ||
/** | ||
@@ -129,10 +162,45 @@ * Graph structure content related types. | ||
const DATA_OBJECT_PROPERTY_NAME = 'http://a.ml/vocabularies/data#Object'; | ||
const DATA_ARRAY_PROPERTY_NAME = 'http://a.ml/vocabularies/data#Array'; | ||
const DATA_MEMBER_PROPERTY_NAME = 'http://www.w3.org/2000/01/rdf-schema#member'; | ||
const DATA_DATATYPE_PROPERTY_NAME = 'http://www.w3.org/ns/shacl#datatype'; | ||
const DATA_VALUE_PROPERTY_NAME = 'http://a.ml/vocabularies/data#value'; | ||
const NAME_PROPERTY_VALUE = 'http://a.ml/vocabularies/core#name'; | ||
function getExtensionValue(extensions, path, type) { | ||
const prop = getProperty(extensions, path); | ||
if (prop !== undefined && prop.value !== undefined && typeof prop.value !== type) { | ||
throw new Error(`Invalid ${path} exension specification type: expected '${type}', but got '${typeof prop.value}'`); | ||
} | ||
return prop === null || prop === void 0 ? void 0 : prop.value; | ||
} | ||
function getProperty(extensions, path) { | ||
let property = undefined; | ||
// remove trailing '/'s from path | ||
const pathItems = path.replace(/^\/|\/$/g, '').split('/'); | ||
let node = extensions; | ||
// retrieve property from path | ||
for (let i = 0; i < pathItems.length && node !== undefined; i++) { | ||
const pathItem = pathItems[i]; | ||
if (isExtensionProperty(node)) { | ||
throw new Error(`Invalid ${path} extension specification: property ${pathItems | ||
.slice(0, i) | ||
.join('/')} has no ${pathItem} sub element`); | ||
} | ||
node = node[pathItem]; | ||
} | ||
// validate retrieved property | ||
if (!isExtensionProperty(node)) { | ||
throw new Error(`Invalid ${path} extension specification: expected to be a property, but has unexpected sub elements`); | ||
} | ||
// new valid occurrence found, merge it to the result | ||
property = Object.assign(property || {}, node); | ||
return property; | ||
} | ||
function isExtensionProperty(node) { | ||
return (node === undefined || | ||
(typeof node === 'object' && | ||
!Array.isArray(node) && | ||
Object.keys(node).length === 2 && | ||
typeof node.fullname === 'string' && | ||
typeof node.value !== 'undefined')); | ||
} | ||
const isScalar = (graph) => graph.types().includes(DATA_SCALAR_PROPERTY_NAME); | ||
const isObject = (graph) => graph.types().includes(DATA_OBJECT_PROPERTY_NAME); | ||
const isArray = (graph) => graph.types().includes(DATA_ARRAY_PROPERTY_NAME); | ||
/** | ||
@@ -154,7 +222,5 @@ * Reads extensions from the parsed node. | ||
? buildExtensionsObject(graph) | ||
: isArray(graph) | ||
? buildExtensionsArray(fullname, graph) | ||
: isScalar(graph) | ||
? buildExtensionScalar(fullname, graph) | ||
: {}; | ||
: isScalar(graph) | ||
? buildExtensionScalar(fullname, graph) | ||
: {}; | ||
/** | ||
@@ -176,19 +242,2 @@ * Builds an Extensions object. | ||
/** | ||
* Builds an Extensions or ExtensionProperty array. | ||
*/ | ||
function buildExtensionsArray(fullname, graph) { | ||
let array = []; | ||
// this node is an array, the 'member' property has all members | ||
const arrayProp = graph.getObjectByProperty(DATA_MEMBER_PROPERTY_NAME); | ||
if (arrayProp.length !== 0) { | ||
if (isScalar(arrayProp[0].graph())) { | ||
array = arrayProp.map((prop) => buildExtensionScalar(fullname, prop.graph())); | ||
} | ||
else { | ||
array = arrayProp.map((prop) => buildExtensionsObject(prop.graph())); | ||
} | ||
} | ||
return array; | ||
} | ||
/** | ||
* Builds an ExtensionProperty item from its detail graph structure. | ||
@@ -207,123 +256,3 @@ */ | ||
/** | ||
* x-connect extension paths of interest. | ||
*/ | ||
const EXTENSIONS_CONNECT = 'connect'; | ||
// EndPoint - family name (used to compute the default Aura controller name). | ||
const EXTENSION_CONNECT_FAMILY = `${EXTENSIONS_CONNECT}/family`; | ||
// Operation - first APEX/Aura generated method name, used as default Aura method name. | ||
const EXTENSION_CONNECT_AURA_METHOD = `${EXTENSIONS_CONNECT}/signatures/0/methodName`; | ||
/** | ||
* x-lds extension paths of interest. | ||
*/ | ||
const EXTENSIONS_LDS = 'lds'; | ||
// Api - extensions version | ||
const ANNOTATIONS_VERSION = `${EXTENSIONS_LDS}/version`; | ||
// EndPoint - Aura controller name, defaults to x-connect/family. | ||
const EXTENSION_LDS_AURA_CONTROLLER = `${EXTENSIONS_LDS}/aura/controller`; | ||
// Operation - Aura method name, defaults to x-connect/signatures/0/methodName or operation id. | ||
const EXTENSION_LDS_AURA_METHOD = `${EXTENSIONS_LDS}/aura/method`; | ||
// Operation - Aura method input payload parameter name. | ||
const EXTENSION_LDS_AURA_BODY_PARAM = `${EXTENSIONS_LDS}/aura/body-param`; | ||
function buildExtensionsHelper(customDomainProperties) { | ||
return new ExtensionsHelper(buildExtensions(customDomainProperties)); | ||
} | ||
/** | ||
* Helper to retrieve property values from a set of extensions using access paths. | ||
* Path to the value is a set of '/' separated names. | ||
*/ | ||
class ExtensionsHelper { | ||
constructor(...allExtensions) { | ||
this.allExtensions = allExtensions; | ||
} | ||
getStringPropertyValue(path) { | ||
return this.getTypedPropertyValue(path, 'string'); | ||
} | ||
getNumericPropertyValue(path) { | ||
return this.getTypedPropertyValue(path, 'number'); | ||
} | ||
getBooleanPropertyValue(path) { | ||
return this.getTypedPropertyValue(path, 'boolean'); | ||
} | ||
getTypedPropertyValue(path, type) { | ||
const prop = this.getProperty(path); | ||
if (prop !== undefined && typeof prop.value !== type) { | ||
throw new Error(`Invalid ${path} exension specification type: expected '${type}', but got '${typeof prop.value}'`); | ||
} | ||
return prop === null || prop === void 0 ? void 0 : prop.value; | ||
} | ||
/** | ||
* Iterates the extensions in specified order, serching for the ones containing an occurrence | ||
* of the specified property, and merge them into one single property (emulates inheritance). | ||
* | ||
* @param path a set of '/' separated names | ||
* @returns an ExtensionProperty if found, or undefined | ||
*/ | ||
getProperty(path) { | ||
let property = undefined; | ||
for (const extensions of this.allExtensions) { | ||
// remove trailing '/'s from path | ||
const pathItems = path.replace(/^\/|\/$/g, '').split('/'); | ||
let node = extensions; | ||
// retrieve property from path | ||
for (let i = 0; i < pathItems.length && node !== undefined; i++) { | ||
const pathItem = pathItems[i]; | ||
if (this.isExtensionProperty(node)) { | ||
throw new Error(`Invalid ${path} extension specification: property ${pathItems | ||
.slice(0, i) | ||
.join('/')} has no ${pathItem} sub element`); | ||
} | ||
node = node[pathItem]; | ||
} | ||
// no property with the specified path found in this extensions set: skip | ||
if (node === undefined) { | ||
continue; | ||
} | ||
// validate retrieved property | ||
if (!this.isExtensionProperty(node)) { | ||
throw new Error(`Invalid ${path} extension specification: expected to be a property, but has unexpected sub elements`); | ||
} | ||
// new valid occurrence found, merge it to the result | ||
property = Object.assign(property || {}, node); | ||
} | ||
return property; | ||
} | ||
isExtensionProperty(node) { | ||
return (node === undefined || | ||
(typeof node === 'object' && | ||
!Array.isArray(node) && | ||
Object.keys(node).length === 2 && | ||
typeof node.fullname === 'string' && | ||
typeof node.value !== 'undefined')); | ||
} | ||
} | ||
class AmfEndPoint { | ||
constructor(amfEndPoint, operations, amfTypeFactory, typeRegistry, logger) { | ||
this.amfEndPoint = amfEndPoint; | ||
this.operations = operations; | ||
this.logger = logger; | ||
this.path = this.amfEndPoint.path.value(); | ||
this.uriParameters = {}; | ||
this.amfEndPoint.parameters.forEach((param) => { | ||
this.uriParameters[`${param.name}`] = { | ||
required: param.required.value(), | ||
type: amfTypeFactory(param.schema, typeRegistry, logger, true), | ||
}; | ||
}); | ||
this.auraController = this.buildAuraController(); | ||
} | ||
buildAuraController() { | ||
const helper = buildExtensionsHelper(this.amfEndPoint.customDomainProperties); | ||
// get/compute Aura controller name | ||
const connectFamilyName = helper.getStringPropertyValue(EXTENSION_CONNECT_FAMILY); | ||
const connectControllerName = connectFamilyName | ||
? `${connectFamilyName}Controller` | ||
: undefined; | ||
const name = helper.getStringPropertyValue(EXTENSION_LDS_AURA_CONTROLLER) || connectControllerName; | ||
return name ? { name } : undefined; | ||
} | ||
} | ||
class AmfOperation { | ||
class AmfBaseOperation { | ||
constructor(amfOperation, amfTypeFactory, typeRegistry, logger) { | ||
@@ -335,15 +264,23 @@ this.amfOperation = amfOperation; | ||
this.method = this.amfOperation.method.value().toUpperCase(); | ||
this.operationType = this.method === 'GET' ? 'query' : 'mutation'; | ||
const operationTypeOverride = getExtensionValue(buildExtensions(this.amfOperation.customDomainProperties), EXTENSION_ONESTORE_OPERATION_TYPE, 'string'); | ||
const position = { | ||
line: this.amfOperation.position.lineFrom.valueOf(), | ||
column: this.amfOperation.position.columnFrom.valueOf(), | ||
}; | ||
if (operationTypeOverride !== undefined) { | ||
if (!['query', 'mutation'].some((op) => op === operationTypeOverride)) { | ||
this.logger.warn(position, `${EXTENSION_ONESTORE_OPERATION_TYPE} must be query or mutation. Got ${operationTypeOverride}.`); | ||
} | ||
else { | ||
this.operationType = operationTypeOverride; | ||
} | ||
} | ||
let operationId = this.amfOperation.operationId.value(); | ||
if (!operationId) { | ||
operationId = this.amfOperation.name.value(); | ||
const position = { | ||
line: this.amfOperation.position.lineFrom.valueOf(), | ||
column: this.amfOperation.position.columnFrom.valueOf(), | ||
}; | ||
if (!operationId) { | ||
logger.warn(position, `operationID not found.`); | ||
logger.error(position, `operationID not found.`); | ||
throw new Error('Operation ID not found'); | ||
} | ||
else { | ||
logger.info(position, `operationID not found, defaulting to displayName.`); | ||
} | ||
} | ||
@@ -367,3 +304,2 @@ this.operationId = operationId; | ||
}); | ||
this.auraMethod = this.buildAuraMethod(); | ||
} | ||
@@ -388,19 +324,112 @@ buildPayloads(r) { | ||
} | ||
} | ||
class AmfHttpOperation extends AmfBaseOperation { | ||
constructor() { | ||
super(...arguments); | ||
this.type = 'http'; | ||
} | ||
} | ||
class AmfAuraReadOperation extends AmfBaseOperation { | ||
constructor(amfOperation, amfTypeFactory, typeRegistry, logger) { | ||
super(amfOperation, amfTypeFactory, typeRegistry, logger); | ||
this.type = 'aura'; | ||
this.method = amfOperation.method.value().toUpperCase(); | ||
if (this.method !== 'GET') { | ||
throw new Error('Method must be GET'); | ||
} | ||
this.auraMethod = this.buildAuraMethod(); | ||
} | ||
buildAuraMethod() { | ||
const helper = buildExtensionsHelper(this.amfOperation.customDomainProperties); | ||
// get Aura method name | ||
const name = helper.getStringPropertyValue(EXTENSION_LDS_AURA_METHOD) || | ||
helper.getStringPropertyValue(EXTENSION_CONNECT_AURA_METHOD); | ||
// get input payload name | ||
const inputPayloadParamName = helper.getStringPropertyValue(EXTENSION_LDS_AURA_BODY_PARAM); | ||
return name || inputPayloadParamName ? { name, inputPayloadParamName } : undefined; | ||
const name = getExtensionValue(buildExtensions(this.amfOperation.customDomainProperties), EXTENSION_ONESTORE_AURA_METHOD, 'string'); | ||
if (name === undefined) { | ||
throw new Error('Aura method must be defined for an Aura operation'); | ||
} | ||
return { name }; | ||
} | ||
} | ||
class AmfAuraMutationOperation extends AmfBaseOperation { | ||
constructor(amfOperation, amfTypeFactory, typeRegistry, logger) { | ||
super(amfOperation, amfTypeFactory, typeRegistry, logger); | ||
this.type = 'aura'; | ||
this.method = amfOperation.method.value().toUpperCase(); | ||
if (!['POST', 'PUT', 'PATCH'].some((op) => op === this.method)) { | ||
throw new Error('Method must be POST, PUT, or PATCH'); | ||
} | ||
this.auraMethod = this.buildAuraMethod(); | ||
} | ||
buildAuraMethod() { | ||
const name = getExtensionValue(buildExtensions(this.amfOperation.customDomainProperties), EXTENSION_ONESTORE_AURA_METHOD, 'string'); | ||
const inputPayloadParamName = getExtensionValue(buildExtensions(this.amfOperation.customDomainProperties), EXTENSION_ONESTORE_AURA_BODY_PARAM, 'string'); | ||
if (name === undefined) { | ||
throw new Error('Aura method must be defined for an Aura operation'); | ||
} | ||
if (inputPayloadParamName === undefined) { | ||
throw new Error('x-onestore/config/aura/body-param must be defined for a mutating Aura operation'); | ||
} | ||
return { name, inputPayloadParamName }; | ||
} | ||
} | ||
function amfEndpointFactory(amfEndpoint, amfTypeFactory, typeRegistry, logger) { | ||
const amfOperations = amfEndpoint.operations.map((operation) => { | ||
return new AmfOperation(operation, amfTypeFactory, typeRegistry, logger); | ||
}); | ||
return new AmfEndPoint(amfEndpoint, amfOperations, amfTypeFactory, typeRegistry, logger); | ||
const endPointType = getExtensionValue(buildExtensions(amfEndpoint.customDomainProperties), EXTENSION_ONESTORE_ENDPOINT_TYPE, 'string'); | ||
if (endPointType === undefined) { | ||
return undefined; | ||
} | ||
if (endPointType === 'aura') { | ||
const operations = amfEndpoint.operations.map((operation) => { | ||
const method = operation.method.value().toUpperCase(); | ||
if (method === 'GET') { | ||
return new AmfAuraReadOperation(operation, amfTypeFactory, typeRegistry, logger); | ||
} | ||
else if (['POST', 'PUT', 'PATCH'].some((op) => op === method)) { | ||
return new AmfAuraMutationOperation(operation, amfTypeFactory, typeRegistry, logger); | ||
} | ||
throw new Error(`Aura endpoints do not support operation method ${method}`); | ||
}); | ||
return new AmfAuraEndpoint(amfEndpoint, operations, amfTypeFactory, typeRegistry, logger); | ||
} | ||
if (endPointType === 'http') { | ||
const operations = amfEndpoint.operations.map((operation) => { | ||
return new AmfHttpOperation(operation, amfTypeFactory, typeRegistry, logger); | ||
}); | ||
return new AmfHttpEndPoint(amfEndpoint, operations, amfTypeFactory, typeRegistry, logger); | ||
} | ||
throw new Error(`Unknown value for 'x-onestore.command-type'. Value must be 'aura' or 'http', got ${endPointType}`); | ||
} | ||
class AmfBaseEndpoint { | ||
constructor(amfEndpoint, operations, amfTypeFactory, typeRegistry, logger) { | ||
this.amfEndpoint = amfEndpoint; | ||
this.operations = operations; | ||
this.logger = logger; | ||
this.path = this.amfEndpoint.path.value(); | ||
this.uriParameters = {}; | ||
this.amfEndpoint.parameters.forEach((param) => { | ||
this.uriParameters[`${param.name}`] = { | ||
required: param.required.value(), | ||
type: amfTypeFactory(param.schema, typeRegistry, logger, true), | ||
}; | ||
}); | ||
} | ||
} | ||
class AmfHttpEndPoint extends AmfBaseEndpoint { | ||
constructor() { | ||
super(...arguments); | ||
this.type = 'http'; | ||
} | ||
} | ||
class AmfAuraEndpoint extends AmfBaseEndpoint { | ||
constructor(amfEndPoint, operations, amfTypeFactory, typeRegistry, logger) { | ||
super(amfEndPoint, operations, amfTypeFactory, typeRegistry, logger); | ||
this.type = 'aura'; | ||
this.auraController = this.buildAuraController(); | ||
} | ||
buildAuraController() { | ||
const name = getExtensionValue(buildExtensions(this.amfEndpoint.customDomainProperties), EXTENSION_ONESTORE_AURA_CONTROLLER, 'string'); | ||
if (name === undefined) { | ||
throw new Error('Aura controller name must be defined for an Aura endpoint.'); | ||
} | ||
return { name }; | ||
} | ||
} | ||
@@ -446,29 +475,2 @@ class AMFBaseType { | ||
/** | ||
* Copyright (c) 2022, Salesforce, Inc., | ||
* All rights reserved. | ||
* For full license text, see the LICENSE.txt file | ||
*/ | ||
function bfs(start, predicate, getChildren) { | ||
const queue = [...start]; | ||
const visited = new Set([...start]); | ||
const matches = new Set(); | ||
while (queue.length) { | ||
const curr = queue.shift(); | ||
if (predicate(curr)) { | ||
// The cast here is useful when predicate does type narrowing | ||
matches.add(curr); | ||
} | ||
const children = getChildren(curr); | ||
for (const child of children) { | ||
if (!visited.has(child)) { | ||
visited.add(child); | ||
queue.push(child); | ||
} | ||
} | ||
} | ||
return matches; | ||
} | ||
class AMFObjectTypeImpl extends AMFBaseType { | ||
@@ -483,5 +485,2 @@ constructor() { | ||
} | ||
getDiscriminatedParent() { | ||
return getDiscriminatedParent(this); | ||
} | ||
typeResolve() { | ||
@@ -492,6 +491,2 @@ var _a; | ||
} | ||
if (!validateInheritance(this, this.logger)) { | ||
this.inherits = []; | ||
return this; | ||
} | ||
// It's now safe to process this object's properties from the | ||
@@ -517,107 +512,25 @@ // AMF Shape and use the TypeRegistry to resolve those. | ||
} | ||
this.resolveDiscriminator(); | ||
this.resolveDiscriminatorValue(); | ||
} | ||
resolveDiscriminator() { | ||
if (!this.shape.discriminator.isNullOrEmpty) { | ||
if (!validateDiscriminator(this, this.logger)) { | ||
return; | ||
} | ||
this.discriminator = this.shape.discriminator.value(); | ||
} | ||
if (!validateDiscriminatorValue(this, this.logger)) { | ||
return; | ||
} | ||
} | ||
resolveDiscriminatorValue() { | ||
var _a; | ||
const discriminatedParent = this.getDiscriminatedParent(); | ||
if (discriminatedParent !== undefined) { | ||
const discriminatorType = discriminatedParent.properties[discriminatedParent.discriminator].type.type; | ||
let discriminatorValue = undefined; | ||
if (!this.shape.discriminatorValue.isNullOrEmpty) { | ||
discriminatorValue = this.shape.discriminatorValue.value(); | ||
} | ||
else if (discriminatorType === 'string') { | ||
discriminatorValue = this.shape.name.value(); | ||
} | ||
// if there is a mapping defined to change the discrimitator value for the current shape, | ||
// use it | ||
const ids = Array.from(resolveShapeIds(this.shape)); | ||
const discriminatorValueMapping = discriminatedParent.shape.discriminatorValueMapping.find((mapping) => Array.from(resolveShapeIds(mapping.targetShape)).filter((mappingId) => ids.includes(mappingId)).length > 0); | ||
if (discriminatorValueMapping) { | ||
discriminatorValue = discriminatorValueMapping.value.value(); | ||
} | ||
if (discriminatorValue !== undefined) { | ||
if (discriminatedParent.discriminatorValueMapping.find((element) => element.value === discriminatorValue) !== undefined) { | ||
this.shape.discriminatorMapping.forEach((el) => { | ||
var _a; | ||
const value = el.templateVariable.value(); | ||
// TODO: Grabbing the last value out of the link expression is pretty hacky. | ||
// We should lookup the type by ID, but I don't think we can do this today. | ||
const linkExpressionSplit = el.linkExpression.value().split('/'); | ||
const typeName = linkExpressionSplit[linkExpressionSplit.length - 1]; | ||
const type = this.typeRegistry.get(typeName); | ||
if (type === undefined) { | ||
const { line = 0, column = 0 } = ((_a = this.shape.position) === null || _a === void 0 ? void 0 : _a.start) || {}; | ||
this.logger.warn({ line, column }, `Duplicate discriminator value set for value ${discriminatorValue} on type ${discriminatedParent.shape.name}.`); | ||
this.logger.error({ line, column }, `Cannot find type ${typeName}`); | ||
throw new Error(); | ||
} | ||
discriminatedParent.discriminatorValueMapping.push({ | ||
value: coerceValueByType(discriminatorValue, discriminatedParent.properties[discriminatedParent.discriminator].type), | ||
type: this, | ||
this.discriminatorValueMapping.push({ | ||
value, | ||
type, | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
function getDiscriminatedParent(type) { | ||
if (type.discriminator !== undefined) { | ||
return type; | ||
} | ||
const discriminatedParents = getParentsWithDeclaredDiscriminators(type); | ||
if (discriminatedParents.size === 0) { | ||
return undefined; | ||
} | ||
return [...discriminatedParents.entries()][0][0]; | ||
} | ||
function validateDiscriminator(type, logger) { | ||
var _a; | ||
const { line = 0, column = 0 } = ((_a = type.shape.position) === null || _a === void 0 ? void 0 : _a.start) || {}; | ||
if (!type.shape.hasExplicitName) { | ||
logger.warn({ line, column }, `discriminator and discriminatorValue must not be defined in any inline type declarations`); | ||
return false; | ||
} | ||
if (bfs(type.inherits, (type) => type.type === 'union', (type) => type.inherits).size > 0) { | ||
logger.warn({ line, column }, `discriminator must not be defined in any union types`); | ||
return false; | ||
} | ||
const discriminator = type.shape.discriminator.value(); | ||
const discriminatorProperty = type.properties[discriminator]; | ||
if (discriminatorProperty === undefined) { | ||
logger.warn({ line, column }, `Invalid discriminator value "${discriminator}", the property does not exist in type "${type.shape.name}"`); | ||
return false; | ||
} | ||
if (!discriminatorProperty.required) { | ||
logger.warn({ line, column }, `Invalid discriminator value "${discriminator}" on type ${type.shape.name}, discriminator properties cannot be optional`); | ||
return false; | ||
} | ||
if (!DiscriminatorValueTypes.has(discriminatorProperty.type.type)) { | ||
logger.warn({ line, column }, `Invalid discriminator value "${discriminator}" on type ${type.shape.name}, discriminator properties must refer to string or number properties, got ${discriminatorProperty.type.type}`); | ||
return false; | ||
} | ||
return true; | ||
} | ||
function validateDiscriminatorValue(type, logger) { | ||
var _a; | ||
const discriminatedParent = type.getDiscriminatedParent(); | ||
const { line = 0, column = 0 } = ((_a = type.shape.position) === null || _a === void 0 ? void 0 : _a.start) || {}; | ||
if (discriminatedParent === undefined && !type.shape.discriminatorValue.isNullOrEmpty) { | ||
logger.warn({ line, column }, `Cannot set discriminator value on non-discriminated type`); | ||
return false; | ||
} | ||
return true; | ||
} | ||
function validateInheritance(type, logger) { | ||
var _a; | ||
const { line = 0, column = 0 } = ((_a = type.shape.position) === null || _a === void 0 ? void 0 : _a.start) || {}; | ||
if (getParentsWithDeclaredDiscriminators(type).size > 1) { | ||
logger.error({ line, column }, 'types can only inherit from one object that declares a discriminator'); | ||
return false; | ||
} | ||
return true; | ||
} | ||
function getParentsWithDeclaredDiscriminators(type) { | ||
return bfs(type.inherits, (type) => type.type === 'object' && type.discriminator !== undefined, (type) => type.inherits); | ||
} | ||
@@ -675,16 +588,4 @@ class AMFNilTypeImpl extends AMFBaseType { | ||
} | ||
class AMFTimeTypeImpl extends AMFEnumerableScalarType { | ||
constructor() { | ||
super(...arguments); | ||
this.type = 'time'; | ||
} | ||
} | ||
class AMFDateTimeOnlyTypeImpl extends AMFEnumerableScalarType { | ||
constructor() { | ||
super(...arguments); | ||
this.type = 'datetime-only'; | ||
} | ||
} | ||
const DATETIME_RFC_DEFAULT = 'rfc3339'; | ||
const DATETIME_RFC_2616 = 'rfc2616'; | ||
const DATE = 'date'; | ||
const DATE_TIME = 'date-time'; | ||
class AMFDateTimeImpl extends AMFEnumerableScalarType { | ||
@@ -696,12 +597,3 @@ constructor(shape, typeRegistry, factory, logger) { | ||
const formatValue = shape.format.value(); | ||
if (formatValue !== DATETIME_RFC_2616 && formatValue !== DATETIME_RFC_DEFAULT) { | ||
const { line, column } = this.shape.position.start; | ||
if (formatValue === null) { | ||
logger.info({ line, column }, `Defaulting datetime format to ${DATETIME_RFC_DEFAULT}`); | ||
} | ||
else { | ||
logger.warn({ line, column }, `Datetime format can only be '${DATETIME_RFC_2616}' or '${DATETIME_RFC_DEFAULT}', got ${formatValue}. Defaulting to '${DATETIME_RFC_DEFAULT}`); | ||
} | ||
} | ||
this.format = formatValue === DATETIME_RFC_2616 ? DATETIME_RFC_2616 : DATETIME_RFC_DEFAULT; | ||
this.format = formatValue === DATE ? DATE : DATE_TIME; | ||
} | ||
@@ -816,8 +708,2 @@ } | ||
break; | ||
case 'time': | ||
ctor = AMFTimeTypeImpl; | ||
break; | ||
case 'datetime-only': | ||
ctor = AMFDateTimeOnlyTypeImpl; | ||
break; | ||
case 'datetime': | ||
@@ -942,5 +828,7 @@ ctor = AMFDateTimeImpl; | ||
if (!this._endpoints) { | ||
this._endpoints = this.document.encodes.endPoints.map((endPoint) => { | ||
this._endpoints = this.document.encodes.endPoints | ||
.map((endPoint) => { | ||
return amfEndpointFactory(endPoint, amfTypeFactory, this.types, this.logger); | ||
}); | ||
}) | ||
.filter((endpoint) => endpoint !== undefined); | ||
} | ||
@@ -988,16 +876,11 @@ return this._endpoints; | ||
checkAnnotationsVersion() { | ||
var _a; | ||
const helper = buildExtensionsHelper(this.webApi.customDomainProperties); | ||
const version = helper.getStringPropertyValue(ANNOTATIONS_VERSION); | ||
if (((_a = this.document.sourceSpec) === null || _a === void 0 ? void 0 : _a.isOas) === true && version === undefined) { | ||
// mandatory in OAS files | ||
throw new Error(`Mandatory x-${ANNOTATIONS_VERSION} missing at document root`); | ||
const version = getExtensionValue(buildExtensions(this.webApi.customDomainProperties), ANNOTATIONS_VERSION, 'string'); | ||
if (version === undefined) { | ||
throw new Error(`Mandatory x-onestore.version missing at document root`); | ||
} | ||
if (version !== undefined) { | ||
// if specified, must match the major[.minor[.patch]] pattern | ||
const versionParts = version.split('.'); | ||
if (versionParts.length > 3 || versionParts.find((part) => !part.match(/^[0-9]+$/))) { | ||
if (!stringIsVersion(version)) { | ||
throw new Error(`Invalid x-${ANNOTATIONS_VERSION} value ${version} (expected format is major[.minor[.patch]])`); | ||
} | ||
if (!'1.0.0'.startsWith(versionParts.map((part) => new Number(part)).join('.'))) { | ||
if (!satisfies('1.0.0', version)) { | ||
throw new Error(`Unsupported x-${ANNOTATIONS_VERSION} value ${version}`); | ||
@@ -1034,2 +917,2 @@ } | ||
export { AMFAnyTypeImpl, AMFArrayTypeImpl, AMFBooleanTypeImpl, AMFDateTimeImpl, AMFDateTimeOnlyTypeImpl, AMFDateTypeImpl, AMFDoubleTypeImpl, AMFEnumerableScalarType, AMFIntegerTypeImpl, AMFNilTypeImpl, AMFNumberTypeImpl, AMFObjectTypeImpl, AMFStringTypeImpl, AMFTimeTypeImpl, AMFUnionTypeImpl, BaseTypeRegistry, DiscriminatorValueTypes, amfTypeFactory, getDiscriminatedParent, parseUrl, resolveShapeIds }; | ||
export { AMFAnyTypeImpl, AMFArrayTypeImpl, AMFBooleanTypeImpl, AMFDateTimeImpl, AMFDateTypeImpl, AMFDoubleTypeImpl, AMFEnumerableScalarType, AMFIntegerTypeImpl, AMFNilTypeImpl, AMFNumberTypeImpl, AMFObjectTypeImpl, AMFStringTypeImpl, AMFUnionTypeImpl, BaseTypeRegistry, amfTypeFactory, parseUrl, resolveShapeIds }; |
@@ -0,1 +1,2 @@ | ||
import { type FileParserLogger } from '@luvio/utils'; | ||
import type { AMFType } from './types'; | ||
@@ -5,3 +6,2 @@ import type { API, Server } from '../API'; | ||
import type * as amf from 'amf-client-js'; | ||
import type { FileParserLogger } from '@luvio/utils'; | ||
import type { EndPoint } from '../Endpoint'; | ||
@@ -8,0 +8,0 @@ export declare class AMFAPI implements API { |
@@ -1,2 +0,2 @@ | ||
import type { AuraController, EndPoint, Parameter } from '../../Endpoint'; | ||
import type { AuraController, AuraEndPoint, BaseEndPoint, HttpEndPoint, Parameter, Operation } from '../../Endpoint'; | ||
import type { AMFType, AMFTypeFactory } from '../types'; | ||
@@ -6,12 +6,21 @@ import type { TypeRegistry } from '../../TypeRegistry'; | ||
import type * as amf from 'amf-client-js'; | ||
import type { AmfOperation } from './amf-operation'; | ||
export declare class AmfEndPoint implements EndPoint { | ||
private amfEndPoint; | ||
operations: AmfOperation[]; | ||
private logger; | ||
export type AmfEndPoint = AmfAuraEndpoint | AmfHttpEndPoint; | ||
export declare function amfEndpointFactory(amfEndpoint: amf.EndPoint, amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger): AmfEndPoint | undefined; | ||
export declare abstract class AmfBaseEndpoint implements BaseEndPoint { | ||
protected amfEndpoint: amf.EndPoint; | ||
operations: ReadonlyArray<Operation>; | ||
protected logger: FileParserLogger; | ||
path: string; | ||
uriParameters: Record<string, Parameter>; | ||
auraController?: AuraController; | ||
constructor(amfEndPoint: amf.EndPoint, operations: AmfOperation[], amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
buildAuraController(): AuraController | undefined; | ||
abstract type: 'aura' | 'http'; | ||
constructor(amfEndpoint: amf.EndPoint, operations: ReadonlyArray<Operation>, amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
} | ||
export declare class AmfHttpEndPoint extends AmfBaseEndpoint implements HttpEndPoint { | ||
readonly type = "http"; | ||
} | ||
export declare class AmfAuraEndpoint extends AmfBaseEndpoint implements AuraEndPoint { | ||
readonly type = "aura"; | ||
auraController: AuraController; | ||
constructor(amfEndPoint: amf.EndPoint, operations: Operation[], amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
buildAuraController(): AuraController; | ||
} |
import { type FileParserLogger } from '@luvio/utils'; | ||
import type * as amf from 'amf-client-js'; | ||
import type { AuraMethod, HttpMethod, Operation, Parameter, Payload, Request, Response } from '../../Endpoint'; | ||
import type { AuraReadOperation, AuraMutationOperation, BaseOperation, HttpMethod, Parameter, Payload, Request, Response, HttpOperation } from '../../Endpoint'; | ||
import type { AMFType, AMFTypeFactory } from '../types'; | ||
import type { TypeRegistry } from '../../TypeRegistry'; | ||
export declare class AmfOperation implements Operation { | ||
private amfOperation; | ||
private amfTypeFactory; | ||
private typeRegistry; | ||
private logger; | ||
export declare abstract class AmfBaseOperation implements BaseOperation { | ||
protected amfOperation: amf.Operation; | ||
protected amfTypeFactory: AMFTypeFactory; | ||
protected typeRegistry: TypeRegistry<AMFType>; | ||
protected logger: FileParserLogger; | ||
method: HttpMethod; | ||
@@ -15,7 +15,34 @@ operationId: string; | ||
responses: Response[]; | ||
auraMethod?: AuraMethod; | ||
operationType: 'query' | 'mutation'; | ||
abstract readonly type: 'aura' | 'http'; | ||
constructor(amfOperation: amf.Operation, amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
buildPayloads(r: amf.Request | amf.Response): Payload[]; | ||
buildParams(p: amf.Parameter[]): Record<string, Parameter>; | ||
buildAuraMethod(): AuraMethod | undefined; | ||
} | ||
export declare class AmfHttpOperation extends AmfBaseOperation implements HttpOperation { | ||
readonly type = "http"; | ||
} | ||
export declare class AmfAuraReadOperation extends AmfBaseOperation implements AuraReadOperation { | ||
readonly type = "aura"; | ||
method: 'GET'; | ||
auraMethod: { | ||
name: string; | ||
}; | ||
constructor(amfOperation: amf.Operation, amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
buildAuraMethod(): { | ||
name: string; | ||
}; | ||
} | ||
export declare class AmfAuraMutationOperation extends AmfBaseOperation implements AuraMutationOperation { | ||
readonly type = "aura"; | ||
method: 'POST' | 'PATCH' | 'PUT'; | ||
auraMethod: { | ||
name: string; | ||
inputPayloadParamName: string; | ||
}; | ||
constructor(amfOperation: amf.Operation, amfTypeFactory: AMFTypeFactory, typeRegistry: TypeRegistry<AMFType>, logger: FileParserLogger); | ||
buildAuraMethod(): { | ||
name: string; | ||
inputPayloadParamName: string; | ||
}; | ||
} |
@@ -16,11 +16,3 @@ import { type AMFTypeProps } from '.'; | ||
}[]; | ||
getDiscriminatedParent(): (AMFObjectType & { | ||
discriminator: string; | ||
}) | undefined; | ||
typeResolve(): this | undefined; | ||
resolveDiscriminator(): void; | ||
resolveDiscriminatorValue(): void; | ||
typeResolve(): void; | ||
} | ||
export declare function getDiscriminatedParent(type: AMFObjectType): (AMFObjectType & { | ||
discriminator: string; | ||
}) | undefined; |
@@ -38,13 +38,10 @@ import { AMFBaseType } from './AMFBaseType'; | ||
} | ||
export declare class AMFTimeTypeImpl extends AMFEnumerableScalarType<string, 'time'> implements AMFTimeType { | ||
type: "time"; | ||
} | ||
export declare class AMFDateTimeOnlyTypeImpl extends AMFEnumerableScalarType<string, 'datetime-only'> implements AMFDateTimeOnlyType { | ||
type: "datetime-only"; | ||
} | ||
declare const DATE = "date"; | ||
declare const DATE_TIME = "date-time"; | ||
export declare class AMFDateTimeImpl extends AMFEnumerableScalarType<string, 'datetime'> implements AMFDateTimeType { | ||
shape: amf.ScalarShape; | ||
type: "datetime"; | ||
format: 'rfc3339' | 'rfc2616'; | ||
format: typeof DATE | typeof DATE_TIME; | ||
constructor(shape: amf.ScalarShape, typeRegistry: TypeRegistry<AMFType>, factory: AMFTypeFactory, logger: FileParserLogger); | ||
} | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import type { EndPoint, Parameter } from './Endpoint'; | ||
import type { EndPoint, UrlParameter } from './Endpoint'; | ||
import type { ReadOnlyTypeRegistry } from './TypeRegistry'; | ||
@@ -19,3 +19,3 @@ export type API = { | ||
url: string; | ||
uriParameters: Record<string, Parameter>; | ||
uriParameters: Record<string, UrlParameter>; | ||
}; |
import type { ArrayType, ScalarType, Type } from './Type'; | ||
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE'; | ||
export type StatusCode = '100' | '101' | '102' | '103' | '200' | '201' | '202' | '203' | '204' | '205' | '206' | '207' | '208' | '226' | '300' | '301' | '302' | '303' | '304' | '305' | '306' | '307' | '308' | '400' | '401' | '402' | '403' | '404' | '405' | '406' | '407' | '408' | '409' | '410' | '411' | '412' | '413' | '414' | '415' | '416' | '417' | '418' | '421' | '422' | '423' | '424' | '425' | '426' | '428' | '429' | '431' | '451' | '500' | '501' | '502' | '503' | '504' | '505' | '506' | '507' | '508' | '510' | '511'; | ||
export type Parameter<T = ScalarType> = { | ||
export type Parameter<T extends Type = ScalarType> = { | ||
type: T; | ||
required: boolean; | ||
}; | ||
export type UrlParameter = Parameter<ScalarType>; | ||
export type QueryParameter = Parameter<ScalarType | (ArrayType & { | ||
items?: ScalarType; | ||
})>; | ||
export type TypeOfParameter<P> = P extends Parameter<infer T> ? T : never; | ||
export type TypeOfParameter<P extends Parameter<any>> = P extends Parameter<infer T> ? T : never; | ||
export type ApplicationJsonContent<T extends Type> = { | ||
@@ -33,15 +34,32 @@ mediaType: 'application/json'; | ||
}; | ||
export type AuraMethod = { | ||
name?: string; | ||
inputPayloadParamName?: string; | ||
}; | ||
export type Operation<ResponseType extends Type = Type, RequestType extends Type = Type> = { | ||
export type BaseOperation<ResponseType extends Type = Type, RequestType extends Type = Type> = { | ||
readonly type: 'aura' | 'http'; | ||
method: HttpMethod; | ||
operationType: 'query' | 'mutation'; | ||
operationId: string; | ||
requests: Request<RequestType>[]; | ||
responses: Response<ResponseType>[]; | ||
auraMethod?: AuraMethod; | ||
}; | ||
export type AuraReadOperation = BaseOperation & { | ||
type: 'aura'; | ||
method: 'GET'; | ||
auraMethod: { | ||
name: string; | ||
}; | ||
}; | ||
export type AuraMutationOperation = BaseOperation & { | ||
type: 'aura'; | ||
method: 'POST' | 'PUT' | 'PATCH'; | ||
auraMethod: { | ||
name: string; | ||
inputPayloadParamName: string; | ||
}; | ||
}; | ||
export type HttpOperation = BaseOperation & { | ||
type: 'http'; | ||
}; | ||
export type AuraOperation = AuraReadOperation | AuraMutationOperation; | ||
export type Operation = HttpOperation | AuraOperation; | ||
export type AuraController = { | ||
name?: string; | ||
name: string; | ||
}; | ||
@@ -51,7 +69,14 @@ /** | ||
*/ | ||
export type EndPoint = { | ||
export type BaseEndPoint = { | ||
readonly type: 'aura' | 'http'; | ||
path: string; | ||
uriParameters: Record<string, Parameter>; | ||
operations: ReadonlyArray<Operation>; | ||
auraController?: AuraController; | ||
}; | ||
export type AuraEndPoint = BaseEndPoint & { | ||
auraController: AuraController; | ||
}; | ||
export type HttpEndPoint = BaseEndPoint & { | ||
type: 'http'; | ||
}; | ||
export type EndPoint = HttpEndPoint | AuraEndPoint; |
@@ -42,3 +42,3 @@ type BaseType = { | ||
type: 'datetime'; | ||
format: 'rfc3339' | 'rfc2616'; | ||
format: 'date' | 'date-time'; | ||
values?: Array<string>; | ||
@@ -51,4 +51,3 @@ } & BaseType; | ||
}; | ||
export type DiscriminatorValue = string | number | boolean; | ||
export declare const DiscriminatorValueTypes: Set<"string" | "number" | "boolean" | "object" | "nil" | "double" | "integer" | "date" | "time" | "datetime-only" | "datetime" | "array" | "union" | "any">; | ||
export type DiscriminatorValue = string; | ||
export type PatternProperty = Property & { | ||
@@ -61,5 +60,2 @@ pattern: string; | ||
patternProperties: PatternProperty[]; | ||
getDiscriminatedParent: () => (ObjectType & { | ||
discriminator: string; | ||
}) | undefined; | ||
discriminator: string | undefined; | ||
@@ -66,0 +62,0 @@ discriminatorValueMapping: { |
{ | ||
"name": "@luvio/model", | ||
"version": "5.5.0", | ||
"version": "5.6.0", | ||
"description": "Luvio model", | ||
@@ -27,3 +27,3 @@ "repository": { | ||
"devDependencies": { | ||
"@luvio/utils": "^5.5.0" | ||
"@luvio/utils": "^5.6.0" | ||
}, | ||
@@ -30,0 +30,0 @@ "volta": { |
57850
24
1388