Skip to content

Commit

Permalink
Merge pull request #915 from orbitjs/build-validators
Browse files Browse the repository at this point in the history
Introduce autoValidate setting for RecordCache and RecordSource
  • Loading branch information
dgeb committed Jan 21, 2022
2 parents c3f7a96 + d980f48 commit b87d5ce
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 69 deletions.
2 changes: 1 addition & 1 deletion packages/@orbit/record-cache/src/async-record-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export abstract class AsyncRecordCache<
? settings.processors
: [AsyncSchemaConsistencyProcessor, AsyncCacheIntegrityProcessor];

if (Orbit.debug && settings.processors === undefined) {
if (settings.autoValidate !== false && settings.processors === undefined) {
processors.push(AsyncSchemaValidationProcessor);
}

Expand Down
27 changes: 24 additions & 3 deletions packages/@orbit/record-cache/src/record-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ export interface RecordCacheSettings<
normalizer?: RecordNormalizer;
validatorFor?: ValidatorForFn<StandardValidator | StandardRecordValidator>;
validators?: Dict<StandardValidator | StandardRecordValidator>;

/**
* Automatically validate the contents of all requests.
*
* If true, builds a `validatorFor` function if one has not been provided.
* This will include standard validators as well as any custom `validators`
* that may be provided.
*
* @default true
*/
autoValidate?: boolean;

queryBuilder?: QueryBuilder;
transformBuilder?: TransformBuilder;
defaultQueryOptions?: DefaultRequestOptions<QueryOptions>;
Expand Down Expand Up @@ -90,14 +102,22 @@ export abstract class RecordCache<
this._keyMap = keyMap;

let { validatorFor, validators } = settings;
if (validatorFor) {
const autoValidate = settings.autoValidate !== false;

if (!autoValidate) {
assert(
'RecordCache should not be constructed with a `validatorFor` or `validators` if `autoValidate === false`',
validators === undefined && validatorFor === undefined
);
} else if (validatorFor !== undefined) {
assert(
'RecordCache can be constructed with either a `validatorFor` or `validators`, but not both',
validators === undefined
);
} else if (Orbit.debug || validators !== undefined) {
} else {
validatorFor = buildRecordValidatorFor({ validators });
}

this._validatorFor = validatorFor;

if (
Expand All @@ -109,7 +129,8 @@ export abstract class RecordCache<
if (normalizer === undefined) {
normalizer = new StandardRecordNormalizer({
schema,
keyMap
keyMap,
validateInputs: autoValidate
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/@orbit/record-cache/src/sync-record-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export abstract class SyncRecordCache<
? settings.processors
: [SyncSchemaConsistencyProcessor, SyncCacheIntegrityProcessor];

if (Orbit.debug && settings.processors === undefined) {
if (settings.autoValidate !== false && settings.processors === undefined) {
processors.push(SyncSchemaValidationProcessor);
}

Expand Down
48 changes: 40 additions & 8 deletions packages/@orbit/record-cache/test/async-record-cache-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Orbit } from '@orbit/core';
import { RecordKeyMap, RecordSchema } from '@orbit/records';
import {
RecordKeyMap,
RecordSchema,
StandardRecordNormalizer
} from '@orbit/records';
import { AsyncSchemaValidationProcessor } from '../src/operation-processors/async-schema-validation-processor';
import { ExampleAsyncRecordCache } from './support/example-async-record-cache';
import { createSchemaWithRemoteKey } from './support/setup';
Expand All @@ -19,25 +22,54 @@ module('AsyncRecordCache', function (hooks) {
assert.ok(cache);
});

test('it is assigned 3 processors by default in debug mode', async function (assert) {
test('it is assigned 3 processors, a validatorFor, transformBuilder, and queryBuilder by default', async function (assert) {
const cache = new ExampleAsyncRecordCache({ schema });
assert.ok(Orbit.debug);

assert.equal(
cache.processors.length,
3,
'processors are assigned by default'
);
assert.ok(cache.validatorFor, 'validatorFor has been created');
assert.ok(cache.queryBuilder, 'queryBuilder has been created');

const normalizer = cache.queryBuilder
.$normalizer as StandardRecordNormalizer;

assert.ok(normalizer, 'normalizer has been created for queryBuilder');
assert.ok(normalizer.validateInputs, 'normalizer will validate inputs');
assert.ok(cache.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
cache.queryBuilder.$normalizer,
cache.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('it is assigned only 2 processors by default in non-debug mode', async function (assert) {
Orbit.debug = false;
const cache = new ExampleAsyncRecordCache({ schema });
test('it is assigned only 2 processors and will NOT be assigned a validatorFor function if autoValidate: false is specified', async function (assert) {
const cache = new ExampleAsyncRecordCache({ schema, autoValidate: false });
assert.equal(
cache.processors.length,
2,
'processors are assigned by default'
);
Orbit.debug = true;
assert.notOk(cache.validatorFor, 'validatorFor has NOT been created');
assert.ok(cache.queryBuilder, 'queryBuilder has been created');

const normalizer = cache.queryBuilder
.$normalizer as StandardRecordNormalizer;

assert.ok(normalizer, 'normalizer has been created for queryBuilder');
assert.notOk(
normalizer.validateInputs,
'normalizer will NOT validate inputs'
);
assert.ok(cache.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
cache.queryBuilder.$normalizer,
cache.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('it requires a schema', function (assert) {
Expand Down
48 changes: 40 additions & 8 deletions packages/@orbit/record-cache/test/sync-record-cache-test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Orbit } from '@orbit/core';
import { RecordKeyMap, RecordSchema } from '@orbit/records';
import {
RecordKeyMap,
RecordSchema,
StandardRecordNormalizer
} from '@orbit/records';
import { SyncSchemaValidationProcessor } from '../src/operation-processors/sync-schema-validation-processor';
import { ExampleSyncRecordCache } from './support/example-sync-record-cache';
import { createSchemaWithRemoteKey } from './support/setup';
Expand All @@ -19,25 +22,54 @@ module('SyncRecordCache', function (hooks) {
assert.ok(cache);
});

test('it is assigned 3 processors by default in debug mode', async function (assert) {
test('it is assigned 3 processors, a validatorFor, transformBuilder, and queryBuilder by default', async function (assert) {
const cache = new ExampleSyncRecordCache({ schema });
assert.ok(Orbit.debug);

assert.equal(
cache.processors.length,
3,
'processors are assigned by default'
);
assert.ok(cache.validatorFor, 'validatorFor has been created');
assert.ok(cache.queryBuilder, 'queryBuilder has been created');

const normalizer = cache.queryBuilder
.$normalizer as StandardRecordNormalizer;

assert.ok(normalizer, 'normalizer has been created for queryBuilder');
assert.ok(normalizer.validateInputs, 'normalizer will validate inputs');
assert.ok(cache.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
cache.queryBuilder.$normalizer,
cache.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('it is assigned only 2 processors by default in non-debug mode', async function (assert) {
Orbit.debug = false;
const cache = new ExampleSyncRecordCache({ schema });
test('it is assigned only 2 processors and will NOT be assigned a validatorFor function if autoValidate: false is specified', async function (assert) {
const cache = new ExampleSyncRecordCache({ schema, autoValidate: false });
assert.equal(
cache.processors.length,
2,
'processors are assigned by default'
);
Orbit.debug = true;
assert.notOk(cache.validatorFor, 'validatorFor has NOT been created');
assert.ok(cache.queryBuilder, 'queryBuilder has been created');

const normalizer = cache.queryBuilder
.$normalizer as StandardRecordNormalizer;

assert.ok(normalizer, 'normalizer has been created for queryBuilder');
assert.notOk(
normalizer.validateInputs,
'normalizer will NOT validate inputs'
);
assert.ok(cache.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
cache.queryBuilder.$normalizer,
cache.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('it requires a schema', function (assert) {
Expand Down
42 changes: 39 additions & 3 deletions packages/@orbit/records/src/record-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,36 @@ export interface RecordSourceSettings<
schema: RecordSchema;
keyMap?: RecordKeyMap;
normalizer?: RecordNormalizer;

/**
* A completely custom set of validators.
*/
validatorFor?: ValidatorForFn<StandardValidator | StandardRecordValidator>;

/**
* Custom validators to override, and be merged with, the standard ones which
* will be built as long as `autoValidate !== false`.
*/
validators?: Dict<StandardValidator | StandardRecordValidator>;

/**
* Automatically validate the contents of all requests.
*
* If true, builds a `validatorFor` function if one has not been provided.
* This will include standard validators as well as any custom `validators`
* that may be provided.
*
* @default true
*/
autoValidate?: boolean;

/**
* Automatically upgrade this source whenever its schema is upgraded.
*
* Override the `upgrade` method to provide an upgrade implementation.
*
* @default true
*/
autoUpgrade?: boolean;
}

Expand Down Expand Up @@ -69,12 +97,19 @@ export abstract class RecordSource<
const { schema } = settings;
let { validatorFor, validators } = settings;

if (validatorFor) {
const autoValidate = settings.autoValidate !== false;

if (!autoValidate) {
assert(
'RecordSource should not be constructed with a `validatorFor` or `validators` if `autoValidate === false`',
validators === undefined && validatorFor === undefined
);
} else if (validatorFor !== undefined) {
assert(
'RecordSource can be constructed with either a `validatorFor` or `validators`, but not both',
validators === undefined
);
} else if (Orbit.debug || validators !== undefined) {
} else {
validatorFor = buildRecordValidatorFor({ validators });
}

Expand All @@ -92,7 +127,8 @@ export abstract class RecordSource<
if (normalizer === undefined) {
normalizer = new StandardRecordNormalizer({
schema,
keyMap: settings.keyMap
keyMap: settings.keyMap,
validateInputs: autoValidate
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,14 @@ import {
import { StandardRecordValidator } from './standard-record-validators';
import { standardRecordValidators } from './standard-record-validator-dict';

export function buildRecordValidatorFor<
V = StandardValidator | StandardRecordValidator
>(settings?: { validators?: Dict<V> }): ValidatorForFn<V> {
const validators: Dict<V> = {};

Object.assign(validators, standardValidators);
Object.assign(validators, standardRecordValidators);

const customValidators = settings?.validators;
if (customValidators) {
Object.assign(validators, customValidators);
}

return buildValidatorFor<V>({
validators
export function buildRecordValidatorFor(settings?: {
validators?: Dict<StandardValidator | StandardRecordValidator>;
}): ValidatorForFn<StandardValidator | StandardRecordValidator> {
return buildValidatorFor<StandardValidator | StandardRecordValidator>({
validators: {
...standardValidators,
...standardRecordValidators,
...settings?.validators
}
});
}
6 changes: 1 addition & 5 deletions packages/@orbit/records/src/standard-record-normalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@ export class StandardRecordNormalizer
this.schema = schema;
this.keyMap = keyMap;
this.cloneInputs = cloneInputs;
if (validateInputs === undefined) {
this.validateInputs = Orbit.debug ? true : false;
} else {
this.validateInputs = validateInputs;
}
this.validateInputs = validateInputs;
}

normalizeRecordType(type: string): string {
Expand Down
51 changes: 43 additions & 8 deletions packages/@orbit/records/test/record-source-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,7 @@ module('Source', function (hooks) {
assert.ok(true, 'after upgrade');
});

test('it will be assigned a transformBuilder and queryBuilder by default', async function (assert) {
source = new MySource({ schema });

assert.ok(source.queryBuilder, 'queryBuilder has been created');
assert.ok(source.transformBuilder, 'transformBuilder has been created');
});

test('it will not be auto-upgraded if autoUpgrade: false option is specified', function (assert) {
test('it will not be auto-upgraded if autoUpgrade: false setting is specified', function (assert) {
assert.expect(1);

class MyDynamicSource extends RecordSource {
Expand All @@ -57,6 +50,48 @@ module('Source', function (hooks) {
assert.ok(true, 'after upgrade');
});

test('it will create a validatorFor, transformBuilder, and queryBuilder by default', async function (assert) {
source = new MySource({ schema });

assert.ok(source.validatorFor, 'validatorFor has been created');
assert.ok(source.queryBuilder, 'queryBuilder has been created');
assert.ok(
source.queryBuilder.$normalizer,
'normalizer has been created for queryBuilder'
);
assert.ok(
source.queryBuilder.$normalizer.validateInputs,
'normalizer will validate inputs'
);
assert.ok(source.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
source.queryBuilder.$normalizer,
source.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('it will NOT be assigned a validatorFor function if autoValidate: false is specified', async function (assert) {
source = new MySource({ schema, autoValidate: false });

assert.notOk(source.validatorFor, 'validatorFor has NOT been created');
assert.ok(source.queryBuilder, 'queryBuilder has been created');
assert.ok(
source.queryBuilder.$normalizer,
'normalizer has been created for queryBuilder'
);
assert.notOk(
source.queryBuilder.$normalizer.validateInputs,
'normalizer will NOT validate inputs'
);
assert.ok(source.transformBuilder, 'transformBuilder has been created');
assert.strictEqual(
source.queryBuilder.$normalizer,
source.transformBuilder.$normalizer,
'normalizer is the same for transformBuilder'
);
});

test('creates a `transformLog`, `requestQueue`, and `syncQueue`, and assigns each the same bucket as the Source', function (assert) {
assert.expect(8);
const bucket = new FakeBucket();
Expand Down
Loading

0 comments on commit b87d5ce

Please sign in to comment.