From a533c15b0979745739a86456742f6ff7ba06aa2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 8 Nov 2020 14:57:50 +0100 Subject: [PATCH] config-loader: document schema lib --- .../config-loader/src/lib/schema/collect.ts | 3 ++ .../config-loader/src/lib/schema/compile.ts | 16 +++++++ .../config-loader/src/lib/schema/filtering.ts | 4 ++ packages/config-loader/src/lib/schema/load.ts | 3 ++ .../config-loader/src/lib/schema/types.ts | 45 +++++++++++++++++++ 5 files changed, 71 insertions(+) diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts index b6d0bffaef..25bdbe7f55 100644 --- a/packages/config-loader/src/lib/schema/collect.ts +++ b/packages/config-loader/src/lib/schema/collect.ts @@ -23,6 +23,9 @@ const req = ? require : __non_webpack_require__; +/** + * This collects all known config schemas across all dependencies of the app. + */ export async function collectConfigSchemas( name: string, opts: unknown = {}, diff --git a/packages/config-loader/src/lib/schema/compile.ts b/packages/config-loader/src/lib/schema/compile.ts index 9ab8627fce..76642690d5 100644 --- a/packages/config-loader/src/lib/schema/compile.ts +++ b/packages/config-loader/src/lib/schema/compile.ts @@ -25,9 +25,19 @@ import { ConfigVisibility, } from './types'; +/** + * This takes a collection of Backstage configuration schemas from various + * sources and compiles them down into a single schema validation function. + * + * It also handles the implementation of the custom "visibility" keyword used + * to specify the scope of different config paths. + */ export function compileConfigSchemas( schemas: ConfigSchemaPackageEntry[], ): ValidationFunc { + // The ajv instance below is stateful and doesn't really allow for additional + // output during validation. We work around this by having this extra piece + // of state that we reset before each validation. const visibilityByPath = new Map(); const ajv = new Ajv({ @@ -63,8 +73,13 @@ export function compileConfigSchemas( const merged = mergeAllOf( { allOf: schemas.map(_ => _.value) }, { + // JSONSchema is typically subtractive, as in it always reduces the set of allowed + // inputs through constraints. This changes the object property merging to be additive + // rather than subtractive. ignoreAdditionalProperties: true, resolvers: { + // This ensures that the visibilities across different schemas are sound, and + // selects the most specific visibility for each path. visibility(values: string[], path: string[]) { const hasApp = values.some(_ => _ === 'frontend'); const hasSecret = values.some(_ => _ === 'secret'); @@ -95,6 +110,7 @@ export function compileConfigSchemas( const valid = validate(config); if (!valid) { + // TODO(Rugvip): better messages here, with more context such as which file the error occurred in const errors = ajv.errorsText(validate.errors); return { errors: [errors], diff --git a/packages/config-loader/src/lib/schema/filtering.ts b/packages/config-loader/src/lib/schema/filtering.ts index 3ed4c958b8..d6dcaf2dd7 100644 --- a/packages/config-loader/src/lib/schema/filtering.ts +++ b/packages/config-loader/src/lib/schema/filtering.ts @@ -17,6 +17,10 @@ import { JsonObject, JsonValue } from '@backstage/config'; import { ConfigVisibility, DEFAULT_CONFIG_VISIBILITY } from './types'; +/** + * This filters data by visibility by discovering the visibility of each + * value, and then only keeping the ones that are specified in `includeVisibilities`. + */ export function filterByVisibility( data: JsonObject, includeVisibilities: ConfigVisibility[], diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index 75fcb86ba7..286b363b50 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -24,6 +24,9 @@ type Options = { dependencies: string[]; }; +/** + * Loads config schema for a Backstage instance. + */ export async function loadConfigSchema( options: Options, ): Promise { diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts index 9e5624ca6a..7e831a7a1c 100644 --- a/packages/config-loader/src/lib/schema/types.ts +++ b/packages/config-loader/src/lib/schema/types.ts @@ -17,30 +17,75 @@ import { AppConfig } from '@backstage/config'; import { JSONSchema7 as JSONSchema } from 'json-schema'; +/** + * An sub-set of configuration schema. + */ export type ConfigSchemaPackageEntry = { + /** + * The configuration schema itself, as JSONSchema draft-07 + */ value: JSONSchema; + /** + * The path that the configuration schema was discovered at. + */ path: string; }; +/** + * A list of all possible configuration value visibilities. + */ export const CONFIG_VISIBILITIES = ['frontend', 'backend', 'secret'] as const; +/** + * A type representing the possible configuration value visibilities + */ export type ConfigVisibility = typeof CONFIG_VISIBILITIES[number]; +/** + * The default configuration visibility if no other values is given. + */ export const DEFAULT_CONFIG_VISIBILITY: ConfigVisibility = 'backend'; +/** + * An explanation of a configuration validation error. + */ type ValidationError = string; +/** + * The result of validating configuration data using a schema. + */ type ValidationResult = { + /** + * Errors that where emitted during validation, if any. + */ errors?: ValidationError[]; + /** + * The configuration visibilities the where discovered during validation. + * + * The path in the key uses the form `////` + */ visibilityByPath: Map; }; +/** + * A function used validate configuration data. + */ export type ValidationFunc = (configs: AppConfig[]) => ValidationResult; +/** + * Options used to process configuration data with a schema. + */ type ConfigProcessingOptions = { + /** + * The visibilities that should be included in the output data. + * If omitted, the data will not be filtered by visibility. + */ visibilities?: ConfigVisibility[]; }; +/** + * A loaded configuration schema that is ready to process configuration data. + */ export type ConfigSchema = { process( appConfigs: AppConfig[],