Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2288-Input and internal config validation #2289

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/geoview-core/src/api/config/config-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import cloneDeep from 'lodash/cloneDeep';

import { CV_DEFAULT_MAP_FEATURE_CONFIG, CV_CONFIG_GEOCORE_TYPE } from '@config/types/config-constants';
import { Cast, TypeJsonValue, TypeJsonObject, toJsonObject, TypeJsonArray } from '@config/types/config-types';
import { MapFeatureConfig } from '@config/types/classes/map-feature-config';
Expand Down Expand Up @@ -341,12 +343,15 @@ export class ConfigApi {
// doesn't accept string config. Note that convertStringToJson returns undefined if the string config cannot
// be translated to a json object.
const providedMapFeatureConfig: TypeJsonObject | undefined =
typeof mapConfig === 'string' ? ConfigApi.#convertStringToJson(mapConfig as string) : (mapConfig as TypeJsonObject);
// We clone to prevent modifications from leaking back to the user object.
typeof mapConfig === 'string' ? ConfigApi.#convertStringToJson(mapConfig as string) : (cloneDeep(mapConfig) as TypeJsonObject);

try {
// If the user provided a valid string config with the mandatory map property, process geocore layers to translate them to their GeoView layers
if (!providedMapFeatureConfig) throw new MapConfigError('The string configuration provided cannot be translated to a json object');
if (!providedMapFeatureConfig.map) throw new MapConfigError('The map property is mandatory');
providedMapFeatureConfig.map.listOfGeoviewLayerConfig = (providedMapFeatureConfig.map.listOfGeoviewLayerConfig ||
[]) as TypeJsonObject;

const inputLength = providedMapFeatureConfig.map.listOfGeoviewLayerConfig.length;
providedMapFeatureConfig.map.listOfGeoviewLayerConfig = (await ConfigApi.convertGeocoreToGeoview(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EsriDynamicLayerEntryConfig } from '@config/types/classes/sub-layer-con
import { GroupLayerEntryConfig } from '@config/types/classes/sub-layer-config/group-layer-entry-config';
import { TypeJsonObject } from '@config/types/config-types';
import { TypeDisplayLanguage, TypeLayerInitialSettings } from '@config/types/map-schema-types';
import { isvalidComparedToSchema } from '@config/utils';
import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema } from '@config/utils';
import { MapFeatureConfig } from '@config/types/classes/map-feature-config';
import { AbstractGeoviewEsriLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-esri-layer-config';
import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class';
Expand All @@ -28,8 +28,11 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewEsriLayerConfig {
*/
constructor(layerConfig: TypeJsonObject, language: TypeDisplayLanguage, mapFeatureConfig?: MapFeatureConfig) {
super(layerConfig, language, mapFeatureConfig);
// Input schema validation.
if (!isvalidComparedToSchema(this.geoviewLayerSchema, layerConfig)) this.setErrorDetectedFlag();

// validate the structure
if (!isvalidComparedToInputSchema(this.geoviewLayerSchema, layerConfig)) this.setErrorDetectedFlag();
if (!isvalidComparedToInternalSchema(this.geoviewLayerSchema, this, true)) this.setErrorDetectedFlag();
// validate the values.
this.validate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EsriFeatureLayerEntryConfig } from '@config/types/classes/sub-layer-con
import { GroupLayerEntryConfig } from '@config/types/classes/sub-layer-config/group-layer-entry-config';
import { TypeJsonObject } from '@config/types/config-types';
import { TypeDisplayLanguage, TypeLayerInitialSettings } from '@config/types/map-schema-types';
import { isvalidComparedToSchema } from '@config/utils';
import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema } from '@config/utils';
import { MapFeatureConfig } from '@config/types/classes/map-feature-config';
import { AbstractGeoviewEsriLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-esri-layer-config';
import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class';
Expand All @@ -28,8 +28,11 @@ export class EsriFeatureLayerConfig extends AbstractGeoviewEsriLayerConfig {
*/
constructor(layerConfig: TypeJsonObject, language: TypeDisplayLanguage, mapFeatureConfig?: MapFeatureConfig) {
super(layerConfig, language, mapFeatureConfig);
// Input schema validation.
if (!isvalidComparedToSchema(this.geoviewLayerSchema, layerConfig)) this.setErrorDetectedFlag();

// validate the structure
if (!isvalidComparedToInputSchema(this.geoviewLayerSchema, layerConfig)) this.setErrorDetectedFlag();
if (!isvalidComparedToInternalSchema(this.geoviewLayerSchema, this, true)) this.setErrorDetectedFlag();
// validate the values.
this.validate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
VALID_PROJECTION_CODES,
CV_MAP_CENTER,
} from '@config/types/config-constants';
import { isvalidComparedToSchema } from '@config/utils';
import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema } from '@config/utils';
import {
Extent,
TypeAppBarProps,
Expand Down Expand Up @@ -96,7 +96,7 @@ export class MapFeatureConfig {
*/
constructor(providedMapFeatureConfig: TypeJsonObject, language: TypeDisplayLanguage) {
// Input schema validation.
this.#errorDetected = !isvalidComparedToSchema(CV_MAP_CONFIG_SCHEMA_PATH, providedMapFeatureConfig);
this.#errorDetected = !isvalidComparedToInputSchema(CV_MAP_CONFIG_SCHEMA_PATH, providedMapFeatureConfig);

this.#language = language;
// set map configuration
Expand Down Expand Up @@ -130,6 +130,7 @@ export class MapFeatureConfig {
this.schemaVersionUsed =
(providedMapFeatureConfig.schemaVersionUsed as TypeValidVersions) || CV_DEFAULT_MAP_FEATURE_CONFIG.schemaVersionUsed;
if (this.#errorDetected) this.#makeMapConfigValid(providedMapFeatureConfig); // Tries to apply a correction to invalid properties
if (!isvalidComparedToInternalSchema(CV_MAP_CONFIG_SCHEMA_PATH, this)) this.setErrorDetectedFlag();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CV_CONST_SUB_LAYER_TYPES, CV_CONST_LEAF_LAYER_SCHEMA_PATH } from '@conf
import { Cast, TypeJsonObject } from '@config/types/config-types';
import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config';
import { AbstractBaseEsriLayerEntryConfig } from '@config/types/classes/sub-layer-config/abstract-base-esri-layer-entry-config';
import { isvalidComparedToSchema } from '@config/utils';
import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema } from '@config/utils';
import {
TypeStyleConfig,
TypeLayerEntryType,
Expand Down Expand Up @@ -48,8 +48,9 @@ export class EsriDynamicLayerEntryConfig extends AbstractBaseEsriLayerEntryConfi
this.setErrorDetectedFlag();
throw new GeoviewLayerInvalidParameterError('LayerIdInvalidType', [this.layerPath]);
}
// Input schema validation. When the entryType property is undefined, isvalidComparedToSchema uses the input schema.
if (!isvalidComparedToSchema(this.schemaPath, layerConfig)) this.setErrorDetectedFlag();
// Validate the structure
if (!isvalidComparedToInputSchema(this.schemaPath, layerConfig)) this.setErrorDetectedFlag();
if (!isvalidComparedToInternalSchema(this.schemaPath, this, true)) this.setErrorDetectedFlag();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CV_CONST_SUB_LAYER_TYPES, CV_CONST_LEAF_LAYER_SCHEMA_PATH } from '@conf
import { Cast, TypeJsonObject } from '@config/types/config-types';
import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config';
import { AbstractBaseEsriLayerEntryConfig } from '@config/types/classes/sub-layer-config/abstract-base-esri-layer-entry-config';
import { isvalidComparedToSchema } from '@config/utils';
import { isvalidComparedToInputSchema, isvalidComparedToInternalSchema } from '@config/utils';
import {
TypeStyleConfig,
TypeLayerEntryType,
Expand Down Expand Up @@ -48,8 +48,9 @@ export class EsriFeatureLayerEntryConfig extends AbstractBaseEsriLayerEntryConfi
this.setErrorDetectedFlag();
throw new GeoviewLayerInvalidParameterError('LayerIdInvalidType', [this.layerPath]);
}
// Input schema validation. When the entryType property is undefined, isvalidComparedToSchema uses the input schema.
if (!isvalidComparedToSchema(this.schemaPath, layerConfig)) this.setErrorDetectedFlag();
// Validate the structure
if (!isvalidComparedToInputSchema(this.schemaPath, layerConfig)) this.setErrorDetectedFlag();
if (!isvalidComparedToInternalSchema(this.schemaPath, this, true)) this.setErrorDetectedFlag();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@
"description": "The id of the layer for referencing within the viewer (does not relate directly to any external service). The id will have the language extension (id-'lang').",
"type": "string"
},
"useInternalSchema": {
"description": "Flag used to select the type of schema validation to apply (input/internal).",
"type": "boolean"
},
"geoviewLayerName": {
"description": "The display name of the layer (English/French). If it is not present the viewer will make an attempt to scrape this information.",
"oneOf": [
Expand Down Expand Up @@ -752,6 +756,10 @@
"description": "The id of the layer to display on the map.",
"type": "string"
},
"useInternalSchema": {
"description": "Flag used to select the type of schema validation to apply (input/internal).",
"type": "boolean"
},
"layerName": {
"description": "The display name of the layer (English/French). If it is not present the viewer will make an attempt to scrape this information.",
"oneOf": [
Expand Down
36 changes: 29 additions & 7 deletions packages/geoview-core/src/api/config/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Ajv from 'ajv';
import cloneDeep from 'lodash/cloneDeep';

import { CV_CONST_SUB_LAYER_TYPES, CV_CONST_LAYER_TYPES } from '@config/types/config-constants';
import { TypeJsonObject } from '@config/types/config-types';
import schema from '@config/types/config-validation-schema.json';
import { MapFeatureConfig } from '@config/types/classes/map-feature-config';
import { TypeGeoviewLayerType, TypeLayerEntryType, TypeLocalizedString } from '@config/types/map-schema-types';
import { EntryConfigBaseClass } from '@/api/config/types/classes/sub-layer-config/entry-config-base-class';

import { logger } from '@/core/utils/logger';

type NewType = TypeGeoviewLayerType;
Expand Down Expand Up @@ -39,17 +41,13 @@ export const convertLayerTypeToEntry = (layerType: NewType): TypeLayerEntryType

/**
* Validate a section of the configuration against the schema identified by the schema path.
*
* Since the useInternalSchema is never provided by the users and set internally before the
* call to this method, we use it as a flag to indicate the schema type (input/internal) to
* use for the schema validation.

*
* @param {string} schemaPath The path to the schema section to use for the validation.
* @param {object} targetObject The map feature configuration to validate.
*
* @returns {boolean} A boolean indicating that the schema section is valid (true) or invalide (false).
*/
export function isvalidComparedToSchema(schemaPath: string, targetObject: object): boolean {
export function isvalidComparedToInputSchema(schemaPath: string, targetObject: object): boolean {
// create a validator object
const validator = new Ajv({
strict: false,
Expand Down Expand Up @@ -77,7 +75,6 @@ export function isvalidComparedToSchema(schemaPath: string, targetObject: object
}
logger.logWarning('='.repeat(200), '\nSchema error: ', error, '\nObject affected: ', node);
}
(targetObject as MapFeatureConfig | EntryConfigBaseClass)?.setErrorDetectedFlag?.();
return false;
}
return true;
Expand All @@ -87,6 +84,31 @@ export function isvalidComparedToSchema(schemaPath: string, targetObject: object
return false;
}

/**
* Validate a section of the configuration against the schema identified by the schema path.
* The internal schema is used internally by the viewer when we instanciate or modify a configuration object
* to make sure nothing has been broken and prove that the GeoView metadata are conform.
*
* Since the useInternalSchema is never provided by the users and set internally before the
* validation call, we use it as a flag to indicate we want to use the internal schema type
* for the schema validation.
*
* The addInternalFlag must be set to true when we want to validate a GeoView layer or a sublayer.
* All other types have the same definition for the input and internal schemas.
*
* @param {string} schemaPath The path to the schema section to use for the validation.
* @param {object} targetObject The map feature configuration to validate.
* @param {boolean} useInternalSchema Adds useInternalSchema flag to the object to be validated.
*
* @returns {boolean} A boolean indicating that the schema section is valid (true) or invalide (false).
*/
export function isvalidComparedToInternalSchema(schemaPath: string, targetObject: object, useInternalSchema = false): boolean {
// The clone operation copies only the public properties, no private using #.
const targetObjectToValidate: object = cloneDeep(targetObject);
if (useInternalSchema) Object.assign(targetObjectToValidate, { useInternalSchema });
return isvalidComparedToInputSchema(schemaPath, targetObjectToValidate);
}

/**
* Normalize the localized string parameter. If a language is set and the other is not, the undefined language is set to
* the value of the other.
Expand Down
Loading