feathers
feathers copied to clipboard
Automatically respect TypeBox default value
Steps to reproduce
This is my schema
// Main data model schema
export const clientSchema = Type.Object(
{
_id: Type.String(),
apiKey: Type.String(),
online: Type.Boolean({ default: false }),
report: Type.Optional(Type.Any({ default: {} }))
},
{ $id: 'Client', additionalProperties: false }
)
For the default values to be used I have to specifiy it like so
export const clientDataResolver = resolve<Client, HookContext>({
properties: {
online: async () => clientSchema.properties.online.default, // <-- Would be great if that could be avoided
report: async () => clientSchema.properties.report.default
}
})
Expected behavior
Use the default value specified in the TypeBox schema
Actual behavior
Have to explicitly use the default value in resolver
System configuration
Tell us about the applicable parts of your setup.
Module versions (especially the part that's not working):
"@feathersjs/feathers": "^5.0.0-pre.32",
"@feathersjs/typebox": "^5.0.0-pre.32",
"@feathersjs/schema": "^5.0.0-pre.32",
"@feathersjs/mongodb": "^5.0.0-pre.32",
Well this is an embarrassingly simple fix.
validators.ts
export const dataValidator = addFormats(new Ajv({ useDefaults: true }), formats)
Reopening since this came up before and it's not a big deal adding it to the generate validators file.
Re-posting from Discord chat as I think it's relevant to this issue.
I have some confusion on how AJV is intended to handle creation vs update. If I have this given schema
{
_id: Type.String(),
name: Type.String({ default: '[No Name]' }),
online: Type.Boolean({ default: false }),
report: Type.Any({ default: {} })
},
{ $id: 'Clients', additionalProperties: false }
)
...
// Schema for creating new entries
export const clientsDataSchema = Type.Pick(clientsSchema, ['report', 'online'], {
$id: 'ClientsData',
additionalProperties: false
})
During creation I don't want online to be specified it should always default to false so I have
export const dataValidator = addFormats(new Ajv({ useDefaults: true }), formats)
In my validators.ts
file
This works fine except if I use patch to update the online status and don't pass anything else. AJV automatically fills reports with the default empty array. Feathers then proceeds to wipe my report.
How would I allow for create with AJV defaults but not have those defaults effect a patch request?
It would be helpful I think if there was a recommended way to handle default values.
I created this utility function with the help of @marshallswain
const withDefault = (defaultValue: any) => {
return async (val: any, data: any, context: HookContext) => {
if (context.method === 'create') return val || defaultValue
return val
}
}
export const buildDefaults = <_ = Static<TObject>>(schema: TObject) => {
type T = Static<typeof schema>
return (Object.keys(schema.properties) as [keyof T]).reduce((acc, propertyName) => {
const { default: defaultVal } = schema.properties[propertyName]
if (defaultVal !== undefined) {
acc[propertyName] = withDefault(defaultVal)
}
return acc
}, {} as { [key in keyof T]: any })
}
Usage
// Main data model schema
export const clientsSchema = Type.Object(
{
_id: Type.String(),
name: Type.Optional(Type.String({ default: '[No Name]' })),
online: Type.Optional(Type.Boolean({ default: false })),
report: Type.Optional(Type.Any({ default: {} })),
enabled_custom_reports: Type.Optional(Type.Array(Type.String(), { default: [] }))
},
{ $id: 'Clients', additionalProperties: false }
)
...
export const clientsDataResolver = resolve<Clients, HookContext>({
properties: {
...buildDefaults<Clients>(clientsSchema)
}
})
@daffl Is this still the recommended way even though the create
and the patch
schemas have been separated?
Hello, Thanks for the code with buildDefaults @AshotN I have the following issue: as the validator runs before the buildDefaults (here in the clientsDataResolver) I get a validation error, if the field (where the default value should be used) is empty before. Any idea how to solve this or any other way to handle the default values in the schema. The problem with using the default value while creating and patching when using the ajv option still exists.
Thanks, Martin
You can move the existing or add a new schemaHooks.resolveData
hook to wherever you need it (e.g. before schemaHooks.validateData
).
Yes, thanks! Creating a 'service'DefaultResolver and adding it before the validation works perfect.
i have some problem when prop type set of union [ Date,String ], that buildDefaults will invald
Ya it won't handle complex types, you would have to modify it to your needs.