UNPKG

@croct/content-model

Version:

A library for modeling, validating and interpolating structured content.

829 lines (828 loc) 25.3 kB
import type { JSONSchema } from 'json-schema-typed'; import type { ContentDefinitionType } from '../../definition'; import type { CodeGenerator } from '../codeGenerator'; /** * A documented property. */ type PropertyDocs<T extends Record<string, any> = Record<never, never>> = Readonly<T & { $title: string; $description: string; }>; /** * A property documented with examples. */ type PropertyDocsWithExamples<T extends Record<string, any> = Record<never, never>> = PropertyDocs<T & { $examples: string[]; }>; /** * The JSON schema documentation. */ export type DefinitionSchemaDocs = { definition: PropertyDocs<{ type: PropertyDocs; title: PropertyDocsWithExamples; description: PropertyDocsWithExamples; }>; boolean: PropertyDocs<{ type: PropertyDocs; label: PropertyDocs<{ true: PropertyDocsWithExamples; false: PropertyDocsWithExamples; }>; default: PropertyDocs; }>; number: PropertyDocs<{ type: PropertyDocs; integer: PropertyDocs; minimum: PropertyDocs; maximum: PropertyDocs; }>; text: PropertyDocs<{ type: PropertyDocs; minimumLength: PropertyDocs; maximumLength: PropertyDocs; format: PropertyDocs; pattern: PropertyDocsWithExamples; choices: PropertyDocs<{ label: PropertyDocsWithExamples; description: PropertyDocsWithExamples; default: PropertyDocs; position: PropertyDocs; }>; }>; list: PropertyDocs<{ type: PropertyDocs; items: PropertyDocs; itemLabel: PropertyDocsWithExamples; minimumLength: PropertyDocs; maximumLength: PropertyDocs; }>; structure: PropertyDocs<{ type: PropertyDocs; attributes: PropertyDocs<{ label: PropertyDocsWithExamples; description: PropertyDocsWithExamples; optional: PropertyDocs; private: PropertyDocs; position: PropertyDocs; }>; }>; union: PropertyDocs<{ type: PropertyDocs; types: PropertyDocs; }>; reference: PropertyDocs<{ type: PropertyDocs; id: PropertyDocs; }>; }; type DefinitionFeatureOptions = { /** * The allowed root definitions. * * @default 'structure-or-union' */ rootDefinition?: 'structure' | 'union' | 'structure-or-union' | 'any'; /** * The maximum depth of the schema. * * Note that providing the reference metadata is required for accurate depth validation. * * No limit by default. */ maximumDepth?: number; /** * The maximum length of annotation titles. * * If omitted, allows any length. */ maximumTitleLength?: number; /** * The maximum length of annotation descriptions. * * If omitted, allows any length. */ maximumDescriptionLength?: number; /** * Whether to allow annotations on root definitions only. * * Allows annotations on any definition by default. */ rootAnnotationsOnly?: boolean; /** * Whether to allow primitive values in lists only. * * Allows any type by default. * * @default false */ primitiveListItemsOnly?: boolean; /** * Whether to allow union of references only. * * Allows references and structures by default. * * @default false */ unionReferenceOnly?: boolean; /** * Whether discriminators must match reference IDs in union definitions. * * Note that specifying the references are required to enforce this requirement. * * Allows any non-empty discriminator by default. * * @default false */ requiresUnionDiscriminatorPairing?: boolean; /** * The pattern allowed for the discriminators. * * Allows any non-empty string by default. * * @default false */ unionDiscriminatorPattern?: string; /** * The maximum number of types allowed in a union. * * No limit by default. */ maximumUnionCardinality?: number; /** * The pattern allowed for attribute names. * * Allows any non-empty string by default. */ attributeNamePattern?: string; /** * The minimum length of attribute names. * * If omitted, allows any length. */ minimumAttributeNameLength?: number; /** * The maximum length of attribute names. * * If omitted, allows any length. */ maximumAttributeNameLength?: number; /** * The maximum length of attribute labels. * * If omitted, allows any length. */ maximumAttributeLabelLength?: number; /** * The maximum length of attribute descriptions. * * If omitted, allows any length. */ maximumAttributeDescriptionLength?: number; /** * The maximum number of attributes allowed in a single structure. * * No limit by default. */ maximumAttributesPerStructure?: number; /** * Whether to disallow empty structures. * * An empty structure is one that has no attributes. * * Allows empty structures by default. * * @default false */ nonEmptyStructure?: boolean; /** * Whether to disallow conflicting constraints. * * Conflicting constraints are those that may not be able to be satisfied together. * For example, specifying a pattern and a minimum length for a text attribute * may result in a conflict if the pattern does not match the minimum length. * * Currently, only the text definition has potentially conflicting constraints. * These constraints are: * * - `minimumLength` and `maximumLength` * - `pattern` * - `format` * - `choices` * * If this option is enabled, these options become mutually exclusive, meaning * that only one of them can be specified at a time. For example, if `minimumLength` * is specified, `maximumLength` is allowed, but `pattern`, `format`, and `choices` * are not. * * @default false */ nonConflictingConstraints?: boolean; /** * The maximum length of text attributes. * * If omitted, allows any length. */ maximumStringLength?: number; /** * The maximum number of elements in a list. * * If omitted, allows any length. */ maximumListLength?: number; /** * The maximum length of list item labels. * * If omitted, allows any length. */ maximumItemLabelLength?: number; /** * The maximum length of boolean labels. * * If omitted, allows any length. */ maximumBooleanLabelLength?: number; /** * The maximum length of text patterns. * * If omitted, allows any length. */ maximumPatternLength?: number; /** * The maximum number of choices in a text attribute. * * If omitted, allows any number of choices. */ maximumChoices?: number; /** * The maximum length of choice values. * * If omitted, allows any length. */ maximumChoiceValueLength?: number; /** * The maximum length of choice labels. * * If omitted, allows any length. */ maximumChoiceLabelLength?: number; /** * The maximum length of choice descriptions. * * If omitted, allows any length. */ maximumChoiceDescriptionLength?: number; /** * Whether to include a custom keyword for enforcing a single default choice. * * This keyword can be used by JSON schema validators to enforce that only one * choice is marked as the default. * * The keyword is defined as follows: * ```json * { * "properties": { * "choices": { * "type": "object", * "exclusiveFlag": "default" * } * } * } * ``` * * @default false */ enforceSingleDefaultChoice?: boolean; /** * The schema documentation. */ docs: DefinitionSchemaDocs; }; /** * A map defining which optional standard feature to support. * * All features are enabled by default. */ type StandardFeaturesToggle = Readonly<{ /** * Whether to emit features in lenient mode. * * In lenient mode, the emitted schema features are emitted without strict checking constraints * involving empty labels, descriptions, and other properties. */ lenientMode?: boolean; /** * Boolean features. */ boolean?: Readonly<{ /** * Whether to support defining labels for boolean values. * * @default true */ label?: boolean; /** * Whether to support defining a default value for boolean values. * * @default true */ default?: boolean; }>; /** * Number features. */ number?: Readonly<{ /** * Whether to support restricting numbers to integers only. * * @default true */ integer?: boolean; /** * Whether to support defining a minimum value for numbers. * * @default true */ minimum?: boolean; /** * Whether to support defining a maximum value for numbers. * * @default true */ maximum?: boolean; }>; /** * Text features. */ text?: Readonly<{ /** * Whether to support defining a maximum length for text values. * * @default true */ minimumLength?: boolean; /** * Whether to support defining a maximum length for text values. * * @default true */ maximumLength?: boolean; /** * Whether to support defining a pre-defined format for text values. */ format?: { /** * Whether to support the color format. * * @default true */ color?: boolean; /** * Whether to support the URL format. * * @default true */ url?: boolean; /** * Whether to support multiline format. */ multiline?: boolean; }; /** * Whether to support defining a regular expression for text values. * * @default true */ pattern?: boolean; /** * Whether to support defining a set of allowed values for text values. * * @default true */ choices?: boolean; }>; /** * List features. */ list?: Readonly<{ /** * Whether to support defining a label for list items. * * @default true */ itemLabel?: boolean; /** * Whether to support defining a minimum number of items required in a list. * * @default true */ minimumLength?: boolean; /** * Whether to support defining a maximum number of items allowed in a list. * * @default true */ maximumLength?: boolean; }>; /** * Structure features. */ structure?: Readonly<{ /** * Whether to support defining a title for a structure. * * @default true */ title?: boolean; /** * Whether to support defining a description for a structure. * * @default true */ description?: boolean; /** * Structure attribute features. */ attributes?: Readonly<{ /** * Whether to support defining a label for an attribute. * * @default true */ label?: boolean; /** * Whether to support defining a description for an attribute. * * @default true */ description?: boolean; /** * Whether to support defining an attribute as optional. * * @default true */ optional?: boolean; /** * Whether to support defining an attribute as private. * * @default true */ private?: boolean; /** * Whether to support defining the attribute's position for display purposes. * * @default true */ position?: boolean; }>; }>; }>; /** * The supported feature extensions. */ type Extension = 'ajv'; /** * Reference metadata used for generating the schema. */ type ReferenceMetadata = { /** * The depth of the reference including nested references. * * The depth must be computed as follows: * - The root has a depth of 0 * - Each level of nesting counts as 1 * - List item counts as a level of nesting * - References and unions do not count as a level of nesting * - The depth of referenced schemas, both directly and indirectly, must be added to the depth */ depth: number; /** * The type of the content. */ type: ContentDefinitionType; /** * Whether the properties of this reference is completely optional. * * When this is true, the `properties` field of the reference is optional. */ fullyOptional?: boolean; /** * The schema of additional properties for the referenced definition. */ schema?: JSONSchema.Object; }; /** * The configuration for generating the schema. */ export type DefinitionSchemaConfiguration = Omit<DefinitionFeatureOptions, 'docs'> & { /** * The validator-specific features to support. * * Defaults to the standard JSON schema features. */ extension?: Extension; /** * The features to support. * * All features are enabled by default. * * @default StandardFeaturesToggle */ features?: StandardFeaturesToggle; /** * The map of reference IDs to metadata. * * If omitted, allows any non-empty string as a reference ID. * * Note that specifying the references is required for accurate depth validation. * * Allows any reference ID by default. */ references?: Record<string, ReferenceMetadata>; /** * The schema documentation. * * Defaults to a documentation in English. */ docs?: DefinitionSchemaDocs; }; /** * A JSON schema generator for validating content definitions. */ export declare class DefinitionJsonSchemaGenerator implements CodeGenerator { /** * The default documentation. */ private static readonly DEFAULT_DOCS; /** * The configuration for generating the schema. */ private readonly configuration; /** * The features to support. */ private readonly features; /** * Construct a new instance. * * @param configuration The configuration for generating the schema. */ constructor(configuration?: DefinitionSchemaConfiguration); /** * Creates a lenient schema generator. * * This static factory method configures a generator for generating schemas * that validate schemas without strict checking constraints involving empty labels, * descriptions, and other properties. * * @param options The configuration options. */ static lenient(options?: Pick<DefinitionSchemaConfiguration, 'features' | 'extension' | 'references' | 'docs'>): DefinitionJsonSchemaGenerator; /** * Returns the features to support. * * @param extension The validator-specific features to support. * @param features The standard features to support. * * @returns The features to support. */ private static getSchemaFeatures; /** * Generate the JSON schema for validating content definitions. * * @return The JSON schema in JSON notation. */ generate(): string; /** * Generate the JSON schema for validating content definitions. * * @return The JSON schema in object form. */ generateSchema(): JSONSchema.Object; /** * Generates schemas for validating definitions at different depths. * * @param maximumDepth The maximum depth for generating schemas. * * @return A map of schemas for validating definitions at different depths. */ private generateDepthDefinitions; /** * Generates a schema for validating the root definition. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateRootSchema; /** * Returns the schema for validating definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getDefinitionSchema; /** * Generates a schema for validating definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateDefinitionSchema; /** * Returns the schema for validating a boolean definition. * * @return The schema for validating a boolean definition. */ private getBooleanSchema; /** * Creates the schema for validating a boolean definition. * * @return The schema for validating a boolean definition. */ private createBooleanSchema; /** * Returns the schema for validating a number definition. * * @return The schema for validating a number definition. */ private getNumberSchema; /** * Creates the schema for validating a number definition. * * @return The schema for validating a number definition. */ private createNumberSchema; /** * Returns the schema for validating a text definition. * * @return The schema for validating a text definition. */ private getTextSchema; /** * Creates the schema for validating a text definition. * * @return The schema for validating a text definition. */ private createTextSchema; /** * Returns the schema for validating list definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getListSchema; /** * Generates a schema for validating list definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateListSchema; /** * Returns the schema for validating list items with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getListItemSchema; /** * Returns the schema for validating structure definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getStructureSchema; /** * Generates a schema for validating structure definitions with at most the given depth. * * @param options The options for generating the schema. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateStructureSchema; /** * Returns the schema for validating union definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getUnionSchema; /** * Returns the schema for validating union definitions with at most the given depth. * * @param options The options for generating the schema. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateUnionSchema; /** * Creates a union schema. * * @param options The options for creating the union schema. * * @return The union schema. */ private createUnionSchema; /** * Returns the schema for validating logical definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private createLogicalTypeSchemas; /** * Returns the schema for validating reference definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * @param unionMember Whether to only include references that are valid union members. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private getReferenceSchema; /** * Generates a schema for validating reference definitions with at most the given depth. * * @param maximumDepth The maximum depth that the schema should allow. * @param unionMember Whether to only include references that are valid union members. * * @return If a maximum depth is specified, the schema for validating definitions with at most * the given depth; otherwise, a schema that allows any depth. */ private generateReferenceSchema; /** * Creates a schema for validating references * * If the set of references is empty, allows any non-empty string as a reference identifier. * Otherwise, allows the identifiers in the set only. * * @param references The set of reference identifiers. * * @return The schema for validating references. */ private createReferenceSchema; /** * Returns the reference identifiers matching the given criteria. * * @param maximumDepth The maximum depth that the references should have. * @param unionMember Whether to include only valid union members. * * @return If a maximum depth is specified, the result is the set of identifiers for all * references with a depth less than or equal to the given maximum depth, excluding * references that are not valid union members if the union member flag is set. */ private getReferences; private getNonRootAnnotationProperties; private getAnnotationProperties; /** * Creates a switch definition schema. * * @param branches The branches of the switch. * * @return The switch definition schema. */ private createSchemaSwitch; private get isCheckedReferences(); /** * Remove all properties from the given map that matches nothing. * * @param properties The map of properties. * * @return The map of properties whose schema is not `false`. */ private static cleanSchemaProperties; private static getLogicalTypeRefId; /** * Returns a hash for a logical type name. * * The purpose of this hash is to act as a seed and decrease the * likelihood of collisions among identifiers for logical type names. * * Although the result is not guaranteed to be unique for different types, * but the collision probability is low enough for practical purposes. * * @param id The type name. */ private static getLogicalTypeCode; } export {};