Skip to content

Commit

Permalink
Partial js doc update
Browse files Browse the repository at this point in the history
  • Loading branch information
DZakh committed Oct 16, 2024
1 parent abdb853 commit 915d292
Showing 1 changed file with 74 additions and 62 deletions.
136 changes: 74 additions & 62 deletions docs/js-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ npm install rescript-schema rescript@11
import * as S from "rescript-schema";

// Create login schema with email and password
const loginSchema = S.object({
const loginSchema = S.schema({
email: S.email(S.string),
password: S.stringMinLength(S.string, 8),
});
Expand All @@ -75,13 +75,16 @@ const loginSchema = S.object({
type LoginData = S.Output<typeof loginSchema>; // { email: string; password: string }

// Throws the S.Error(`Failed parsing at ["email"]. Reason: Invalid email address`)
loginSchema.parseOrThrow({ email: "", password: "" });
S.parseWith({ email: "", password: "" }, loginSchema);

// Returns data as { email: string; password: string }
loginSchema.parseOrThrow({
email: "[email protected]",
password: "12345678",
});
S.parseWith(
{
email: "[email protected]",
password: "12345678",
},
loginSchema
);
```

## Primitives
Expand Down Expand Up @@ -164,10 +167,10 @@ const datetimeSchema = S.datetime(S.string);
// The datetimeSchema has the type S.Schema<Date, string>
// String is transformed to the Date instance

datetimeSchema.parseOrThrow("2020-01-01T00:00:00Z"); // pass
datetimeSchema.parseOrThrow("2020-01-01T00:00:00.123Z"); // pass
datetimeSchema.parseOrThrow("2020-01-01T00:00:00.123456Z"); // pass (arbitrary precision)
datetimeSchema.parseOrThrow("2020-01-01T00:00:00+02:00"); // fail (no offsets allowed)
S.parseWith("2020-01-01T00:00:00Z", datetimeSchema); // pass
S.parseWith("2020-01-01T00:00:00.123Z", datetimeSchema); // pass
S.parseWith("2020-01-01T00:00:00.123456Z", datetimeSchema); // pass (arbitrary precision)
S.parseWith("2020-01-01T00:00:00+02:00", datetimeSchema); // fail (no offsets allowed)
```

## Numbers
Expand Down Expand Up @@ -202,7 +205,7 @@ You can make any schema optional with `S.optional`.
```ts
const schema = S.optional(S.string);

schema.parseOrThrow(undefined); // => returns undefined
S.parseWith(undefined, schema); // => returns undefined
type A = S.Output<typeof schema>; // string | undefined
```

Expand All @@ -211,7 +214,7 @@ You can pass a default value to the second argument of `S.optional`.
```ts
const stringWithDefaultSchema = S.optional(S.string, "tuna");

stringWithDefaultSchema.parseOrThrow(undefined); // => returns "tuna"
S.parseWith(undefined, stringWithDefaultSchema); // => returns "tuna"
type A = S.Output<typeof stringWithDefaultSchema>; // string
```

Expand All @@ -220,9 +223,9 @@ Optionally, you can pass a function as a default value that will be re-executed
```ts
const numberWithRandomDefault = S.optional(S.number, Math.random);

numberWithRandomDefault.parseOrThrow(undefined); // => 0.4413456736055323
numberWithRandomDefault.parseOrThrow(undefined); // => 0.1871840107401901
numberWithRandomDefault.parseOrThrow(undefined); // => 0.7223408162401552
S.parseWith(undefined, numberWithRandomDefault); // => 0.4413456736055323
S.parseWith(undefined, numberWithRandomDefault); // => 0.1871840107401901
S.parseWith(undefined, numberWithRandomDefault); // => 0.7223408162401552
```

Conceptually, this is how **rescript-schema** processes default values:
Expand All @@ -236,26 +239,28 @@ Similarly, you can create nullable types with `S.nullable`.

```ts
const nullableStringSchema = S.nullable(S.string);
nullableStringSchema.parseOrThrow("asdf"); // => "asdf"
nullableStringSchema.parseOrThrow(null); // => undefined
S.parseWith("asdf", nullableStringSchema); // => "asdf"
S.parseWith(null, nullableStringSchema); // => undefined
```

Notice how the `null` input transformed to `undefined`.

## Nullish

A convenience method that returns a "nullish" version of a schema. Nullish schemas will accept both `undefined` and `null`. Read more about the concept of "nullish" [in the TypeScript 3.7 release notes](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#nullish-coalescing).

```ts
const nullishStringSchema = S.nullish(S.string);
nullishStringSchema.parseOrThrow("asdf"); // => "asdf"
nullishStringSchema.parseOrThrow(null); // => undefined
nullishStringSchema.parseOrThrow(undefined); // => undefined
S.parseWith("asdf", nullishStringSchema); // => "asdf"
S.parseWith(null, nullishStringSchema); // => undefined
S.parseWith(undefined, nullishStringSchema); // => undefined
```

## Objects

```ts
// all properties are required by default
const dogSchema = S.object({
const dogSchema = S.schema({
name: S.string,
age: S.number,
});
Expand All @@ -272,10 +277,10 @@ type Dog = {

### Literal shorthand

Besides passing schemas for values in `S.object`, you can also pass **any** Js value.
Besides passing schemas for values in `S.schema`, you can also pass **any** Js value.

```ts
const meSchema = S.object({
const meSchema = S.schema({
id: S.number,
name: "Dmitry Zakharov",
age: 23,
Expand All @@ -299,23 +304,29 @@ const userSchema = S.object((s) => ({
name: s.field("USER_NAME", S.string),
}));

userSchema.parseOrThrow({
USER_ID: 1,
USER_NAME: "John",
});
S.parseWith(
{
USER_ID: 1,
USER_NAME: "John",
},
userSchema
);
// => returns { id: 1, name: "John" }

// Infer output TypeScript type of the userSchema
type User = S.Output<typeof userSchema>; // { id: number; name: string }
```

Compared to using `S.transform`, the approach has 0 performance overhead. Also, you can use the same schema to transform the parsed data back to the initial format:
Compared to using `S.transform`, the approach has 0 performance overhead. Also, you can use the same schema to convert the parsed data back to the initial format:

```ts
userSchema.serializeOrThrow({
id: 1,
name: "John",
});
S.convertWith(
{
id: 1,
name: "John",
},
S.reverse(userSchema)
);
// => returns { USER_ID: 1, USER_NAME: "John" }
```

Expand All @@ -325,15 +336,18 @@ By default **rescript-schema** object schema strip out unrecognized keys during

```ts
const personSchema = S.Object.strict(
S.object({
S.schema({
name: S.string,
})
);

personSchema.parseOrThrow({
name: "bob dylan",
extraKey: 61,
});
S.parseWith(
{
name: "bob dylan",
extraKey: 61,
},
personSchema
);
// => throws S.Error
```

Expand All @@ -346,8 +360,8 @@ You can use the `S.Object.strip` function to reset an object schema to the defau
You can add additional fields to an object schema with the `merge` function.

```ts
const baseTeacherSchema = S.object({ students: S.array(S.string) });
const hasIDSchema = S.object({ id: S.string });
const baseTeacherSchema = S.schema({ students: S.array(S.string) });
const hasIDSchema = S.schema({ id: S.string });

const teacherSchema = S.merge(baseTeacherSchema, hasIDSchema);
type Teacher = S.Output<typeof teacherSchema>; // => { students: string[], id: string }
Expand Down Expand Up @@ -377,7 +391,7 @@ Unlike arrays, tuples have a fixed number of elements and each element can have
const athleteSchema = S.tuple([
S.string, // name
S.number, // jersey number
S.object({
S.schema({
pointsScored: S.number,
}), // statistics
]);
Expand All @@ -396,7 +410,7 @@ const athleteSchema = S.tuple((s) => ({
jerseyNumber: s.item(1, S.number),
statistics: s.item(
2,
S.object({
S.schema({
pointsScored: S.number,
})
),
Expand Down Expand Up @@ -428,8 +442,8 @@ The schema function `union` creates an OR relationship between any number of sch

const stringOrNumberSchema = S.union([S.string, S.number]);

stringOrNumberSchema.parseOrThrow("foo"); // passes
stringOrNumberSchema.parseOrThrow(14); // passes
S.parseWith("foo", stringOrNumberSchema); // passes
S.parseWith(14, stringOrNumberSchema); // passes
```

### Discriminated unions
Expand All @@ -442,19 +456,19 @@ stringOrNumberSchema.parseOrThrow(14); // passes
// | { kind: "triangle"; x: number; y: number };

const shapeSchema = S.union([
S.object({
{
kind: "circle" as const,
radius: S.number,
}),
S.object({
},
{
kind: "square" as const,
x: S.number,
}),
S.object({
},
{
kind: "triangle" as const,
x: S.number,
y: S.number,
}),
},
]);
```

Expand All @@ -478,12 +492,10 @@ It's a helper built on `S.literal`, `S.object`, and `S.tuple` to create schemas
```typescript
type Shape = { kind: "circle"; radius: number } | { kind: "square"; x: number };

let circleSchema = S.schema(
(s): Shape => ({
kind: "circle",
radius: s.matches(S.number),
})
);
let circleSchema: S.Schema<Shape> = S.schema({
kind: "circle" as const,
radius: S.number,
});
// The same as:
// S.object(s => ({
// kind: s.field("kind", S.literal("circle")),
Expand All @@ -498,15 +510,15 @@ The `S.json` schema makes sure that the value is compatible with JSON.
It accepts a boolean as an argument. If it's true, then the value will be validated as valid JSON; otherwise, it unsafely casts it to the `S.Json` type.

```ts
S.json(true).parseOrThrow(`"foo"`); // passes
S.parseWith(`"foo"`, S.json(true)); // passes
```

## JSON string

```ts
const schema = S.jsonString(S.int);

schema.parseOrThrow("123");
S.parseWith("123", schema);
// => 123
```

Expand Down Expand Up @@ -553,7 +565,7 @@ type Node = {
};

let nodeSchema = S.recursive<Node>((nodeSchema) =>
S.object({
S.schema({
id: S.string,
children: S.array(nodeSchema),
})
Expand All @@ -567,19 +579,19 @@ let nodeSchema = S.recursive<Node>((nodeSchema) =>
**rescript-schema** lets you provide custom validation logic via refinements. It's useful to add checks that's not possible to cover with type system. For instance: checking that a number is an integer or that a string is a valid email address.

```ts
const shortStringSchema = S.refine(S.string, (value, s) =>
const shortStringSchema = S.refine(S.string, (value, s) => {
if (value.length > 255) {
s.fail("String can't be more than 255 characters")
s.fail("String can't be more than 255 characters");
}
)
});
```

The refine function is applied for both parser and serializer.

Also, you can have an asynchronous refinement (for parser only):

```ts
const userSchema = S.object({
const userSchema = S.schema({
id: S.asyncParserRefine(S.uuid(S.string), async (id, s) => {
const isActiveUser = await checkIsActiveUser(id);
if (!isActiveUser) {
Expand Down

2 comments on commit 915d292

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 915d292 Previous: 90ab99c Ratio
Parse string 167473546 ops/sec (±0.62%) 811431552 ops/sec (±0.15%) 4.85
Reverse convert string 289353233 ops/sec (±0.17%)
Advanced object schema factory 458099 ops/sec (±0.58%) 472293 ops/sec (±0.54%) 1.03
Parse advanced object 50951372 ops/sec (±0.82%) 56799894 ops/sec (±0.57%) 1.11
Assert advanced object - compile 163578012 ops/sec (±0.13%)
Assert advanced object 166175268 ops/sec (±0.11%) 171920805 ops/sec (±0.20%) 1.03
Create and parse advanced object 90844 ops/sec (±1.15%) 95083 ops/sec (±0.21%) 1.05
Create and parse advanced object - with S.schema 99385 ops/sec (±1.04%)
Parse advanced strict object 22116343 ops/sec (±0.41%) 25471962 ops/sec (±0.17%) 1.15
Assert advanced strict object 28911140 ops/sec (±0.16%) 30460141 ops/sec (±0.20%) 1.05
Reverse convert advanced object 58649274 ops/sec (±0.28%)
Reverse convert advanced object - with S.schema 55030806 ops/sec (±0.41%)
Reverse convert advanced object - compile 799342944 ops/sec (±0.59%)

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 915d292 Previous: 90ab99c Ratio
Parse string 167473546 ops/sec (±0.62%) 811431552 ops/sec (±0.15%) 4.85

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.