Giter Club home page Giter Club logo

valita's People

Contributors

dimatakoy avatar github-actions[bot] avatar jviide avatar marvinhagemeister avatar reinismu avatar rslabbert avatar townsheriff avatar

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

valita's Issues

Can t.Infer be made to _not_ unroll deep schemas?

This is partly a question, and partly a feature request.

Usually when TypeScript sees a type that matches something you've given a name, it displays that thing's type as the name. Sometimes that's not what I want, and I have to work around it, like so:

type User = {
  name: string,
  id: number,
}

type Post = {
  user: User,
  date: Date,
}

const post: Post = {
  user: { name: 'myndzi', id: 1 },
  date: new Date()
}

type Simplify<T extends {}> = {[K in keyof T]: T[K]} & unknown;

type simplified = Simplify<typeof post>;
// type simplified = {
//     user: User;
//     date: Date;
// }

In Valita, this seems to be the default (only?) behavior of t.Infer:

import * as v from '@badrap/valita';

const User = v.object({
  name: v.string(),
  id: v.number(),
});

const Post = v.object({
  user: User,
  date: v.string()
});

type post = v.Infer<typeof Post>;
// type post = {
//   user: {
//     name: string;
//     id: number;
//   };
//   date: string;
// };

Unfortunately, this means that if you include even a single field that is moderately complex, the editor unrolls the full thing, making the "high level view" of the type in question useless -- since it eliminates most of the direct keys in favor of showing the full deep structure instead (the contents of user above).

It would be nice if t.Infer did not do this, but I'm not entirely sure where the behavior is coming from. Is Valita explicitly using something like the Simplify trick above? If so, would you be open to not doing that? (It's easy to turn a "collapsed" object into an "expanded" object, but not the other way around).

An alternative might be to export AbstractType and allow usage something like:

type User = v.Infer<typeof User>;

const Post = v.object({
  user: User as AbstractType<User>,
  date: v.string()
});

... or else, something with chain, assert, etc. that lets the user specify a type alias for a complex type.

The desired behavior using the above (trivial) example would be for the type of Post to be shown as:

type post = {
  user: User,
  date: string
}

Is there any way to get all issues and not the only first one?

    const testData = {};

    const testSchema = v.object({
        test: v.string(),
        test2: v.string()
    });

    testSchema.parse(testData);

The error only speaks of one problem but actually two fields are missing:

ValitaError: missing_key at . (missing key \"test\")

Issue with generics

One thing I would like to do is create a generic parsing method for network requests. It would take in shape and return Infer. Currently it doesn't work fully.

const fetchAndValidate = async <A extends Type>(url: string, responseShape: A): Promise<Infer<A>> => {
    const result = await fetch(url);
    return responseShape.parse(result); // Type 'unknown' is not assignable to type 'SomethingOutputOf<A>'
}

Am I doing something wrong or there is something off with types?

How to express intersection types?

A quick vanilla example:

type T1<F> = {field: F}
type T2 = {label: 'stringy'}
type X = T1<string> & T2

I want to do this in valita... I expected this to work:

import {Type, Infer, object, string, literal} from '@badrap/valita';
const T1 = <FT extends Type>(F: FT) => object({field: F});
const T1s = T1(string());
const T2 = object({label: literal('stringy')});
const X = T1s.chain(T2.parse);
type X = Infer<typeof T1s> & Infer<typeof T2>;

(Note: in this example the X type is correctly inferred)

but tsc doesn't accept .chain(T2.parse) maybe there should be a .isect(T2) that just accepts the type?

Passing ParseOptions to chain?

It is a nice how chain and try works together and you can create nice combinations

import * as v from '@badrap/valita';
const s1 = v.object({
  x: v.number(),
});
const s2 = v.object({
  y: v.number(),
});
const s3 = s1.chain(value => s2.try(value));
console.log(s3.parse({x: 1, y: 2})); // throws
console.log(s3.parse({x: 1, y: 2}, {mode: 'passthrough'})); // also throws

I think that chain should take the options so that the last line above could be made to work.

Tuple type

I'm very excited about this library. It has speed and everything I need except tuple :/

Any plans to add it?

Thanks!

Help developer catch error early

I noticed that it's too easy to make an error when working with .check(...). What about a runtime warning or something like this when a developer tries to chain schema with .check(...)?

or... maybe disallow this chaining like you already do it with .shape prop in case of .assert()?

Extending objects

Zod had .extend() method on most types.

I can use v.object({ ...BaseType, <Wriet my stuff>})

It works fine in all cases except when I have v.object({}).assert(...). Then I can't get shape out of it. I guess even if I would get it, I would love my assert. Any ideas on how I could improve this?

Exporting AbstractType

I currently have some HTTP request functions with a signature similar to this:

private async _request<TSchema extends v.ObjectType>(
    requestPath: string,
    schema: TSchema,
): Promise<v.Infer<TSchema>> {

This is really nice, because I can do generic validation of a JSON response body. However, it only works with object types, not with union types.

The type that v.Infer requires is T extends AbstractType. However, I cannot write that into my function signature because AbstractType isn't exported. Would it be possible to export AbstractType?

`Pick` and `Omit`

This could be a nice addition to objects. There are cases when we need to take only a part of the object.

cosnt TestObject = v.object({
    id: v.string(),
    someValue: v.string().asserts(...)
});

const input = TestObject.pick(['someValue']);

What are your thoughts on this?

Input & output types

Hi there,

We really wanted to use your lib over Zod (for performance reasons), but this is the only feature that we miss here on Valita :

const stringToNumber = z.string().transform((val) => val.length);

type input = z.input<typeof stringToNumber>; // string
type output = z.output<typeof stringToNumber>; // number

Is there any way to achieve this with Valita or is this feature planned ?

Regards

Recursive Types Cannot be Defined

I might have a Typescript type like this:

type T = string | T[]

But if I try to define this type in valita I cannot pass the identifier to its own instantiator:

const T = v.union(v.string(), v.array(T)); // error: identifier used before it is defined

I mulled over this a bit, but I can't think of a smart way to implement it that doesn't involve somehow the union after it is instantiated, and then having to trick typescript into giving it a different type...

Improved error reporting for .map()

Report refers to this diff:

image

The old version used .map(...) to convert a byte array to a custom type. If validation fails, an Error is thrown.

The new version uses .chain(...).

With map, the error message on validation failure looked like this:

Error: Could not validate EdToNd message: Error: Expected key to be 32 bytes but has 0 bytes

With chain, it looks like this:

Error: Could not validate EdToNd message: ValitaError: custom_error at .essentialData.groups.0.group.profilePicture.updated.blob.key (Expected 32-byte blob key, but found 0 bytes) (+ 2 other issues)

The error message for chain is much better, because it shows the source of the validation error in a complex object. With map, only the exception message itself is reported.

Is there a technical reason why this is the case? Or could exceptions thrown by map functions be caught and handled in a way similar to chain?

Readonly fields on inferred type

Is there a way to get fields in the inferred type to be marked as readonly?

I'm thinking of something like this:

export const SCHEMA = v.object({
    regular_field: v.string(),
    optional_field: v.string().optional(),
    readonly_field: v.string().readonly(),
});
export type InferredType = v.Infer<typeof SCHEMA>;

..resulting in:

type InferredType = {
    optional_field?: string | undefined;
    regular_field: string;
    readonly readonly_field: string;
}

RFE: Add a way to infer readonly types

It would be nice to have a way to mark types (array/tuples/objects/records) as readonly.

const schemaX = valita.object({
   x: valita.number(),
});
type T = valita.Infer<typeof schemaX>;
// {x: number}

I can use the TS Readonly type but it gets inconvenient when you have nested types.

type ReadonlyT = Readonly<valita.Infer<typeof schemaX>>;
// {readonly x: number}

const schemaY = valita.object({
   y: schemaX,
});
type T2 = valita.Infer<typeof schemaY>;
// {y: {x: number}}

type ReadonlyT2 = Readonly<valita.Infer<typeof schema>>;
// {readonly y: {x: number}}

I know I can use DeepReadonly type combinators but those tends to fail pretty fast for me because my types are too deep.

I guess one problem with adding a way to flag things as readonly is that it is not clear if we also need to have a runtime check for readonly-ness?

Deno support

Hi guys, is there any interest to support deno?

I am already using valita with Deno and it is completely compatible. The only issue is that I have to use a bundled version and import it from a CDN like https://cdn.jsdelivr.net/gh/badrap/valita@d5d0125c467fb307addb9cc2badd098bfc66b834/src/index.ts .

Maybe you could implement webhooks pointing to the index.ts file which would publish the new releases on https://deno.land/x. automatically You can find more about it on Deno's website here.

Thanks!

Helper methods

Zod and MyZod have many helper methods .int() .max(10) .pattern() .safeParse() etc.

How do you look at these?

Currently it is possible to solve this all with .assert()

Sendable Types?

is there a way to convert a Valita type instance into a sendable type?

e.g.

const T = v.union(v.string(), v.number());
const sendT = T.sendable();

// this represents sending the type
const sentT = JSON.parse(JSON.stringify(sendT));

const U = v.fromSendable(sentT)
const u = U.parse(4);

Validating typed arrays

I use a lot of Uint8Array values in my objects. Is it possible to validate those?

Here's a reproducer:

import * as v from "@badrap/valita";

const BytesHolder = v.object({
  fourBytes: v.array(v.number()).assert((val) => val.length === 4)
});

console.log('Primitive array');
BytesHolder.parse({
    fourBytes: [1, 2, 3, 4],
});

console.log('Typed array');
BytesHolder.parse({
    fourBytes: new Uint8Array([1, 2, 3, 4]),
});

The second call fails.

Annotation support?

One more:

I have a function that accepts a schema in the library I'm migrating away from and integrates it with yargs. To do that, it examines the expected input type of the schema. I see that Valita does expose a name property on its schema objects, but that property winds up being useless if you use anything but the bare types exposed by valita (chain, assert, optional, etc...).

It would be nice to be able to associate some user-supplied context with a type, or at least allow .chain an argument to set the name of a type. Preferably, the orthogonal concerns of "what it is" and "how to treat it" (optional string) are both exposed, and while I'm not eager to tack something like .annotate({type: 'string', optional: true}) everywhere, it does make a simple building block with which more robust behavior can be built.

In general, introspection behavior would be helpful (the ability to traverse and understand the input requirements of a type at runtime)

Question: How to combine records and objects

I'm not sure if this can be done with the existing APIs but I want to express something like the following TypeScript type:

type T = {
  x: number;
  [key: string]: string | number;
}

The best I could come up with is:

const schema = valita
  .record(valita.union(valita.string(), valita.number()))
  .chain(v =>
    valita.object({x: valita.number()}).try(v, {mode: 'passthrough'}),
  );

Value validation

Is it possible to do validations on the value, which is not part of the type information (e.g. only allowing numbers 0-255, or only strings matching a RegEx)? Or is that out of scope?

There seems to be code to validate the length of arrays, but I think right now this is only used for tuples, which are covered by TypeScript's type system.

export type classes

After writing some mini libraries, I realized that we need to export the classes so we don't have to use type assetions on every line.

// import hierarhy that i suggest
import * as v from '@badrap/valita' // like right now
import * as valitaLib from '@badrap/valita/lib' // export classes for library authors here

TS 4094, issueTrue is private

Hi there. I'm trying this library out and quite pleased with it -- particularly the flexibility to define parsing and validation behavior. This is hampered somewhat by a couple things, which I'm filing as separate issues to keep them discreet. The first of them is:

export const foo = (val: string) => {
  if (val === 'oh noes') {
    return v.err('sad');
  }
  return v.ok(v);
};

Since the return value of v.err includes private members, TypeScript fails when doing something like this.

Example usage:

import { foo } from './common.js';

const MyObj = v.object({
  foo: v.string().chain(foo)
});

It's okay if you define it in the same file as your schema, but you can't export the helper function. I'm not sure if other things (e.g. CustomError) would also need to be exported.

Should we add `intersect` method to UnionType?

I found a multiple cases where i do

 const a = v.union(v.literal("started"));
 const b = v.union(v.literal("completed"));

 const schema = v.union(...a.options, ...b.options);

So, can we add intersect (or, maybe with better name), to UnionType, like we did it for ObjectType before (.extend)?

Undefined error message in Firefox

For some reason the error message in Firefox is undefined instead of showing the path to the key where the validation error occurred. This only happens when the error is logged to console. If we inspect the error object manually we can see that all properties are present as they should be.

Reproduction: https://i6nu3.csb.app/

Screenshot 2021-05-11 at 16 48 06

v.lazy() causes error

Example:

try {
    type P = {p?: P};
    const P: v.Type<P> = v.object({
        p: v.lazy(() => P).optional(),
    })
    
    P.parse({p: {}});
} catch (e) {
    console.error(e);
}

This causes a RangeError: Maximum call stack size exceeded

It gets stuck in an infinite recursion calling genFunc() on itself

The `protoless` optimization breaks Firebase/Firestore

The following optimization to use {__proto__: null} as the [[Prototype]] of cloned objects

valita/src/index.ts

Lines 568 to 578 in 5db630e

// When an object matcher needs to create a copied version of the input,
// it initializes the new objects with Object.create(protoless).
//
// Using Object.create(protoless) instead of just {} makes setting
// "__proto__" key safe. Previously we set object properties with a helper
// function that special-cased "__proto__". Now we can just do
// `output[key] = value` directly.
//
// Using Object.create(protoless) instead of Object.create(null) seems to
// be faster on V8 at the time of writing this (2023-08-07).
const protoless = Object.freeze(Object.create(null));

causes, Firebase/Firestore to fail its check for "plain objects".

https://github.com/googleapis/nodejs-firestore/blob/d2b97c4e041ca6f3245b942540e793d429f8e5c5/dev/src/util.ts#L107C1-L114C2

Could we remove the protoless optimization and use null as the [[Prototype]]?

// Using Object.create(protoless) instead of Object.create(null) seems to
// be faster on V8 at the time of writing this (2023-08-07).

Do you have a perf test for this? Is there a V8 tracking bug?

External fields (for objects and tuples)

This is a feature request. I have some code that looks like this:

import * as v from '@badrap/valita';

// setup
const externalSymbol = Symbol('external') as any;
const isExternal = (v: unknown) => v === externalSymbol;
const external = <T>(t: v.Type<T>): v.Type<T> =>
    t.default(externalSymbol)
    .assert(isExternal, 'field must be empty');

// child type includes fields that are constructed by another parser
// so they must not get values from the input, but must be expected on the type
type Child = v.Infer<typeof Child>;
const Child = v.object({
    name: external(v.string()),
    color: v.string(),
});

// parent includes the mapping that assigns children their name
type Parent = v.Infer<typeof Parent>
const Parent = v.object({children: v.record(Child)})
    .map(parent => {
        const children = Object.entries(parent.children);
        for (const [childname, child] of children) {
            child.name = childname;
        }
        return parent;
    });

// results
// after parsing, example's children know their own names
const okExample = Parent.parse({
    children: {
        john: {color: 'mauve'},
        frank: {color: 'red'},
    }
});
console.log(okExample.children.john.name);

// will not parse, child defines external field
const badExample = Parent.parse({
    children: {
        john: {color: 'mauve', name: 'lucas'},
        frank: {color: 'red'},
    }
});

Parent is responsible for completing the construction of Child (because it has the necessary information). To facilitate this Child provides type information for the field, but also marks it as external, this way if the input tries to provide a value it will result in a failure to parse.

The feature request is to add the type modifier .external() to types to express this use case as follows:

type Child = v.Infer<typeof Child>;
const Child = v.object({
    name: v.string().external(),
    color: v.string(),
});

Bonus points if forgetting to assign them creates a parse error later!

TS4023 for exported type instance

In my TSConfig I set declaration to true, and then I started getting TS4023 Errors for almost every exported type.

It appears that there is some issue with the fact that ObjectType is not an export, and so typescript cannot create declarations for instances that are defined with it.

import * as v from '@badrap/valita'

export const T = v.object({a: v.string()}); // TS4023

This is my tsconfig.json:

{
    "compilerOptions": {
        "target": "esnext",
        "moduleResolution": "node",
        "outDir": "dist",
        "strict": true,
        "noImplicitAny": true,
        "declaration": true,
    },
    "include": ["src/**/*"]
}

Runtime schema inspection / type narrowing

I've noticed that the API is reaaaally defensive. I expect this is an intentional choice, but it makes some things quite difficult. The workaround mentioned in #26 is okay (in contrast with extending Type) for creating new types, but I just hit a problem that I don't think I can get around without access to the actual ObjectType class.

What I'm trying to do is validate "as much of the data as I currently have" against a subset of the schema I've constructed. ObjectType includes useful class functions (partial, omit, pick, etc...) -- but since I'm pulling the subset from a generic, I can't access them. I also can't use instanceof to narrow the type so that TypeScript will allow me to call .partial on the schema.

Concrete details follow, but you needn't read further if you agree that it's useful to be able to write:

const unknownSchema: unknown;
if (unknownSchema instanceof v.ObjectType) {
  unknownSchema.partial();
}

The setup this time is a hierarchical config schema, where the top-level keys are the config necessary for some component, for example:

const schema = v.object({
  common: v.object({
    configFile: v.string().default('config.json')
  }),
  db: v.object({ ... }),
  web: v.object({ ... }),
})

I'm using the reasonably common pattern of a default config file location, with the ability to specify an alternate config file to use via environment variables / cli args. The path to the config file is in common as shown above. So, the application logic is something like this:

  • load CLI and env vars into the config store
  • load content from the config file into the config store
  • etc...

At the stage where I'm loading the file, I want to retrieve the contents of only the common key, and force all the values to be optional, so that validation does not fail if some are missing. However, at final config validation, they should not be optional, and the final validated config should be as tight as possible.

I'm doing this essentially by:

schema['shape']['common'].parse(configdata['common'])

... which guarantees me the shape and types of any values that do exist, but requires me to write extra-defensive code. This is fine for this stage, but not desirable all the time once the app is fully bootstrapped.

Hopefully you can agree that this is a worthwhile use-case for exposing the instances (not just the types) of some of the basic tools here (if not the AbstractType class, or the Type class, then at least the leaf nodes -- ObjectType, ArrayType, etc.). Thoughts?

(p.s. sorry for the sudden influx of issues. This library is very useful, which is why I've spent all day attempting a migration to see if it will suit :) it is very close...)

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.