diff --git a/src/validation/SchemaResolver.ts b/src/validation/SchemaResolver.ts deleted file mode 100644 index ecf4ffa3..00000000 --- a/src/validation/SchemaResolver.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { defined } from "3d-tiles-tools"; -import { Buffers } from "3d-tiles-tools"; -import { Schema } from "3d-tiles-tools"; -import { ValidationContext } from "./ValidationContext"; -import { IoValidationIssues } from "../issues/IoValidationIssue"; - -/** - * Internal utility class for resolving a metadata `Schema` object. - * - * This receives the `schema` and `schemaUri` (for example, - * from a `tileset` object), and returns the resulting - * schema, resolving the `schemaUri` if necessary. - * - * @internal - */ -export class SchemaResolver { - /** - * Resolves the schema for the given object. - * - * The result will be an object with the following properties: - * - * `hasSchemaDefinition`: This is `true` if there either was a - * `schema` or a `schemaUri` - * - * `schema`: This is either the `schema`, or the schema that was - * read from the `schemaUri`. If the latter could not be resolved, - * then `schema` will be `undefined`. - * - * @param path - The path for validation issues - * @param schema - The schema - * @param schemaUri - The schema URI - * @param context - The `ValidationContext` - * @returns A promise that resolves with the result object - */ - static async resolveSchema( - path: string, - schema: any, - schemaUri: any, - context: ValidationContext - ): Promise<{ hasSchemaDefinition: boolean; schema?: Schema }> { - if (defined(schema) && typeof schema === "object") { - return { - hasSchemaDefinition: true, - schema: schema, - }; - } - if (defined(schemaUri) && typeof schemaUri === "string") { - const resourceResolver = context.getResourceResolver(); - const schemaBuffer = await resourceResolver.resolveData(schemaUri); - if (!defined(schemaBuffer)) { - const schemaUriPath = path + "/schemaUri"; - const message = `The 'schemaUri' is '${schemaUri}' and could not be resolved`; - const issue = IoValidationIssues.IO_ERROR(schemaUriPath, message); - context.addIssue(issue); - return { - hasSchemaDefinition: true, - schema: undefined, - }; - } - - const bom = Buffers.getUnicodeBOMDescription(schemaBuffer); - if (defined(bom)) { - const message = `Unexpected BOM in schema JSON buffer: ${bom}`; - const issue = IoValidationIssues.IO_ERROR(schemaUri, message); - context.addIssue(issue); - return { - hasSchemaDefinition: true, - schema: undefined, - }; - } - - const schemaString = schemaBuffer.toString(); - try { - const resolvedSchema = JSON.parse(schemaString); - return { - hasSchemaDefinition: true, - schema: resolvedSchema, - }; - } catch (error) { - //console.log(error); - const issue = IoValidationIssues.JSON_PARSE_ERROR(path, "" + error); - context.addIssue(issue); - return { - hasSchemaDefinition: true, - schema: undefined, - }; - } - } - return { - hasSchemaDefinition: false, - schema: undefined, - }; - } - -} \ No newline at end of file diff --git a/src/validation/TilesetValidator.ts b/src/validation/TilesetValidator.ts index 71839ee3..e5625390 100644 --- a/src/validation/TilesetValidator.ts +++ b/src/validation/TilesetValidator.ts @@ -1,5 +1,4 @@ import { defined } from "3d-tiles-tools"; -import { Buffers } from "3d-tiles-tools"; import { Validator } from "./Validator"; import { ValidationState } from "./ValidationState"; @@ -12,7 +11,7 @@ import { TilesetTraversingValidator } from "./TilesetTraversingValidator"; import { RootPropertyValidator } from "./RootPropertyValidator"; import { ExtendedObjectsValidators } from "./ExtendedObjectsValidators"; -import { SchemaValidator } from "./metadata/SchemaValidator"; +import { SchemaDefinitionValidator } from "./metadata/SchemaDefinitionValidator"; import { MetadataEntityValidator } from "./metadata/MetadataEntityValidator"; import { Tileset } from "3d-tiles-tools"; @@ -21,9 +20,7 @@ import { Group } from "3d-tiles-tools"; import { IoValidationIssues } from "../issues/IoValidationIssue"; import { StructureValidationIssues } from "../issues/StructureValidationIssues"; -import { JsonValidationIssues } from "../issues/JsonValidationIssues"; import { SemanticValidationIssues } from "../issues/SemanticValidationIssues"; -import { SchemaResolver } from "./SchemaResolver"; /** * A class that can validate a 3D Tiles tileset. @@ -136,35 +133,6 @@ export class TilesetValidator implements Validator { } } - // The schema and schemaUri MUST NOT be present at the same time - if (defined(tileset.schema) && defined(tileset.schemaUri)) { - const issue = JsonValidationIssues.ONE_OF_ERROR( - path, - "tileset", - "schema", - "schemaUri" - ); - context.addIssue(issue); - result = false; - } - - // Validate the schemaUri - const schemaUri = tileset.schemaUri; - const schemaUriPath = path + "/schemaUri"; - if (defined(schemaUri)) { - // The schemaUri MUST be a string - if ( - !BasicValidator.validateString( - schemaUriPath, - "schemaUri", - schemaUri, - context - ) - ) { - result = false; - } - } - // Create the ValidationState, and fill it with information // about the presence of a schema, and the validated schema // (if the schema is valid) @@ -174,23 +142,25 @@ export class TilesetValidator implements Validator { validatedGroups: undefined, hasGroupsDefinition: false, }; - const schemaResult = await SchemaResolver.resolveSchema( - "", - tileset.schema, - tileset.schemaUri, - context - ); - validationState.hasSchemaDefinition = schemaResult.hasSchemaDefinition; - - // Validate the schema - const schema = schemaResult.schema; - const schemaPath = path + "/schema"; - if (defined(schema)) { - if (SchemaValidator.validateSchema(schemaPath, schema, context)) { - validationState.validatedSchema = schema; - } else { - result = false; - } + const schemaDefinition = + await SchemaDefinitionValidator.validateSchemaDefinition( + path, + "tileset", + tileset.schema, + tileset.schemaUri, + context + ); + validationState.hasSchemaDefinition = schemaDefinition.hasSchemaDefinition; + validationState.validatedSchema = schemaDefinition.validatedSchema; + + // When there was a schema definition, but the schema + // itself was not valid, then the overall result + // is invalid + if ( + schemaDefinition.hasSchemaDefinition && + !defined(schemaDefinition.validatedSchema) + ) { + result = false; } // Validate the groups. @@ -458,7 +428,6 @@ export class TilesetValidator implements Validator { return result; } - /** * Validates the given `tileset.groups` * diff --git a/src/validation/metadata/SchemaDefinitionValidator.ts b/src/validation/metadata/SchemaDefinitionValidator.ts new file mode 100644 index 00000000..77c21a25 --- /dev/null +++ b/src/validation/metadata/SchemaDefinitionValidator.ts @@ -0,0 +1,184 @@ +import { defined } from "3d-tiles-tools"; +import { Buffers } from "3d-tiles-tools"; +import { Schema } from "3d-tiles-tools"; + +import { ValidationContext } from "../ValidationContext"; +import { BasicValidator } from "../BasicValidator"; +import { SchemaValidator } from "./SchemaValidator"; + +import { IoValidationIssues } from "../../issues/IoValidationIssue"; +import { JsonValidationIssues } from "../../issues/JsonValidationIssues"; + +/** + * Utility class for validating the definition of a metadata schema. + * + * The metadata schema can either be defined as an inlined `schema` + * object in the JSON, or via a `schemUri` (but not both!). + * This class resolves and validates the schema from either of + * these sources, and returns information about that validation + * result. + */ +export class SchemaDefinitionValidator { + /** + * Validates the given schema definition. + * + * The returned object will contain two properties: + * - `hasSchemaDefintiion`: Whether any schema definition was given + * - `validatedSchema`: The validated `Schema` object + * + * If neither `schema` nor `schemaUri` are given, then the result + * will be `{false, undefined}`. + * + * If the `schema` and `schemaUri` are both given, then an error will be + * added to the given context, `hasSchemaDefintiion` will be `true`, + * and the `validatedSchema` will be `undefined`. + * + * If the `schemaUri` is given but invalid, then an error will be + * added to the given context, `hasSchemaDefintiion` will be `true`, + * and the `validatedSchema` will be `undefined`. + * + * If the `schema` was given, or the schema could be resolved from + * the `schemaUri`, then `hasSchemaDefintiion` will be `true`, and + * the `validatedSchema` will be defined if and only if the + * respective schema could be validated. + * + * @param path - The path for `ValidationIssue` instances + * @param name - A name for the containing object (usually 'tileset') + * @param schema - The `schema` object from the JSON + * @param schemaUri - The `schemaUri` from the JSON + * @param context - The `ValidatonContext` + * @returns The schema definition validation result + */ + static async validateSchemaDefinition( + path: string, + name: string, + schema: any, + schemaUri: any, + context: ValidationContext + ): Promise< + { hasSchemaDefinition: boolean; validatedSchema?: Schema } + > { + // The schema and schemaUri MUST NOT be present at the same time + if (defined(schema) && defined(schemaUri)) { + const issue = JsonValidationIssues.ONE_OF_ERROR( + path, + name, + "schema", + "schemaUri" + ); + context.addIssue(issue); + return { + hasSchemaDefinition: true, + validatedSchema: undefined, + }; + } + + // Validate the schemaUri + const schemaUriPath = path + "/schemaUri"; + if (defined(schemaUri)) { + // The schemaUri MUST be a string + if ( + !BasicValidator.validateString( + schemaUriPath, + "schemaUri", + schemaUri, + context + ) + ) { + return { + hasSchemaDefinition: true, + validatedSchema: undefined, + }; + } + } + + // The schema to validate is either the given one, + // or the one that is resolved from the schemaUri + let schemaToValidate = undefined; + if (defined(schema)) { + schemaToValidate = schema; + } else if (defined(schemaUri)) { + const resolvedSchema = await SchemaDefinitionValidator.resolveSchema( + path, + schemaUri, + context + ); + if (!defined(resolvedSchema)) { + return { + hasSchemaDefinition: true, + validatedSchema: undefined, + }; + } + schemaToValidate = resolvedSchema; + } else { + // Neither the schema nor the schemaUri have been given + return { + hasSchemaDefinition: false, + validatedSchema: undefined, + }; + } + + // Validate the schema + let validatedSchema = undefined; + const schemaPath = path + "/schema"; + if (defined(schemaToValidate)) { + if ( + SchemaValidator.validateSchema(schemaPath, schemaToValidate, context) + ) { + validatedSchema = schemaToValidate; + } + } + + return { + hasSchemaDefinition: true, + validatedSchema: validatedSchema, + }; + } + + /** + * Resolves the schema from the given URI + * + * @param path - The path for validation issues + * @param schemaUri - The schema URI + * @param context - The `ValidationContext` + * @returns A promise that resolves with the result object, + * or `undefined` if the schema could not be resolved + */ + private static async resolveSchema( + path: string, + schemaUri: any, + context: ValidationContext + ): Promise { + if (defined(schemaUri) && typeof schemaUri === "string") { + const resourceResolver = context.getResourceResolver(); + const schemaBuffer = await resourceResolver.resolveData(schemaUri); + if (!defined(schemaBuffer)) { + const schemaUriPath = path + "/schemaUri"; + const message = `The 'schemaUri' is '${schemaUri}' and could not be resolved`; + const issue = IoValidationIssues.IO_ERROR(schemaUriPath, message); + context.addIssue(issue); + return undefined; + } + + const bom = Buffers.getUnicodeBOMDescription(schemaBuffer); + if (defined(bom)) { + const message = `Unexpected BOM in schema JSON buffer: ${bom}`; + const issue = IoValidationIssues.IO_ERROR(schemaUri, message); + context.addIssue(issue); + return undefined; + } + + const schemaString = schemaBuffer.toString(); + try { + const resolvedSchema = JSON.parse(schemaString); + return resolvedSchema; + } catch (error) { + //console.log(error); + const issue = IoValidationIssues.JSON_PARSE_ERROR(path, `${error}`); + context.addIssue(issue); + return undefined; + } + } + return undefined; + } +} diff --git a/src/validation/metadata/SchemaValidator.ts b/src/validation/metadata/SchemaValidator.ts index 0a324347..2aa08cbd 100644 --- a/src/validation/metadata/SchemaValidator.ts +++ b/src/validation/metadata/SchemaValidator.ts @@ -1,4 +1,5 @@ import { defined } from "3d-tiles-tools"; +import { Schema } from "3d-tiles-tools"; import { ValidationContext } from "./../ValidationContext"; import { Validator } from "./../Validator"; @@ -9,8 +10,6 @@ import { ExtendedObjectsValidators } from "./../ExtendedObjectsValidators"; import { MetadataClassValidator } from "./MetadataClassValidator"; import { MetadataEnumValidator } from "./MetadataEnumValidator"; -import { Schema } from "3d-tiles-tools"; - import { IoValidationIssues } from "../../issues/IoValidationIssue"; /**