UNPKG

@superhero/oas

Version:

OpenAPI Specification (OAS) router

1,303 lines (1,161 loc) 35.5 kB
import deepmerge from '@superhero/deep/merge' import deepclone from '@superhero/deep/clone' import net from 'node:net' import ComponentsAbstraction from './abstraction.js' /** * @memberof Oas.Components */ export default class Schemas extends ComponentsAbstraction { validComponentAttributes = [ 'title', 'description', 'type', 'default', 'deprecated', 'example', 'examples', 'nullable', 'readOnly', 'writeOnly', 'externalDocs', 'minLength', 'maxLength', 'pattern', 'format', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'items', 'minItems', 'maxItems', 'uniqueItems', 'prefixItems', 'properties', 'required', 'additionalProperties', 'minProperties', 'maxProperties', 'propertyNames', 'allOf', 'oneOf', 'anyOf', 'not', 'const', 'enum', 'additionalItems', 'if', 'then', 'else', '$ref' ] validateNumberFormat = new Map validateStringFormat = new Map constructor() { super() this.validateNumberFormat.set('float', this.validateNumberFormatFloat) this.validateNumberFormat.set('double', this.validateNumberFormatDouble) this.validateNumberFormat.set('int32', this.validateNumberFormatInt32) this.validateNumberFormat.set('int64', this.validateNumberFormatInt64) this.validateStringFormat.set('date', this.validateStringFormatDate) this.validateStringFormat.set('time', this.validateStringFormatTime) this.validateStringFormat.set('datetime', this.validateStringFormatDatetime) this.validateStringFormat.set('date-time', this.validateStringFormatDatetime) this.validateStringFormat.set('base64', this.validateStringFormatBase64) this.validateStringFormat.set('byte', this.validateStringFormatBase64) this.validateStringFormat.set('email', this.validateStringFormatEmail) this.validateStringFormat.set('ipv4', this.validateStringFormatIpv4) this.validateStringFormat.set('ipv6', this.validateStringFormatIpv6) this.validateStringFormat.set('url', this.validateStringFormatUrl) this.validateStringFormat.set('uuid', this.validateStringFormatUuid) } conform(component, instance, isWriting) { if(component.$ref) { return this.conformRef(component.$ref, instance, isWriting) } if('readOnly' in component && true === !!isWriting) { return } if('writeOnly' in component && false === !!isWriting) { return } instance = this.conformDefault(component, instance) instance = this.conformIfThenElse(component, instance, isWriting) instance = this.conformAllOf(component, instance, isWriting) instance = this.conformAnyOf(component, instance, isWriting) instance = this.conformOneOf(component, instance, isWriting) const instanceType = Object.prototype.toString.call(instance) if('[object Null]' !== instanceType && '[object Undefined]' !== instanceType) { switch(component.type) { case 'array': { instance = this.conformTypeArray(component, instance, instanceType, isWriting) break } case 'boolean': { instance = this.conformTypeBoolean(instance, instanceType) break } case 'integer': case 'number': { instance = this.conformTypeNumber(component, instance, instanceType) break } case 'object': { instance = this.conformTypeObject(component, instance, instanceType, isWriting) break } case 'string': { instance = this.conformTypeString(component, instance, instanceType) break } case 'null': { instance = this.conformTypeNull(component, instance, instanceType) break } case undefined: { break } default: { const error = new Error(`Invalid component type ${component.type}`) error.code = 'E_OAS_INVALID_SPECIFICATION' throw error } } } if(null === instance && true !== component.nullable && 'null' !== component.type) { const error = new Error(`Invalid instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = 'The instance must NOT be null' throw error } this.validateNot(component, instance, isWriting) this.validateConst(component, instance) this.validateEnum(component, instance) return instance } conformDefault(component, instance) { if('default' in component) { if(undefined === instance) { instance = component.default } } return instance } conformIfThenElse(component, instance, isWriting) { if('if' in component) { if('then' in component || 'else' in component) { try { this.conform(component.if, instance, isWriting) if('then' in component) { instance = this.conform(component.then, instance, isWriting) } } catch(error) { if('else' in component) { instance = this.conform(component.else, instance, isWriting) } } } } return instance } conformAllOf(component, instance, isWriting) { if('allOf' in component) { const instances = [] for(const allOfComponent of component.allOf) { instances.push(this.conform(allOfComponent, instance, isWriting)) } instance = deepmerge(...instances) } return instance } conformAnyOf(component, instance, isWriting) { if('anyOf' in component) { let errors = 0 for(const anyOfComponent of component.anyOf) { try { instance = this.conform(anyOfComponent, instance, isWriting) break } catch(error) { errors++ } } if(errors === component.anyOf.length) { const error = new Error(`Invalid instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The instance must match at least one of the "anyOf" specifiations') throw error } } return instance } conformOneOf(component, instance, isWriting) { if('oneOf' in component) { let errors = 0 for(const oneOfComponent of component.oneOf) { try { instance = this.conform(oneOfComponent, instance, isWriting) } catch(error) { errors++ } } if(errors !== component.oneOf.length - 1) { const error = new Error(`Invalid instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = 'The instance must match exactly one of the "oneOf" specifiations' throw error } } return instance } conformTypeArray(component, instance, instanceType, isWriting) { this.validateTypeArrayInstanceType(instanceType) instance = this.conformTypeArrayItems(component, instance, isWriting) this.validateTypeArrayAdditionalItems(component, instance) this.validateTypeArrayMinItems(component, instance) this.validateTypeArrayMaxItems(component, instance) instance = this.validateTypeArrayUniqueItems(component, instance) return instance } // Attempting to support both 3.0 and 3.1 OAS tuple validation conformTypeArrayItems(component, instance, isWriting) { if(false === !!component.items) { const error = new Error(`Invalid "array" schema component`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = 'The schema type: "array" must have an "items" attribute' throw error } if(Array.isArray(component.items) && component.items.length === 0) { const error = new Error(`Invalid "array" schema component`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = 'The schema type: "array" must have an "items" attribute with at least one item' throw error } const items = Array.isArray(component.items) ? component.items : [ component.items ], output = [], conform = (itemComponent, itemInstance) => { const conformed = this.conform(itemComponent, itemInstance, isWriting) if(conformed !== undefined || itemInstance === undefined) { output.push(conformed) } } let i = 0 if(component.prefixItems) { const prefixItems = Array.isArray(component.prefixItems) ? component.prefixItems : [ component.prefixItems ] for(; i < instance.length && i < prefixItems.length; i++) { conform(prefixItems[i], instance[i]) } } for(let n = 0; i < instance.length; i++, n++) { conform(items[n % items.length], instance[i]) } return output } conformTypeBoolean(instance, instanceType) { if('[object Boolean]' !== instanceType) { switch(String(instance).toLowerCase().trim()) { case 'true': case 'on': case 'yes': case '1': { instance = true break } case 'false': case 'off': case 'no': case '0': { instance = false break } default: { const error = new Error(`Invalid boolean instance type ${instanceType}`) error.code = 'E_OAS_INVALID_INSTANCE' throw error } } } return instance } conformTypeNumber(component, instance, instanceType) { instance = this.transformTypeNumber(instance, instanceType) this.validateTypeNumberInstanceType(component, instance) this.validateTypeNumberInteger(component, instance) this.validateTypeNumberMinimum(component, instance) this.validateTypeNumberMaximum(component, instance) this.validateTypeNumberMultipleOf(component, instance) this.validateTypeNumberFormat(component, instance) return instance } transformTypeNumber(instance, instanceType) { switch(instanceType) { case '[object String]' : return Number(instance) case '[object Date]' : return instance.getTime() default : return instance } } conformTypeObject(component, instance, instanceType, isWriting) { this.validateTypeObjectInstanceType(instanceType) instance = this.confirmTypeObjectProperties(component, instance, isWriting) this.validateTypeObjectRequired(component, instance) this.validateTypeObjectAdditionalProperties(component, instance) this.validateTypeObjectMinProperties(component, instance) this.validateTypeObjectMaxProperties(component, instance) this.validateTypeObjectPropertyNames(component, instance) return instance } confirmTypeObjectProperties(component, instance, isWriting) { const output = deepclone(instance) if('properties' in component) { for(const name in component.properties) { output[name] = this.conform(component.properties[name], output[name], isWriting) if(undefined === output[name]) { delete output[name] } } } return output } conformTypeString(component, instance, instanceType) { this.validateTypeStringInstanceType(instanceType) this.validateTypeStringMinLength(component, instance) this.validateTypeStringMaxLength(component, instance) this.validateTypeStringPattern(component, instance) this.validateTypeStringFormat(component, instance) return instance } conformTypeNull(component, instance, instanceType) { if(null !== instance) { switch(String(instance).toLowerCase().trim()) { case 'null': case '': { instance = null break } default: { const error = new Error(`Invalid null instance type ${instanceType}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = 'The instance value must be null' throw error } } } return instance } validateRefPointer(pointer) { super.validateRefPointer(pointer) if(false === pointer.startsWith('/components/schemas/')) { const error = new Error(`The ref pointer "#${pointer}" must point to a schema component`) error.code = 'E_OAS_INVALID_SPECIFICATION' throw error } } validateComponent(component) { super.validateComponent(component) const invalidAttributes = [] switch(component.type) { case 'array': { this.validateComponentArray(component, invalidAttributes) break } case 'boolean': { this.validateComponentBoolean(component, invalidAttributes) break } case 'integer': case 'number': { this.validateComponentNumber(component, invalidAttributes) break } case 'string': { this.validateComponentString(component, invalidAttributes) break } case 'object': { this.validateComponentObject(component, invalidAttributes) break } case 'null': { this.validateComponentNull(component, invalidAttributes) break } case undefined: { break } default: { const error = new Error(`Invalid component type ${component.type}`) error.code = 'E_OAS_INVALID_SPECIFICATION' throw error } } for(const invalidAttribute of invalidAttributes) { if(invalidAttribute in component) { const error = new Error(`Invalid attribute "${invalidAttribute}" for type ${component.type}`) error.code = 'E_OAS_INVALID_SPECIFICATION' throw error } } } validateComponentArray(component, invalidAttributes) { if('enum' in component) { for(const item of component.enum) { if(null === item && component.nullable) { continue } if(false === Array.isArray(item)) { const error = new Error('Invalid component enum') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The component enum must be an array of arrays') throw error } } } if(false === !!component.prefixItems && false === !!component.items) { const error = new Error('Invalid component array') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = 'The component array must have an "items" attribute' throw error } invalidAttributes.push( 'pattern', 'minimum', 'maximum', 'minLength', 'maxLength', 'format', 'properties', 'minProperties', 'maxProperties', 'additionalProperties', 'required', 'propertyNames', 'multipleOf') } validateComponentBoolean(component, invalidAttributes) { if('enum' in component) { for(const item of component.enum) { if('boolean' !== typeof item) { if(null === item && component.nullable) { continue } const error = new Error('Invalid component enum') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The component enum must be an array of booleans') throw error } } } invalidAttributes.push( 'pattern', 'minimum', 'maximum', 'minLength', 'maxLength', 'format', 'properties', 'minProperties', 'items', 'minItems', 'maxProperties', 'additionalProperties', 'maxItems', 'required', 'uniqueItems', 'propertyNames', 'multipleOf') } validateComponentNumber(component, invalidAttributes) { if('enum' in component) { for(const item of component.enum) { if('number' !== typeof item) { if(null === item && component.nullable) { continue } const error = new Error('Invalid component enum') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The component enum must be an array of numbers') throw error } } } invalidAttributes.push( 'items', 'properties', 'minItems', 'maxItems', 'minLength', 'maxLength', 'pattern', 'minProperties', 'maxProperties', 'additionalProperties', 'required', 'uniqueItems', 'propertyNames') } validateComponentString(component, invalidAttributes) { if('enum' in component) { for(const item of component.enum) { if('string' !== typeof item) { if(null === item && component.nullable) { continue } const error = new Error('Invalid component enum') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The component enum must be an array of strings') throw error } } } invalidAttributes.push( 'items', 'minimum', 'maximum', 'properties', 'minItems', 'maxItems', 'minProperties', 'maxProperties', 'propertyNames', 'additionalProperties', 'required', 'uniqueItems', 'multipleOf') } validateComponentObject(component, invalidAttributes) { if('enum' in component) { for(const item of component.enum) { if('[object Object]' !== Object.prototype.toString.call(item)) { if(null === item && component.nullable) { continue } const error = new Error('Invalid component enum') error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The component enum must be an array of objects') throw error } } } invalidAttributes.push( 'pattern', 'minimum', 'maximum', 'minLength', 'maxLength', 'items', 'minItems', 'maxItems', 'uniqueItems', 'multipleOf') } validateComponentNull(component, invalidAttributes) { invalidAttributes.push( 'pattern', 'minimum', 'maximum', 'minLength', 'maxLength', 'items', 'properties', 'minItems', 'maxItems', 'minProperties', 'maxProperties', 'propertyNames', 'additionalProperties', 'required', 'uniqueItems', 'multipleOf', 'format') } validateTypeArrayInstanceType(instanceType) { if('[object Array]' !== instanceType) { const error = new Error(`Invalid array instance type ${instanceType}`) error.code = 'E_OAS_INVALID_INSTANCE' throw error } } validateTypeArrayAdditionalItems(component, instance) { if('additionalItems' in component) { if(false === component.additionalItems) { const items = Array.isArray(component.items) ? component.items : [ component.items ] if(instance.length > items.length) { const error = new Error(`Invalid amount of array items ${instance.length}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The array can not have additional items') throw error } } } } validateTypeArrayMinItems(component, instance) { if('minItems' in component) { if(instance.length < component.minItems) { const error = new Error(`Invalid amount of array items ${instance.length}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The array must have at least ${component.minItems} items`) throw error } } } validateTypeArrayMaxItems(component, instance) { if('maxItems' in component) { if(instance.length > component.maxItems) { const error = new Error(`Invalid amount of array items ${instance.length}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The array can not have more than ${component.maxItems} items`) throw error } } } validateTypeArrayUniqueItems(component, instance) { if(component.uniqueItems) { const uniqueItems = [] next : for(let i = 0; i < instance.length; i++) { for(let n = i + 1; n < instance.length; n++) { try { this.deepEqual(instance[i], instance[n]) continue next // not unique, so continue to next } catch(error) { continue } } // If we reach this point, then // the item is unique. uniqueItems.push(instance[i]) } instance = uniqueItems } return instance } validateTypeNumberInstanceType(component, instance) { if(false === Number.isFinite(instance)) { const error = new Error(`Invalid ${component.type} instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = `The instance: "${instance}" is not a valid ${component.type}` throw error } } validateTypeNumberInteger(component, instance) { if('integer' === component.type && false === Number.isInteger(instance)) { const error = new Error(`Invalid ${component.type} insance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} is not an integer`) throw error } } validateTypeNumberMinimum(component, instance) { if('minimum' in component) { if(component.exclusiveMinimum) { if(instance <= component.minimum) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be greater than ${component.minimum}`) throw error } } else { if(instance < component.minimum) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be greater than or equal to ${component.minimum}`) throw error } } } } validateTypeNumberMaximum(component, instance) { if('maximum' in component) { if(component.exclusiveMaximum) { if(instance >= component.maximum) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be less than ${component.maximum}`) throw error } } else { if(instance > component.maximum) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be less than or equal to ${component.maximum}`) throw error } } } } validateTypeNumberMultipleOf(component, instance) { if('multipleOf' in component) { if(instance % component.multipleOf) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be a multiple of ${component.multipleOf}`) throw error } } } validateTypeNumberFormat(component, instance) { if('format' in component) { if(false === this.validateNumberFormat.has(component.format)) { const error = new Error(`Invalid format ${component.format}`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The provided format is unknown') throw error } const validateNumberFormat = this.validateNumberFormat.get(component.format) validateNumberFormat(instance) } } validateNumberFormatFloat(instance) { // no validation required imo } validateNumberFormatDouble(instance) { // no validation required imo } validateNumberFormatInt32(instance) { if(instance < -2147483648 || instance > 2147483647) { const error = new Error(`Invalid int32 format ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The int32 instance must be between -2147483648 and 2147483647') throw error } } validateNumberFormatInt64(instance) { if(instance < -9007199254740991 || instance > 9007199254740991) { const error = new Error(`Invalid int64 format ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The int64 instance must be between -9007199254740991 and 9007199254740991') throw error } } validateTypeObjectInstanceType(instanceType) { if('[object Object]' !== instanceType) { const error = new Error(`Invalid object instance type ${instanceType}`) error.code = 'E_OAS_INVALID_INSTANCE' throw error } } validateTypeObjectRequired(component, instance) { if('required' in component) { for(const name of component.required) { if(undefined === instance[name]) { const error = new Error(`Invalid object instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The object must have a property named ${name}`) throw error } } } } validateTypeObjectAdditionalProperties(component, instance) { if(false === component.additionalProperties) { const properties = new Set(Object.keys(component.properties ?? {})) for(const name in instance) { if(false === properties.has(name)) { delete instance[name] } } } } validateTypeObjectMinProperties(component, instance) { if('minProperties' in component) { if(Object.keys(instance).length < component.minProperties) { const error = new Error(`Invalid amount of object instance properties`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The object must have at least ${component.minProperties} properties`) throw error } } } validateTypeObjectMaxProperties(component, instance) { if('maxProperties' in component) { if(Object.keys(instance).length > component.maxProperties) { const error = new Error(`Invalid amount of object instance properties`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The object can not have more than ${component.maxProperties} properties`) throw error } } } validateTypeObjectPropertyNames(component, instance) { if('propertyNames' in component) { let regexp if('pattern' in component.propertyNames) { try { regexp = new RegExp(component.propertyNames.pattern) } catch(reason) { const error = new Error(`Invalid regexp pattern ${component.propertyNames.pattern}`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = reason throw error } for(const name in instance) { if(false === regexp.test(name)) { const error = new Error(`Invalid object instance property name ${name}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The object property name must match the pattern ${component.propertyNames.pattern}`) throw error } } } else { const error = new Error(`Invalid propertyNames in component`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The propertyNames component must have a "pattern" attribute') throw error } } } validateTypeStringInstanceType(instanceType) { if('[object String]' !== instanceType) { const error = new Error(`Invalid string instance type ${instanceType}`) error.code = 'E_OAS_INVALID_INSTANCE' throw error } } validateTypeStringMinLength(component, instance) { if('minLength' in component) { if(instance.length < component.minLength) { const error = new Error(`Invalid string instance length ${instance.length}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The string must have at least ${component.minLength} characters`) throw error } } } validateTypeStringMaxLength(component, instance) { if('maxLength' in component) { if(instance.length > component.maxLength) { const error = new Error(`Invalid string instance length ${instance.length}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The string can not have more than ${component.maxLength} characters`) throw error } } } validateTypeStringPattern(component, instance) { if('pattern' in component) { let regexp try { regexp = new RegExp(component.pattern) } catch(reason) { const error = new Error(`Invalid regexp pattern ${component.pattern}`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = reason throw error } if(false === regexp.test(instance)) { const error = new Error(`Invalid string instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The string must match the pattern ${component.pattern}`) throw error } } } validateTypeStringFormat(component, instance) { if('format' in component) { if(false === this.validateStringFormat.has(component.format)) { const error = new Error(`Invalid format ${component.format}`) error.code = 'E_OAS_INVALID_SPECIFICATION' error.cause = new Error('The provided format is unknown') throw error } const validateStringFormat = this.validateStringFormat.get(component.format) validateStringFormat(instance) } } validateNot(component, instance, isWriting) { if('not' in component) { let valid = false try { this.conform(component.not, instance, isWriting) } catch(error) { valid = true } if(false === valid) { const error = new Error(`Invalid instance`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The instance must not match the "not" specifiations') throw error } } } validateConst(component, instance) { if('const' in component) { try { this.deepEqual(instance, component.const) } catch(reason) { const error = new Error(`Invalid instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The instance must be equal to ${component.const}`) error.cause.cause = reason throw error } } } validateEnum(component, instance) { if('enum' in component) { let errors = 0 for(const item of component.enum) { try { this.deepEqual(instance, item) break } catch(error) { errors++ } } if(errors === component.enum.length) { const enumsJson = component.enum.map(JSON.stringify), enumsList = this.listFormat.format(enumsJson) if(component.type) { const error = new Error(`Invalid ${component.type} instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The ${component.type} must be one of: ${enumsList}`) throw error } else { const error = new Error(`Invalid instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error(`The instance must be one of: ${enumsList}`) throw error } } } } validateStringFormatDate(instance) { const date = new Date(instance).toJSON()?.slice(0, 10) if(date !== instance) { const error = new Error(`Invalid date instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The date must be in the format YYYY-MM-DD') throw error } } validateStringFormatTime(instance) { const time = new Date('2000-01-01T' + instance + 'Z').toJSON()?.slice(11, 23) if(false === !!time?.startsWith(instance)) { const error = new Error(`Invalid time instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The time must be in the format HH:MM:SS[.sss]') throw error } } validateStringFormatDatetime(instance) { if(isNaN(new Date(instance))) { const error = new Error(`Invalid datetime instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The datetime must be a valid date') throw error } } validateStringFormatBase64(instance) { if(false === /^[a-z0-9\+\/]*={0,2}$/i.test(instance)) { const error = new Error(`Invalid base64 instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The base64 must be in the format aGVsbG8=') throw error } } validateStringFormatEmail(instance) { if(false === /^[^@]+@[^@]+$/.test(instance)) { const error = new Error(`Invalid email instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The email must be in the format example@domain') throw error } } validateStringFormatIpv4(instance) { if(false === net.isIPv4(instance)) { const error = new Error(`Invalid ipv4 instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The ipv4 must be in valid ipv4 format') throw error } } validateStringFormatIpv6(instance) { if(false === net.isIPv6(instance)) { const error = new Error(`Invalid ipv6 instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The ipv6 must be in valid ipv6 format') throw error } } validateStringFormatUrl(instance) { try { new URL(instance) } catch(reason) { const error = new Error(`Invalid URL instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = reason throw error } } // RFC 4122. Section 3. Format validateStringFormatUuid(instance) { if(false === /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(instance)) { const error = new Error(`Invalid UUID instance ${instance}`) error.code = 'E_OAS_INVALID_INSTANCE' error.cause = new Error('The UUID must be in the format FFFFFFFF-FFFF-5FFF-BFFF-FFFFFFFFFFFF') throw error } } }