1
0
Fork 0

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:
ssube 2021-03-27 19:31:44 -05:00
parent fc1f4d01a2
commit 51038a412a
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
3 changed files with 26 additions and 14 deletions

View File

@ -6,7 +6,7 @@ import { regexpType } from './type/Regexp';
import { streamType } from './type/Stream';
export interface SchemaOptions {
include: IncludeOptions;
include: Readonly<IncludeOptions>;
}
/**
@ -14,8 +14,8 @@ export interface SchemaOptions {
*
* @public
*/
export function createSchema(options: SchemaOptions) {
const includeType = createInclude(options.include);
export function createSchema(options: Readonly<SchemaOptions>) {
const {includeType, setSchema} = createInclude(options.include);
const schema = DEFAULT_SCHEMA.extend([
envType,
includeType,
@ -23,7 +23,7 @@ export function createSchema(options: SchemaOptions) {
streamType,
]);
options.include.schema = schema;
setSchema(schema);
return schema;
}

View File

@ -1,5 +1,5 @@
import { InvalidArgumentError, NotFoundError } from '@apextoaster/js-utils';
import { load, Schema, Type as YamlType } from 'js-yaml';
import { InvalidArgumentError, mustCoalesce, NotFoundError, Optional } from '@apextoaster/js-utils';
import { DEFAULT_SCHEMA, load, Schema, Type as YamlType } from 'js-yaml';
export type ReaderEncoding = 'ascii' | 'utf-8';
export interface ReaderOptions {
@ -14,15 +14,17 @@ export interface IncludeOptions {
join: (...path: Array<string>) => string;
read: IncludeReader;
resolve: (path: string) => string;
schema: Schema;
schema: Optional<Schema>;
}
/**
* Instantiate an includer with closure over the provided options.
* @public
*/
export function createInclude(options: IncludeOptions) {
return new YamlType('!include', {
export function createInclude(options: Readonly<IncludeOptions>) {
const optionsCopy = {...options};
const includeType = new YamlType('!include', {
kind: 'scalar',
resolve(path: string) {
try {
@ -43,11 +45,21 @@ export function createInclude(options: IncludeOptions) {
return load(options.read(abs, {
encoding: 'utf-8',
}), {
schema: options.schema,
schema: mustCoalesce(optionsCopy.schema, DEFAULT_SCHEMA),
});
} catch (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,
};
}

View File

@ -17,12 +17,12 @@ const TEST_OPTIONS: IncludeOptions = {
describe('include config type', 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);
});
it('should throw when resolving missing files', async () => {
const includeType = createInclude({
const {includeType} = createInclude({
...TEST_OPTIONS,
resolve: () => {
throw new NotFoundError();
@ -35,12 +35,12 @@ describe('include config type', 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');
});
it('should throw when constructing missing files', async () => {
const includeType = createInclude({
const {includeType} = createInclude({
...TEST_OPTIONS,
read: () => {
throw new InvalidArgumentError();