Skip to content

Commit

Permalink
fix: added default options when Joi is enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
BasKiers authored and sebelga committed Apr 26, 2019
1 parent 76a0db2 commit 528da24
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 19 deletions.
4 changes: 2 additions & 2 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,13 +372,13 @@ function parseId(self, id) {

function buildEntityData(self, data) {
const { schema } = self;
const isJoiSchema = !is.undef(schema._joi);
const isJoiSchema = schema.isJoi;

let entityData;

// If Joi schema, get its default values
if (isJoiSchema) {
const { error, value } = schema._joi.validate(data);
const { error, value } = schema.validateJoi(data);

if (!error) {
entityData = Object.assign({}, value);
Expand Down
9 changes: 2 additions & 7 deletions lib/helpers/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,10 @@ const validate = (entityData, schema, entityKind, datastore) => {

const props = Object.keys(entityData);
const totalProps = Object.keys(entityData).length;
const isJoi = !is.undef(schema._joi);

if (isJoi) {
if (schema.isJoi) {
// We leave the validation to Joi
const joiOptions = schema.options.joi.options || {};
joiOptions.stripUnknown = {}.hasOwnProperty.call(joiOptions, 'stripUnknown')
? joiOptions.stripUnknown
: schema.options.explicitOnly !== false;
return schema._joi.validate(entityData, joiOptions);
return schema.validateJoi(entityData);
}

for (let i = 0; i < totalProps; i += 1) {
Expand Down
17 changes: 15 additions & 2 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,19 @@ export class Schema<T = SchemaPath> {
method: string,
callback: funcReturningPromise | funcReturningPromise[]
): void;

/**
* Executes joi.validate on given data. If schema does not have a joi config object data is returned
*
* @param {*} data The data to sanitize
* @returns {*} The data sanitized
*/
validateJoi(data: { [propName: string]: any }): Validation<{ [P in keyof T]: T[P] }>;

/**
* Checks if the schema has a joi config object.
*/
readonly isJoi: boolean;
}

export interface Model<T = { [propName: string]: any }> {
Expand Down Expand Up @@ -844,9 +857,9 @@ export interface QueryFindAroundOptions extends QueryOptions {
showKey?: boolean;
}

export interface Validation {
export interface Validation<T = any> {
error: ValidationError;
value: any;
value: T;
}

declare class GstoreError extends Error {
Expand Down
14 changes: 9 additions & 5 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,20 +644,24 @@ class Model extends Entity {
return null;
}

const isJoiSchema = !is.undef(schema._joi);
const isJoiSchema = schema.isJoi;

let sanitized;
let joiOptions;
if (isJoiSchema) {
sanitized = schema._joi.validate(data).value;
const { error, value } = schema.validateJoi(data);
if (!error) {
sanitized = Object.assign({}, value);
}
joiOptions = schema.options.joi.options || {};
} else {
}
if (sanitized === undefined) {
sanitized = Object.assign({}, data);
}

const isSchemaExplicitOnly = isJoiSchema
? !joiOptions.allowUnknown
: schema.options.explicitOnly !== false;
? joiOptions.stripUnknown
: schema.options.explicitOnly === true;

const isWriteDisabled = options.disabled.includes('write');
const hasSchemaRefProps = Boolean(schema.__meta.refProps);
Expand Down
40 changes: 37 additions & 3 deletions lib/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Joi = optional('joi');

const { queries } = require('./constants');
const VirtualType = require('./virtualType');
const { ValidationError, errorCodes } = require('./errors');

const IS_QUERY_HOOK = {
update: true,
Expand Down Expand Up @@ -90,7 +91,7 @@ class Schema {
// });

if (options) {
this._joi = buildJoiSchema(properties, options.joi);
this._joi = buildJoiSchema(properties, this.options.joi);
}
}

Expand Down Expand Up @@ -165,6 +166,23 @@ class Schema {
}
return this.virtuals[propName];
}

validateJoi(entityData) {
if (!this.isJoi) {
return {
error: new ValidationError(
errorCodes.ERR_GENERIC,
'Schema does not have a joi configuration object'
),
value: entityData,
};
}
return this._joi.validate(entityData, this.options.joi.options || {});
}

get isJoi() {
return !is.undef(this._joi);
}
}

/**
Expand All @@ -183,21 +201,37 @@ Schema.Types = {
function defaultOptions(options) {
const optionsDefault = {
validateBeforeSave: true,
explicitOnly: true,
queries: {
readAll: false,
format: queries.formats.JSON,
},
};
options = extend(true, {}, optionsDefault, options);
if (options.joi) {
const joiOptionsDefault = {
options: {
allowUnknown: options.explicitOnly !== true,
},
};
if (is.object(options.joi)) {
options.joi = extend(true, {}, joiOptionsDefault, options.joi);
} else {
options.joi = Object.assign({}, joiOptionsDefault);
}
if (!Object.prototype.hasOwnProperty.call(options.joi.options, 'stripUnknown')) {
options.joi.options.stripUnknown = options.joi.options.allowUnknown !== true;
}
}
return options;
}

function buildJoiSchema(schema, joiConfig) {
if (is.undef(joiConfig)) {
if (!is.object(joiConfig)) {
return undefined;
}

const hasExtra = is.object(joiConfig) && is.object(joiConfig.extra);
const hasExtra = is.object(joiConfig.extra);
const joiKeys = {};

Object.keys(schema).forEach((k) => {
Expand Down
16 changes: 16 additions & 0 deletions test/model-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,22 @@ describe('Model', () => {
assert.isDefined(entityData.unknown);
});

it('should return the same value object from Model.sanitize and Entity.validate in Joi schema', () => {
schema = new Schema({
foo: { joi: Joi.object({ bar: Joi.any() }).required() },
createdOn: { joi: Joi.date().default(() => new Date('01-01-2019'), 'static createdOn date') },
}, { joi: true });
GstoreModel = gstore.model('BlogJoi', schema, gstore);

const data = { foo: { unknown: 123 } };
const entityData = GstoreModel.sanitize(data);
const { value: validationData, error: validationError } = new GstoreModel(data).validate();

assert.isUndefined(entityData.foo.unknown);
assert.isNull(validationError);
assert.deepEqual(entityData, validationData);
});

it('should preserve the datastore.KEY', () => {
const key = GstoreModel.key(123);
let data = { foo: 'bar' };
Expand Down

0 comments on commit 528da24

Please sign in to comment.