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'; 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;
} }

View File

@ -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,
};
} }

View File

@ -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();