Git Product home page Git Product logo

schema's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

schema's Issues

Bug in Performance graphics ?

I think you have a bug in Percentage column of performance test.
If avg for your lib is 0.5us and zod is 100x slower (53us) it should be -10000% ?

image (15)

sideEffects is invalid in package.json declaration

Problem

The following declaration in the package.json is invalid.

{
  "sideEffects": false
}

The @typeofweb/schema library does have side effects and cannot be tree shaked (dead code removed) when not used but other variables are imported from the same module containing variables from @typeofweb/schema.

How to reproduce the issue

If you use:

You would see @typeofweb/schema is identified as having a side effect here.

Screenshot Screen Shot 2021-03-19 at 8 06 21 PM

Expected behavior

Class ValidationError shouldn't have side effects

Suggestion: Make minLength modifier accept number

const atLeast2 = validate(minLength(2)(number()));

const notOk = atLeast2(1) // <- Error!
const ok = atLeast2(10) // <- {foo:10}!

This is just my suggestion, it can be rejected if not necessary

validate API. Request for changes

How it works

import * as s from "@typeofweb/schema"

const a = s.object({
  key1: s.optional(s.string()),
  key2: s.optional(s.string()),
})()
const validatorA = s.validate(a)

const value = validatorA({
  key1: "hello"
})
console.log(value) // { key1: "hello", key2: undefined }

Requested change

I don't know whether the above is intentional, but I believe it'd be at least unexpected for many other users like me.
Please consider not to return undefined values; in other words, I'd expect the above to be the following instead:

console.log(value) // { key1: "hello" }

How users can overcome this issue as of now

If anyone stumbles upon it and wants and immediate solution, you could use the following:

import * as s from "@typeofweb/schema"

export const validate = <S extends s.SomeSchema<any>>(schema: S) => {
  const validator = s.validate(schema)

  const validatorModified: typeof validator = (value) => {
    const withoutUndefinedValues = validator(value)
    for (const key of Object.keys(withoutUndefinedValues)) {
      if (withoutUndefinedValues[key] === undefined) {
        delete withoutUndefinedValues[key]
      }
    }
    return withoutUndefinedValues
  }

  return validatorModified
}

Create resolver for react-hook-form

React hook form is a popular form validation library that supports forms schema resolving by resolver API (https://github.com/react-hook-form/resolvers). Resolvers are already implemented for validators like yup, superstruct, etc which are natural competitors for this library.

For me, it seems like a common use-case to use schema definitions to validate forms. But to do that we need to first support reporting errors per object attribute (#13).

Resolver draft (trying to do it here):

export const schemaResolver = (schema) => {
  const validator = validate(schema);

  return (values) => {
    try {
      return {
        values: validator(values),
        errors: {}
      };
    } catch (e) {
      const errors = /* get errors per schema attribute, each error should be a string */

      return {
        values: {},
        errors: errors
      };
    }
  };
}

Expose object's schema shape

Consider following schema

const mySchema = object({
  id: string(),
  name: pipe(string, minStringLength(10))
})

Now, I would like to create another schema to validate only part of mySchema. For that reason I can create another schema and pick only a few validators from mySchema, potentially like this:

const anotherSchema = object({
  name: mySchema.name
})

But the object's validator shape is not available after the validator is built. I took a look at the code and this is potentially possible but the types in refine function are too complex for me at the moment :)

Object schema validation does not convert its values

As is it presented in documentation object schema validation should convert its values into the expected ones, for instance: strings into numbers. Instead of this we get error with invalid type.

Version: 0.6.1
Use case: https://schema.typeofweb.com/docs/validate#example
Expected result:

{
    dateFrom: new Date('2020-10-15T00:00:00.000Z'), // date instance
    dateTo: new Date('2020-10-15T00:00:00.000Z'), // date instance
    resultsPerPage: 10 // number
}

Received result:
ValidationError: Invalid type! Expected { dateFrom: Date, dateTo: Date, resultsPerPage: number } but got {"dateFrom":"2020-10-15","dateTo":"2020-10-15","resultsPerPage":"10"}!

validate API. Safe validate against multiple validators

Problem

Currently, it is impossible to safely validate against multiple validators.

Proposed solution

export const validateSafe = <V, Validators extends ((v) => any)[]>(
  value: V,
  validators: Validators,
) => {
  let result: V | undefined
  for (const validator of validators) {
    try {
      result = validator(value)
    } catch {}
  }

  return result
}

const body = validateSafe(value, [validator1, validator2])

RFC: Refinements

RFC: Refinements

Version 2, 2020-01-27

Goals and use cases

  1. Introduce a mechanism which will allow users to extend schema with their own rules of validation without the need to modify the core of the library. These rules are not expressible in terms of TypeScript typesystem and need to be enforced purely in the runtime.
  2. Allow users to transform values before validation in order to support custom formats and inputs.
  3. Support nominal types implemented by users.
  4. Allow to bail out from further validation and assume value valid or invalid.

Proposal

New higher order function refine provided by the library:

refine(refinement);
const refinement = (value, t) => {
  return value + 1; // continue validation on transformed value
  return t.left(value); // bail out with error
  return t.right(value); // bail out with success
  return t.right(value + 1); // bail out with success and transformed value
  return t.toEither(value > 10); // bail out with error or success depending on the condition
};

The result of calling refine is a function which can (optionally) take another schema as an argument and it might be passed to schemas:

const myRefinement = refine(refinement);
const validator = pipe(string, myRefinement, date, validate);

// alternatively
// validate(date(myRefinement(string())));

Examples

const even = refine((value, t) => t.toEither(value % 2 === 0));

pipe(number, even, validate)(12); // 12
// validate(even(number()))(12); // 12

pipe(number, even, validate)(3); // ValidationError
// validate(even(number()))(3); // ValidationError
const noDuplicateItems = refine((arr, t) => {
  const allUnique = arr.every((item, index) => index === arr.indexOf(item));
  return allUnique ? arr : t.left(arr);
});

const uniqueStringArrayValidator = pipe(array(string), noDuplicateItems, validate);
// const uniqueStringArrayValidator = validate(noDuplicateItems(array(string())));
uniqueStringArrayValidator(['a', 'b']); // ['a', 'b']
uniqueStringArrayValidator(['a', 'b', 'a']); // ValidationError
const allowTimestamps = refine((value) => (Number.isInteger(value) ? new Date(value) : value));

pipe(allowTimestamps, date, validate)(1231231231231); // Date(2009-01-06T08:40:31.231Z)
// validate(date(allowTimestamps()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)

pipe(allowTimestamps, date, validate)(Infinity); // ValidationError
// validate(date(allowTimestamps()))(Infinity); // ValidationError
const usDateString = refine((value) => {
  const result = typeof value === 'string' && value.match(/(?<m>\d\d)\/(?<d>\d\d)\/(?<y>\d\d\d\d)/);
  if (!result) {
    return value;
  }
  const { y, m, d } = result.groups;
  return `${y}-${m}-${d}`;
});

pipe(string, usDateString, date, validate)('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
// validate(date(usDateString(string())))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)

pipe(string, usDateString, date, validate)('14/12/2021'); // ValidationError
// validate(date(usDateString(string())))('14/12/2021'); // ValidationError
const integer = refine<Integer>((value, t) => t.toEither(Number.isInteger(value)));

pipe(number, integer, validate)(1); // Integer
// validate(integer(number()))(1); // Integer

pipe(number, integer, validate)(1.5); // ValidationError
// validate(integer(number()))(1.5); // ValidationError
const email = refine<Email>((value, t) => t.toEither(value.includes('@')));

pipe(string, email, validate)('[email protected]'); // Email '[email protected]'
// validate(email(string()))('[email protected]'); // Email '[email protected]'

pipe(string, email, validate)('just hello'); // ValidationError
// validate(email(string()))('just hello'); // ValidationError
const allowFalse = refine((value, t) => (value === false ? t.right(value) : value));

pipe(allowFalse, string, validate())(false); // false
// validate(string(allowFalse()))(false); // false

pipe(allowFalse, string, validate())(true); // ValidationError
// validate(string(allowFalse()))(true); // ValidationError
const isInObject = <T extends object>(object: T, key: keyof any): key is keyof T =>
  Object.prototype.hasOwnProperty.call(object, key);

const hasProperties = (properties: string[]) =>
  refine((value, t) =>
    properties.every((prop) => isInObject(value, prop)) ? value : t.left(value),
  );

pipe(hasProperties(['foo']), validate)({ foo: 'bar' }); // { foo: "bar" }
// validate(hasProperties(["foo"])())({ foo: "bar" }); // { foo: "bar" }

pipe(hasProperties(['foo']), validate)({ bar: 'foo' }); // ValidationError
// validate(hasProperties(["foo"])())({ bar: "foo" }); // ValidationError
const url = refine<URL>((value, t) => t.toEither(value.includes('https://')));

pipe(string, url, validate)('https://github.com/') // URL 'https://github.com/'
validate(url(string()))('https://github.com/') // URL 'https://github.com/'

pipe(string, url, validate)('abc') // ValidationError
validate(url(string()))('abc') // ValidationError

Previous versions

Version 1, 2020-01-26 ## Goals and use cases

Goal 1

  1. Introduce a mechanism which will allow users to extend schema with their own rules of validation without the need to modify the core of the library. These rules are not expressible in terms of TypeScript typesystem and need to be enforced purely in the runtime.
validate(evenNumber)(12); // 12
validate(evenNumber)(3); // ValidationError
validate(noDuplicateItems)(['a', 'b']); // ['a', 'b']
validate(noDuplicateItems)(['a', 'b', 'a']); // ValidationError

Goal 2

  1. Allow users to transform values before validation in order to support custom formats and inputs.
validate(timestamp(date()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)
validate(timestamp(date()))(Infinity); // ValidationError
validate(weirdDate(date()))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
validate(weirdDate(date()))('14/12/2021'); // ValidationError

Goal 3

  1. Support nominal types implemented by users.
type Integer = Nominal<number>; // `Nominal` is provided by user
const integer = (x: unknown): x is Integer => { ?? } // shape of this function is to be designed
validate(integer())(1); // Integer
validate(integer())(1.5); // ValidationError
type Email = Nominal<string>; // `Nominal` is provided by user
const email = (x: unknown): x is Email => { ?? } // shape of this function is to be designed
validate(email())('[email protected]'); // Email
validate(email())('just hello'); // ValidationError

Goal 4

  1. Allow to bail out from further validation and assume value valid or invalid.
validate(allowFalse(string()))(false); // false
validate(allowFalse(string()))(true); // ValidationError

Suggestion

New higher order function refine and refineAfter provided by the library:

refineAfter(beforeSchema)(refinement);
refine(refinement);
const refinement = (value, t) => {
  return value + 1; // continue validation on transformed value
  return t.left(value); // bail out with error
  return t.right(value); // bail out with success
  return t.right(value + 1); // bail out with success and transformed value
  return t.toEither(value > 10); // bail out with error or success depending on the condition
};

Examples

const evenNumber = refineAfter(number())((value, t) => t.toEither(value % 2 === 0));
validate(evenNumber)(12); // 12
validate(evenNumber)(3); // ValidationError
const noDuplicateItems = (arr, t) => {
  const allUnique = arr.every((item, index) => index === arr.indexOf(item));
  return allUnique ? arr : t.left(arr);
};
const uniqueStringArray = refineAfter(array(string()))(noDuplicateItems);
validate(uniqueStringArray)(['a', 'b']); // ['a', 'b']
validate(uniqueStringArray)(['a', 'b', 'a']); // ValidationError
const withTimestamps = refine((value) => (Number.isInteger(value) ? new Date(value) : value));
validate(withTimestamps(date()))(1231231231231); // Date(2009-01-06T08:40:31.231Z)
validate(withTimestamps(date()))(Infinity); // ValidationError
const parseUsDateString = (value: string) => {
  const result = value.match(/(?<m>\d\d)\/(?<d>\d\d)\/(?<y>\d\d\d\d)/);
  if (!result) {
    return null;
  }
  const { y, m, d } = result.groups;
  return `${y}-${m}-${d}`;
};
const weirdDate = refineAfter(string())((value) => parseUsDateString(value) ?? value);
validate(weirdDate(date()))('01/12/2021'); // Date(2021-01-12T00:00:00.000Z)
validate(weirdDate(date()))('14/12/2021'); // ValidationError
const integer = refineAfter<Integer>(number())((value, t) => t.toEither(Number.isInteger(value)));
validate(integer())(1); // Integer
validate(integer())(1.5); // ValidationError
const email = refineAfter<Email>(string())((value, t) => t.toEither(value.includes('@')));
validate(email())('[email protected]'); // Email
validate(email())('just hello'); // ValidationError
const allowFalse = refine((value, t) => (value === false ? t.right(value) : value));
validate(allowFalse(string()))(false); // false
validate(allowFalse(string()))(true); // ValidationError

Optional object fields

In the first docs example:

const personSchema = object({
  name: string(),
  age: number(),
  email: optional(string()),
});

const mark = {
  name: 'Mark',
  age: 29,
};

mark will fail to validate because it's really the value that's optional and not the field itself.

Can we think of a way to make whole fields optional?

Thanks!

Refactor validators

Currently, we have one huge function responsible for validation. We should split it into separate functions bundled together with specific validators. The aim is to solve two problems:

  • limited tree-shaking โ€“ make validation code tree-shakable
  • broken Single Responsibility Principle โ€“ when adding new validators we need to modify two places

This should not be a breaking change in the user-facing API.
Validators implementation details should be considered private.

How do I get a type from schema/validator?

Hey, a friend just shared this library with me. It looks pretty cool, and the benchmarks seem impressive :)

Reading the README I'm not sure how do I extract a type from validator?

Looking at the examples, I'd guess that this is possible, but it seems to be missing from the README. (I'm sorry if it's there and I missed it)

type Person = FromSchema<typeof personSchema>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.