Skip to content

Commit

Permalink
feat(objectschemaasarray utility): converts object schema to array wi…
Browse files Browse the repository at this point in the history
…th map function and rules

Function that takes object type schema, optional data, pick. omit and order rules and map callback,
returns an array of map callback results. Map callback maps list of object fields after filtering
and ordering according to pick, omit and order rules.

BREAKING CHANGE: orderFields and filter functions are deleted from the export

re #57
  • Loading branch information
tyv committed May 9, 2022
1 parent 7dcd4e2 commit 85b0dc8
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 42 deletions.
23 changes: 11 additions & 12 deletions packages/core/src/auto-view/default-items.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';

import {buildJsonPointer} from '../utils';
import {allFields, buildJsonPointer, filterAndOrderFields} from '../utils';

import {AutoView, AutoViewProps} from './auto-view';
import {filter, getHints, orderFields} from './utils';
import {getHints} from './utils';

export interface AutoFieldsProps extends AutoViewProps {
render?(
Expand All @@ -26,16 +26,15 @@ export function autoFieldsProps(
} = autoViewProps;

const {order, hidden} = getHints(uiSchema, schemaPointer);
const allProperties: string[] = Object.keys({
...properties,
// if schema allows additionalProperties
// iterate over them in data
...(additionalProperties ? data : {})
});
const filtered = filter(allProperties, pick, omit, hidden);
const fields = orderFields(
filtered,
order && order.length > 0 ? order : pick || []

const fields = filterAndOrderFields(
allFields(
{type: 'object', properties, additionalProperties},
additionalProperties ? data : {}
), // if schema has additionalProperties, take fields from `data`
pick,
omit ?? hidden,
order
);

return fields.map(field => ({
Expand Down
30 changes: 0 additions & 30 deletions packages/core/src/auto-view/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,3 @@ export function getComponentOptions(
).getComponentOptions(repo);
return compOptions && compOptions.options;
}

export function orderFields(source: string[], rules?: string[]): string[] {
if (!rules || !rules.length) {
return source;
}
const orderedByRules = rules.filter(rule => source.includes(rule));
const orderedByDefault = source.filter(field => !rules.includes(field));
return orderedByRules.concat(orderedByDefault);
}

export function filter(
properties: string[],
toPick?: string[],
toOmit?: string[],
hidden?: string[]
): string[] {
if (properties && toPick) {
return properties.filter(prop => toPick.includes(prop));
}

if (properties && toOmit) {
return properties.filter(prop => !toOmit.includes(prop));
}

if (properties && hidden) {
return properties.filter(prop => !hidden.includes(prop));
}

return properties;
}
15 changes: 15 additions & 0 deletions packages/core/src/utils/all-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {CoreSchemaMetaSchema} from '../models';

export const allFields = (
{type, properties}: CoreSchemaMetaSchema,
additional: Record<string, any>
) => {
if (type !== 'object' || !properties) {
throw Error('Object schema is required');
}

return Object.keys({
...properties,
...additional
});
};
14 changes: 14 additions & 0 deletions packages/core/src/utils/filter-and-order-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {filterFields} from './filter-fields';
import {orderFields} from './order-fields';

export const filterAndOrderFields = (
fields: string[],
pick?: string[],
omit?: string[],
order?: string[]
) => {
return orderFields(
filterFields(fields, pick, omit),
order && order.length > 0 ? order : pick ?? []
);
};
15 changes: 15 additions & 0 deletions packages/core/src/utils/filter-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function filterFields(
fields: string[],
toPick?: string[],
toOmit?: string[]
): string[] {
if (toPick) {
return fields.filter(prop => toPick.includes(prop));
}

if (toOmit) {
return fields.filter(prop => !toOmit.includes(prop));
}

return fields;
}
5 changes: 5 additions & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ export * from './json-schema-resolver';
export * from './is-required';
export * from './get-component-metadata';
export * from './compose-repos';
export * from './order-fields';
export * from './filter-fields';
export * from './filter-and-order-fields';
export * from './all-fields';
export * from './object-schema-as-array';
45 changes: 45 additions & 0 deletions packages/core/src/utils/object-schema-as-array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {CoreSchemaMetaSchema} from '../models';

import {allFields} from './all-fields';
import {filterAndOrderFields} from './filter-and-order-fields';

type Rules = {
pick?: string[];
omit?: string[];
order?: string[];
};

export type ObjectSchemaAsArrayMapResult = {
field: string;
originalSchema: CoreSchemaMetaSchema | undefined;
};

export const objectSchemaAsArrayMapFn = (
field: string,
schema: CoreSchemaMetaSchema
): ObjectSchemaAsArrayMapResult => ({
field,
originalSchema:
schema.properties![field] ??
(typeof schema.additionalProperties === 'object'
? schema.additionalProperties
: undefined)
});

export const objectSchemaAsArray = <T>(
schema: CoreSchemaMetaSchema,
data: Record<string, any>,
{pick, omit, order}: Rules = {},
mapFunction: (field: string, schema: CoreSchemaMetaSchema) => T
) => {
if (schema.type !== 'object' || !schema.properties) {
throw Error('Object schema is required');
}

return filterAndOrderFields(
allFields(schema, schema.additionalProperties ? data : {}),
pick,
omit,
order
).map(field => mapFunction(field, schema));
};
8 changes: 8 additions & 0 deletions packages/core/src/utils/order-fields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function orderFields(source: string[], rules?: string[]): string[] {
if (!rules || !rules.length) {
return source;
}
const orderedByRules = rules.filter(rule => source.includes(rule));
const orderedByDefault = source.filter(field => !rules.includes(field));
return orderedByRules.concat(orderedByDefault);
}
141 changes: 141 additions & 0 deletions packages/core/tests/object-schema-as-array.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
CoreSchemaMetaSchema,
objectSchemaAsArray,
objectSchemaAsArrayMapFn
} from '../src';

describe('Convert Object Schema to Array', () => {
const schema: CoreSchemaMetaSchema = {
type: 'object',
properties: {
a: {
type: 'string',
title: 'A'
},
b: {
type: 'number',
title: 'B'
},
c: {
type: 'boolean',
title: 'C',
maxLength: 10
},
d: {
type: 'array',
title: 'D',
items: {
type: 'string'
}
}
}
};

const expectedData = Object.keys(schema.properties!).map(field => ({
field,
originalSchema: schema.properties![field]
}));

it('should throw if schema type is not object', () => {
expect(() =>
objectSchemaAsArray({type: 'array'}, {}, {}, () => null)
).toThrow();
});

it('should do basic conversion', () => {
expect(
objectSchemaAsArray(schema, {}, {}, objectSchemaAsArrayMapFn)
).toEqual(expectedData);
});

describe('with additional properties', () => {
it('should use data fields, if `additionalProperties` is true', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: true},
{e: 'e'},
{},
objectSchemaAsArrayMapFn
)
).toEqual([...expectedData, {field: 'e'}]);
});

it('should use data fields, if `additionalProperties` is schema', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: {type: 'string'}},
{e: 'e'},
{},
objectSchemaAsArrayMapFn
)
).toEqual([
...expectedData,
{field: 'e', originalSchema: {type: 'string'}}
]);
});
});

describe('with rules', () => {
it('should pick fields', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: true},
{e: 'e'},
{pick: ['a', 'b', 'c', 'e']},
objectSchemaAsArrayMapFn
)
).toEqual([
...expectedData.filter(({field}) => field !== 'd'),
{field: 'e'}
]);
});

it('should omit fields', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: true},
{e: 'e'},
{omit: ['d']},
objectSchemaAsArrayMapFn
)
).toEqual([
...expectedData.filter(({field}) => field !== 'd'),
{field: 'e'}
]);
});

it('should order pick result', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: true},
{e: 'e'},
{pick: ['a', 'b', 'c', 'e'], order: ['e', 'a']},
objectSchemaAsArrayMapFn
)
).toEqual([
{field: 'e'},
expectedData.find(({field}) => field === 'a'),
...expectedData.filter(
({field}) => field !== 'd' && field !== 'a'
)
]);
});

it('should order omit result', () => {
expect(
objectSchemaAsArray(
{...schema, additionalProperties: true},
{e: 'e'},
{omit: ['d'], order: ['e', 'a']},
objectSchemaAsArrayMapFn
)
).toEqual([
{field: 'e'},
expectedData.find(({field}) => field === 'a'),
...expectedData.filter(
({field}) => field !== 'd' && field !== 'a'
)
]);
});
});
});

0 comments on commit 85b0dc8

Please sign in to comment.