Skip to content

Commit

Permalink
Merge branch 'release/v2.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
sebelga committed Oct 19, 2017
2 parents 4aafaeb + 36efe8d commit e87aa6b
Show file tree
Hide file tree
Showing 20 changed files with 1,396 additions and 957 deletions.
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Its main features are:
- **shortcuts** queries
- pre & post **middleware** (hooks)
- **custom methods** on entity instances
- :tada: **NEW** Joi schema definition/validation (since v2.0.0)

This library is in active development, please report any issue you might find.

Expand All @@ -33,7 +34,7 @@ Import gstore-node and @google-cloud/datastore and configure your project.
For the information on how to configure @google-cloud/datastore [read the docs here](https://googlecloudplatform.github.io/google-cloud-node/#/docs/datastore/master/datastore).

```js
const gstore = require('gstore-node');
const gstore = require('gstore-node')();
const datastore = require('@google-cloud/datastore')({
projectId: 'my-google-project-id',
});
Expand All @@ -57,7 +58,7 @@ The [complete documentation of gstore-node](https://sebelga.gitbooks.io/gstore-n
Initialize gstore-node in your server file
```js
// server.js
const gstore = require('gstore-node');
const gstore = require('gstore-node')();
const datastore = require('@google-cloud/datastore')({
projectId: 'my-google-project-id',
});
Expand Down Expand Up @@ -115,6 +116,21 @@ const userSchema = new Schema({
},
});

// Or with **Joi** schema definition
const userSchema = new Schema({
firstname: { joi: Joi.string().required() },
email: { joi: Joi.string().email() },
password: { joi: Joi.string() },
...
}, {
joi: {
extra: {
// validates that when "email" is present, "password" must be too
when: ['email', 'password'],
},
}
);

/**
* List entities query shortcut
*/
Expand Down Expand Up @@ -183,7 +199,7 @@ const getUsers(req ,res) {
.then((entities) => {
res.json(entities);
})
.catch(err => res.status(500).json(err));
.catch(err => res.status(400).json(err));
}

const getUser(req, res) {
Expand All @@ -192,7 +208,7 @@ const getUser(req, res) {
.then((entity) => {
res.json(entity.plain());
})
.catch(err => res.status(500).json(err));
.catch(err => res.status(400).json(err));
}

const createUser(req, res) {
Expand All @@ -206,13 +222,13 @@ const createUser(req, res) {
.catch((err) => {
// If there are any validation error on the schema
// they will be in this error object
res.status(500).json(err);
res.status(400).json(err);
})
}

const updateUser(req, res) {
const userId = +req.params.id;
const entityData = User.sanitize(req.body); // ex: { email: 'john@snow.com' }
const entityData = User.sanitize(req.body); // { email: 'john@snow.com' }

/**
* This will fetch the entity, merge the data and save it back to the Datastore
Expand All @@ -224,7 +240,7 @@ const updateUser(req, res) {
.catch((err) => {
// If there are any validation error on the schema
// they will be in this error object
res.status(500).json(err);
res.status(400).json(err);
})
}

Expand All @@ -234,7 +250,7 @@ const deleteUser(req, res) {
.then((response) => {
res.json(response);
})
.catch(err => res.status(500).json(err));
.catch(err => res.status(400).json(err));
}

module.exports = {
Expand Down
50 changes: 31 additions & 19 deletions lib/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Entity {
}

// create entityData from data passed
this.entityData = buildEntityData(this, data);
this.entityData = buildEntityData(this, data || {});

// wrap entity with hook methods
hooks.wrap(this);
Expand Down Expand Up @@ -161,29 +161,40 @@ function createKey(self, id, ancestors, namespace) {

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

let entityData = {};

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

if (!error) {
entityData = Object.assign({}, value);
}
}

Object.keys(data).forEach((k) => {
entityData[k] = data[k];
});
}

// set default values & excludedFromIndex
Object.keys(schema.paths).forEach((k) => {
const schemaProperty = schema.paths[k];
const prop = schema.paths[k];
const hasValue = {}.hasOwnProperty.call(entityData, k);
const isOptional = {}.hasOwnProperty.call(schemaProperty, 'optional') && schemaProperty.optional !== false;
const isRequired = {}.hasOwnProperty.call(schemaProperty, 'required') && schemaProperty.required === true;
const isOptional = {}.hasOwnProperty.call(prop, 'optional') && prop.optional !== false;
const isRequired = {}.hasOwnProperty.call(prop, 'required') && prop.required === true;

if (!hasValue && !isOptional) {
// Set Default Values
if (!isJoiSchema && !hasValue && !isOptional) {
let value = null;

if ({}.hasOwnProperty.call(schemaProperty, 'default')) {
if (typeof schemaProperty.default === 'function') {
value = schemaProperty.default();
if ({}.hasOwnProperty.call(prop, 'default')) {
if (typeof prop.default === 'function') {
value = prop.default();
} else {
value = schemaProperty.default;
value = prop.default;
}
}

Expand All @@ -193,23 +204,24 @@ function buildEntityData(self, data) {
* then execute the handler for that shortcut
*/
value = defaultValues.__handler__(value);
} else if (value === null && {}.hasOwnProperty.call(schemaProperty, 'values') && !isRequired) {
// Default to first value of the allowed values is **not** required
[value] = schemaProperty.values;
} else if (value === null && {}.hasOwnProperty.call(prop, 'values') && !isRequired) {
// Default to first value of the allowed values if **not** required
[value] = prop.values;
}

entityData[k] = value;
}

if (schemaProperty.excludeFromIndexes === true) {
// Set excludeFromIndexes
if (prop.excludeFromIndexes === true) {
self.excludeFromIndexes.push(k);
} else if (!is.boolean(schemaProperty.excludeFromIndexes)) {
} else if (!is.boolean(prop.excludeFromIndexes)) {
// For embedded entities we can set which properties are excluded from indexes
// by passing a string | array of properties
const excludeFromIndexes = arrify(schemaProperty.excludeFromIndexes);
const excludeFromIndexes = arrify(prop.excludeFromIndexes);

let excludedProps;
if (schemaProperty.type === 'array') {
if (prop.type === 'array') {
excludedProps = excludeFromIndexes.map(excluded => `${k}[].${excluded}`);
} else {
excludedProps = excludeFromIndexes.map(excluded => `${k}.${excluded}`);
Expand All @@ -219,7 +231,7 @@ function buildEntityData(self, data) {
}
});

// add Symbol Key to data
// add Symbol Key to the entityData
entityData[self.gstore.ds.KEY] = self.entityKey;

return entityData;
Expand Down
52 changes: 0 additions & 52 deletions lib/error.js

This file was deleted.

106 changes: 106 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* eslint no-use-before-define: "off" */

'use strict';

const util = require('util');
const is = require('is');

const errorCodes = {
ERR_GENERIC: 'ERR_GENERIC',
ERR_VALIDATION: 'ERR_VALIDATION',
ERR_PROP_TYPE: 'ERR_PROP_TYPE',
ERR_PROP_VALUE: 'ERR_PROP_VALUE',
ERR_PROP_NOT_ALLOWED: 'ERR_PROP_NOT_ALLOWED',
ERR_PROP_REQUIRED: 'ERR_PROP_REQUIRED',
ERR_PROP_IN_RANGE: 'ERR_PROP_IN_RANGE',
};

const message = (text, ...args) => util.format(text, ...args);

const messages = {
ERR_GENERIC: 'An error occured',
ERR_VALIDATION: entityKind => message('The entity data does not validate against the "%s" Schema', entityKind),
ERR_PROP_TYPE: (prop, type) => message('Property "%s" must be a %s', prop, type),
ERR_PROP_VALUE: (value, prop) => message('"%s" is not a valid value for property "%s"', value, prop),
ERR_PROP_NOT_ALLOWED: (prop, entityKind) => (
message('Property "%s" is not allowed for entityKind "%s"', prop, entityKind)
),
ERR_PROP_REQUIRED: prop => message('Property "%s" is required but no value has been provided', prop),
ERR_PROP_IN_RANGE: (prop, range) => message('Property "%s" must be one of [%s]', prop, range && range.join(', ')),
};

class GstoreError extends Error {
constructor(code, msg, args) {
if (code && code in messages) {
if (is.function(messages[code])) {
msg = messages[code](...args.messageParams);
} else {
msg = messages[code];
}
}

if (!msg) {
msg = messages.ERR_GENERIC;
}

super(msg, code, args);
this.name = 'GstoreError';
this.message = msg;
this.code = code || errorCodes.ERR_GENERIC;

if (args) {
Object.keys(args).forEach((k) => {
if (k !== 'messageParams') {
this[k] = args[k];
}
});
}

Error.captureStackTrace(this, this.constructor);
}

static get TypeError() {
return class extends TypeError {};
}

static get ValueError() {
return class extends ValueError {};
}

static get ValidationError() {
return class extends ValidationError {};
}
}

class ValidationError extends GstoreError {
constructor(...args) {
super(...args);
this.name = 'ValidationError';
Error.captureStackTrace(this, this.constructor);
}
}

class TypeError extends GstoreError {
constructor(...args) {
super(...args);
this.name = 'TypeError';
Error.captureStackTrace(this, this.constructor);
}
}

class ValueError extends GstoreError {
constructor(...args) {
super(...args);
this.name = 'ValueError';
Error.captureStackTrace(this, this.constructor);
}
}

module.exports = {
GstoreError,
ValidationError,
TypeError,
ValueError,
message,
errorCodes,
};
6 changes: 0 additions & 6 deletions lib/helper.js

This file was deleted.

10 changes: 10 additions & 0 deletions lib/helpers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

'use strict';

const queryHelpers = require('./queryhelpers');
const validation = require('./validation');

module.exports = {
queryHelpers,
validation,
};
Loading

0 comments on commit e87aa6b

Please sign in to comment.