fix(include): return schema setter, default to default schema
BREAKING CHANGE: the include type will make a copy of its options and return a setter for the `schema`, fixing a bug in createSchema and allowing it to take readonly options rather than mutating them.
This commit is contained in:
parent
fc1f4d01a2
commit
51038a412a
|
@ -6,7 +6,7 @@ import { regexpType } from './type/Regexp';
|
||||||
import { streamType } from './type/Stream';
|
import { streamType } from './type/Stream';
|
||||||
|
|
||||||
export interface SchemaOptions {
|
export interface SchemaOptions {
|
||||||
include: IncludeOptions;
|
include: Readonly<IncludeOptions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,8 +14,8 @@ export interface SchemaOptions {
|
||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function createSchema(options: SchemaOptions) {
|
export function createSchema(options: Readonly<SchemaOptions>) {
|
||||||
const includeType = createInclude(options.include);
|
const {includeType, setSchema} = createInclude(options.include);
|
||||||
const schema = DEFAULT_SCHEMA.extend([
|
const schema = DEFAULT_SCHEMA.extend([
|
||||||
envType,
|
envType,
|
||||||
includeType,
|
includeType,
|
||||||
|
@ -23,7 +23,7 @@ export function createSchema(options: SchemaOptions) {
|
||||||
streamType,
|
streamType,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
options.include.schema = schema;
|
setSchema(schema);
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils';
|
import { InvalidArgumentError, mustCoalesce, NotFoundError, Optional } from '@apextoaster/js-utils';
|
||||||
import { load, Schema, Type as YamlType } from 'js-yaml';
|
import { DEFAULT_SCHEMA, load, Schema, Type as YamlType } from 'js-yaml';
|
||||||
|
|
||||||
export type ReaderEncoding = 'ascii' | 'utf-8';
|
export type ReaderEncoding = 'ascii' | 'utf-8';
|
||||||
export interface ReaderOptions {
|
export interface ReaderOptions {
|
||||||
|
@ -14,15 +14,17 @@ export interface IncludeOptions {
|
||||||
join: (...path: Array<string>) => string;
|
join: (...path: Array<string>) => string;
|
||||||
read: IncludeReader;
|
read: IncludeReader;
|
||||||
resolve: (path: string) => string;
|
resolve: (path: string) => string;
|
||||||
schema: Schema;
|
schema: Optional<Schema>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiate an includer with closure over the provided options.
|
* Instantiate an includer with closure over the provided options.
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function createInclude(options: IncludeOptions) {
|
export function createInclude(options: Readonly<IncludeOptions>) {
|
||||||
return new YamlType('!include', {
|
const optionsCopy = {...options};
|
||||||
|
|
||||||
|
const includeType = new YamlType('!include', {
|
||||||
kind: 'scalar',
|
kind: 'scalar',
|
||||||
resolve(path: string) {
|
resolve(path: string) {
|
||||||
try {
|
try {
|
||||||
|
@ -43,11 +45,21 @@ export function createInclude(options: IncludeOptions) {
|
||||||
return load(options.read(abs, {
|
return load(options.read(abs, {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
}), {
|
}), {
|
||||||
schema: options.schema,
|
schema: mustCoalesce(optionsCopy.schema, DEFAULT_SCHEMA),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new InvalidArgumentError('error including file', err);
|
throw new InvalidArgumentError('error including file', err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// callback to avoid circular dependency (type must be created before schema)
|
||||||
|
function setSchema(schema: Schema) {
|
||||||
|
optionsCopy.schema = schema;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
includeType,
|
||||||
|
setSchema,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,12 @@ const TEST_OPTIONS: IncludeOptions = {
|
||||||
|
|
||||||
describe('include config type', async () => {
|
describe('include config type', async () => {
|
||||||
it('should resolve existing files', async () => {
|
it('should resolve existing files', async () => {
|
||||||
const includeType = createInclude(TEST_OPTIONS);
|
const {includeType} = createInclude(TEST_OPTIONS);
|
||||||
expect(includeType.resolve(join(TEST_ROOT, 'include.yml'))).to.equal(true);
|
expect(includeType.resolve(join(TEST_ROOT, 'include.yml'))).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when resolving missing files', async () => {
|
it('should throw when resolving missing files', async () => {
|
||||||
const includeType = createInclude({
|
const {includeType} = createInclude({
|
||||||
...TEST_OPTIONS,
|
...TEST_OPTIONS,
|
||||||
resolve: () => {
|
resolve: () => {
|
||||||
throw new NotFoundError();
|
throw new NotFoundError();
|
||||||
|
@ -35,12 +35,12 @@ describe('include config type', async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should construct data from file', async () => {
|
it('should construct data from file', async () => {
|
||||||
const includeType = createInclude(TEST_OPTIONS);
|
const {includeType} = createInclude(TEST_OPTIONS);
|
||||||
expect(includeType.construct(join(TEST_ROOT, 'include.yml'))).to.equal('test');
|
expect(includeType.construct(join(TEST_ROOT, 'include.yml'))).to.equal('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw when constructing missing files', async () => {
|
it('should throw when constructing missing files', async () => {
|
||||||
const includeType = createInclude({
|
const {includeType} = createInclude({
|
||||||
...TEST_OPTIONS,
|
...TEST_OPTIONS,
|
||||||
read: () => {
|
read: () => {
|
||||||
throw new InvalidArgumentError();
|
throw new InvalidArgumentError();
|
||||||
|
|
Loading…
Reference in New Issue