catalog-model: replace yup with ajv, for validation of catalog entities
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Replace `yup` with `ajv`, for validation of catalog entities.
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
Bump config-loader to ajv 7, to enable v7 feature use elsewhere
|
||||
Bump `config-loader` to `ajv` 7, to enable v7 feature use elsewhere
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/catalog-model': patch
|
||||
---
|
||||
|
||||
Introduce jsonschema variants of the yup vlidation schemas
|
||||
Introduce json schema variants of the `yup` validation schemas
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"@types/yup": "^0.29.8",
|
||||
"ajv": "^7.0.3",
|
||||
"json-schema": "^0.2.5",
|
||||
"lodash": "^4.17.15",
|
||||
"uuid": "^8.0.0",
|
||||
@@ -45,7 +46,6 @@
|
||||
"yaml": "^1.9.2"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"schema"
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -14,32 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import { Entity, EntityLink } from '../Entity';
|
||||
import Ajv, { ValidateFunction } from 'ajv';
|
||||
import entitySchema from '../../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../../schema/shared/common.schema.json';
|
||||
import { Entity } from '../Entity';
|
||||
import { EntityPolicy } from './types';
|
||||
|
||||
const DEFAULT_ENTITY_SCHEMA = yup
|
||||
.object({
|
||||
apiVersion: yup.string().required(),
|
||||
kind: yup.string().required(),
|
||||
metadata: yup
|
||||
.object({
|
||||
uid: yup.string().notRequired().min(1),
|
||||
etag: yup.string().notRequired().min(1),
|
||||
generation: yup.number().notRequired().integer().min(1),
|
||||
name: yup.string().required(),
|
||||
namespace: yup.string().notRequired(),
|
||||
description: yup.string().notRequired(),
|
||||
labels: yup.object<Record<string, string>>().notRequired(),
|
||||
annotations: yup.object<Record<string, string>>().notRequired(),
|
||||
tags: yup.array<string>().notRequired(),
|
||||
links: yup.array<EntityLink>().notRequired(),
|
||||
})
|
||||
.required(),
|
||||
spec: yup.object({}).notRequired(),
|
||||
})
|
||||
.required();
|
||||
|
||||
/**
|
||||
* Ensures that the entity spec is valid according to a schema.
|
||||
*
|
||||
@@ -48,17 +29,28 @@ const DEFAULT_ENTITY_SCHEMA = yup
|
||||
* typescript type.
|
||||
*/
|
||||
export class SchemaValidEntityPolicy implements EntityPolicy {
|
||||
private readonly schema: yup.Schema<Entity>;
|
||||
|
||||
constructor(schema: yup.Schema<Entity> = DEFAULT_ENTITY_SCHEMA) {
|
||||
this.schema = schema;
|
||||
}
|
||||
private validate: ValidateFunction<Entity> | undefined;
|
||||
|
||||
async enforce(entity: Entity): Promise<Entity> {
|
||||
try {
|
||||
return await this.schema.validate(entity, { strict: true });
|
||||
} catch (e) {
|
||||
throw new Error(`Malformed envelope, ${e}`);
|
||||
if (!this.validate) {
|
||||
const ajv = new Ajv({ allowUnionTypes: true });
|
||||
this.validate = ajv
|
||||
.addSchema([commonSchema, entityMetaSchema], undefined, undefined, true)
|
||||
.compile<Entity>(entitySchema);
|
||||
}
|
||||
|
||||
const result = this.validate(entity);
|
||||
if (result === true) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
const [error] = this.validate.errors || [];
|
||||
if (!error) {
|
||||
throw new Error(`Malformed envelope, Unknown error`);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Malformed envelope, ${error.dataPath || '<root>'} ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,27 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/API.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'API' as const;
|
||||
|
||||
const schema = yup.object<Partial<ApiEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required().min(1),
|
||||
lifecycle: yup.string().required().min(1),
|
||||
owner: yup.string().required().min(1),
|
||||
definition: yup.string().required().min(1),
|
||||
system: yup.string().notRequired().min(1),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface ApiEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -47,8 +36,9 @@ export interface ApiEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const apiEntityV1alpha1Validator = schemaValidator(
|
||||
export const apiEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,29 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/Component.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Component' as const;
|
||||
|
||||
const schema = yup.object<Partial<ComponentEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required().min(1),
|
||||
lifecycle: yup.string().required().min(1),
|
||||
owner: yup.string().required().min(1),
|
||||
subcomponentOf: yup.string().notRequired().min(1),
|
||||
providesApis: yup.array(yup.string().required()).notRequired(),
|
||||
consumesApis: yup.array(yup.string().required()).notRequired(),
|
||||
system: yup.string().notRequired().min(1),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface ComponentEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -51,8 +38,9 @@ export interface ComponentEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const componentEntityV1alpha1Validator = schemaValidator(
|
||||
export const componentEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,23 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/Domain.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Domain' as const;
|
||||
|
||||
const schema = yup.object<Partial<DomainEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
owner: yup.string().required().min(1),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface DomainEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -39,8 +32,9 @@ export interface DomainEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const domainEntityV1alpha1Validator = schemaValidator(
|
||||
export const domainEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,40 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/Group.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Group' as const;
|
||||
|
||||
const schema = yup.object<Partial<GroupEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required().min(1),
|
||||
profile: yup
|
||||
.object({
|
||||
displayName: yup.string().min(1).notRequired(),
|
||||
email: yup.string().min(1).notRequired(),
|
||||
picture: yup.string().min(1).notRequired(),
|
||||
})
|
||||
.notRequired(),
|
||||
parent: yup.string().notRequired().min(1),
|
||||
// Use these manual tests because yup .required() requires at least
|
||||
// one element and there is no simple workaround -_-
|
||||
// the cast is there to convince typescript that the array itself is
|
||||
// required without using .required()
|
||||
children: yup.array(yup.string().required()).test({
|
||||
name: 'isDefined',
|
||||
message: 'children must be defined',
|
||||
test: v => Boolean(v),
|
||||
}) as yup.ArraySchema<string, object>,
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface GroupEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -63,8 +39,9 @@ export interface GroupEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const groupEntityV1alpha1Validator = schemaValidator(
|
||||
export const groupEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,25 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/Location.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Location' as const;
|
||||
|
||||
const schema = yup.object<Partial<LocationEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().notRequired().min(1),
|
||||
target: yup.string().notRequired().min(1),
|
||||
targets: yup.array(yup.string().required()).notRequired(),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface LocationEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -43,8 +34,9 @@ export interface LocationEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const locationEntityV1alpha1Validator = schemaValidator(
|
||||
export const locationEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,25 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/Resource.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Resource' as const;
|
||||
|
||||
const schema = yup.object<Partial<ResourceEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required().min(1),
|
||||
owner: yup.string().required().min(1),
|
||||
system: yup.string().notRequired().min(1),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface ResourceEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -43,8 +34,9 @@ export interface ResourceEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const resourceEntityV1alpha1Validator = schemaValidator(
|
||||
export const resourceEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,24 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/System.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'System' as const;
|
||||
|
||||
const schema = yup.object<Partial<SystemEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
owner: yup.string().required().min(1),
|
||||
domain: yup.string().notRequired().min(1),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface SystemEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -41,8 +33,9 @@ export interface SystemEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const systemEntityV1alpha1Validator = schemaValidator(
|
||||
export const systemEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,27 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import schema from '../schema/kinds/Template.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import type { JSONSchema } from '../types';
|
||||
import { schemaValidator } from './util';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'Template' as const;
|
||||
|
||||
const schema = yup.object<Partial<TemplateEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required().min(1),
|
||||
path: yup.string(),
|
||||
schema: yup.object().required(),
|
||||
templater: yup.string().required(),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface TemplateEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -46,8 +36,9 @@ export interface TemplateEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const templateEntityV1alpha1Validator = schemaValidator(
|
||||
export const templateEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,38 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import type { Entity } from '../entity/Entity';
|
||||
import { schemaValidator } from './util';
|
||||
import schema from '../schema/kinds/User.v1alpha1.schema.json';
|
||||
import entitySchema from '../schema/Entity.schema.json';
|
||||
import entityMetaSchema from '../schema/EntityMeta.schema.json';
|
||||
import commonSchema from '../schema/shared/common.schema.json';
|
||||
import { ajvCompiledJsonSchemaValidator } from './util';
|
||||
|
||||
const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const;
|
||||
const KIND = 'User' as const;
|
||||
|
||||
const schema = yup.object<Partial<UserEntityV1alpha1>>({
|
||||
apiVersion: yup.string().required().oneOf(API_VERSION),
|
||||
kind: yup.string().required().equals([KIND]),
|
||||
spec: yup
|
||||
.object({
|
||||
profile: yup
|
||||
.object({
|
||||
displayName: yup.string().min(1).notRequired(),
|
||||
email: yup.string().min(1).notRequired(),
|
||||
picture: yup.string().min(1).notRequired(),
|
||||
})
|
||||
.notRequired(),
|
||||
// Use this manual test because yup .required() requires at least one
|
||||
// element and there is no simple workaround -_-
|
||||
// the cast is there to convince typescript that the array itself is
|
||||
// required without using .required()
|
||||
memberOf: yup.array(yup.string().required()).test({
|
||||
name: 'isDefined',
|
||||
message: 'memberOf must be defined',
|
||||
test: v => Boolean(v),
|
||||
}) as yup.ArraySchema<string, object>,
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
|
||||
export interface UserEntityV1alpha1 extends Entity {
|
||||
apiVersion: typeof API_VERSION[number];
|
||||
kind: typeof KIND;
|
||||
@@ -59,8 +37,9 @@ export interface UserEntityV1alpha1 extends Entity {
|
||||
};
|
||||
}
|
||||
|
||||
export const userEntityV1alpha1Validator = schemaValidator(
|
||||
export const userEntityV1alpha1Validator = ajvCompiledJsonSchemaValidator(
|
||||
KIND,
|
||||
API_VERSION,
|
||||
schema,
|
||||
[commonSchema, entityMetaSchema, entitySchema],
|
||||
);
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Ajv, { AnySchema } from 'ajv';
|
||||
import * as yup from 'yup';
|
||||
import { KindValidator } from './types';
|
||||
|
||||
/**
|
||||
* @deprecated We no longer use yup for the catalog model. This utility method will be removed.
|
||||
*/
|
||||
export function schemaValidator(
|
||||
kind: string,
|
||||
apiVersion: readonly string[],
|
||||
@@ -24,10 +28,7 @@ export function schemaValidator(
|
||||
): KindValidator {
|
||||
return {
|
||||
async check(envelope) {
|
||||
if (
|
||||
kind !== envelope.kind ||
|
||||
!apiVersion.includes(envelope.apiVersion as any)
|
||||
) {
|
||||
if (kind !== envelope.kind || !apiVersion.includes(envelope.apiVersion)) {
|
||||
return false;
|
||||
}
|
||||
await schema.validate(envelope, { strict: true });
|
||||
@@ -35,3 +36,38 @@ export function schemaValidator(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function ajvCompiledJsonSchemaValidator(
|
||||
kind: string,
|
||||
apiVersion: readonly string[],
|
||||
schema: AnySchema,
|
||||
extraSchemas?: AnySchema[],
|
||||
): KindValidator {
|
||||
const ajv = new Ajv({ allowUnionTypes: true });
|
||||
if (extraSchemas) {
|
||||
ajv.addSchema(extraSchemas, undefined, undefined, true);
|
||||
}
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
return {
|
||||
async check(envelope) {
|
||||
if (kind !== envelope.kind || !apiVersion.includes(envelope.apiVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const result = validate(envelope);
|
||||
if (result === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const [error] = validate.errors || [];
|
||||
if (!error) {
|
||||
throw new TypeError(`Malformed ${kind}, Unknown error`);
|
||||
}
|
||||
|
||||
throw new TypeError(
|
||||
`Malformed ${kind}, ${error.dataPath || '<root>'} ${error.message}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+28
@@ -84,6 +84,34 @@
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "array",
|
||||
"description": "A list of external hyperlinks related to the entity. Links can provide additional contextual information that may be located outside of Backstage itself. For example, an admin dashboard or external CMS page.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["url"],
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "A url in a standard uri format.",
|
||||
"examples": ["https://admin.example-org.com"],
|
||||
"minLength": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "A user friendly display name for the link.",
|
||||
"examples": ["Admin Dashboard"],
|
||||
"minLength": 1
|
||||
},
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"description": "A key representing a visual icon to be displayed in the UI.",
|
||||
"examples": ["dashboard"],
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2478,6 +2478,7 @@
|
||||
"@backstage/config" "^0.1.2"
|
||||
"@types/json-schema" "^7.0.5"
|
||||
"@types/yup" "^0.29.8"
|
||||
ajv "^7.0.3"
|
||||
json-schema "^0.2.5"
|
||||
lodash "^4.17.15"
|
||||
uuid "^8.0.0"
|
||||
@@ -2489,6 +2490,7 @@
|
||||
"@backstage/config" "^0.1.2"
|
||||
"@types/json-schema" "^7.0.5"
|
||||
"@types/yup" "^0.29.8"
|
||||
ajv "^7.0.3"
|
||||
json-schema "^0.2.5"
|
||||
lodash "^4.17.15"
|
||||
uuid "^8.0.0"
|
||||
@@ -7970,6 +7972,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5, ajv
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ajv@^7.0.3:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2"
|
||||
integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==
|
||||
dependencies:
|
||||
fast-deep-equal "^3.1.1"
|
||||
json-schema-traverse "^1.0.0"
|
||||
require-from-string "^2.0.2"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
alphanum-sort@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
|
||||
@@ -16954,6 +16966,11 @@ json-schema-traverse@^0.4.1:
|
||||
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||
|
||||
json-schema-traverse@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||
|
||||
json-schema@0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||
@@ -22693,6 +22710,11 @@ require-directory@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
|
||||
|
||||
require-from-string@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||
|
||||
require-main-filename@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
|
||||
|
||||
Reference in New Issue
Block a user