Skip to content

Commit

Permalink
Improve compiled code and fix path for errors during compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
DZakh committed Jul 29, 2023
1 parent 8608297 commit a9e707c
Show file tree
Hide file tree
Showing 11 changed files with 718 additions and 635 deletions.
1 change: 1 addition & 0 deletions CHANGELOG_NEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- `UnexpectedType` renamed to `InvalidType` and contains the failed struct and provided input instead of their names. Also, it's not returned for literals anymore, literal structs always fail with `InvlidLiteral` error code now.
- `UnexpectedValue` renamed to `InvlidLiteral` and contains the expected literal and provided input instead of their names.
- `MissingSerializer` and `MissingParser` renamed to single `InvalidOperation({description: string})`
- Added `S.Path.dynamic` and fixed `S.error.path` for errors happening during operation compilation phase
- `S.deprecate` doesn't make a struct optional anymore (it used to use `S.option` internally)
- `S.default` now uses `S.option` internally, so you don't need to call it yourself
- Updated `S.name` logic and added `S.setName` to be able customize it. Name is used for errors, codegen and external tools
Expand Down
1 change: 1 addition & 0 deletions IDEAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ let trimContract: S.contract<string => string> = S.contract(s => {
- Add built-in refinements to TS API
// TODO: Update gen.ts
- Update S_Js.res to create new structs instead of mixin in the methods
- Write tests for generated functions

## v5.1

Expand Down
30 changes: 30 additions & 0 deletions __tests__/S_failWithError_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,33 @@ test("FIXME: Should keep operation of the error passed to advanced fail", t => {
(),
)
})

test("Works with failing outside of the parser", t => {
let struct = S.object(s =>
s.field(
"root",
S.array(
S.string->S.transform(
s =>
s.failWithError({
code: OperationFailed("User error"),
operation: Serializing,
path: S.Path.fromArray(["a", "b"]),
}),
),
),
)
)

t->Assert.deepEqual(
["Hello world!"]->S.parseAnyWith(struct),
Error({
code: OperationFailed("User error"),
operation: Parsing,
path: S.Path.fromLocation("root")
->S.Path.concat(S.Path.dynamic)
->S.Path.concat(S.Path.fromArray(["a", "b"])),
}),
(),
)
})
21 changes: 21 additions & 0 deletions __tests__/S_object_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ test("Fails to parse object with inlinable string field", t => {
)
})

test(
"Fails to parse object with custom user error in array field (should have correct path)",
t => {
let struct = S.object(s =>
{
"field": s.field("field", S.array(S.string->S.refine(s => _ => s.fail("User error")))),
}
)

t->Assert.deepEqual(
%raw(`{field: ["foo"]}`)->S.parseAnyWith(struct),
Error({
code: OperationFailed("User error"),
operation: Parsing,
path: S.Path.fromArray(["field", "0"]),
}),
(),
)
},
)

test("Successfully parses object with inlinable bool field", t => {
let struct = S.object(s =>
{
Expand Down
26 changes: 25 additions & 1 deletion __tests__/S_transform_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ test("Uses the path from failWithError called in the transform serializer", t =>
)
})

test("Transform doesn't ignore non rescript-struct errors", t => {
test("Transform parser passes through non rescript-struct errors", t => {
let struct = S.array(
S.string->S.transform(_ => {parser: _ => Js.Exn.raiseError("Application crashed")}),
)
Expand All @@ -112,6 +112,30 @@ test("Transform doesn't ignore non rescript-struct errors", t => {
)
})

test("Transform parser passes through other rescript exceptions", t => {
let struct = S.array(S.string->S.transform(_ => {parser: _ => TestUtils.raiseTestException()}))

t->TestUtils.assertThrowsTestException(() => {["Hello world!"]->S.parseAnyWith(struct)}, ())
})

test("Transform definition passes through non rescript-struct errors", t => {
let struct = S.array(S.string->S.transform(_ => Js.Exn.raiseError("Application crashed")))

t->Assert.throws(
() => {["Hello world!"]->S.parseAnyWith(struct)},
~expectations={
message: "Application crashed",
},
(),
)
})

test("Transform definition passes through other rescript exceptions", t => {
let struct = S.array(S.string->S.transform(_ => TestUtils.raiseTestException()))

t->TestUtils.assertThrowsTestException(() => {["Hello world!"]->S.parseAnyWith(struct)}, ())
})

test("Successfully serializes primitive with transformation to the same type", t => {
let struct = S.string->S.transform(_ => {serializer: value => value->Js.String2.trim})

Expand Down
30 changes: 30 additions & 0 deletions __tests__/TestUtils.bs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

import * as Js_dict from "rescript/lib/es6/js_dict.js";
import * as Caml_option from "rescript/lib/es6/caml_option.js";
import * as Caml_exceptions from "rescript/lib/es6/caml_exceptions.js";
import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js";

var Test = /* @__PURE__ */Caml_exceptions.create("TestUtils-RescriptStruct.Test");

function raiseTestException() {
throw {
RE_EXN_ID: Test,
Error: new Error()
};
}

function assertThrowsTestException(t, fn, message, param) {
try {
fn(undefined);
return t.fail("Didn't throw");
}
catch (raw_exn){
var exn = Caml_js_exceptions.internalToOCamlException(raw_exn);
if (exn.RE_EXN_ID === Test) {
t.pass(message !== undefined ? Caml_option.valFromOption(message) : undefined);
return ;
} else {
return t.fail("Thrown another exception");
}
}
}

function cleanUpStruct(struct) {
var $$new = {};
Expand Down Expand Up @@ -32,6 +59,9 @@ function unsafeAssertEqualStructs(t, s1, s2, message, param) {
var assertEqualStructs = unsafeAssertEqualStructs;

export {
Test ,
raiseTestException ,
assertThrowsTestException ,
cleanUpStruct ,
unsafeAssertEqualStructs ,
assertEqualStructs ,
Expand Down
15 changes: 15 additions & 0 deletions __tests__/TestUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ external magic: 'a => 'b = "%identity"
external castAnyToUnknown: 'any => unknown = "%identity"
external castUnknownToAny: unknown => 'any = "%identity"

exception Test
let raiseTestException = () => raise(Test)

let assertThrowsTestException = {
(t, fn, ~message=?, ()) => {
try {
let _ = fn()
t->Assert.fail("Didn't throw")
} catch {
| Test => t->Assert.pass(~message?, ())
| _ => t->Assert.fail("Thrown another exception")
}
}
}

let rec cleanUpStruct = struct => {
let new = Js.Dict.empty()
struct
Expand Down
3 changes: 3 additions & 0 deletions packages/ppx-test/__tests__/TestUtils.res
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ open Ava

external magic: 'a => 'b = "%identity"

exception Test
let raiseTestException = () => raise(Test)

let assertEqualStructs = {
let rec cleanUpStruct = (struct: S.t<'v>): S.t<'v> => {
let new = Dict.make()
Expand Down
Loading

1 comment on commit a9e707c

@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: a9e707c Previous: 06917fe Ratio
Parse string 587483015 ops/sec (±0.24%) 768626561 ops/sec (±1.37%) 1.31
Serialize string 580814074 ops/sec (±1.40%) 762449917 ops/sec (±0.91%) 1.31
Advanced object struct factory 252160 ops/sec (±0.90%) 175313 ops/sec (±1.22%) 0.70
Parse advanced object 20887955 ops/sec (±0.40%) 19649867 ops/sec (±1.35%) 0.94
Create and parse advanced object 61144 ops/sec (±1.02%) 44890 ops/sec (±1.18%) 0.73
Parse advanced strict object 11407243 ops/sec (±0.79%) 11823782 ops/sec (±2.57%) 1.04
Serialize advanced object 35001744 ops/sec (±0.39%) 9024665 ops/sec (±1.18%) 0.26

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

Please sign in to comment.