Skip to content

Commit

Permalink
feat(schema): Allow resolvers to validate a schema (#2465)
Browse files Browse the repository at this point in the history
  • Loading branch information
daffl committed Oct 6, 2021
1 parent 4be9db6 commit 7d9590b
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 22 deletions.
14 changes: 10 additions & 4 deletions packages/schema/src/resolver.ts
@@ -1,4 +1,5 @@
import { BadRequest } from '@feathersjs/errors';
import { Schema } from './schema';

export type PropertyResolver<T, V, C> = (
value: V|undefined,
Expand All @@ -12,6 +13,8 @@ export type PropertyResolverMap<T, C> = {
}

export interface ResolverConfig<T, C> {
schema?: Schema<any>,
validate?: 'before'|'after'|false,
properties: PropertyResolverMap<T, C>
}

Expand Down Expand Up @@ -52,8 +55,9 @@ export class Resolver<T, C> {
return resolver(value, data, context, resolverStatus);
}

async resolve<D> (data: D, context: C, status?: Partial<ResolverStatus<T, C>>): Promise<T> {
const { properties: resolvers } = this.options;
async resolve<D> (_data: D, context: C, status?: Partial<ResolverStatus<T, C>>): Promise<T> {
const { properties: resolvers, schema, validate } = this.options;
const data = schema && validate === 'before' ? await schema.validate(_data) : _data;
const propertyList = (Array.isArray(status?.properties)
? status?.properties
// By default get all data and resolver keys but remove duplicates
Expand All @@ -66,7 +70,7 @@ export class Resolver<T, C> {

// Not the most elegant but better performance
await Promise.all(propertyList.map(async name => {
const value = (data as any)[name];
const value = data[name];

if (resolvers[name]) {
try {
Expand All @@ -93,7 +97,9 @@ export class Resolver<T, C> {
throw new BadRequest(`Error resolving data ${status?.properties.join('.')}`, errors);
}

return result;
return schema && validate === 'after'
? await schema.validate(result)
: result;
}
}

Expand Down
63 changes: 45 additions & 18 deletions packages/schema/test/resolver.test.ts
Expand Up @@ -4,26 +4,26 @@ import { BadRequest } from '@feathersjs/errors';
import { schema, resolve, Infer } from '../src';

describe('@feathersjs/schema/resolver', () => {
it('simple resolver', async () => {
const userSchema = schema({
$id: 'simple-user',
type: 'object',
required: ['firstName', 'lastName'],
additionalProperties: false,
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
password: { type: 'string' }
}
} as const);
const context = {
isContext: true
};
const userSchema = schema({
$id: 'simple-user',
type: 'object',
required: ['firstName', 'lastName'],
additionalProperties: false,
properties: {
firstName: { type: 'string' },
lastName: { type: 'string' },
password: { type: 'string' }
}
} as const);
const context = {
isContext: true
};

type User = Infer<typeof userSchema> & {
name: string
};
type User = Infer<typeof userSchema> & {
name: string
};

it('simple resolver', async () => {
const userResolver = resolve<User, typeof context>({
properties: {
password: async (): Promise<string> => {
Expand Down Expand Up @@ -64,6 +64,33 @@ describe('@feathersjs/schema/resolver', () => {
});
});

it('simple resolver with schema and validation', async () => {
const userBeforeResolver = resolve<User, typeof context>({
schema: userSchema,
validate: 'before',
properties: {
name: async (_name, user) => `${user.firstName} ${user.lastName}`
}
});
const userAfterResolver = resolve<User, typeof context>({
schema: userSchema,
validate: 'after',
properties: {
firstName: async () => undefined
}
});

await assert.rejects(() => userBeforeResolver.resolve({}, context), {
message: 'validation failed'
});
await assert.rejects(() => userAfterResolver.resolve({
firstName: 'Test',
lastName: 'Me'
}, context), {
message: 'validation failed'
});
});

it('resolving with errors', async () => {
const dummyResolver = resolve({
properties: {
Expand Down

0 comments on commit 7d9590b

Please sign in to comment.