Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
150 lines
3.8 KiB
Plaintext
150 lines
3.8 KiB
Plaintext
import {isPlainObject} from 'lodash'
|
|
import {isCompound, JSONSchema, SchemaType} from './types/JSONSchema'
|
|
|
|
/**
|
|
* Duck types a JSONSchema schema or property to determine which kind of AST node to parse it into.
|
|
*
|
|
* Due to what some might say is an oversight in the JSON-Schema spec, a given schema may
|
|
* implicitly be an *intersection* of multiple JSON-Schema directives (ie. multiple TypeScript
|
|
* types). The spec leaves it up to implementations to decide what to do with this
|
|
* loosely-defined behavior.
|
|
*/
|
|
export function typesOfSchema(schema: JSONSchema): Set<SchemaType> {
|
|
// tsType is an escape hatch that supercedes all other directives
|
|
if (schema.tsType) {
|
|
return new Set(['CUSTOM_TYPE'])
|
|
}
|
|
|
|
// Collect matched types
|
|
const matchedTypes = new Set<SchemaType>()
|
|
for (const [schemaType, f] of Object.entries(matchers)) {
|
|
if (f(schema)) {
|
|
matchedTypes.add(schemaType as SchemaType)
|
|
}
|
|
}
|
|
|
|
// Default to an unnamed schema
|
|
if (!matchedTypes.size) {
|
|
matchedTypes.add('UNNAMED_SCHEMA')
|
|
}
|
|
|
|
return matchedTypes
|
|
}
|
|
|
|
const matchers: Record<SchemaType, (schema: JSONSchema) => boolean> = {
|
|
ALL_OF(schema) {
|
|
return 'allOf' in schema
|
|
},
|
|
ANY(schema) {
|
|
if (Object.keys(schema).length === 0) {
|
|
// The empty schema {} validates any value
|
|
// @see https://json-schema.org/draft-07/json-schema-core.html#rfc.section.4.3.1
|
|
return true
|
|
}
|
|
return schema.type === 'any'
|
|
},
|
|
ANY_OF(schema) {
|
|
return 'anyOf' in schema
|
|
},
|
|
BOOLEAN(schema) {
|
|
if ('enum' in schema) {
|
|
return false
|
|
}
|
|
if (schema.type === 'boolean') {
|
|
return true
|
|
}
|
|
if (!isCompound(schema) && typeof schema.default === 'boolean') {
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
CUSTOM_TYPE() {
|
|
return false // Explicitly handled before we try to match
|
|
},
|
|
NAMED_ENUM(schema) {
|
|
return 'enum' in schema && 'tsEnumNames' in schema
|
|
},
|
|
NAMED_SCHEMA(schema) {
|
|
// 8.2.1. The presence of "$id" in a subschema indicates that the subschema constitutes a distinct schema resource within a single schema document.
|
|
return '$id' in schema && ('patternProperties' in schema || 'properties' in schema)
|
|
},
|
|
NEVER(schema: JSONSchema | boolean) {
|
|
return schema === false
|
|
},
|
|
NULL(schema) {
|
|
return schema.type === 'null'
|
|
},
|
|
NUMBER(schema) {
|
|
if ('enum' in schema) {
|
|
return false
|
|
}
|
|
if (schema.type === 'integer' || schema.type === 'number') {
|
|
return true
|
|
}
|
|
if (!isCompound(schema) && typeof schema.default === 'number') {
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
OBJECT(schema) {
|
|
return (
|
|
schema.type === 'object' &&
|
|
!isPlainObject(schema.additionalProperties) &&
|
|
!schema.allOf &&
|
|
!schema.anyOf &&
|
|
!schema.oneOf &&
|
|
!schema.patternProperties &&
|
|
!schema.properties &&
|
|
!schema.required
|
|
)
|
|
},
|
|
ONE_OF(schema) {
|
|
return 'oneOf' in schema
|
|
},
|
|
REFERENCE(schema) {
|
|
return '$ref' in schema
|
|
},
|
|
STRING(schema) {
|
|
if ('enum' in schema) {
|
|
return false
|
|
}
|
|
if (schema.type === 'string') {
|
|
return true
|
|
}
|
|
if (!isCompound(schema) && typeof schema.default === 'string') {
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
TYPED_ARRAY(schema) {
|
|
if (schema.type && schema.type !== 'array') {
|
|
return false
|
|
}
|
|
return 'items' in schema
|
|
},
|
|
UNION(schema) {
|
|
return Array.isArray(schema.type)
|
|
},
|
|
UNNAMED_ENUM(schema) {
|
|
if ('tsEnumNames' in schema) {
|
|
return false
|
|
}
|
|
if (
|
|
schema.type &&
|
|
schema.type !== 'boolean' &&
|
|
schema.type !== 'integer' &&
|
|
schema.type !== 'number' &&
|
|
schema.type !== 'string'
|
|
) {
|
|
return false
|
|
}
|
|
return 'enum' in schema
|
|
},
|
|
UNNAMED_SCHEMA() {
|
|
return false // Explicitly handled as the default case
|
|
},
|
|
UNTYPED_ARRAY(schema) {
|
|
return schema.type === 'array' && !('items' in schema)
|
|
},
|
|
}
|