kristianmandrup / schema-to-yup

Schema to Yup validation
Other
283 stars 50 forks source link

Errors with default attributes #104

Closed leonbloy closed 2 years ago

leonbloy commented 2 years ago

I'm experiencing weird errors with schemas that include default values.

First, I start with this stripped version of the schema in the docs (which works ok)

const SCHEMA = {
  $schema: 'http://json-schema.org/draft-07/schema#',
  $id: 'http://example.com/person.schema.json',
  title: 'Person',
  description: 'A person',
  type: 'object',
  properties: {
    name: {
      description: 'Name of the person',
      type: 'string',
    },
    age: {
      description: 'Age of person',
      type: 'integer',
      moreThan: 0,
      max: 130,
      default: 32,
      required: false,
      nullable: true,
    },
    email: {
      type: 'string',
      format: 'email',
    },
  },
  required: ['name', 'email'],
};

const yupSchema = buildYup(SCHEMA);

This works for me, But iI breaks if I add any default to the string property:

    name: {
      description: 'Name of the person',
      type: 'string',
      default: '',
    },
 TypeError: Found non-callable @@iterator
      at it.YupSchemaEntry [as error] (node_modules/schema-to-yup/src/base-property-value-resolver.js:34:11)
      at it.YupSchemaEntryError [as defaultType] (node_modules/schema-to-yup/src/entry.js:15:7)
      at it.singleTypeResolver [as toDefaultEntry] (node_modules/schema-to-yup/src/entry.js:13:7)
      at it.resolve (node_modules/schema-to-yup/src/property-value-resolver.js:43:12)
      at at.toEntry (node_modules/schema-to-yup/src/yup-builder.js:10:3)
      at Object.ot [as createYupSchemaEntry] (node_modules/schema-to-yup/src/yup-builder.js:14:3)
      at lt.builder [as createYupSchemaEntry] (node_modules/schema-to-yup/src/validator-bridge/index.js:33:1)
      at lt.propToYupSchemaEntry (node_modules/schema-to-yup/src/yup-builder.js:161:7)
      at node_modules/schema-to-yup/src/yup-builder.js:148:20

It also breaks if I remove most restrictions from age attribute:

  age: {
      description: 'Age of person',
      type: 'number',
      default: 32,
    },

I'm using schema-to-yup@1.11.5 with node 14, inside React with CRA 5 (webpack 5).

kristianmandrup commented 2 years ago

Hi @leonbloy,

I don't think there was ever any built-in support for default values. Could you please specify how it would translate to Yup validator. Then it should be easy to add. You can even try a simple experiment using a custom constraint handler function

kristianmandrup commented 2 years ago

I see there is a simple way to map it as described here: https://stackoverflow.com/questions/68136485/yup-validation-convert-empty-string-to-default-value

// Add method
yup.addMethod(yup.string, 'stripEmptyString', function () {
  return this.transform((value) => (value === '' ? undefined : value));
});

// Usage
const mySchema = yup.object().shape({
  name: yup.string('Name must be a string').stripEmptyString().default('John Doe')
});

I can see there was a simple attempt to add support for it here: https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L285 to https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L308

Line 209 results in addValueConstraint("default") but that is hardly a valid call to addValueConstraint as there is no value passed in. What should happen can be seen in https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L263

 addValueConstraint(propName, opts) {
    const constraint = this.constraintBuilder.addValueConstraint(
      propName,
      opts
    );

The fix should be in https://github.com/kristianmandrup/schema-to-yup/blob/master/src/types/mixed/mixed.js#L299

    const fn = this[fnName];
    const value = this.constraints[key];
    constraintNames.map(constraintName => {
      fn(constraintName, { value });
    });

That should sort it.

kristianmandrup commented 2 years ago

The latest commit to master should contain a fix to this issue. Please try it out. I've also renamed the method addValueConstraint to addTrueValueConstraint since it forces the value of the constraint to be true.

I also moved the default constraint to a be "simple" constraint, ie. one that passes the unprocessed constraint value to addConstraint.

  get constraintsMap() {
    return {
      simple: ["default", "required", "notRequired", "nullable"],
      trueValue: ["strict"]
    };
  }