effect
effect copied to clipboard
@effect/schema/Arbitrary: Schema.pattern wipes out earlier constraints on Schema.string
What version of Effect is running?
2.4.5
What steps can reproduce the bug?
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@effect/schema": "^0.64.1",
"effect": "^2.4.5",
"esbuild": "^0.20.1",
"fast-check": "^3.16.0",
"typescript": "^5.4.2"
}
}
tsconfig.json
{
"compilerOptions": {
"strict": true,
"module": "nodenext",
"target": "ESNext",
"lib": [
"ESNext"
],
"moduleResolution": "nodenext",
"removeComments": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"typeRoots": [
"node_modules/@types"
],
"sourceMap": true,
"incremental": true,
"composite": true,
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"noErrorTruncation": true,
"downlevelIteration": true,
"baseUrl": ".",
"skipLibCheck": true
}
}
index.ts
import * as Arbitrary from '@effect/schema/Arbitrary';
import * as S from '@effect/schema/Schema';
import { Effect } from 'effect';
import * as fc from 'fast-check';
export const GoodSsid = S.string.pipe(S.pattern(/^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$/), S.nonEmpty());
export const BadSsid = S.string.pipe(S.nonEmpty(), S.pattern(/^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$/));
const goodSsids = Arbitrary.make(GoodSsid)(fc);
const badSsids = Arbitrary.make(BadSsid)(fc);
console.log('GoodSsid');
Effect.runSync(S.decode(S.array(GoodSsid))(fc.sample(goodSsids, 1000)));
console.log('BadSsid');
Effect.runSync(S.decode(S.array(BadSsid))(fc.sample(badSsids, 1000)));
$ pnpm i
$ pnpm esbuild --bundle index.ts > index.js
$ node --version
v20.11.1
$ node index.js
What is the expected behavior?
Both schemas produce valid examples using fast-check and the ordering of constraints not mattering when using @effect/schema/Arbitrary.
What do you see instead?
Note that BadSsid prints showing that GoodSsid did not throw.
The key part of the error is Expected a non empty string, actual "" which shows that the constraint applied by S.nonEmpty was lost when it comes before the S.pattern.
GoodSsid
BadSsid
<REDACTED>/test/index.js:13377
throw fiberFailure(result.i0);
^
ReadonlyArray<a string matching the pattern ^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$>
└─ [4]
└─ a string matching the pattern ^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$
└─ From side refinement failure
└─ a non empty string
└─ Predicate refinement failure
└─ Expected a non empty string, actual ""
at parseError (<REDACTED>/test/index.js:15726:31)
at <REDACTED>/test/index.js:14896:20
at <REDACTED>/test/index.js:27:20
at <REDACTED>/test/index.js:15734:36
at <REDACTED>/test/index.js:24611:50
at Object.<anonymous> (<REDACTED>/test/index.js:24612:3)
at Module._compile (node:internal/modules/cjs/loader:1376:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12) {
toJSON: [Function (anonymous)],
toString: [Function (anonymous)],
[Symbol(effect/Runtime/FiberFailure)]: Symbol(effect/Runtime/FiberFailure),
[Symbol(effect/Runtime/FiberFailure/Cause)]: {
_tag: 'Fail',
error: ParseError: ReadonlyArray<a string matching the pattern ^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$>
└─ [4]
└─ a string matching the pattern ^[- !#%&'()*,./0123456789:;<=>@A-Z^_`a-z{|}~]*$
└─ From side refinement failure
└─ a non empty string
└─ Predicate refinement failure
└─ Expected a non empty string, actual ""
at parseError (<REDACTED>/test/index.js:15726:31)
at <REDACTED>/test/index.js:14896:20
at <REDACTED>/test/index.js:27:20
at <REDACTED>/test/index.js:15734:36
at <REDACTED>/test/index.js:24611:50
at Object.<anonymous> (<REDACTED>/test/index.js:24612:3)
at Module._compile (node:internal/modules/cjs/loader:1376:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12) {
error: Tuple {
ast: TupleType {
elements: [],
rest: [ [Refinement] ],
isReadonly: true,
annotations: {},
_tag: 'TupleType'
},
actual: [
'06/(1;|', '>) 5* &|=', ';#)})3~7', '{l3((3&@2', '',
'|8%:', '__proto_', 'arguments', ')', '@&V{>',
'-9%(`/18', ',#', '#QlC|9', '3', 'G(3_~_}',
'', '', '', '9:224.A(', ">&'",
'=>(%', ')efi(', '/&=<{9-P*~', '~', '#;21/',
')~name&dup', ';~*%5f', ' } x#(0#/|', '7!', '*2',
"'%z:>%|8}6", '#~`>~87{-/', '#%', '@%.0 #*.|', '5l',
'{', ':&*>>', '3', 'hasOwnProp', "&^`-_&w'=*",
"%@/66)'", '->2/', '2} ', ';#35~&%(`7', '>_m',
'<(@({2_#', '#7-< /', "::< -6'", ';(!-.', '~{&~4r,}^',
")178=6'(3:", '>.48@_<', '}/4/.Z29:6', '%!~`T%8*:', '0_',
'9=%03>8#|@', '(*`,4)', '#', "'5", "'(U%",
'(/=`99.(', ' 4_;()8}@', '2;`#~2-&', '', '',
'*.-1', '4~{4}391=', '.1', '', '|:.b*}5',
'-|v;<4}', '!~^>{)', '>', '0', "'|",
'=25) ', '8', '*;)9*|6*^', '~', '7-4Y',
')', '88_', '3@}', '9>%)%_|', 'G8;J~= ',
'', '= -', '^,1', 'v}^ )=_', '=>=',
'isPrototy', "6>~'_*", '!', '%0,13}@2#3', '',
"9'o", '', '!{)@,86', '^=5@/', '!',
... 900 more items
],
errors: [ Index { index: 4, error: [Refinement2], _tag: 'Index' } ],
_tag: 'TupleType'
},
_tag: 'ParseError'
}
},
[Symbol(nodejs.util.inspect.custom)]: [Function (anonymous)]
}
Additional information
No response
@gcanti I think this is user error in retrospect. The regex /^[...]*$/ is effectively zero or more, so in that case it looks to be working as intended. It works fine using /^[...]+$/ for one or more.
If you agree, this can be closed.
I think there might be an issue actually, checking the code if you use pattern any filters preceding it are ignored.
This is because pattern sets a custom hook for the Arbitrary compiler: https://github.com/Effect-TS/effect/blob/0c0b98d0c39513322385219de0aaa23d39077e0a/packages/schema/src/Schema.ts#L3084
@gcanti I think this is user error in retrospect. The regex
/^[...]*$/is effectively zero or more, so in that case it looks to be working as intended. It works fine using/^[...]+$/for one or more.
Encountered the same issue with stringMatching
const SchemaTest = Schema.string.pipe(Schema.pattern(/^(?=.*[A-Za-z]).*$/i));
const x = Arbitrary.make(SchemaTest)(fc);
console.log(x);
/*
node_modules/fast-check/lib/arbitrary/stringMatching.js:149
throw new globals_2.Error(`Unable to use "stringMatching" against a regex using the flag ${flag}`);
^
Error: Unable to use "stringMatching" against a regex using the flag i
at Object.stringMatching
*/
@steffanek it's a separate issue, it appears to be a limitation upstream in stringMatching https://github.com/dubzzz/fast-check/pull/3925