Skip to content
This repository has been archived by the owner on Mar 20, 2022. It is now read-only.

Commit

Permalink
Add ability to dynamically set nested schema type (#415)
Browse files Browse the repository at this point in the history
* Add ability to dynamically set nested schema type

* Re-use type

* Use blocks

* Add response type

* Update types, add tests

Co-authored-by: Paul Armstrong <[email protected]>
  • Loading branch information
OliverJAsh and paularmstrong committed Jan 23, 2020
1 parent 3246f91 commit a61b8cf
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 11 deletions.
7 changes: 5 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ declare namespace schema {
}

export class Object<T = any> {
constructor(definition: {[key: string]: Schema<T>})
constructor(definition: SchemaObject<T>)
define(definition: Schema): void
}

Expand All @@ -46,8 +46,11 @@ export type Schema<T = any> =
| SchemaObject<T>
| SchemaArray<T>;

export type SchemaValueFunction<T> = (t: T) => Schema<T>;
export type SchemaValue<T> = Schema<T> | SchemaValueFunction<T>;

export interface SchemaObject<T> {
[key: string]: Schema<T>
[key: string]: SchemaValue<T>
}

export interface SchemaArray<T> extends Array<Schema<T>> {}
Expand Down
41 changes: 41 additions & 0 deletions src/__tests__/__snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,47 @@ Object {
}
`;

exports[`normalize can normalize entity nested inside entity using property from parent 1`] = `
Object {
"entities": Object {
"linkables": Object {
"1": Object {
"data": 2,
"id": 1,
"module_type": "article",
"schema_type": "media",
},
},
"media": Object {
"2": Object {
"id": 2,
"url": "catimage.jpg",
},
},
},
"result": 1,
}
`;

exports[`normalize can normalize entity nested inside object using property from parent 1`] = `
Object {
"entities": Object {
"media": Object {
"2": Object {
"id": 2,
"url": "catimage.jpg",
},
},
},
"result": Object {
"data": 2,
"id": 1,
"module_type": "article",
"schema_type": "media",
},
}
`;

exports[`normalize can use fully custom entity classes 1`] = `
Object {
"entities": Object {
Expand Down
53 changes: 53 additions & 0 deletions src/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,59 @@ describe('normalize', () => {

expect(() => normalize(test, testEntity)).not.toThrow();
});

test('can normalize entity nested inside entity using property from parent', () => {
const linkablesSchema = new schema.Entity('linkables');
const mediaSchema = new schema.Entity('media');
const listsSchema = new schema.Entity('lists');

const schemaMap = {
media: mediaSchema,
lists: listsSchema
};

linkablesSchema.define({
data: (parent) => schemaMap[parent.schema_type]
});

const input = {
id: 1,
module_type: 'article',
schema_type: 'media',
data: {
id: 2,
url: 'catimage.jpg'
}
};

expect(normalize(input, linkablesSchema)).toMatchSnapshot();
});

test('can normalize entity nested inside object using property from parent', () => {
const mediaSchema = new schema.Entity('media');
const listsSchema = new schema.Entity('lists');

const schemaMap = {
media: mediaSchema,
lists: listsSchema
};

const linkablesSchema = {
data: (parent) => schemaMap[parent.schema_type]
};

const input = {
id: 1,
module_type: 'article',
schema_type: 'media',
data: {
id: 2,
url: 'catimage.jpg'
}
};

expect(normalize(input, linkablesSchema)).toMatchSnapshot();
});
});

describe('denormalize', () => {
Expand Down
10 changes: 9 additions & 1 deletion src/schemas/Entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ export default class EntitySchema {
Object.keys(this.schema).forEach((key) => {
if (processedEntity.hasOwnProperty(key) && typeof processedEntity[key] === 'object') {
const schema = this.schema[key];
processedEntity[key] = visit(processedEntity[key], processedEntity, key, schema, addEntity, visitedEntities);
const resolvedSchema = typeof schema === 'function' ? schema(input) : schema;
processedEntity[key] = visit(
processedEntity[key],
processedEntity,
key,
resolvedSchema,
addEntity,
visitedEntities
);
}
});

Expand Down
3 changes: 2 additions & 1 deletion src/schemas/Object.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export const normalize = (schema, input, parent, key, visit, addEntity, visitedE
const object = { ...input };
Object.keys(schema).forEach((key) => {
const localSchema = schema[key];
const value = visit(input[key], input, key, localSchema, addEntity, visitedEntities);
const resolvedLocalSchema = typeof localSchema === 'function' ? localSchema(input) : localSchema;
const value = visit(input[key], input, key, resolvedLocalSchema, addEntity, visitedEntities);
if (value === undefined || value === null) {
delete object[key];
} else {
Expand Down
24 changes: 17 additions & 7 deletions typescript-tests/object.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { normalize, schema } from '../index'

const data = {
/* ...*/
};
type Response = {
users: Array<{ id: string }>
}
const data: Response = { users: [ { id: 'foo' } ] };
const user = new schema.Entity('users');

const responseSchema = new schema.Object({ users: new schema.Array(user) });
const normalizedData = normalize(data, responseSchema);
{
const responseSchema = new schema.Object({ users: new schema.Array(user) });
const normalizedData = normalize(data, responseSchema);
}

const responseSchemaAlt = { users: new schema.Array(user) };
const normalizedDataAlt = normalize(data, responseSchemaAlt);
{
const responseSchema = new schema.Object<Response>({ users: (response: Response) => new schema.Array(user) });
const normalizedData = normalize(data, responseSchema);
}

{
const responseSchema = { users: new schema.Array(user) };
const normalizedData = normalize(data, responseSchema);
}

0 comments on commit a61b8cf

Please sign in to comment.