From 0a2078800f05b335b30c88d9cf06d988c826bdef Mon Sep 17 00:00:00 2001 From: Giulio Canti Date: Sun, 15 Oct 2023 13:02:38 +0200 Subject: [PATCH] Arbitrary: use Math.fround when creating number constraints, closes #484 (#485) --- .changeset/small-days-bake.md | 5 +++ src/Arbitrary.ts | 53 +++++++++++++++++++------------- test/Arbitrary/Arbitrary.test.ts | 5 +++ test/number/clamp.test.ts | 9 +++++- 4 files changed, 49 insertions(+), 23 deletions(-) create mode 100644 .changeset/small-days-bake.md diff --git a/.changeset/small-days-bake.md b/.changeset/small-days-bake.md new file mode 100644 index 000000000..3cd205845 --- /dev/null +++ b/.changeset/small-days-bake.md @@ -0,0 +1,5 @@ +--- +"@effect/schema": patch +--- + +Arbitrary: use Math.fround when creating number constraints, closes #484 diff --git a/src/Arbitrary.ts b/src/Arbitrary.ts index 986fce1b9..f5a6c30ef 100644 --- a/src/Arbitrary.ts +++ b/src/Arbitrary.ts @@ -241,16 +241,34 @@ interface NumberConstraints { readonly constraints: FastCheck.FloatConstraints } +const numberConstraints = (constraints: NumberConstraints["constraints"]): NumberConstraints => { + if (Predicate.isNumber(constraints.min)) { + constraints.min = Math.fround(constraints.min) + } + if (Predicate.isNumber(constraints.max)) { + constraints.max = Math.fround(constraints.max) + } + return { _tag: "NumberConstraints", constraints } +} + interface StringConstraints { readonly _tag: "StringConstraints" readonly constraints: FastCheck.StringSharedConstraints } +const stringConstraints = (constraints: StringConstraints["constraints"]): StringConstraints => { + return { _tag: "StringConstraints", constraints } +} + interface IntegerConstraints { readonly _tag: "IntegerConstraints" readonly constraints: FastCheck.IntegerConstraints } +const integerConstraints = (constraints: IntegerConstraints["constraints"]): IntegerConstraints => { + return { _tag: "IntegerConstraints", constraints } +} + type Constraints = NumberConstraints | StringConstraints | IntegerConstraints /** @internal */ @@ -260,18 +278,12 @@ export const getConstraints = (ast: AST.Refinement): Constraints | undefined => switch (TypeAnnotationId) { case Schema.GreaterThanTypeId: case Schema.GreaterThanOrEqualToTypeId: - return { - _tag: "NumberConstraints", - constraints: { min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum } - } + return numberConstraints({ min: jsonSchema.exclusiveMinimum ?? jsonSchema.minimum }) case Schema.LessThanTypeId: case Schema.LessThanOrEqualToTypeId: - return { - _tag: "NumberConstraints", - constraints: { max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum } - } + return numberConstraints({ max: jsonSchema.exclusiveMaximum ?? jsonSchema.maximum }) case Schema.IntTypeId: - return { _tag: "IntegerConstraints", constraints: {} } + return integerConstraints({}) case Schema.BetweenTypeId: { const min = jsonSchema.minimum const max = jsonSchema.maximum @@ -282,12 +294,12 @@ export const getConstraints = (ast: AST.Refinement): Constraints | undefined => if (Predicate.isNumber(max)) { constraints.max = max } - return { _tag: "NumberConstraints", constraints } + return numberConstraints(constraints) } case Schema.MinLengthTypeId: - return { _tag: "StringConstraints", constraints: { minLength: jsonSchema.minLength } } + return stringConstraints({ minLength: jsonSchema.minLength }) case Schema.MaxLengthTypeId: - return { _tag: "StringConstraints", constraints: { maxLength: jsonSchema.maxLength } } + return stringConstraints({ maxLength: jsonSchema.maxLength }) } } @@ -306,19 +318,19 @@ export const combineConstraints = ( case "NumberConstraints": { switch (c2._tag) { case "NumberConstraints": { - const out: NumberConstraints = { - _tag: "NumberConstraints", - constraints: { ...c1.constraints, ...c2.constraints } + const constraints: NumberConstraints["constraints"] = { + ...c1.constraints, + ...c2.constraints } const min = getMax(c1.constraints.min, c2.constraints.min) if (Predicate.isNumber(min)) { - out.constraints.min = min + constraints.min = min } const max = getMin(c1.constraints.max, c2.constraints.max) if (Predicate.isNumber(max)) { - out.constraints.max = max + constraints.max = max } - return out + return numberConstraints(constraints) } case "IntegerConstraints": { const out: IntegerConstraints = { ...c2 } @@ -338,10 +350,7 @@ export const combineConstraints = ( case "StringConstraints": { switch (c2._tag) { case "StringConstraints": { - const out: StringConstraints = { - _tag: "StringConstraints", - constraints: { ...c1.constraints, ...c2.constraints } - } + const out: StringConstraints = stringConstraints({ ...c1.constraints, ...c2.constraints }) const min = getMax(c1.constraints.minLength, c2.constraints.minLength) if (Predicate.isNumber(min)) { out.constraints.minLength = min diff --git a/test/Arbitrary/Arbitrary.test.ts b/test/Arbitrary/Arbitrary.test.ts index 88195de68..1665175f5 100644 --- a/test/Arbitrary/Arbitrary.test.ts +++ b/test/Arbitrary/Arbitrary.test.ts @@ -371,4 +371,9 @@ describe("Arbitrary/Arbitrary", () => { const schema = S.string.pipe(S.pattern(regexp)) propertyTo(schema) }) + + it("should support doubles as constraints", () => { + const schema = S.number.pipe(S.clamp(1.3, 3.1)) + propertyTo(schema) + }) }) diff --git a/test/number/clamp.test.ts b/test/number/clamp.test.ts index 0a47614f9..cc60c34da 100644 --- a/test/number/clamp.test.ts +++ b/test/number/clamp.test.ts @@ -2,10 +2,17 @@ import * as S from "@effect/schema/Schema" import * as Util from "@effect/schema/test/util" describe("number/clamp", () => { - const schema = S.number.pipe(S.clamp(-1, 1)) it("decoding", async () => { + const schema = S.number.pipe(S.clamp(-1, 1)) await Util.expectParseSuccess(schema, 3, 1) await Util.expectParseSuccess(schema, 0, 0) await Util.expectParseSuccess(schema, -3, -1) }) + + it("should support doubles as constraints", async () => { + const schema = S.number.pipe(S.clamp(1.3, 3.1)) + await Util.expectParseSuccess(schema, 4, 3.1) + await Util.expectParseSuccess(schema, 2, 2) + await Util.expectParseSuccess(schema, 1, 1.3) + }) })