diff --git a/packages/datastore/__tests__/DataStore.ts b/packages/datastore/__tests__/DataStore.ts index c6b9609b29b..922c9752da5 100644 --- a/packages/datastore/__tests__/DataStore.ts +++ b/packages/datastore/__tests__/DataStore.ts @@ -131,6 +131,8 @@ describe('DataStore tests', () => { const metadata = new Metadata({ author: 'some author', tags: [], + rewards: [], + nominations: [], }); expect(metadata).toBeInstanceOf(Metadata); @@ -200,6 +202,8 @@ describe('DataStore tests', () => { const nonModel = new Metadata({ author: 'something', + rewards: [], + nominations: [], }); expect(() => { @@ -315,16 +319,57 @@ describe('DataStore tests', () => { 'Field field1 should be of type string, number received. 1234' ); + expect( + new Model({ + field1: 'someField', + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + nominations: [], + }), + }).metadata.tags + ).toBeUndefined(); + expect(() => { new Model({ field1: 'someField', metadata: new Metadata({ author: 'Some author', tags: undefined, + rewards: [null], + nominations: [], }), }); }).toThrowError( - 'Field tags should be of type string[], undefined received. undefined' + 'All elements in the rewards array should be of type string, [object] received. ' + ); + + expect(() => { + new Model({ + field1: 'someField', + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + nominations: null, + }), + }); + }).toThrowError('Field nominations is required'); + + expect(() => { + new Model({ + field1: 'someField', + metadata: new Metadata({ + author: 'Some author', + tags: undefined, + rewards: [], + penNames: [undefined], + nominations: [], + }), + }); + }).toThrowError( + 'All elements in the penNames array should be of type string, [undefined] received. ' ); expect(() => { @@ -333,10 +378,12 @@ describe('DataStore tests', () => { metadata: new Metadata({ author: 'Some author', tags: [1234], + rewards: [], + nominations: [], }), }); }).toThrowError( - 'All elements in the tags array should be of type string, [number] received. 1234' + 'All elements in the tags array should be of type string | null | undefined, [number] received. 1234' ); expect( @@ -347,9 +394,11 @@ describe('DataStore tests', () => { Model.copyOf(undefined, d => d); }).toThrow('The source object is not a valid model'); expect(() => { - const source = new Model( {field1: 'something'}); - Model.copyOf(source, d => d.field1 = 1234); - }).toThrow('Field field1 should be of type string, number received. 1234'); + const source = new Model({ field1: 'something' }); + Model.copyOf(source, d => (d.field1 = 1234)); + }).toThrow( + 'Field field1 should be of type string, number received. 1234' + ); }); test('Delete params', async () => { @@ -417,6 +466,8 @@ describe('DataStore tests', () => { const metadata = new Metadata({ author: 'some author', tags: [], + rewards: [], + nominations: [], }); await expect(DataStore.save(metadata)).rejects.toThrow( @@ -616,6 +667,9 @@ declare class Model { export declare class Metadata { readonly author: string; readonly tags?: string[]; + readonly rewards: string[]; + readonly penNames?: string[]; + readonly nominations: string[]; constructor(init: Metadata); } @@ -687,8 +741,31 @@ function testSchema(): Schema { isArray: true, type: 'String', isRequired: false, + isArrayNullable: true, + attributes: [], + }, + rewards: { + name: 'rewards', + isArray: true, + type: 'String', + isRequired: true, + attributes: [], + }, + penNames: { + name: 'penNames', + isArray: true, + type: 'String', + isRequired: true, + isArrayNullable: true, attributes: [], }, + nominations: { + name: 'nominations', + isArray: true, + type: 'String', + isRequired: false, + attributes: [], + } }, }, }, diff --git a/packages/datastore/src/datastore/datastore.ts b/packages/datastore/src/datastore/datastore.ts index 3bf096b71c6..f3b17fbbb88 100644 --- a/packages/datastore/src/datastore/datastore.ts +++ b/packages/datastore/src/datastore/datastore.ts @@ -50,6 +50,7 @@ import { SYNC, USER, } from '../util'; +import { isNullOrUndefined } from 'util'; setAutoFreeze(true); @@ -236,9 +237,9 @@ const validateModelFields = (modelDefinition: SchemaModel | SchemaNonModel) => ( const fieldDefinition = modelDefinition.fields[k]; if (fieldDefinition !== undefined) { - const { type, isRequired, name, isArray } = fieldDefinition; + const { type, isRequired, isArrayNullable, name, isArray } = fieldDefinition; - if (isRequired && (v === null || v === undefined)) { + if (((!isArray && isRequired) || (isArray && !isArrayNullable)) && (v === null || v === undefined)) { throw new Error(`Field ${name} is required`); } @@ -246,17 +247,25 @@ const validateModelFields = (modelDefinition: SchemaModel | SchemaNonModel) => ( const jsType = GraphQLScalarType.getJSType(type); if (isArray) { - if (!Array.isArray(v)) { + let errorTypeText: string = jsType; + if (!isRequired) { + errorTypeText = `${jsType} | null | undefined`; + } + + if (!Array.isArray(v) && !isArrayNullable) { throw new Error( - `Field ${name} should be of type ${jsType}[], ${typeof v} received. ${v}` + `Field ${name} should be of type [${errorTypeText}], ${typeof v} received. ${v}` ); } - if ((<[]>v).some(e => typeof e !== jsType)) { + if ( + !isNullOrUndefined(v) && + (<[]>v).some(e => typeof e !== jsType || (isNullOrUndefined(e) && isRequired)) + ) { const elemTypes = (<[]>v).map(e => typeof e).join(','); throw new Error( - `All elements in the ${name} array should be of type ${jsType}, [${elemTypes}] received. ${v}` + `All elements in the ${name} array should be of type ${errorTypeText}, [${elemTypes}] received. ${v}` ); } } else if (typeof v !== jsType && v !== null) { diff --git a/packages/datastore/src/types.ts b/packages/datastore/src/types.ts index 5ad4e164222..4324831957e 100644 --- a/packages/datastore/src/types.ts +++ b/packages/datastore/src/types.ts @@ -159,6 +159,7 @@ type ModelField = { | EnumFieldType; isArray: boolean; isRequired?: boolean; + isArrayNullable?: boolean; association?: ModelAssociation; attributes?: ModelAttributes[]; };