config-loader: document schema lib
This commit is contained in:
@@ -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[],
|
||||
|
||||
Reference in New Issue
Block a user