Git Product home page Git Product logo

dzakh / rescript-schema Goto Github PK

View Code? Open in Web Editor NEW
147.0 147.0 7.0 1.68 MB

๐Ÿงฌ The fastest parser in the entire JavaScript ecosystem with a focus on small bundle size and top-notch DX

License: MIT License

ReScript 68.25% JavaScript 24.86% TypeScript 4.79% Shell 0.07% Batchfile 0.01% OCaml 2.02%
ajv contract json parse rescript schema struct ts typescript typescript-library valibot zod

rescript-schema's People

Contributors

dzakh 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

rescript-schema's Issues

Consistency troubles with nullable fields and ppx

Hey. Trying to use the new ppx (fantastic job btw)

I have a bit "dirty" payload that has fields that can be either undefined or null or have some sort of value. The correct exact type is this:

    type item = {
      subscription: option<Null.t<subscription>>,
    }

I find it a bit confusing that S.null converts to option because now I need to type the payload differently based on whether I'm using ppx or not.

With ppx this works:

    @schema
    type item = {
      subscription: @schema(S.option(S.null(subscriptionSchema))) option<Null.t<subscription>>,
    }

But this doesn't (because ofc null !== undefined)

    @schema
    type item = {
      subscription: option<option<subscription>>,
    }

But then again, when using the lib without ppx this seems to be the way, right?

    type item = {
      subscription: option<option<subscription>>,
    }
    let itemSchema = S.object(s => {
      subscription: s.field("subscription", S.option(S.null(subscriptionSchema))),
    })

So maybe you can see what I mean when I say it's a bit confusing. Or maybe I'm doing something wrong?

Also it'd be nice if the lib/ppx could handle Null.t from rescript-core natively, maybe?

I'm using latest ReScript 11 rc.6 here with rescript-core: 0.6.0 (not Belt)

Intersection Types

Apologies in advance if this is an ignorant question, I am not really very familiar with Rescript, I am trying out using the library via the TypeScript API.

Is there anyway to re-use parts of the schema, which are not themselves fields, eg a group of fields? In my mind this would be analogous to a mix-in or how you might use 'intersection' algebraic types in TypeScript

Module parse failed

Hello @DZakh !

First, thanks for your work, it looks amazing !

I want to switch from decco to rescript-schema in my project, but I encounter an error while compiling. I'm using Rescript v11 (without uncurried mode) and Next.js. Here is the error :

./node_modules/rescript-schema/src/S.res.js
Module parse failed: 'import' and 'export' may appear only with 'sourceType: module' (13:0)
| };
| // Generated by ReScript, PLEASE EDIT WITH CARE
> import * as S_Core$RescriptSchema from "./S_Core.res.js";
| var Literal = S_Core$RescriptSchema.Literal;
| var Path_toArray = S_Core$RescriptSchema.Path.toArray;

It may be a configuration issue, but I don't know where to investigate. I'm using es6-global as module in my package-specs.
Do you have an idea which could help me ?

Thanks !

Error: The field is registered multiple times

Hi @DZakh! I just tested rescript-struct 5 and really love the API improvements.

However, I ran into the following problem:

type appVersionSpec = {
  current: string,
  minimum: string,
}

let appVersionSpecStruct = S.object(s => {
  current: s.field("current", S.string),
  minimum: s.field("minimum", S.string),
})

type appVersions = {ios: appVersionSpec, android: appVersionSpec}

let appVersionsStruct = S.object(s => {
  ios: s.field("ios", appVersionSpecStruct),
  android: s.field("android", appVersionSpecStruct),
})

let appVersions = {
  ios: {current: "1.1", minimum: "1.0"},
  android: {current: "1.2", minimum: "1.1"},
}

let _ = appVersions->S.serializeOrRaiseWith(appVersionsStruct)

gives me the exception:

RescriptStructError: Failed serializing at ["android"]. Reason: The field "current" is registered multiple times. If you want to duplicate the field, use S.transform instead

๐Ÿ‘œ Collect Remaining Object Fields

I'd like to able to collect the remaining object fields into a Dict that can be put in the output rather than being stripped out.

It'd also be nice to be able to validate these as to say all remaining fields must validate their their names and/or values against some discriminator.

And it'd be really really nice if rescript-json-schema could generate these from the patternProperties and additionalProperties keywords.

I ran into this when manually creating a schema for package.json before realizing I could've used rescript-json-schema ๐Ÿ˜„.

Awesome libraries btw!

(docs) consider explicitly mentioning email validation incompleteness

Several parser libraries include disclaimers about email validation:

https://valibot.dev/api/email/

https://github.com/ianstormtaylor/superstruct?tab=readme-ov-file#why

https://github.com/effect-ts/effect/tree/main/packages/schema#email

https://github.com/jquense/yup?tab=readme-ov-file#stringemailmessage-string--function-schema

I think it's good to have this reminder; I've seen many beginner developers using email regex blindly without realizing pros and cons.

S.optional accepts undefined but does not make the property optional on the Inferred type

Based on my experience with complex types and other libraries, I would prefer to maintain my type definitions separate from the Rescript Schema schema definitions. However, I obviously need to make sure that the types and schemas are in sync, so I setup type assertions using a utility type that will throw type errors if the types are not equivalent.

However, because S.optional indicates the property can accept 'undefined' values, but does not actually indicate the property itself is optional, these assertions fail in those cases.

I am not entirely sure if this can be solved, but I thought it was worth bringing up.

Wrap/Unwrap string variant

In case of I have types like this:

module Wallet = {
  type id = WalletId(string)
  type t = {
    balance: int,
    id: id,
  }
}

to make a struct for Wallet.t, I wanna have a method to wrap/unwrap the id type without a tag.

For example:

  let struct = S.object(o => {
    balance: o->S.field("balance", S.int()),
    id: o->S.field("id", S.string()->S.String.unwrap(id => {
       let WalletId(id) = id
       id
    }))
  })

Composable decoders

I think it's related to "S.merge" accepting only plain object schemas (e.g no unions, no primitives):

const integerSchema = S.refine(S.number, (value, s) => {
  if (value % 1 !== 0) {
    throw s.fail('Must be an integer');
  }
});

const nonNegativeNumberSchema = S.refine(S.number, (value, s) => {
  if (value < 0) {
    throw s.fail('Must be a positive number');
  }
});

// have to repeat, I didn't find a way to combine integerSchema and nonNegativeNumberSchema
// e.g. S.merge() works only with objects
const nonNegativeIntegerSchema = S.refine(integerSchema, (value, s) => {
  if (value < 0) {
    throw s.fail('Must be a positive number');
  }
});

Usage of eval doesn't allow the library to run on Cloudflare workers

Just wanted to open up an issue to track this. I am currently working on a Remix site that is deployed to a Cloudflare worker and it throws an error due to the use of eval.

EvalError: Code generation from strings disallowed for this context
    at new Function (<anonymous>)
    at build (index.js:31463:10)
    at init (index.js:31522:19)
    at parseAnyOrRaiseWith (index.js:31531:13)
    at parseAnyWith (index.js:31539:11)
    at index.js:32491:12
    at async loader (index.js:32488:14)
    at async callRouteLoaderRR (index.js:3797:16)
    at async callLoaderOrAction (index.js:2877:16)
    at async Promise.all (index 0)

[Suggestion] Support @tag from ppx

Hello,

Now with ReScript v11, we are able to create a discriminant tag with @tag directive.
I think it could be awesome to have it support through ppx to reduce the boilerplate.

Based on the union example :

// TypeScript type for reference:
// type Shape =
// | { kind: "circle"; radius: number }
// | { kind: "square"; x: number }
// | { kind: "triangle"; x: number; y: number };
type shape = Circle({radius: float}) | Square({x: float}) | Triangle({x: float, y: float})

let shapeSchema = S.union([
  S.object(s => {
    s.tag("kind", "circle")
    Circle({
      radius: s.field("radius", S.float),
    })
  }),
  S.object(s => {
    s.tag("kind", "square")
    Square({
      x: s.field("x", S.float),
    })
  }),
  S.object(s => {
    s.tag("kind", "triangle")
    Triangle({
      x: s.field("x", S.float),
      y: s.field("y", S.float),
    })
  }),
])

Could become something like this :

@schema @tag("kind")
type shape = 
  | @as("circle") Circle({radius: float}) 
  | @as("square") Square({x: float}) 
  | @as("triangle") Triangle({x: float, y: float})

My skills for ppx development are pretty low so I don't really know if it's something difficult or not, sorry in advance ๐Ÿ˜… . If it's an easy addition, I would be glad to help :)

Thanks for the lib and your awesome work !

Difficulty with discriminated union

The docs/samples on your home page are pretty good but I couldn't figure out the discriminated union. I looked at the tests and found the shapes example, but all the cases have data. I'm getting a runtime problem in my code about this and can't figure it out. The syntax is kind of weird with the let _ - why is that being ignored? I'm getting the error The object defention contains fields that weren't registered. and I think it has to do with this code.

type t = Forgot | Remembered({mistakes: bool, elapsedMs: int})

let struct = {
  let forgot = S.object(o => {
    let _ = o->S.field("kind", S.literal(String("forgot")))
    Forgot
  })

  let remembered = S.object(o => {
    let _ = o->S.field("kind", S.literal(String("remembered")))
    Remembered({
      mistakes: o->S.field("mistakes", S.bool()),
      elapsedMs: o->S.field("elapsedMs", S.int()->S.Int.min(1)),
    })
  })
  S.union([forgot, remembered])
}

Documentation for Discriminated Unions in TypeScript

Thanks for your hard work. I have been looking forward to using this library for a while. Starting my first project with it, things are going pretty well, very easy to use and quite powerful.

One issue I have run into is it's not clear to me what is the correct way to implement discriminated unions. I found some older issues which referenced 'discriminator' and 'field' with 'ignore'. I wasn't really able to put any of this into practice. Some specific documentation on discriminated types in the TypeScript documentation would be super helpful.

Incorrect parsing with S.object + S.nullable + S.refine

The following seems to be a regression in rescript-schema 7 + 8. It works fine in rescript-schema 6.

let jsonString = `{"myField": "test"}`

let schema = S.object(s => s.field("myField", S.option(S.string)))->S.refine(_ => _ => ())
// โœ… { TAG: 'Ok', _0: 'test' }
Console.log(S.parseJsonStringWith(jsonString, schema))

let schema = S.object(s => s.field("myField", S.nullable(S.string)))->S.refine(_ => _ => ())
// ๐Ÿ˜ฑ { TAG: 'Ok', _0: undefined }
Console.log(S.parseJsonStringWith(jsonString, schema))

Q: transforming string to int

Hello,

This is probably more of question than an actual issue.
I want to define a field that will be a string value but should actually be convertible to an integer.

Something like:

type yow = {
  x: int,
  y: string,
}

exception NotAnInt

let transformer: S.transformDefinition<string, int> = {
  asyncParser: s => {
    switch s->Int.fromString {
    | None => () => Promise.reject(NotAnInt)
    | Some(i) => () => Promise.resolve(i)
    }
  },
}

let yowSchema = S.object(s => {
  {
    x: s.field("x", S.string->S.transform(_ => transformer)),
    y: s.field("y", S.string),
  }
})

let formData = %raw(`{ "x": "1", "y": "yow" }`)

S.parseAnyWith(yowSchema, formData)->Console.log

This code compiles, but I'm wondering if this is correct usage of the library?
Or am I abusing promises in this case?

(Side note, I can't run the compiled version of this, bun src/Yow.re.js)

941 | 
942 | function parseAnyWith(any, schema) {
943 |   try {
944 |     return {
945 |             TAG: "Ok",
946 |             _0: schema.parseOrThrow(any)
                             ^
TypeError: schema.parseOrThrow is not a function. (In 'schema.parseOrThrow(any)', 'schema.parseOrThrow' is undefined)
      at parseAnyWith (/home/nojaf/projects/my-pkmn-rescript-app/node_modules/rescript-schema/src/S_Core.re.js:946:24)
      at /home/nojaf/projects/my-pkmn-rescript-app/src/Yow.re.js:39:30

Bun v1.1.24 (Linux x64)

Accessing type-specific refinements from TS?

I cannot figure out how to access any type-specific refinements from TypeScript.

Screenshot 2023-07-07 at 18 08 10

What am I doing wrong here? It looks like it's simply not being compiled into the d.ts at all.

.recursive is missing from TS api

with "rescript-schema": "^8.0.1",,

and import * as S from "rescript-schema";,

S.recursive doesn't seem to be there

I can use all other methods though, except .recursive

Annotating types with variants and unions

Hi, with the following code:

let thing = S.union([
  S.literalVariant(String("apple"), #apple),
  S.literalVariant(String("orange"), #orange),
])

i get an error message in rescript of:

This expression's type contains type variables that can't be generalized:
RescriptStruct.S.t<_[> #apple | #orange]>

This happens when the type system senses there's a mutation/side-effect,
in combination with a polymorphic value.
Using or annotating that value usually solves it

So how would i go about annotatin it?
I tried the following:

type thingT = RescriptStruct.S.t<[> #apple | #orange]>

but i get an error message of:

A type variable is unbound in this type declaration.
In type RescriptStruct.S.t<([> #apple | #orange] as 'a)>
the variable 'a is unbound

Missing dependency S-RescriptStruct in search path when package-specs: modules: es6-global

Well first, I want to say thank you for your work, really enjoy using rescript-struct :)
I need to export my rescript code in es6 format, so I tried to change my package-specs in bsconfig.json,
but I got this error from my IDE:

Missing dependency S-RescriptStruct in search path

Also have this flag in my bsconfig: "bsc-flags": ["-open ReScriptStruct"]
Not sure why and how it can be fix though, I guess I can copy S.res in my project but I would prefer avoid that.

Any idea ?

[PPX] Cannot use @as inside inline records

Hey, thanks a lot for the lib and for the PPX!

I bumped into this issue, let's say you define a variant with inline records like this:

@schema @tag("type")
type t =
  | @as("foo") Foo({@as("Foo") foo: string})
  | @as("bar") Bar({@as("Bar") bar: string})

You would get the following error:

The field Foo is not part of the record argument for the t.Foo constructor
Hint: Did you mean foo?

I don't know if it's a known issue or if it could be fixed.

How to generate Firestore security rules?

I'm using this library. Very cool! After I define a schema, like a string that is a minimum of 4 characters and a max of 10 characters, matching a regex pattern, I want to generate a Firebase Firestore security rules file that ensures data in the db matches this definition. The rules file has text in it like...

data.size() >= 4 && data.size()<=10 && data.matches("[a-z]+")

How would I go about converting a struct here to this kind of text?

ReScript 12 compatibility

This may be a bit early as there is not even a first alpha version of ReScript 12 out yet, but I wanted to give you a heads-up already.

It seems something broke with rescript-lang/rescript-compiler#6611.

E.g., if I have a simple union schema

type x = A | B

let schema = S.union([S.literal(A), S.literal(B)])

then S.parseOrRaiseWith(JSON.String("B"), schema) gives me the error

Failed parsing at root. Reason: Expected "A", received "B"

on latest (ReScript) master.

module type issue

I'm trying to use your package in a project of mine. my package. I've been experimenting with functors to make untagged unions and wanted to see what happens if I use your library to validate each case in the union. I included your code as a dev-dependency and added it to bs-dependencies. But I'm getting this error below. My package.json has "type": "module" and my bsconfig has "module": "es6" so I'm not sure what I need to do.

(node:82597) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/justinmagaram/Source/rescript-extras/node_modules/rescript-struct/src/S.bs.js:3
import * as Js_exn from "rescript/lib/es6/js_exn.js";

Ignoring some fields on serializing object

For type t which has some functions, won't be serialized.

module Wallet = {
  type id = WalletId(string)
  type t = {
    balance: int,
    id: id,
    transfer: Transaction.t => Js.Promise2.t<unit>,
  }
}

I can transform the transfer field into meaningless value, but how can I totally ignore the field when serializing? (and re-init when deserializing? I guess)

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.