config-loader: document schema lib

This commit is contained in:
Patrik Oldsberg
2020-11-08 14:57:50 +01:00
parent b652ebc4e7
commit a533c15b09
5 changed files with 71 additions and 0 deletions
@@ -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 = {},
@@ -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<string, ConfigVisibility>();
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],
@@ -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[],
@@ -24,6 +24,9 @@ type Options = {
dependencies: string[];
};
/**
* Loads config schema for a Backstage instance.
*/
export async function loadConfigSchema(
options: Options,
): Promise<ConfigSchema> {
@@ -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 `/<key>/<sub-key>/<array-index>/<leaf-key>`
*/
visibilityByPath: Map<string, ConfigVisibility>;
};
/**
* 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[],