-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* float initial implementation * add back in error handling * changes based on feedback * Fix tests, exceptions Co-authored-by: Mitchell Hamilton <[email protected]> * thanks Mitchell * no null * Update packages/keystone/src/fields/types/float/tests/non-null/test-fixtures.ts * Apply suggestions from code review * added changeset and docs updates * Update packages/keystone/src/fields/types/float/views/index.tsx * check validations are finite numbers * small fixes * remove bad regex * Fix things * Update docs/pages/docs/apis/fields.mdx Co-authored-by: Noviny <[email protected]> Co-authored-by: Mitchell Hamilton <[email protected]>
- Loading branch information
1 parent
3d5e0ba
commit 002e1d8
Showing
7 changed files
with
357 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@keystone-next/keystone': major | ||
--- | ||
|
||
In the `float` field, `defaultValue` is now a static number, `isRequired` has moved to `validation.isRequired`, along with new `validation.min` and `validation.max` options. The `float` field can also be made non-nullable at the database-level with the `isNullable` option which defaults to `true`. `graphql.read.isNonNull` can also be set if the field has `isNullable: false` and you have no read access control and you don't intend to add any in the future, it will make the GraphQL output field non-nullable. `graphql.create.isNonNull` can also be set if you have no create access control and you don't intend to add any in the future, it will make the GraphQL create input field non-nullable. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,170 @@ | ||
// Float in GQL: A signed double-precision floating-point value. | ||
import { humanize } from '../../../lib/utils'; | ||
import { | ||
BaseGeneratedListTypes, | ||
FieldTypeFunc, | ||
CommonFieldConfig, | ||
fieldType, | ||
graphql, | ||
fieldType, | ||
orderDirectionEnum, | ||
FieldDefaultValue, | ||
filters, | ||
} from '../../../types'; | ||
import { assertCreateIsNonNullAllowed, assertReadIsNonNullAllowed } from '../../non-null-graphql'; | ||
import { resolveView } from '../../resolve-view'; | ||
|
||
export type FloatFieldConfig<TGeneratedListTypes extends BaseGeneratedListTypes> = | ||
CommonFieldConfig<TGeneratedListTypes> & { | ||
defaultValue?: FieldDefaultValue<number, TGeneratedListTypes>; | ||
isRequired?: boolean; | ||
defaultValue?: number; | ||
isIndexed?: boolean | 'unique'; | ||
}; | ||
isNullable?: boolean; | ||
validation?: { | ||
min?: number; | ||
max?: number; | ||
isRequired?: boolean; | ||
}; | ||
graphql?: { | ||
create?: { | ||
isNonNull?: boolean; | ||
}; | ||
}; | ||
} & ( | ||
| { isNullable?: true; defaultValue?: number } | ||
| { | ||
isNullable: false; | ||
graphql?: { read?: { isNonNull?: boolean } }; | ||
} | ||
); | ||
|
||
export const float = | ||
<TGeneratedListTypes extends BaseGeneratedListTypes>({ | ||
isIndexed, | ||
isRequired, | ||
validation, | ||
defaultValue, | ||
...config | ||
}: FloatFieldConfig<TGeneratedListTypes> = {}): FieldTypeFunc => | ||
meta => | ||
fieldType({ | ||
meta => { | ||
if ( | ||
defaultValue !== undefined && | ||
(typeof defaultValue !== 'number' || !Number.isFinite(defaultValue)) | ||
) { | ||
throw new Error( | ||
`The float field at ${meta.listKey}.${meta.fieldKey} specifies a default value of: ${defaultValue} but it must be a valid finite number` | ||
); | ||
} | ||
|
||
if ( | ||
validation?.min !== undefined && | ||
(typeof validation.min !== 'number' || !Number.isFinite(validation.min)) | ||
) { | ||
throw new Error( | ||
`The float field at ${meta.listKey}.${meta.fieldKey} specifies validation.min: ${validation.min} but it must be a valid finite number` | ||
); | ||
} | ||
|
||
if ( | ||
validation?.max !== undefined && | ||
(typeof validation.max !== 'number' || !Number.isFinite(validation.max)) | ||
) { | ||
throw new Error( | ||
`The float field at ${meta.listKey}.${meta.fieldKey} specifies validation.max: ${validation.max} but it must be a valid finite number` | ||
); | ||
} | ||
|
||
if ( | ||
validation?.min !== undefined && | ||
validation?.max !== undefined && | ||
validation.min > validation.max | ||
) { | ||
throw new Error( | ||
`The float field at ${meta.listKey}.${meta.fieldKey} specifies a validation.max that is less than the validation.min, and therefore has no valid options` | ||
); | ||
} | ||
|
||
if (config.isNullable === false) { | ||
assertReadIsNonNullAllowed(meta, config); | ||
} | ||
assertCreateIsNonNullAllowed(meta, config); | ||
|
||
const mode = config.isNullable === false ? 'required' : 'optional'; | ||
|
||
const fieldLabel = config.label ?? humanize(meta.fieldKey); | ||
|
||
return fieldType({ | ||
kind: 'scalar', | ||
mode: 'optional', | ||
mode, | ||
scalar: 'Float', | ||
index: isIndexed === true ? 'index' : isIndexed || undefined, | ||
default: | ||
typeof defaultValue === 'number' ? { kind: 'literal', value: defaultValue } : undefined, | ||
})({ | ||
...config, | ||
hooks: { | ||
...config.hooks, | ||
async validateInput(args) { | ||
const value = args.resolvedData[meta.fieldKey]; | ||
|
||
if ((validation?.isRequired || config.isNullable === false) && value === null) { | ||
args.addValidationError(`${fieldLabel} is required`); | ||
} | ||
|
||
if (typeof value === 'number') { | ||
if (validation?.max !== undefined && value > validation.max) { | ||
args.addValidationError( | ||
`${fieldLabel} must be less than or equal to ${validation.max}` | ||
); | ||
} | ||
|
||
if (validation?.min !== undefined && value < validation.min) { | ||
args.addValidationError( | ||
`${fieldLabel} must be greater than or equal to ${validation.min}` | ||
); | ||
} | ||
} | ||
|
||
await config.hooks?.validateInput?.(args); | ||
}, | ||
}, | ||
input: { | ||
where: { | ||
arg: graphql.arg({ type: filters[meta.provider].Float.optional }), | ||
arg: graphql.arg({ type: filters[meta.provider].Float[mode] }), | ||
resolve: filters.resolveCommon, | ||
}, | ||
create: { arg: graphql.arg({ type: graphql.Float }) }, | ||
create: { | ||
arg: graphql.arg({ | ||
type: config.graphql?.create?.isNonNull | ||
? graphql.nonNull(graphql.Float) | ||
: graphql.Float, | ||
defaultValue: | ||
config.graphql?.create?.isNonNull && typeof defaultValue === 'number' | ||
? defaultValue | ||
: undefined, | ||
}), | ||
resolve(value) { | ||
if (value === undefined) { | ||
return defaultValue ?? null; | ||
} | ||
return value; | ||
}, | ||
}, | ||
update: { arg: graphql.arg({ type: graphql.Float }) }, | ||
orderBy: { arg: graphql.arg({ type: orderDirectionEnum }) }, | ||
}, | ||
output: graphql.field({ type: graphql.Float }), | ||
output: graphql.field({ | ||
type: | ||
config.isNullable === false && config.graphql?.read?.isNonNull | ||
? graphql.nonNull(graphql.Float) | ||
: graphql.Float, | ||
}), | ||
views: resolveView('float/views'), | ||
__legacy: { isRequired, defaultValue }, | ||
getAdminMeta() { | ||
return { | ||
validation: { | ||
min: validation?.min || null, | ||
max: validation?.max || null, | ||
isRequired: validation?.isRequired ?? false, | ||
}, | ||
defaultValue: defaultValue ?? null, | ||
}; | ||
}, | ||
}); | ||
}; |
38 changes: 38 additions & 0 deletions
38
packages/keystone/src/fields/types/float/tests/non-null/test-fixtures.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { float } from '../..'; | ||
|
||
export const name = 'Float with isNullable: false'; | ||
export const typeFunction = (x: any) => float({ isNullable: false, ...x }); | ||
export const exampleValue = () => 6.28; | ||
export const exampleValue2 = () => 6.283; | ||
export const supportsUnique = true; | ||
export const fieldName = 'testField'; | ||
export const skipRequiredTest = true; | ||
export const supportsGraphQLIsNonNull = true; | ||
|
||
export const getTestFields = () => ({ | ||
testField: float({ isFilterable: true, isNullable: false }), | ||
}); | ||
|
||
export const initItems = () => { | ||
return [ | ||
{ name: 'post1', testField: -21.5 }, | ||
{ name: 'post2', testField: 0 }, | ||
{ name: 'post3', testField: 1.2 }, | ||
{ name: 'post4', testField: 2.3 }, | ||
{ name: 'post5', testField: 3 }, | ||
{ name: 'post6', testField: 5 }, | ||
{ name: 'post7', testField: 20.8 }, | ||
]; | ||
}; | ||
|
||
export const storedValues = () => [ | ||
{ name: 'post1', testField: -21.5 }, | ||
{ name: 'post2', testField: 0 }, | ||
{ name: 'post3', testField: 1.2 }, | ||
{ name: 'post4', testField: 2.3 }, | ||
{ name: 'post5', testField: 3 }, | ||
{ name: 'post6', testField: 5 }, | ||
{ name: 'post7', testField: 20.8 }, | ||
]; | ||
|
||
export const supportedFilters = () => []; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.