Skip to content

Commit

Permalink
Merge pull request #531 from dobrynin/error-unknown
Browse files Browse the repository at this point in the history
errorUnknown
  • Loading branch information
fishcharlie committed Jan 12, 2019
2 parents 830e3b2 + 40cb269 commit ee87f2e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 6 deletions.
10 changes: 10 additions & 0 deletions docs/_docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ var schema = new Schema({...}, {
});
```

**errorUnknown**: boolean

Specifies that any attributes not defined in the _schema_ will throw an error if encountered while parsing records from DynamoDB. This defaults to false.

```js
var schema = new Schema({...}, {
errorUnknown: true
});
```

**attributeToDynamo**: function

A function that accepts `name, json, model, defaultFormatter, options`.
Expand Down
12 changes: 8 additions & 4 deletions lib/Attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,13 +575,17 @@ Attribute.prototype.parseDynamo = async function(json) {
if(!v){ return; }
let val = {};

const { attributes, schema } = attr;
// loop over all the properties of the input
for(const [name, value] of Object.entries(v)) {
let subAttr = attr.attributes[name];
let subAttr = attributes[name];
// if saveUnknown is activated the input has an unknown attribute, let's create one on the fly.
if (!subAttr && (attr.schema.options.saveUnknown || Array.isArray(attr.options.saveUnknown) && attr.options.saveUnknown.indexOf(name) >= 0)) {
subAttr = createUnknownAttributeFromDynamo(attr.schema, name, value);
attr.schema.attributes[name] = subAttr;
if (!subAttr && schema.options.errorUnknown) {
throw new errors.ParseError(`Unknown nested attribute ${name} with value: ${JSON.stringify(value)}`);
}
if (!subAttr && (schema.options.saveUnknown || Array.isArray(schema.options.saveUnknown) && schema.options.saveUnknown.indexOf(name) >= 0)) {
subAttr = createUnknownAttributeFromDynamo(schema, name, value);
attr.attributes[name] = subAttr;
}
if (subAttr) {
const attrVal = await subAttr.parseDynamo(value);
Expand Down
9 changes: 9 additions & 0 deletions lib/Schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,15 @@ Schema.prototype.parseDynamo = async function(model, dynamoObj) {
for(const name in dynamoObj) {
let attr = this.attributes[name];

if (!attr && this.options.errorUnknown) {
const hashKey = this.hashKey && this.hashKey.name && dynamoObj[this.hashKey.name] && JSON.stringify(dynamoObj[this.hashKey.name]);
const rangeKey = this.rangeKey && this.rangeKey.name && JSON.stringify(dynamoObj[this.rangeKey.name]);
let errorMessage = `Unknown top-level attribute ${name} on model ${model.$__.name} with `;
if (hashKey) errorMessage += `hash-key ${hashKey} and `;
if (rangeKey) errorMessage += `range-key ${rangeKey} and `;
errorMessage += `value: ${JSON.stringify(dynamoObj[name])}`;
throw new errors.ParseError(errorMessage)
}
if((!attr && this.options.saveUnknown === true) || (Array.isArray(this.options.saveUnknown) && this.options.saveUnknown.indexOf(name) >= 0)) {
attr = Attribute.createUnknownAttributeFromDynamo(this, name, dynamoObj[name]);
this.attributes[name] = attr;
Expand Down
84 changes: 82 additions & 2 deletions test/Schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -956,9 +956,11 @@ describe('Schema tests', function (){
},
anotherMap: {
M: {
aNestedAttribute: { S: 'I am a nested attribute' }
aNestedAttribute: { S: 'I am a nested unknown sub-attribute of a known top-level attribute' },
weHaveTheSameName: { S: 'I should be independent of the top-level field with the same name' },
}
},
weHaveTheSameName: { N: 123 },
listAttrib: {
L: [
{ S: 'v1' },
Expand All @@ -974,8 +976,10 @@ describe('Schema tests', function (){
aNumber: 5,
},
anotherMap: {
aNestedAttribute: 'I am a nested attribute'
aNestedAttribute: 'I am a nested unknown sub-attribute of a known top-level attribute',
weHaveTheSameName: 'I should be independent of the top-level field with the same name'
},
weHaveTheSameName: 123,
listAttrib: [
'v1',
'v2',
Expand Down Expand Up @@ -1213,6 +1217,82 @@ describe('Schema tests', function (){
});
});


it('Errors when encountering an unknown attribute if errorUnknown is set to true', async function () {
const schema = new Schema({
myHashKey: {
hashKey: true,
type: String,
},
myRangeKey: {
rangeKey: true,
type: String,
},
knownAttribute: String,
}, {
errorUnknown: true,
});

let err;
const model = {['$__']: {
name: 'OnlyKnownAttributesModel'
}};
try {
await schema.parseDynamo(model, {
myHashKey: 'I am the hash key',
myRangeKey: 'I am the range key',
knownAttribute: { S: 'I am known to the schema. Everything is groovy.' },
unknownAttribute: { S: 'I am but a stranger to the schema. I should cause an error.' }
});
} catch (e) {
err = e;
}

err.should.be.instanceof(errors.ParseError);
err.message.should.equal('Unknown top-level attribute unknownAttribute on model OnlyKnownAttributesModel with hash-key "I am the hash key" and range-key "I am the range key" and value: {"S":"I am but a stranger to the schema. I should cause an error."}');
});


it('Errors when encountering an unknown nested attribute if errorUnknown is set to true', async function () {
const schema = new Schema({
myHashKey: {
hashKey: true,
type: String,
},
myRangeKey: {
rangeKey: true,
type: String,
},
knownAttribute: String,
myMap: Map,
}, {
errorUnknown: true,
});

let err;
const model = {['$__']: {
name: 'OnlyKnownAttributesModel'
}};

try {
await schema.parseDynamo(model, {
myHashKey: 'I am the hash key',
myRangeKey: 'I am the range key',
knownAttribute: { S: 'I am known to the schema. Everything is groovy.' },
myMap: {
M: {
nestedUnknownAttribute: { S: 'I too am a stranger. Will the schema be able to find me down here?' }
}
}
});
} catch (e) {
err = e;
}

err.should.be.instanceof(errors.ParseError);
err.message.should.match(/Unknown nested attribute nestedUnknownAttribute with value: {"S":"I too am a stranger. Will the schema be able to find me down here\?"}/);
});

it('Should throw error when type is map but no map is provided', function (done) {
let err;
try {
Expand Down

0 comments on commit ee87f2e

Please sign in to comment.