Skip to content

v4.0.0

Choose a tag to compare

@DZakh DZakh released this 05 Apr 22:07
· 644 commits to main since this release

New Features

  • Added first-class support for Js.Json.t parsing and serializing
    Now it's the default mode for better compatibility with existing tools:
Input type Output type V4 V3
Js.Json.t 'value parseWith -
'any 'value parseAnyWith parseWith
string 'value parseJsonWith -
Js.Json.t 'value parseOrRaiseWith -
'any 'value parseAnyOrRaiseWith parseOrRaiseWith
Js.Json.t 'value parseAsyncWith -
'any 'value parseAnyAsyncWith parseAsyncWith
Js.Json.t 'value parseAsyncInStepsWith -
'any 'value parseAnyAsyncInStepsWith parseAsyncInStepsWith
'value Js.Json.t serializeWith -
'value unknown serializeToUnknownWith serializeWith
'value string serializeToJsonWith -
'value Js.Json.t serializeOrRaiseWith -
'value unknown serializeToUnknownOrRaiseWith serializeOrRaiseWith
  • Added new error code InvalidJsonStruct
    It is returned when calling S.serializeWith or S.serializeOrRaiseWith with a struct incompatible with JSON.

  • Added S.describe and S.description
    This can be useful for documenting a field, for example, in a JSON Schema using a library like rescript-json-schema.

  • Added S.unit, which is an alias for S.literal(EmptyOption)

  • Added experimental S.inline
    It returns a string with ReScript code featuring how to create provided struct. The idea is to use it for code generation.

  • Started embedding meta-information about built-in refinements
    You can get it using S.String.refinements / S.Int.refinements / S.Float.refinements / S.Array.refinements. It will be useful for tooling that converts rescript-struct to something else like rescript-json-schema.

  • Added ability to pass asyncParser argument to the S.refine / S.transform / S.custom

Improvements

  • Made the S.object implementation more reliable
    The order of S.field calls doesn't matter anymore.

  • The S.discriminant became redundant and was removed
    Use S.field with ignore instead.

  • Made the error's path an opaque type and moved it to S.Path.t
    You can find many utility functions to work with a path in the S.Path module.

  • Updated namespace name from ReScriptStruct to RescriptStruct
    Discussion: https://forum.rescript-lang.org/t/rescript-vs-rescript-for-the-lib-module-name/4317.

  • Renamed S.deprecated to S.deprecate
    For a more consistent Zod-like API.

  • Renamed S.defaulted to S.default
    For a more consistent Zod-like API.

  • Renamed S.String.trimmed to S.String.trim
    For a more consistent Zod-like API.

  • Renamed S.Error.raise to S.fail and added the ability to pass a custom path

  • Renamed S.Error.raiseCustom to S.advancedFail

  • Removed ability to call S.deprecate without a deprecation message

  • Updated S.default to accept a function for getting a default value
    The function runs every time a default value is needed. Before, you had to pass a value that was allocated even before the struct was created.

  • Updated S.custom's argument types to better align with S.refine and S.transform functions
    No more labeled arguments for the parser and serializer.

  • Removed S.asyncRefine in favor of S.refine with asyncParser

  • Changed S.Defaulted.classify to S.Default.classify with updated return value

  • Changed S.Deprecated.classify to S.deprecation with updated return value

  • Removed unused field type.

Bug fixes

  • Fixed S.tuple5-S.tuple10 types.

Semi-automated migration

The release contains a lot of clean up with API breaking change, so I've prepared a script you can run with comby.dev that will do parts of the migration for you automatically.

  1. Create migration.toml in your project root

  2. Copy the following content to the migration.toml:

[parse-with-to-parse-any-with]
match="S.parseWith"
rewrite="S.parseAnyWith"

[parse-or-raise-with-to-parse-any-or-raise-with]
match="S.parseOrRaiseWith"
rewrite="S.parseAnyOrRaiseWith"

[parse-async-with-to-parse-any-async-with]
match="S.parseAsyncWith"
rewrite="S.parseAnyAsyncWith"

[parse-async-in-steps-with-to-parse-any-async-in-steps-with]
match="S.parseAsyncInStepsWith"
rewrite="S.parseAnyAsyncInStepsWith"

[serialize-with-to-serialize-to-unknown-with]
match="S.serializeWith"
rewrite="S.serializeToUnknownWith"

[serialize-or-raise-with-to-serialize-to-unknown-or-raise-with]
match="S.serializeOrRaiseWith"
rewrite="S.serializeToUnknownOrRaiseWith"

[use-unit]
match="S.literal(EmptyOption)"
rewrite="S.unit()"

[discriminant-to-field]
match="S.discriminant(:[fieldName], :[struct])"
rewrite="S.field(:[fieldName], :[struct])->ignore"

[namespace]
match="ReScriptStruct"
rewrite="RescriptStruct"

[deprecated-with-message-a]
match=":->S.deprecated(~message=:[message], ())"
rewrite="->S.deprecate(:[message])"

[deprecated-with-message-b]
match="S.deprecated(:[struct], ~message=:[message], ())"
rewrite=":[struct]->S.deprecate(:[message])"

[deprecated-without-message]
match="->S.deprecated()"
rewrite="->S.deprecate(\"Deprecated\")"

[trimmed]
match="S.String.trimmed"
rewrite="S.String.trim"

[defaulted-a]
match="->S.defaulted(:[value])"
rewrite="->S.default(() => :[value])"

[defaulted-b]
match="S.defaulted(:[struct], :[value])"
rewrite=":[struct]->S.default(() => :[value])"

[error-raise]
match="S.Error.raise"
rewrite="S.fail"

[error-raise-custom]
match="S.Error.raiseCustom"
rewrite="S.advancedFail"

[async-refine]
match="S.asyncRefine(~parser=:[parser], ())"
rewrite="S.refine(~asyncParser=:[parser], ())"
  1. Run the script in your project root. Assumes migration.toml has been copied in place to your project root.
comby -config migration.toml -f .res -matcher .re -exclude-dir node_modules,__generated__ -i

The migration script is a set of instructions that Comby runs in sequence. You're encouraged to take migration.toml and tweak it so it fits your needs. Comby is powerful. It can do interactive rewriting and numerous other useful stuff. Check it out, but please note it's not intended to cover all of the migration necessary. You'll still likely need to do a few manual fixes after running the migration scripts.