UNPKG

@stackbit/sdk

Version:
122 lines (112 loc) 4.67 kB
import _ from 'lodash'; import Joi from 'joi'; import { ContentModelMap, StackbitConfig } from '@stackbit/types'; import { contentModelsSchema, stackbitConfigBaseSchema, stackbitConfigFullSchema } from './config-schema'; import { ConfigValidationError } from './config-errors'; import { Config, Model } from './config-types'; export interface ConfigValidationResult { config: Config; valid: boolean; errors: ConfigValidationError[]; } export function validateBaseConfig(config: StackbitConfig): { config: StackbitConfig; valid: boolean; errors: ConfigValidationError[]; } { const validationOptions = { abortEarly: false, context: { config: config } }; const validationResult = stackbitConfigBaseSchema.validate(config, validationOptions); const errors = mapJoiErrorsToConfigValidationErrors(validationResult); return { config: validationResult.value, valid: _.isEmpty(errors), errors }; } export function validateConfig(config: Config): ConfigValidationResult { const validationOptions = { abortEarly: false, context: { config: config } }; const validationResult = stackbitConfigFullSchema.validate(config, validationOptions); const validatedConfig: Config = validationResult.value; const errors = mapJoiErrorsToConfigValidationErrors(validationResult); const valid = _.isEmpty(errors); const invalidModelNames = getInvalidModelNames(errors, 'models', config); const validatedModels = validatedConfig.models ?? []; _.forEach(validatedModels, (model): any => { if (invalidModelNames.includes(model.name)) { _.set(model, '__metadata.invalid', true); } }); return { config: validatedConfig, valid, errors }; } export interface ContentModelsValidationResult { contentModels: ContentModelMap; valid: boolean; errors: ConfigValidationError[]; } export function validateContentModels(contentModels: ContentModelMap, models: Model[]): ContentModelsValidationResult { const modelMap: Record<string, Model> = _.keyBy(models, 'name'); const config = { contentModels: contentModels }; const validationResult = contentModelsSchema.validate(config, { abortEarly: false, context: { modelMap: modelMap } }); const validatedConfig: Config = validationResult.value; const errors = mapJoiErrorsToConfigValidationErrors(validationResult); const valid = _.isEmpty(errors); const invalidModelNames = getInvalidModelNames(errors, 'contentModels', config); const validatedContentModels = validatedConfig.contentModels ?? {}; _.forEach(validatedContentModels, (contentModel, modelName) => { if (invalidModelNames.includes(modelName)) { _.set(contentModel, '__metadata.invalid', true); } }); return { contentModels: validatedContentModels, valid, errors }; } function mapJoiErrorsToConfigValidationErrors(validationResult: Joi.ValidationResult): ConfigValidationError[] { const joiErrors = validationResult.error?.details || []; return joiErrors.map((validationError): ConfigValidationError => { const normFieldPath = validationError.path.slice(); if (validationError.path[0] === 'models' && typeof validationError.path[1] == 'string') { const modelName = validationError.path[1]; normFieldPath[1] = _.findIndex(validationResult.value.models, { name: modelName }); } return new ConfigValidationError({ type: validationError.type, message: validationError.message, fieldPath: validationError.path, normFieldPath: normFieldPath, value: validationError.context?.value }); }); } function getInvalidModelNames(errors: ConfigValidationError[], configKey: string, value: any) { // get array of invalid model names by iterating errors and filtering these // having fieldPath starting with ['models', modelName] return _.reduce( errors, (modelNames: string[], error: ConfigValidationError) => { if (error.fieldPath[0] === configKey) { if (typeof error.fieldPath[1] == 'string') { modelNames.push(error.fieldPath[1]); } else if (typeof error.fieldPath[1] == 'number') { const model = value[configKey][error.fieldPath[1]]; if (model?.name) { modelNames.push(model?.name); } } } return modelNames; }, [] ); }