Closed BreizhReloaded closed 2 years ago
Hi @BreizhReloaded,
I don't believe you are missing anything really. I think the gist of it is that I likely never got around to supporting oneOf
(or there might be a bug?).
https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L308
It is essentially the same as multi type, such as passing an array of types or an array of constraints.
myField: ["string", {
"type": "number",
// ... more specific number constraints
}
This is where the ultiPropertyValueResolver
at https://github.com/kristianmandrup/schema-to-yup/blob/master/src/multi-property-value-resolver.js is supposed to take over.
As you can see, this has never been implemented, but you can supply your own functionality via the config
object
const toMultiType = this.config.toMultiType;
if (toMultiType) {
return toMultiType(this);
}
It should basically just call yup.oneOf
with the list of resolved yup objects for each element in the array. For your case, it should likely normalise to this array in https://github.com/kristianmandrup/schema-to-yup/blob/master/src/entry.js
class YupSchemaEntry extends Base {
constructor(opts) {
super(opts.config);
const { schema, name, key, value, config, builder } = opts;
this.builder = builder
this.opts = opts;
this.schema = schema;
this.key = key;
this.value = value || {};
this.config = config || {};
this.name = name;
const type = Array.isArray(value) ? "array" : value.type;
this.kind = type === "array" ? "multi" : "single";
this.type = type;
this.setTypeHandlers();
this.setPropertyHandler();
}
Ideally, try subclassing YupSchemaEntry
and pass your custom factory method createYupSchemaEntry
in config
which returns an instance of this subclass.
See https://github.com/kristianmandrup/schema-to-yup/blob/master/src/yup-builder.js#L25
Would be amazing to then integrate your solution in the library source code so it comes out of the box.
It should be resolved similar to how refValueFor
does it: https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L298
field.required().oneOf([
yup.ref(propRefName),
// more resolved yup field schemas
])
I can see there are tests for oneOf
for the string
type: https://github.com/kristianmandrup/schema-to-yup/blob/master/test/types/string/oneOf.test.js
Perhaps try running these tests: jest oneOf
and work with them, adding your particular scenarios. The latest version of the lib has support for much more detailed logging:
{
logging: true,
logDetailed: [{
propName: 'exclusiveMinimum',
key: 'age'
}],
}
The latest commit to master contains a sample implementation for https://github.com/kristianmandrup/schema-to-yup/blob/master/src/multi-property-value-resolver.js#L25 multi type resolution
Such a detailed answer, thank you!
Should I then run some tests with your latest commit?
I can see there are already tests for oneOf
but rather simple ones (testing oneOf
on a list of strings). I suggest you start from there and expand on those tests to include your specific scenario that fails, then turn on detailed logging for your particular field where oneOf
constraint is used.
Currently oneOf
assumes it will receive a list of simple values, as in: values = Array.isArray(values) ? values : [values];
which are passed directly to addConstraint
which builds the yup field constraint for those values.
oneOf() {
let values =
this.constraints.enum || this.constraints.oneOf || this.constraints.anyOf;
if (this.isNothing(values)) return this;
values = Array.isArray(values) ? values : [values];
// using alias
const alias = ["oneOf", "enum", "anyOf"].find(key => {
return this.constraints[key] !== undefined;
});
return this.addConstraint(alias, { values });
}
notOneOf() {
const { not, notOneOf } = this.constraints;
let values = notOneOf || (not && (not.enum || not.oneOf));
if (this.isNothing(values)) return this;
values = Array.isArray(values) ? values : [values];
return this.addConstraint("notOneOf", { values });
}
What needs to happen is a further step, something like this
oneOf() {
let values =
this.constraints.enum || this.constraints.oneOf || this.constraints.anyOf;
if (this.isNothing(values)) return this;
values = Array.isArray(values) ? values : [values];
const resolvedValues = this.resolveValues(values)
// using alias
const alias = ["oneOf", "enum", "anyOf"].find(key => {
return this.constraints[key] !== undefined;
});
return this.addConstraint(alias, { values: resolvedValues });
}
notOneOf() {
const { not, notOneOf } = this.constraints;
let values = notOneOf || (not && (not.enum || not.oneOf));
if (this.isNothing(values)) return this;
values = Array.isArray(values) ? values : [values];
const resolvedValues = this.resolveValues(values)
return this.addConstraint("notOneOf", { values: resolvedValues });
}
resolveValues(values) {
const schemaValues = values
const resolvedValidatorSchemas = schemaValues.map(value => {
return this.isObjectType(value) ? resolveValue(value) : value
})
return this.mixed().oneOf(resolvedValidatorSchemas)
}
resolveValue(value) {
const { createYupSchemaEntry } = this.config
const opts = { schema: this.schema, key: this.key, value, config: this.config }
return createYupSchemaEntry(opts)
}
I don't believe the library currently supports const
, which seem to be one of the core issues.
A simple const handler could be implemented something like this
const() {
let value =this.constraints.const
if (this.isNothing(value)) return this;
// TODO: resolve const data ref if valid format
if (this.isDataRef(value)) {
const dataRefPath = this.normalizeDataRefPath(value)
value = yup.ref(dataRefPath)
}
return this.addConstraint('const', { value });
}
// TODO: investigate yup.ref
normalizeDataRefPath(value) {
// remove first part before /
const parts = value.split('/').shift()
return parts.join('/')
}
isDataRef(value) {
return this.isPresent(value.$data)
}
You can expand with additional constraint handlers (such as for const
) using the config
object as well as described in https://github.com/kristianmandrup/schema-to-yup#custom-constraint-handler-functions
https://ajv.js.org/json-schema.html#const
const The value of this keyword can be anything. The data is valid if it is deeply equal to the value of the keyword.
Example
schema: {const: "foo"}
valid: "foo"
invalid: any other value
The same can be achieved with enum keyword using the array with one item. But const keyword is more than just a syntax sugar for enum. In combination with the $data reference it allows to define equality relations between different parts of the data. This cannot be achieved with enum keyword even with $data reference because $data cannot be used in place of one item - it can only be used in place of the whole array in enum keyword.
Example
schema:
{
type: "object",
properties: {
foo: {type: "number"},
bar: {const: {$data: "1/foo"}}
}
}
valid: {foo: 1, bar: 1}, {}
invalid: {foo: 1}
, {bar: 1}
, {foo: 1, bar: 2}
See the latest commit for the changes highlighted above. Should provide the guiding light to build on.
The latest release should include everything you need. Now has proper support for oneOf
, const
and even title
and description
metadata for use in error messages etc. If you still have issues/questions, please let me know.
Hello,
I've been working with schema-to-yup a bit and it's working great so far. But I faced an issue I can't solve at the moment. My schema looks like this at some point:
When validating with
optionA
oroptionB
as value, I getmyField must be one of the following values: [object Object], [object Object]
. I seems it uses the whole const/title pair as requested values. When using typeobject
instead ofstring
, the field doen't trigger any validation.My schema works perfectly when checking on https://www.jsonschemavalidator.net/. What am I missing?
Thanks!