[⬅ Back to highlights](/README.md) # Sury PPX ReScript PPX to generate **Sury** schema from types. > 🧠 It's 100% opt-in. You can use **Sury** without ppx. ## Table of contents - [Table of contents](#table-of-contents) - [Install](#install) - [Basic usage](#basic-usage) - [API reference](#api-reference) ## Install ```sh npm install sury sury-ppx ``` Then update your `rescript.json` config: ```diff { ... + "bs-dependencies": ["sury"], + "ppx-flags": ["sury-ppx/bin"], } ``` ## Basic usage ```rescript // 1. Define a type and add @schema attribute @schema type rating = | @as("G") GeneralAudiences | @as("PG") ParentalGuidanceSuggested | @as("PG13") ParentalStronglyCautioned | @as("R") Restricted @schema type film = { @as("Id") id: float, @as("Title") title: string, @as("Tags") tags: @s.default([]) array, @as("Rating") rating: rating, @as("Age") deprecatedAgeRestriction: @s.meta({deprecated: true}) option, } // 2. Generated by PPX ⬇️ let ratingSchema = S.union([ S.literal(GeneralAudiences), S.literal(ParentalGuidanceSuggested), S.literal(ParentalStronglyCautioned), S.literal(Restricted), ]) let filmSchema = S.object(s => { id: s.field("Id", S.float), title: s.field("Title", S.string), tags: s.fieldOr("Tags", S.array(S.string), []), rating: s.field("Rating", ratingSchema), deprecatedAgeRestriction: s.field("Age", S.option(S.int)->S.meta({deprecated: true})), }) // 3. Parse data using the schema // The data is validated and transformed to a convenient format %raw(`{ "Id": 1, "Title": "My first film", "Rating": "R", "Age": 17 }`)->S.parseOrThrow(filmSchema) // Ok({ // id: 1., // title: "My first film", // tags: [], // rating: Restricted, // deprecatedAgeRestriction: Some(17), // }) // 4. Transform data back using the same schema { id: 2., tags: ["Loved"], title: "Sad & sed", rating: ParentalStronglyCautioned, deprecatedAgeRestriction: None, }->S.decodeOrThrow(~from=filmSchema, ~to=S.unknown) // Ok(%raw(`{ // "Id": 2, // "Title": "Sad & sed", // "Rating": "PG13", // "Tags": ["Loved"], // "Age": undefined, // }`)) // 5. Use schema as a building block for other tools // For example, create a JSON schema and use it for OpenAPI generation let filmJSONSchema = filmSchema->S.toJSONSchema ``` > 🧠 Read more about schema usage in the _[ReScript Schema for ReScript users](/docs/rescript-usage.md)_ documentation. ## API reference ### `@schema` **Applies to**: type declarations, type signatures Indicates that a schema should be generated for the given type. ### `@s.matches(S.t<'value>)` **Applies to**: type expressions Specifies custom schema for the type. ```rescript @schema type t = @s.matches(S.string->S.url) string // Generated by PPX ⬇️ let schema = S.string->S.url ``` ### `@s.null` **Applies to**: option type expressions Tells to use `S.nullAsOption` for the option schema constructor. ```rescript @schema type t = @s.null option // Generated by PPX ⬇️ let schema = S.nullAsOption(S.string) ``` Not allowed on optional record fields without an `option<_>` type (`foo?: @s.null string`) — `S.nullAsOption` rejects `undefined`, which an optional field must accept. Use `@s.null option<'value>` for the field type or `@s.nullable` instead. ### `@s.nullable` **Applies to**: option type expressions and optional record fields Tells to use `S.nullableAsOption` for the option schema constructor. ```rescript @schema type t = @s.nullable option // Generated by PPX ⬇️ let schema = S.nullableAsOption(S.string) ``` It also works on optional record fields, where `S.nullableAsOption` is used instead of `S.option` for the field optionality: ```rescript @schema type t = { foo?: @s.nullable string, } // Generated by PPX ⬇️ let schema = S.schema(s => { foo: ?s.matches(S.nullableAsOption(S.string)), }) ``` ### `@s.default('value)` **Applies to**: type expressions Wraps the type expression schema into an option with the provided default value. ```rescript @schema type t = @s.default("Unknown") string // Generated by PPX ⬇️ let schema = S.option(S.string)->S.Option.getOr("Unknown") ``` It might also be used together with `@s.null`: ```rescript @schema type t = @s.null @s.default("Unknown") string // Generated by PPX ⬇️ let schema = S.nullAsOption(S.string)->S.Option.getOr("Unknown") ``` ### `@s.defaultWith(unit => 'value)` **Applies to**: type expressions Wraps the type expression schema into an option with callback to get the default value. ```rescript @schema type t = @s.defaultWith(() => []) array // Generated by PPX ⬇️ let schema = S.option(S.array(S.string))->S.Option.getOrWith(() => []) ``` > 🧠 The same as `@s.default` it might be used together with `@s.null` ### `@s.meta(S.meta)` **Applies to**: type expressions Adds metadata to the generated schema. ```rescript @schema type t = @s.meta({description: "A useful bit of text, if you know what to do with it."}) string // Generated by PPX ⬇️ let schema = S.string->S.meta({description: "A useful bit of text, if you know what to do with it."}}) ``` This can be useful for documenting, JSON Schema creation, etc. ```rescript schema->S.toJSONSchema // { // "type": "string", // "description": "A useful bit of text, if you know what to do with it." // } ``` Read more about `S.meta` in the [Sury documentation](../../docs/js-usage.md#meta). ### Other expression attributes There's also support for `@s.noValidation`, `@s.strict`, `@s.strip`, `@s.deepStrict` and `@s.deepStrip` attributes. Create a PR if you need more.