dzakh / rescript-schema Goto Github PK
View Code? Open in Web Editor NEW๐งฌ The fastest parser in the entire JavaScript ecosystem with a focus on small bundle size and top-notch DX
License: MIT License
๐งฌ The fastest parser in the entire JavaScript ecosystem with a focus on small bundle size and top-notch DX
License: MIT License
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)
for better interoperability
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
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 !
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
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!
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.
This works runtime and compile time, but just has a bit weird typing: I haven't encountered "voids" being thrown before
ref: https://github.com/DZakh/rescript-schema/blob/main/docs/js-usage.md#refinements
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.
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
}))
})
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');
}
});
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)
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 !
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])
}
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.
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))
For example:
type rec t = File({content: string}) | Directory({files: array<t>})
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)
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
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
Hey, I ran into this rather interesting bug which originates to validateJsonableSchema
function, I believe. I think it relates to wrapping a recursive (union?) schema inside a non-recursive schema. Or at least that's how I can cause the bug reliably.
I made a somewhat minimal repro. It can be found in this repository. See readme.md
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 ?
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.
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?
e.g. fails on 019122ba-bb79-75ef-9a97-190f1effbb54
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.
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";
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)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.