GithubHelp home page GithubHelp logo

runtypes / runtypes Goto Github PK

View Code? Open in Web Editor NEW
2.5K 14.0 93.0 1.32 MB

Runtime validation for static types

License: MIT License

TypeScript 99.50% Shell 0.20% Just 0.30%
typescript runtime types validation

runtypes's People

Contributors

72636c avatar artalar avatar bbenezech avatar breathe avatar chris-pardy avatar codemariner avatar davidje13 avatar dependabot[bot] avatar elderapo avatar flawyte avatar forbeslindesay avatar googol avatar greenkeeper[bot] avatar henrinormak avatar hzrdirl avatar iyegoroff avatar justingrant avatar keithlayne avatar kestred avatar liamolucko avatar mariusschulz avatar mitchellstern avatar pabloszx avatar pabra avatar patrickocoffeyo avatar pelotom avatar richgieg avatar runeh avatar simenandre avatar yuhr 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  avatar  avatar  avatar  avatar  avatar

runtypes's Issues

Partialise a Record type?

const Info = Record({
  x: Number
}).And(Partial({
  y: Number
}));
const AllPartialInfo = Partialize(Info);
type AllPartialInfo = Static<typeof AllPartialInfo>;
// -> { x?: number | undefined; } & { y?: number | undefined; }

Is there something like above Partialize?

Add example of creating an interface from Static to README

When reading the README, as a TypeScript n00b, I thought “oh, this is so close—if only I could use Static<T> to make an interface because that would be more usable for me than type.” I didn’t realize I could just use:

interface IAsteroid extends Static<typeof Asteroid> {}

I think it could be helpful to add an example of this to the README.

rt.Static<> can't extract static type for Runtype.Record with nested Record ...?

I'm noticing that I can't seem to correctly extract the static type from Runtype's with nested rt.Record({}).

const Example = rt.Record({
  b: rt.Record({
    c: rt.String
  })
});

type StaticExample = rt.Static<typeof Example>;

Effective type of StaticExample (as reported by vscode):

type StaticExample = {
    b: any;
}

And this produces no type error ...

const x: StaticExample = {
  b: ""
};

Am I doing something wrong or is this a bug or a known limitation? I'm using runtypes 2.0.3 with typescript 2.7.2 and this tsconfig:

{
  "compileOnSave": true,
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./lib",
    "lib": ["es2017", "es2017.object", "esnext.asynciterable"],
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "module": "commonjs",
    "esModuleInterop": true,
    "target": "es2015",
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "experimentalDecorators": true,
    "inlineSourceMap": true,
    "inlineSources": true,
    "forceConsistentCasingInFileNames": true,
    "strictFunctionTypes": false,
    "noErrorTruncation": true,
    "strictPropertyInitialization": false
  },
  "include": ["./src/**/*"],
  "exclude": ["node_modules"]
}

Is converting values in the scope of this library ?

A common use case when validating data from external APIs is to have dates encoded as strings.
Let's say I get the following data from a JSON API:

{ "date": "2015-10-12 12:00:00" }

I would want to represent it with the following Runtype:

Record({ date: InstanceOf(Date) })

Is there a simple way to go from the first to the second without having to use an intermediate Record({ date: String }) runtype, and doing the validation ?

I think that having a convertFrom method on the Runtype interface would help a lot.

interface Runtype<A> {
  // ...
  convertFrom<B> : (checkBefore: (x: any) => B) => Runtype<A>;
}

The returned Runtype would be exactly the same, but the check method would be changed to include the additional validation in checkBefore before the one from the initial Runtype.

I could then do:

const Input = Record({ 
  date: InstanceOf(Date).convertFrom(x => {
    const strX = String.check(x);
    const timestamp = Date.parse(strX);
    if(isNan(timestamp)) {
      throw new ValidationError('Invalid date string ' + strX);
    }
    return new Date(timestamp);
 })
});

Do you think it could be added to the library (I would be willing to do it)?

Make error messages at least as informative as those given by TypeScript

Some examples of TypeScript's static error messages:

;(): number | boolean => 'hello'
// [ts] Type '"hello"' is not assignable to type 'number | boolean'.

;(): { x: number } => ({})
// [ts]
// Type '{}' is not assignable to type '{ x: number; }'.
//   Property 'x' is missing in type '{}'.

;(): { x: number } => ({ x: 'hello' })
// [ts]
// Type '{ x: string; }' is not assignable to type '{ x: number; }'.
//   Types of property 'x' are incompatible.
//     Type 'string' is not assignable to type 'number'.
// Record({ x: Number }).check({ x: 'hello' })

;(): { x: number } | { x: boolean } => ({ x: 'hello' })
// [ts]
// Type '{ x: "hello"; }' is not assignable to type '{ x: number; } | { x: boolean; }'.
//   Type '{ x: "hello"; }' is not assignable to type '{ x: boolean; }'.
//     Types of property 'x' are incompatible.
//       Type '"hello"' is not assignable to type 'boolean'.

;(): { x: number } | { x: boolean } => ({})
// [ts]
// Type '{}' is not assignable to type '{ x: number; } | { x: boolean; }'.
//   Type '{}' is not assignable to type '{ x: boolean; }'.
//     Property 'x' is missing in type '{}'.

Some notes:

  • One thing we will need to do this fully is a way to infer the type of a given value (or at least a string representation of it), although this is may be more of a nice-to-have.
  • We already have string representations of runtypes, via show
  • Most type errors are flat, but records and unions can have hierarchical errors. In both cases it would be an improvement over TypeScript's static error messages if we allowed multiple sub-errors:
    • For records, multiple sub-errors represent all the fields which are missing or of the wrong type (I believe doing this correctly would address #8)
    • For unions, multiple sub-errors represent the ways in which the supplied value failed to conform to each alternative in turn. TypeScript union error messages are confusing because they show you only how it failed to match the last alternative, which may well be different than the type you were attempting to match.

Fails to build with declaration `true`

The following simple example fails to build for me when setting declaration to true in TypeScript's compilerOptions.

Code:

import * as rt from 'runtypes';

export const Test = rt.Record({ });

tsconfig.json:

{
  "include": [
    "src/conversion/test.ts"
  ],
  "compilerOptions": {
    "noEmit": false,
    "noUnusedLocals": true,
    "strict": true,
    "rootDir": ".",
    "outDir": "./lib",
    "declaration": true
  }
}

Error:

src/conversion/test.ts(3,14): error TS4023: Exported variable 'Test' has or is using name 'Record' from external module ".../node_modules/runtypes/lib/types/record" but cannot be named.

Any idea why that is?

cannot use Static in *.d.ts file

I'd really like to be able to define my types in runtypes, and then use the type MyType = Static<typeof MyType> feature, but I cannot figure out how to incorporate this into my project

The examples show this all being done in one file, but what if I have a types.d.ts file in my project where I am declaring the types that are shared throughout multiple files? I tried importing the runtypes that I declared and the Static function into my types.d.ts file and then declaring the type there, but that seemed to make my d.ts file invalid.

Any ideas?

Thanks in advance and thanks for this project.

Support for optional properties

Currently the Record type's validation verifies that a property exists before checking their types. This causes problems when some properties on a Record should be optional.

Current Behavior:

Record({
 id: String,
 name: Union(Undefined,String)
}).validate({id: '1234'}).success === false

Expected Behavior:

Record({
 id: String,
 name: Union(Undefined,String)
}).validate({id: '1234'}).success === true

Also acceptable:

Record({
 id: String,
 name: Optional(String)
}).validate({id: '1234'}).success === true

Current Workaround:

Intersect(
  Record({
    id: String,
  }),
 Partial({
   name: String
 })
).validate({id: '1234'}).success === true

Provide error meta data

It would be great to provide error meta data, something like this:

let error = {
 success: false,
 message: '...',
 path: ['key1', 'key2', ...],
}

This can be useful for form validation because we validate the whole form with an object type, but need to show the message in each field.

`check()` returns any for nested types

In this simple example:

const SubValue = Record({
    a: Number,
});

const ParentValue = Record({
    v: Array(SubValue),
});

const pv = ParentValue.check({});

The type returns from check() is {v: any[]}. But when I look at the type of ParentValue it's fully realized. What am I missing?

Suggestions around pattern matching

First of all, thank you for putting such an amazing library. It utilizes both typescript and javascript really well, producing such an empowering and comprehensive developer experience :)

I found your project while looking at the ways to pattern match in typescript. So my suggestions are biased.

Let's say we have this:

const One = Record({ tag: Literal('one'), descOfOne: String });
const Two = Record({ tag: Literal('two'), descOfTwo: String });

type OneOrTwo = Static<typeof One> | Static<typeof Two>;

While matching I want to extract payload as well. Something like:

const DescriptionOfOne = One.pipe(x=>x.descOfOne); 
//not Runtype anymore, can be used only for pattern matching

// OneOrTwo | null -> string 
const getDescription = matchCase<OneOrTwo | null>()
  .of(DescriptionOfOne, desc => desc) // acceptes extracted prop
  .of(Two, ({descOfTwo}) => descOfTwo) // normal types can be used just fine
  .of(Void, ()=> 'nothing');

DescriptionOfOne has different type now. Lets say Pattern<One, string>.

const NonEmptyDesc = DescriptionOfOne.withContraint( desc => desc.lenght >0) 
// Error! Will not compile. Patterns can only be mapped over.

const OneDescLenght = DescriptionOfOne.map(decs => desc.lenght);
// totally valid though.

I think it makes sense to map them further to produce different pipelines.

But if we use Records extensively for matching we have perf issues :( "check" checks all props recursively.

const TaggedOne = Tag(One, 'tag'); //new Runtype that remembers which one is the tag
// disables deep tree matching and checks only tags. 
// useful for application types (like redux actions)
// It might make sense to introduce Tagged({shape}) and TaggedLiteral('one') for that purpose as well.

Also with typescript 2.8 we will have a proper Diff<T,U>. And if I understand it correctly it opens this

const EmptyOne = One.withConstraint( one => one.descOfOne.length === 0);

//Possible from typescript 2.8 with a help of Diff
const getDescription = matchCase<OneOrTwo | null>()
  .of(EmptyOne, _ => 'empty one') // leaves the same type
  .guard(One, _ => "one") // removes One, so after we can guard only "Two | Void"
  .guard(Two, _ => "two") // leaves just "Void" to check
  .guard(Void, ()=> 'nothing'); // "never" after this point
  .exhaustive() // can be applied only to "never". 
// So it should give compile time error if not all types are ruled out
// .default(()=>'def val') same as of(Always, ()=>{}).

Note probably constrained runtypes cannot be guarded. Otherwise PositiveNumber can rule out Number < 0. Note: guarding on non constrained Patterns should be totally fine.

++ Side note:
Looking through the sources I noticed that "validate" uses try/catch from "check" to produce boolean result. Was there a specific reason for that? I guess if we hit an if statement that "validates" to false often there might be significant overhead for try/catch as well as allocating a Result object.
Does it make sense to have validateInternal(): true | string, aka either passed or failed with message. And then "check" can throw with a proper message.

+++ Side note n2:
You library is brilliant and inspiring. I felt that I love my job even more just thinking about these suggestions :)

I would love to collaborate/contribute if you like these ideas

Add a flag to check/validate/guard which allows enforcing exact match (not just structurally assignable)

Perhaps I'm missing something fundamental here about how this library makes assertions, but the following obviously errors in Typescript:

interface ISeries {
  name: string;
  value: number;
}

const series: ISeries = {
  name: 'test',
  value: 10,
  foo: true,
};

but this check() does not error with runtypes:

const Series = Record({
  name: String,
  value: Number,
});

Series.check({
  name: 'test',
  value: 10,
  foo: true
});

Why is the additional key foo allowed through in this case?

Automatic argument checking

Sometimes we need type-check multiples of multiple parameters which are passed from the external world i.e. there're some callbacks to implement, and appending type-checking code for each param to every one of their function bodies is kind of painful. In such case I write a decorator which reduces keypresses more or less and makes functions' logic innate-looking, for example,

@checked(Runtype1, Runtype2, Runtype3)
method(param1: Static1, param2: Static2, param3: Static3) {
  ...
}

instead of

method(param1: Static1, param2: Static2, param3: Static3) {
  Runtype1.check(param1)
  Runtype2.check(param2)
  Runtype3.check(param3)
  ...
}

How do you think about adding this feature to your library?

Install error in PowerShell (VS Code)

Within the integrated PowerShell of Visual Studio Code i cannot install Runtypes.
It fails with the error:

PS C:\Source\Ionic\flexApp> npm install runtypes --save
npm ERR! path C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c
npm ERR! code EPERM
npm ERR! errno -4048
npm ERR! syscall rename
npm ERR! Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR!     at Error (native)
npm ERR!  { Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR!     at Error (native)
npm ERR!   cause:
npm ERR!    { Error: EPERM: operation not permitted, rename 'C:\Source\Ionic\flexApp\node_modules\.staging\runtypes-20e0969c' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes'
npm ERR!        at Error (native)
npm ERR!      errno: -4048,
npm ERR!      code: 'EPERM',
npm ERR!      syscall: 'rename',
npm ERR!      path: 'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c',
npm ERR!      dest: 'C:\\Source\\Ionic\\flexApp\\node_modules\\runtypes' },
npm ERR!   stack: 'Error: EPERM: operation not permitted, rename \'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c\' -> \'C:\\Source\\Ionic\\flexApp\\node_modules\\r
untypes\'\n    at Error (native)',
npm ERR!   errno: -4048,
npm ERR!   code: 'EPERM',
npm ERR!   syscall: 'rename',
npm ERR!   path: 'C:\\Source\\Ionic\\flexApp\\node_modules\\.staging\\runtypes-20e0969c',
npm ERR!   dest: 'C:\\Source\\Ionic\\flexApp\\node_modules\\runtypes',
npm ERR!   parent: 'flexapp' }
npm ERR!
npm ERR! Please try running this command again as root/Administrator.

Whereas the same command (without Admin privileges!) is running successfully in the normal Windows commandline:

C:\Source\Ionic\flexApp>npm install runtypes --save
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: ts-jest@19.0.6 (node_modules\runtypes\node_modules\ts-jest):
npm WARN enoent SKIPPING OPTIONAL DEPENDENCY: ENOENT: no such file or directory, rename 'C:\Source\Ionic\flexApp\node_modules\runtypes\node_modules\ts-jest' -> 'C:\Source\Ionic\flexApp\node_modules\runtypes\node_modules\.ts-jest.DELETE'

+ runtypes@0.10.1
added 427 packages in 83.487s

Any Idea whats going wrong in Powershell within VS Code?

Related library: rest.ts

Hi @pelotom ,

I'm developing a module to bridge the safety gap across HTTP APIs, with first-class support for runtypes.

The module is called rest.ts and it lets you write API definitions which you can bind to client and servers in order to benefit from type safety on both ends of the API.
When used with runtypes, the server-side module validates all incoming data automatically.

I thought you might like this module because basically it is to HTTP APIs what runtypes is to basic types. It's almost like a natural extension of your work.

Please give it a try if you can and let me know what you think of it, and if you think it's worthy of the "related libraries" section of the readme.

support for sanitization

Not sure whether this in scope of the library, but for example io-ts, joi, yup all etc. can sanitize values (i.e. coerce them within certain boundaries) which is actually useful in some use cases. It would be nice if runtypes could do that too. joi and yup all do sanitization for some of their built in types and in io-ts one can do sanitization in the validator function of custom types by passing the sanitized value to t.success(sanitizedValue).

I was actually looking at all the above mentioned libraries incl. runtypes for the use case of API validation. While I really wanted to use io-ts or runtypes (I use typescript everywhere) but I decided against the use of runtypes because of the lack of sanitization and it wasn't clear to me how to create custom types, and against io-ts because of the lack of an ability to create validation specific error messages.

Support for nested/recursive types?

Hi there,

this library looks amazing and seems like what i am currently missing in native typescript.
But there is one thing, i hadn't success to achieve with this library: nested recursive types.
I need to define the structure types for an object literal like:

type item = {
  Prop1: string,
  Prop2: number
}

type group = {
  Prop3: string,
  Items: Array<item | group>
}

const JSON: group = {
  Prop3: "Root",
  Items: [
    {
      Prop1: "Item 1",
      Prop2: 123
    },
    {
      Prop3: "Nested Group 1",
      Items: [
        /*
          ... and so on
        */
      ]
    }
  ]
}

If i try to achieve this with runtypes the following way:

const item = Record({
  Prop1: String,
  Prop2: Number
});

const group = Record({
  Prop3: String,
  Items: Array(Union(item, group))
});

it gives a compiler error "block-scoped variable 'group' is used before its declaration".
If i replace const group with var group the compiler error disappears but typescript recognizes group only as type any..

Is there any way to describe the desired structure?
Thanks for any hint on this...

How to represent 'any' types?

I am trying to convert an existing codebase to use runtypes.

However I am struggling to work out how to represent the arguments key here with a typescript any type:

export type Description = {
  name: string;
  type: 'fanout' | 'topic' | 'direct';
  durable?: boolean;
  internal?: boolean;
  autoDelete?: boolean;
  alternateExchange?: string;
  arguments?: any;
};

So far I have this:

Record({
  name: String,
  type: Union(Literal('fanout'), Literal('topic'), Literal('direct'))
}).And(
  Partial({
    alternateExchange: String,
    autoDelete: Boolean,
    durable: Boolean,
    internal: Boolean
    //arguments: Any // Any is not exported from runtypes
  })
)

Runtime type checking works:

Description.check({
  name:'foo',
  type: 'fanout',
  arguments: {
    ding:'pop'
  }
}); // no errors

However the following causes type errors in editor:

const myDescription: Static<typeof Description> ={
  name:'foo',
  type: 'fanout',
  arguments: { // shows error 
    ding:'pop'
  }
}

The error is

Object literal may only specify known properties, and 'arguments' does not exist in type...

How do I infer this property should be any?

Regression on the "More descriptive errors" feature

It seems that #51 has regressed in later versions.

In 2.0.5 I get error messages like

Expected restEndpoint to be string

but in 2.1.6 they are just

Expected string, but was undefined

Reproduce with

const Options = rt.Record({
    restEndpoint: rt.String,
    userId: rt.String,
});
Options.check({userId: ""});

[Support] How to deal with enums and types defined by external libraries?

I have the following TypeScript code

import * as admin from "firebase-admin";

export enum Parser {
  STANDARD = "standard",
  FIVE_FILTERS = "fiveFilters",
}

export enum Status {
  DRAFT = "draft",
  PUBLISHED = "published",
}

export interface IWriter {
  feedName: string;
  writerRef: admin.firestore.DocumentReference;
}

export interface IEntryDocument {
  cronExpression: string;
  forceTeams: boolean;
  forceWriters: boolean;
  id: string;
  name: string;
  parser: Parser;
  status: Status;
  url: string;
  writers: IWriter[];
}

I need to validate the data that comes from the DB so I need to perform validation based on the IEntryDocument interface typings.

I started on converting these interfaces into runtypes format but I'm stuck with enums and external libraries' types.

This is where I reached so far:

import * as admin from "firebase-admin";
import {Boolean, String, Static, Array, Record} from "runtypes";

export enum Parser {
  STANDARD = "standard",
  FIVE_FILTERS = "fiveFilters",
}

export enum Status {
  DRAFT = "draft",
  PUBLISHED = "published",
}

const Writer = Record({
  feedName: String,
  // writerRef: admin.firestore.DocumentReference,
});

export interface IWriter {
  feedName: string;
  writerRef: admin.firestore.DocumentReference;
}

const EntryDocument = Record({
  cronExpression: String,
  forceTeams: Boolean,
  forceWriters: Boolean,
  id: String,
  name: String,
  // parser: Parser,
  // status: Status,
  url: String,
  writers: Array(Writer),
});

export interface IWriter extends Static<typeof Writer> {}
export interface IEntryDocument extends Static<typeof EntryDocument> {}

Have a look at the lines that are commented out. I'm stuck with 2 issues:

  1. I don't know what to do with admin.firestore.DocumentReference
  2. I don't know how to deal with Parser and Status enums

Checking objects on values only?

Is there any runtypes equivalent of following type definition? I don't want to specify key strings but check if the type of their values is string.

type Json = { [key: string]: string };

No way to skip optional field check

Hi,

I tried the following:

const OptionalString = String.Or(Undefined);
type OptionalString = Static<typeof OptionalString>;


const IOrganization = Record({
    id: NonEmptyString,
    name: NonEmptyString,
    description: OptionalString,
    link: OptionalString,
});
type IOrganization = Static<typeof IOrganization>; 

if I call

const o: IOrganization = {
      id: "a",
      name: "the name",
};
IOrganization.check(o);

The check function throws an error! There is a way to avoid the throwing if an optional field is missing in the argument object?

Support for optional properties

Suggestion : support for optional properties on Record

  • .check would succeed if the optional properties are not present on the input object
  • the inferred static type would have the optional properties denoted by the ? sign.

Use case : OpenAPI schemas and Typescript support optional properties, thus such support in runtypes would help in handling request bodies on Web api calls. Same for JSON data coming from no-SQL databases, where all fields are not always present.

`[]` passes `Record` `check`

I have a type like the following specified:

const ConfigType = rt.Record({})
.And(rt.Partial({
  x: rt.String,
}));

Unfortunately running the following code succeeds without error:

ConfigType.check([])

Return multiple errors

Would it be possible for validate/check to return all errors instead of just the first?

This would be useful for validating user input, giving the user all errors instead of just one.

Creating objects

I have the following code:

import { Boolean, Number, String, Literal, Array, Tuple, Record, Union } from 'runtypes';

const Asteroid = Record({
  type: Literal('asteroid'),
  location: Vector,
  mass: Number,
});

Is there any easy way to create an object of type Asteroid? Ideally, I'd like to use something like:

Asteroid.create({type: 'asteroid', location: [], mass: 1}); //valid
Asteroid.create({}); // error - the args supplied don't meet the contraints

Is this possible? I noticed that none of the examples is creating objects at all.

Possible to make a RunType that uses instanceof?

Sometimes I'm expecting an instance of a particular class. Is there a way to make a constraint field based on instanceof check or other typeguard?

I want to express something like this ...

class PopulationDistribution { ... }

const Planet = Record({
  type: Literal('planet'),
  location: Vector,
  mass: Number,
  population: InstanceOf(PopulationDistribution),
  habitable: Boolean
})

Static type not recognizing that a field is nullable

Hi, first of all. Thanks for this awesome package! I've noticed that typescript is not recognizing that a Record field is .Or(Null). Instead typescript is inferring it to be non-null.

Is this expected behaviour?

See the screenshot below:

screen shot 2019-02-17 at 10 19 48 am

Serialization/Deserialization of Runtypes

Love this library. We're thinking about using it to type and validate our api responses. One problem we've encountered though is that for very large response objects validation isn't fast enough (can take up to a few seconds in some cases). A possible solution we've come up with is using web workers to perform this validation since it isn't essential we know right away if the response json is invalid. Problem is getting the runtype into the web worker. If we could serialize/deserialize the runtype we'd be able to pass it to the worker. It seems like it'd be pretty trivial to serialize/deserialize a runtype provided it's representing json. Thoughts on this? Would serialization of runtypes be a feature you'd be willing to include in this library?

Am-m-mazing!

Using this package for 3 months already in production projects, who would've thought it can be such a game changer, thank you!

"Better" support for nullable properties in Record?

Hi,

at first i didn't know, how to declare nullable properties in a Record and if it is possible at all.
The only way i found so far is:

const A = Record({
  T: String,
  N: Number
}).And(Partial({
  Z: Boolean
}));

which results in a type:

type AType = {
    T: string;
    N: number;
} & {
    Z?: boolean;
}

Wouldn't it be more easy and intuitive if one could do:

const A = Record({
  T: String,
  N: Number,
  Z: Nullable(Boolean) // or Boolean.AsNullable()
  /*
       or even better as this is the normal typescript syntax?:
       Z?: Boolean
  */
});

Something like that shouldn't be that hard, or do i miss something?

Thanks and regards..

Add a TaggedUnion runtype.

There's a fairly common pattern for using union types called tagged unions. Using the current Union runtype for these is functionally complete, however when a .check() fails, it's usually near impossible to work out why. I'd like to propose a new TaggedUnion runtype. You could use it as follows:

const AB = TaggedUnion("tag", {
  a: Record({ aprop: String }),
  b: Record({ bprop: Number }),
});

AB.check({ tag: "a", aprop: "foo" }); // success.
AB.check({ tag: "b", bprop: 10 }); // success.
AB.check({ tag: "c", cprop: "bar" }); // Error: Unknown tag: Expected "a" | "b", but was "c"
AB.check({ tag: "a", bprop: 10 }); // Error: Expected { tag: "a"; aprop: string; }, but was object.

AB.checkTag({ tag: "a" }); // true
AB.check({ tag: "a" }); // Error: Expected { tag: "a"; aprop: string; }, but was object.
AB.checkTag({ tag: "c", cprop: "bar" }); // false

Having a seperate checkTag is often useful for deserialising input from changing upstream providers such as webhooks where they may add new event types which you want to discard or log differently because you typically care less about them, but you do care if you get an event you thought you knew about which has a structure other than you were expecting.

As an example of why I feel this is necessary, the current approach leaves me with errors like the following while trying to deserialise Mailgun webhooks (if there is an error):

Error: Expected (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "delivered"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?:string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "opened"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "clicked"; url: string; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { event: "complained"; }) | ((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { client-info: { client-name: string; client-os: string; client-type: string; device-type: string; user-agent: string; }; geolocation: { city: string; country: string; region: string; }; ip: string; }) & { event: "unsubscribed"; }) | (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "failed"; reason: string; severity: "temporary"; }) | (((({ campaigns: always[]; id: string; log-level: string; message: { headers: { message-id: string; } & { from?: string; subject?: string; to?: string; }; }; recipient: string; tags: string[]; timestamp: number; user-variables: { cortex?: string; }; } & { recipient-domain?: string; }) & { envelope: { sender?: string; sending-ip?: string; targets?: string; transport?: string; }; flags: { is-authenticated?: boolean; is-routed?: boolean; is-system-test?: boolean; is-test-mode?: boolean; }; message: { attachments: {}[]; size: number; }; }) & { delivery-status: { attempt-no: number; code: number; description: string; message: string; session-seconds: number; } & { certificate-verified?: boolean; mx-host?: string; retry-seconds?: number; tls?: boolean; utf8?: boolean; }; storage: { key: string; url: string; }; }) & { event: "failed"; reason: string; severity: "permanent"; }), but was object

Is there any way to declare generic key?

TypeScript

interface Foo {
  [s: string]: number;
}

Is there any way to declare a runtype for the same? The following does't work. I assume there is a way that I don't know.

const Foo = Record({
  [s: string]: Number,
})

Option to create tagged Union from Union() runtype ...?

It would be pretty nice if runtypes supported the ability to make a tagged Union from a runtype Union by adding an _type field not present in the original data with appropriate value ... It would be useful when validating weird/legacy json structures that you don't control ...

Something like

const Asteroid = Record({
  location: Vector,
  mass: Number,
})

const Planet = Record({
  location: Vector,
  mass: Number,
  population: Number,
  habitable: Boolean,
})

const SpaceObject = TaggedUnion(Asteroid, Planet)

const asteroid = { ...}
const planet = { ... }

const asteroidOrPlanet = SpaceObject.check(asteroid)
console.log(asteroidOrPlanet._type) // "Asteroid"
console.log(SpaceObject.check(planet))._type // "Planet"

Extracting union of strings to runtime array?

I am curious if that's possible somehow? You see, I am using library mobx-state-tree which is also checked at runtime, but currently, it's impossible to use Typescript types for modeling.

type State = 'Unknown' | 'Online' | 'Offline'

const Model = types.model({
  connState: types.enumeration(['Unknown', 'Online', 'Offline'])
})

The first step should be probably to support Symbol.iterator and then I can do this.

const State = Union(
  Literal('Unknown'),
  Literal('Online'),
  Literal('Offline')
)

const Model = types.model({
  connState: types.enumeration(Array.from(State))
})

However, that's not enough since it would give an array of Literal objects. I am not how to proceed there to keep the ease of use. Perhaps some custom method like State.toArray() could be more feasible?

Array index

Hi Tom,

How would you model {[k: string]: any}, if possible ?

I tried

RT.Record({
    [RT.String as any as string]: RT.String
})

It typechecks correctly to RT.RunType<[x: string]: string;>, but runtime checks does not work as expected.

How to type a Date object?

First off thanks for a really useful library. I've already used it very successfully on a small project.

However, now I've hit a problem with typing a Date object. I'm probably missing something fundamental here.

With the following:

import { Number, Record, InstanceOf, Static } from 'runtypes'

const Coords = Record({
  latitude: Number,
  longitude: Number,
  time: InstanceOf(Date)
})

type Coords = Static<typeof Coords>

let data: Coords = {
  latitude: 1.0,
  longitude: 1.0,
  time: new Date(2017, 0, 1, 10, 0, 0)
}

I get the type error:

...
Types of property 'time' are incompatible.
        Type 'Date' is not assignable to type 'DateConstructor'.

What am I missing?

if this is not the correct way to type a Date object, what is?

Incorrect generic type constraint for interface Record?

Hi,
When I open record.ts in Visual Studio Code with TypeScript 2.8.1, the following error is shown:

image

Current definition:

export interface Record<O extends { [_ in never]: Runtype }>
  extends Runtype<{ [K in keyof O]: Static<O[K]> }> {
  tag: 'record';
  fields: O;
}

Isn't the following more appropriate? If not, I would appreciate if you explain me why! I don't understand the current constraint for the generic type.

export interface Record<O extends { [_: string]: Runtype }> // [_: string] instead of [_ in never]
  extends Runtype<{ [K in keyof O]: Static<O[K]> }> {
  tag: 'record';
  fields: O;
}

Thank you in advance.

`check` should be able to return branded type

The check method of a runtype with constraint returns a value of the type without constraint, so sometimes I need to use branded types like this:

const A = String.withConstraint(x => x === 'a');
type A = Static<typeof A> & { __brandA: any };
const x: string = 'a';
const a: A = A.check(x); // Type 'string' is not assignable to type 'A'.
const a: A = A.check(x) as A; // OK but looks verbose

Then maybe it's good that check or some another (defined anew?) method can return some user defined type, for example:

let A = String.withConstraint(x => x === 'a');
type A = Static<typeof A> & { __brandA: any };
A = Brand<A>(A);
const x: string = 'a';
const a: A = A.verify(x); // Should be OK

I think this is still ugly but absolutely better than doing as A whenever A.check(x).

How do you think?

Map runtypes to JSON schema

Hi, Just reaching out to get some thoughts on potentially generating JSONschema from runtypes type representations, and if such a library exists to achieve this.

Just for some background, we currently have a project where we have several dozen json endpoints and for each endpoint, we have a TRequest and TResponse TypeScript type as well as a corresponding JSONSchema type (for both request, response). As you can imagine, this is quite the duplication, and we feel this duplication could potentially be removed with the use of runtypes.

One of our considerations however is we do like the idea of publishing TRequest / TResponse schema information to our clients (similar to WSDL). In fact our preference is to publish JSONSchema as a standard JSON schematic (analogous to XSD, WSDL) but to do so, we would need to map runtypes to JSONSchema somehow.

So wondering if there is functionality to do something similar to the following....

const Asteroid = Record({
  type: Literal('asteroid'),
  location: Vector,
  mass: Number,
});
// runtypes representation
// Asteroid = { tag: 'record',
//  fields:
//   { type:
//      { tag: 'literal',
//        value: 'asteroid',
//        check: [Function],

const AsteroidSchema = Asteroid.toJsonSchema()
// json schema representation
// AsteroidSchema = {
//     type: 'object',
//     properties: {
//       type: {type: 'string'},
//       location: {
//          x: { type: 'number'},
//          y: { type: 'number'},
//       ...

From this, we would be able to integrate runtypes with our existing json schema validators (we are currently using the ajv json schema validator configured for draft 6).

Any information you can provide would be great.

Kind Regards
Sinclair

Coerce isn't the right term

Very minor nitpick, but coerce means forcing something to happen, or to change. Does runtime's coerce method do anything apart from checking that an object is already conforming to the type?

(Otherwise, very interesting library!)

[0.7.0] -> [0.8.0] Dictionary regression

Considering

const Errors = RT.Dictionary(RT.Array(RT.String))
type ErrorsType = RT.Static<typeof Errors>

0.7.0 correct

type ErrorsType = {
    [_: string]: string[];
}

0.8.0 incorrect

type ErrorsType = {
    [_: string]: Arr<String>;
}

Do you know intuitively what is causing this or would you like me to investigate further on?

Not urgent, I can stick on 0.7.0 for a while, it works perfectly for me.

Thanks again.

Possible for union.match to support methods which return different type's ...?

It might be convenient if the match(...) method on union types were able to support functions which return different types ...

Maybe it be possible to implement .match() in such a way as to support a signature like this ...?

export interface Match2<A extends Rt, B extends Rt> {
    <Z1, Z2>(a: Case<A, Z1>, b: Case<B, Z2>): Matcher2<A, B, Z1, Z2>;

Instead of:

export interface Match2<A extends Rt, B extends Rt> {
    <Z>(a: Case<A, Z>, b: Case<B, Z>): Matcher2<A, B, Z>;

I took a stab at it with below PR ... Kind of ugly but seems to work? Is this a better type for .match() maybe? Without it the constraints on the functions provided to .match(...) are very tight ...

Straw-man example of what doesn't work without this change ...:

const Type1 = rt.Record({ status: rt.Literal('failure'), msg: rt.String });
const Type2 = rt.Record({
  status: rt.Literal('success'),
  username: rt.String,
  authenticationState: rt.String,
});
const Response = rt.Union(Type1, Type2);

const extracted = Response.match(
  failure => {
    return { status: failure.status, authenticationState: 'not_logged_in', data: { ...failure } };
  },
  success => {
    return {
      status: success.status,
      authenticationState: success.authenticationState,
      data: { ...success },
    };
  },
);

How does this compare to io-ts ?

Hi! Kudos for sharing this library. I'm doing some validation on incoming data from http API, and this made it so much easier, compared to implementing types, and then also validators.

I also found that another project io-ts seem to do very similar thing. I wonder if there are any fundamental difference to the taken approach?

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.