lawspec 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API-MIGRATION.md +7 -0
- package/LANGUAGE.md +57 -7
- package/PRIMITIVES.md +1 -1
- package/README.md +25 -15
- package/REFINEMENTS.md +1 -1
- package/RELEASE-0.13.md +55 -0
- package/bin/lawspec.mjs +2 -2
- package/build.json +11 -9
- package/core.wasm +0 -0
- package/examples/specs/domain_modeling.lawspec +59 -0
- package/package.json +2 -2
package/API-MIGRATION.md
CHANGED
|
@@ -287,3 +287,10 @@ Each item names the owning unit, the contract `declaration`, the `stage`
|
|
|
287
287
|
`runtime-checked`, a `reason`, and the `claim` as a typed expression. Schemas 3
|
|
288
288
|
and 4 are otherwise unchanged; clients that ignore unknown fields need no
|
|
289
289
|
changes. Generated definition code no longer re-checks proved postconditions.
|
|
290
|
+
|
|
291
|
+
## Construction evidence (0.13)
|
|
292
|
+
|
|
293
|
+
`evidence` items may have stage `construction`: a constructor field constraint,
|
|
294
|
+
including a `wrapper` constraint, checked whenever a value is constructed or
|
|
295
|
+
decoded. Its `declaration` is the constructor identity and its `status` is
|
|
296
|
+
`runtime-checked`. The TypeScript API names these records `ObligationEvidence`.
|
package/LANGUAGE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# LawSpec language and compiler boundary (0.
|
|
1
|
+
# LawSpec language and compiler boundary (0.13)
|
|
2
2
|
|
|
3
3
|
LawSpec describes portable laws, concrete examples, and adapter contracts. The
|
|
4
4
|
compiler is written in Haskell. Rust is an output backend alongside Java, Python,
|
|
@@ -213,6 +213,54 @@ the `evidence` field. A proved definition is a natural reference model for a
|
|
|
213
213
|
native adapter, as in `append xs ys = concatV xs ys` in the
|
|
214
214
|
[indexed example](examples/specs/indexed_families.lawspec).
|
|
215
215
|
|
|
216
|
+
## Domain modeling
|
|
217
|
+
|
|
218
|
+
### Wrappers and constrained primitives
|
|
219
|
+
|
|
220
|
+
A `wrapper` gives a primitive or container a domain meaning. It declares a
|
|
221
|
+
distinct nominal type with one field, `value`, and an optional constraint:
|
|
222
|
+
|
|
223
|
+
```lawspec
|
|
224
|
+
wrapper UnitQuantity is Int32 where value >= 1 && value <= 1000 end
|
|
225
|
+
wrapper OrderId is Text where prelude.length value > 0 end
|
|
226
|
+
wrapper NonEmptyList (a :: Type) is List a where prelude.length value > 0 end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`UnitQuantity` is not interchangeable with `Int32` or with another wrapper over
|
|
230
|
+
`Int32`. Its constructor, `UnitQuantity 5`, checks the constraint, so an invalid
|
|
231
|
+
value cannot be constructed in an example, produced by a generator, or decoded
|
|
232
|
+
from native code. `valueOfUnitQuantity` unwraps it. A wrapper elaborates to a
|
|
233
|
+
single-constructor product with a refined field and a checked definition, and
|
|
234
|
+
its native representation is that product on every target. The constraint is
|
|
235
|
+
reported as a `construction` obligation in the evidence.
|
|
236
|
+
|
|
237
|
+
### Workflows and state distinctions
|
|
238
|
+
|
|
239
|
+
A workflow names a pipeline of adapter steps between distinct state types:
|
|
240
|
+
|
|
241
|
+
```lawspec
|
|
242
|
+
type UnvalidatedOrder is UnvalidatedOrder id :: Text quantity :: Int32 end
|
|
243
|
+
type ValidatedOrder is ValidatedOrder id :: OrderId quantity :: UnitQuantity end
|
|
244
|
+
type PricedOrder is PricedOrder id :: OrderId quantity :: UnitQuantity total :: Int64 end
|
|
245
|
+
|
|
246
|
+
workflow placeOrder :: UnvalidatedOrder -> Either OrderError PricedOrder is
|
|
247
|
+
validateOrder :: UnvalidatedOrder -> Either OrderError ValidatedOrder
|
|
248
|
+
priceOrder :: ValidatedOrder -> Either OrderError PricedOrder
|
|
249
|
+
end
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Each step is an ordinary adapter declaration with one input. The compiler checks
|
|
253
|
+
that every step accepts the state the previous step produces, that fallible steps
|
|
254
|
+
(those returning `Either E T`) share one error type, and that the workflow's
|
|
255
|
+
declared result matches: `Either E T` if any step can fail, otherwise `T`. It then
|
|
256
|
+
adds the law `placeOrder composes its steps`: the native workflow must equal the
|
|
257
|
+
railway composition of the native steps, where the first `Left` is the result. A
|
|
258
|
+
step declared again with the same type is shared between workflows.
|
|
259
|
+
|
|
260
|
+
Because each state is its own type, a `ValidatedOrder` can only hold checked
|
|
261
|
+
values, and a step that needs one cannot be given raw input. See the
|
|
262
|
+
[domain modeling example](examples/specs/domain_modeling.lawspec).
|
|
263
|
+
|
|
216
264
|
## Total definitions
|
|
217
265
|
|
|
218
266
|
A unit can supply an implementation as a checked total definition:
|
|
@@ -442,13 +490,15 @@ Still open, for evaluation in later releases: index equalities between sibling
|
|
|
442
490
|
fields (perfect trees whose subtrees share one index), non-linear indices, and
|
|
443
491
|
GADTs that refine type arguments.
|
|
444
492
|
|
|
445
|
-
### 0.13 Wlaschin-style domain modeling primitives
|
|
446
|
-
|
|
447
|
-
- Semantic wrappers and constrained primitives.
|
|
448
|
-
- Make illegal states unrepresentable.
|
|
449
|
-
- Explicit domain workflows and state distinctions.
|
|
493
|
+
### 0.13 Wlaschin-style domain modeling primitives (released)
|
|
450
494
|
|
|
451
|
-
|
|
495
|
+
- Semantic wrappers and constrained primitives: `wrapper` declarations with
|
|
496
|
+
checked constructors.
|
|
497
|
+
- Illegal states are unrepresentable: wrapper and field constraints are checked
|
|
498
|
+
on construction, generation and native decoding, and reported as evidence.
|
|
499
|
+
- Explicit domain workflows and state distinctions: `workflow` declarations with
|
|
500
|
+
checked step types and a generated railway composition law.
|
|
501
|
+
- See [domain modeling](#domain-modeling).
|
|
452
502
|
|
|
453
503
|
### 0.14 Cross-unit imports and packages
|
|
454
504
|
|
package/PRIMITIVES.md
CHANGED
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**State the law once. Check it everywhere.**
|
|
4
4
|
|
|
5
|
-
LawSpec 0.
|
|
5
|
+
LawSpec 0.13 compiles reusable laws into native property tests, executable examples,
|
|
6
6
|
and implementation adapters. The compiler is Haskell, distributed as prebuilt
|
|
7
7
|
WebAssembly with a Node CLI and an asynchronous, typed JavaScript API.
|
|
8
8
|
|
|
@@ -69,8 +69,18 @@ needs no runtime postcondition. Every contract obligation is reported as
|
|
|
69
69
|
[proved indices](LANGUAGE.md#proved-indices-and-evidence) and the
|
|
70
70
|
[release notes](RELEASE-0.12.md).
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
## Domain modeling in 0.13
|
|
73
|
+
|
|
74
|
+
LawSpec 0.13 adds `wrapper` declarations for constrained primitives, such as a
|
|
75
|
+
`UnitQuantity` that cannot hold 0, and `workflow` declarations for typed
|
|
76
|
+
pipelines between domain states, with a generated law that the workflow is the
|
|
77
|
+
railway composition of its steps. See
|
|
78
|
+
[domain modeling](LANGUAGE.md#domain-modeling), the
|
|
79
|
+
[example](examples/specs/domain_modeling.lawspec) and the
|
|
80
|
+
[release notes](RELEASE-0.13.md).
|
|
81
|
+
|
|
82
|
+
Planned: 0.14 cross-unit packages and 0.15 evidence reporting. See the
|
|
83
|
+
[roadmap](LANGUAGE.md#roadmap).
|
|
74
84
|
|
|
75
85
|
## Install and try it
|
|
76
86
|
|
|
@@ -81,7 +91,7 @@ support to use it; GitHub currently uses the Haskell fallback.
|
|
|
81
91
|
Install [LawSpec from npm](https://www.npmjs.com/package/lawspec):
|
|
82
92
|
|
|
83
93
|
```sh
|
|
84
|
-
npm install --save-dev lawspec@0.
|
|
94
|
+
npm install --save-dev lawspec@0.13.0
|
|
85
95
|
npx lawspec --version
|
|
86
96
|
```
|
|
87
97
|
|
|
@@ -95,8 +105,8 @@ local dependencies so LawSpec can create its `package.json` and test script:
|
|
|
95
105
|
```sh
|
|
96
106
|
mkdir lawspec-example
|
|
97
107
|
cd lawspec-example
|
|
98
|
-
npm exec --package=lawspec@0.
|
|
99
|
-
npm install --save-dev lawspec@0.
|
|
108
|
+
npm exec --package=lawspec@0.13.0 -- lawspec init --target javascript
|
|
109
|
+
npm install --save-dev lawspec@0.13.0
|
|
100
110
|
npx lawspec check
|
|
101
111
|
npx lawspec explain 'example.atoi_codec::itoa and then atoi yields a'
|
|
102
112
|
npx lawspec doctor
|
|
@@ -306,7 +316,7 @@ reusable laws accept these curried functions, their partial applications, and
|
|
|
306
316
|
scalar parameters. Quantified test inputs remain scalar.
|
|
307
317
|
|
|
308
318
|
The prelude defines the following laws. Every row has an executable example in
|
|
309
|
-
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
319
|
+
[algebra.lawspec](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/algebra.lawspec), including both sides of every
|
|
310
320
|
combined law. `f` and `g` are binary operations, `inverse` is unary, and `e` and
|
|
311
321
|
`zero` are scalar parameters. All these laws require equality of the element type.
|
|
312
322
|
|
|
@@ -374,7 +384,7 @@ their entire consequence. Every conjunct is type-checked and emitted. As with
|
|
|
374
384
|
existing assertions, the first failure stops that individual test. `and` is now
|
|
375
385
|
a reserved word.
|
|
376
386
|
|
|
377
|
-
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
387
|
+
[Currying examples](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/currying.lawspec) demonstrate a four-argument
|
|
378
388
|
function partially applied twice, a formatter with four heterogeneous arguments,
|
|
379
389
|
and composition after partial application. Each example states its exact outputs.
|
|
380
390
|
Run `node npm/bin/lawspec.mjs examples` after rebuilding to inspect all nine units
|
|
@@ -426,7 +436,7 @@ law `valid ports round trip` is
|
|
|
426
436
|
end
|
|
427
437
|
```
|
|
428
438
|
|
|
429
|
-
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
439
|
+
The [complete port example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/parse_port.lawspec)
|
|
430
440
|
defines valid ports as 1–65535, and covers both endpoints, ordinary ports, zero,
|
|
431
441
|
negative values, and 65536. All explicit `expect` assertions run regardless of
|
|
432
442
|
the law's condition. A false condition skips only the consequence: invalid ports
|
|
@@ -444,7 +454,7 @@ The prelude includes `satisfies predicate` (the predicate holds for every input)
|
|
|
444
454
|
and `left inverse when predicate parse render` (the guarded round trip above).
|
|
445
455
|
These reusable laws preserve the condition and its lexical bindings when expanded.
|
|
446
456
|
`equivalent` can also compare two predicates, since `Bool` supports equality.
|
|
447
|
-
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
457
|
+
The [Boolean flags example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/boolean_flags.lawspec)
|
|
448
458
|
checks that flipping twice restores both `false` and `true`; all targets generate
|
|
449
459
|
Boolean property inputs and explicit tests for both Boolean boundary values. Java caps Boolean-only JetCheck runs at the number of
|
|
450
460
|
possible input combinations (up to 100), avoiding generator exhaustion.
|
|
@@ -523,7 +533,7 @@ This expands to `for all (x :: Int32) . render (x) = referenceRender (x)`.
|
|
|
523
533
|
The example inherits the input name `x` from the prelude. Both functions are
|
|
524
534
|
user-owned adapter functions; either may delegate to your existing code.
|
|
525
535
|
|
|
526
|
-
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
536
|
+
[The complete example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/equivalent.lawspec) compares decimal
|
|
527
537
|
renderers and two implementations that clamp negative integers to zero. For
|
|
528
538
|
JavaScript, their adapters can be:
|
|
529
539
|
|
|
@@ -560,7 +570,7 @@ law `normalizers agree` is
|
|
|
560
570
|
end
|
|
561
571
|
```
|
|
562
572
|
|
|
563
|
-
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
573
|
+
The [slug example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/slug.lawspec)
|
|
564
574
|
compares two implementations of ASCII-space replacement. It includes empty,
|
|
565
575
|
Unicode and escaped text. Each target uses its native string generator:
|
|
566
576
|
JetCheck `Generator.stringsOf(Generator.asciiPrintableChars())`, Hypothesis `st.text()`, fast-check `fc.string()`,
|
|
@@ -587,7 +597,7 @@ law `canonicalization reaches a fixed point` is
|
|
|
587
597
|
end
|
|
588
598
|
```
|
|
589
599
|
|
|
590
|
-
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
600
|
+
The [canonical URL example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/canonical_url.lawspec)
|
|
591
601
|
uses removal of **all trailing slashes** as a small fixed-point demonstration,
|
|
592
602
|
not a complete URL canonicalization algorithm. For JavaScript:
|
|
593
603
|
|
|
@@ -596,7 +606,7 @@ export const canonicalize = value => value.replace(/\/+$/, "");
|
|
|
596
606
|
```
|
|
597
607
|
|
|
598
608
|
Removing just one trailing slash fails the supplied repeated-slash example.
|
|
599
|
-
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.
|
|
609
|
+
The [mixed-input example](https://github.com/brain-fuel/lawspec/blob/v0.13.0/examples/specs/mixed_inputs.lawspec)
|
|
600
610
|
shows `Text` and `Int32` in the same quantified property and executable example.
|
|
601
611
|
The JavaScript API represents example bindings as typed records whose values use
|
|
602
612
|
`DataValue`: lossless tagged scalar payloads or structural constructors. Expected results are
|
|
@@ -679,7 +689,7 @@ by the JS shim.
|
|
|
679
689
|
## Build and verify
|
|
680
690
|
|
|
681
691
|
For contributors working from a repository checkout, build a local archive with
|
|
682
|
-
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.
|
|
692
|
+
`npm pack ./npm` and install it with `npm install --save-dev ./lawspec-0.13.0.tgz`.
|
|
683
693
|
The package payload lives in `npm/`.
|
|
684
694
|
|
|
685
695
|
```sh
|
package/REFINEMENTS.md
CHANGED
package/RELEASE-0.13.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# LawSpec 0.13.0
|
|
2
|
+
|
|
3
|
+
## Wrappers and constrained primitives
|
|
4
|
+
|
|
5
|
+
```lawspec
|
|
6
|
+
wrapper UnitQuantity is Int32 where value >= 1 && value <= 1000 end
|
|
7
|
+
wrapper NonEmptyList (a :: Type) is List a where prelude.length value > 0 end
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
A wrapper declares a distinct nominal type with one field, `value`, and an
|
|
11
|
+
optional constraint. Its constructor checks the constraint, so an invalid value
|
|
12
|
+
cannot be constructed in an example (the compiler rejects `UnitQuantity 0`),
|
|
13
|
+
produced by a generator, or decoded from native code. `valueOf<Name>` unwraps a
|
|
14
|
+
value. Wrappers may take type parameters. They elaborate to a single-constructor
|
|
15
|
+
product with a refined field, so all eight targets represent them natively with
|
|
16
|
+
no new runtime machinery.
|
|
17
|
+
|
|
18
|
+
## Workflows and state distinctions
|
|
19
|
+
|
|
20
|
+
```lawspec
|
|
21
|
+
workflow placeOrder :: UnvalidatedOrder -> Either OrderError PricedOrder is
|
|
22
|
+
validateOrder :: UnvalidatedOrder -> Either OrderError ValidatedOrder
|
|
23
|
+
priceOrder :: ValidatedOrder -> Either OrderError PricedOrder
|
|
24
|
+
end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A workflow declares its steps as adapters and checks the pipeline: each step
|
|
28
|
+
must accept the previous step's state, and fallible steps must share an error
|
|
29
|
+
type. The workflow's result must match `Either E T` when any step can fail, or
|
|
30
|
+
`T` otherwise. The compiler adds the law `placeOrder composes its steps`: the
|
|
31
|
+
native workflow must equal the railway composition of the native steps.
|
|
32
|
+
Diagnostics name the step and the mismatched types, with the workflow's source
|
|
33
|
+
location.
|
|
34
|
+
|
|
35
|
+
## Evidence
|
|
36
|
+
|
|
37
|
+
Constructor field constraints, including wrapper constraints, appear in the
|
|
38
|
+
evidence as `construction` obligations with status `runtime-checked`, next to
|
|
39
|
+
the contract obligations introduced in 0.12. The TypeScript API declares these
|
|
40
|
+
records as `ObligationEvidence`; the 0.12 typing was not published.
|
|
41
|
+
|
|
42
|
+
## Examples and acceptance
|
|
43
|
+
|
|
44
|
+
`examples/specs/domain_modeling.lawspec` combines wrappers, a parameterized
|
|
45
|
+
`NonEmptyList`, order states and the workflow. `tools/domain-integration.mjs`
|
|
46
|
+
runs it on all eight targets in both machine profiles. Correct adapters pass;
|
|
47
|
+
mutants that break a wrapper invariant, the railway composition, or a non-empty
|
|
48
|
+
list operation fail. The example runners now share
|
|
49
|
+
`tools/example-acceptance.mjs`.
|
|
50
|
+
|
|
51
|
+
## Compatibility
|
|
52
|
+
|
|
53
|
+
`wrapper` and `workflow` begin new declarations. Specifications that do not use
|
|
54
|
+
them compile as in 0.12. API schemas 3 and 4 are unchanged, apart from the
|
|
55
|
+
additive `construction` stage in `evidence`.
|
package/bin/lawspec.mjs
CHANGED
|
@@ -173,13 +173,13 @@ async function main() {
|
|
|
173
173
|
}
|
|
174
174
|
if (!verb || ["help", "--help", "-h"].includes(verb)) {
|
|
175
175
|
output(
|
|
176
|
-
"LawSpec 0.
|
|
176
|
+
"LawSpec 0.13.0\nUsage: lawspec init --target <language> [--project <directory>] [--minify]\n lawspec check | doctor | explain <unit>::<law> | generate\n lawspec examples [--example payments] [--target <language>] [--output <directory>]\nOptions: --config <path>, --target <language>, --machine-bits <32|64>, --json\nGeneration: --dry-run, --check, --minify\nTargets: " +
|
|
177
177
|
targets.join(", "),
|
|
178
178
|
);
|
|
179
179
|
return;
|
|
180
180
|
}
|
|
181
181
|
if (verb === "--version") {
|
|
182
|
-
output("0.
|
|
182
|
+
output("0.13.0");
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
185
|
if (positional.length > (verb === "explain" ? 1 : 0))
|
package/build.json
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"src/LawSpec/Data.hs",
|
|
58
58
|
"src/LawSpec/DefinitionTotality.hs",
|
|
59
59
|
"src/LawSpec/Domain.hs",
|
|
60
|
+
"src/LawSpec/DomainModel.hs",
|
|
60
61
|
"src/LawSpec/Elaboration.hs",
|
|
61
62
|
"src/LawSpec/Emit.hs",
|
|
62
63
|
"src/LawSpec/Eval.hs",
|
|
@@ -128,8 +129,8 @@
|
|
|
128
129
|
"wasm/lawspec-wasm.cabal"
|
|
129
130
|
],
|
|
130
131
|
"digests": {
|
|
131
|
-
"lawspec.cabal": "
|
|
132
|
-
"package.yaml": "
|
|
132
|
+
"lawspec.cabal": "2aab726a6d4e9c64e218c0563d52fab738b0753f9597683b9d650dde1baa948c",
|
|
133
|
+
"package.yaml": "95807129285b33f8ac2bf7452aae1ee049f8f7711f0dc9f962fdf9586b58fcc8",
|
|
133
134
|
"runtime/LawSpecCodecs.hs": "90cb1ed15d7ab664a17a054580c849408d77cfd2b2adcb4a3228b05132caeaca",
|
|
134
135
|
"runtime/LawSpecDataStrategies.hs": "cbe9f1946228db1076c19cd3158e8798fe32766cfdbea3f66b63d22d3d217508",
|
|
135
136
|
"runtime/LawSpecDataStrategies.java": "e3416efe8a0d7b1276cb4d1a8105fd01b1f5f3f02a803bf55d955d9e3ec39095",
|
|
@@ -153,8 +154,8 @@
|
|
|
153
154
|
"runtime/lawspec_schema.mjs": "60eb4204461eaa2c207fc7c3394014e2a814e4383b7045c065b4dae7c7a202c2",
|
|
154
155
|
"runtime/lawspec_schema.py": "b6967ef1a33fe6a1a180d5573ec37cd8bb2079c06e961ac2eeb05f23cffcdd07",
|
|
155
156
|
"runtime/lawspec_strategies.rs": "cac9ca301d4614fff9acfe61e7cc5678fabc044edadfa89ec11599b6780dc5f3",
|
|
156
|
-
"runtime/rust/Cargo.lock": "
|
|
157
|
-
"runtime/rust/Cargo.toml": "
|
|
157
|
+
"runtime/rust/Cargo.lock": "deb6b268124278aa237f64c8e821caf2388ccda3df92c2e814a5863331ae8c92",
|
|
158
|
+
"runtime/rust/Cargo.toml": "ed36668f25943b9c8a3ea9eb5a805f2953540aabb763919029947d342cc23978",
|
|
158
159
|
"src/LawSpec/Api.hs": "392f6e8c1a9ae81a95e219ac071e5b4bea03ee330d63b829036f8f54b2eda8a7",
|
|
159
160
|
"src/LawSpec/Backend.hs": "03c3b0cd62467ca677c88d29d7b12b2ab505cc0076d93d1f48fbddb0e9bcf133",
|
|
160
161
|
"src/LawSpec/Capabilities.hs": "e43d4fc3a4481fbb8aa3ff1c3f41f7e7fca5db01bfe256a30162a45d75a00c1c",
|
|
@@ -166,7 +167,7 @@
|
|
|
166
167
|
"src/LawSpec/Core/DefinitionContracts.hs": "b7f60f038b231187ecf898e585ae1a8fb1fc113bb55bad6d1bb2660350fd969e",
|
|
167
168
|
"src/LawSpec/Core/Definitions.hs": "52f024fca59661142f70f9aa7dd2cbdb4892e5d3b1ca8079c878c5a1ba667112",
|
|
168
169
|
"src/LawSpec/Core/Eval.hs": "e1ad8af5b240dd99c3b1126408d2be5f9b42b86ca6189ecd3868680a5119d931",
|
|
169
|
-
"src/LawSpec/Core/Evidence.hs": "
|
|
170
|
+
"src/LawSpec/Core/Evidence.hs": "460af282299258c87ddba58b55ad09290d7d89e6e7d43a02f5470fbd5b7974a3",
|
|
170
171
|
"src/LawSpec/Core/Expression.hs": "84985f4f685126253d3af7b6b605d80d28fc9aa115bd5e2946f989eeb1355224",
|
|
171
172
|
"src/LawSpec/Core/Payload.hs": "df6a2b250c27c12bb6ac16b84847c52546081d066a3dca7575ed0d743cc13d50",
|
|
172
173
|
"src/LawSpec/Core/PayloadPlan.hs": "f2a7d2dc58c6a0f79707bdc24773bd3aad3a3b8eea2735a8284b2a9f3a4af4a8",
|
|
@@ -184,6 +185,7 @@
|
|
|
184
185
|
"src/LawSpec/Data.hs": "9dbeafa120276b114dad790ec69e617f7ad0c632e3c718962f2f7faf8ca327f4",
|
|
185
186
|
"src/LawSpec/DefinitionTotality.hs": "7671b7efb0cac5d796b074d48b2c10940e5bd0b5df082b926aee694903285efa",
|
|
186
187
|
"src/LawSpec/Domain.hs": "222d15f0012ae630802238a6d7e1ee78d1e718b599f5ebb5a1ddc4e8d29d68c3",
|
|
188
|
+
"src/LawSpec/DomainModel.hs": "3be67117c44e66a39d42d7ce094c2940c48ea1541017b64027afa6ea4b77092f",
|
|
187
189
|
"src/LawSpec/Elaboration.hs": "86e3ef77a977cb1b55985e2939648135acd0c2c015afe76e34e09615d555f53e",
|
|
188
190
|
"src/LawSpec/Emit.hs": "9f34f5765915a2a12a66a6dca647ddaef9289372956802c6d99cf0cfec447c7a",
|
|
189
191
|
"src/LawSpec/Eval.hs": "b0872ba8a6974463b32776653c2939826a6be87318c15641401f913f6b139de2",
|
|
@@ -220,11 +222,11 @@
|
|
|
220
222
|
"src/LawSpec/Model.hs": "1d7ceb7bd66e3d496eef89eaa86403e6896472317ca9a4be86e0c933a7cce403",
|
|
221
223
|
"src/LawSpec/NativeBinding.hs": "33959b0fd432ad4150f3b685f6438e284b801c2a59d9ef3132486a1cc7c8925a",
|
|
222
224
|
"src/LawSpec/NativeRequest.hs": "fb2dc88f42424ca520533700eab68c407a5de83807d1b0d8c976e6c4f3d65212",
|
|
223
|
-
"src/LawSpec/Parser.hs": "
|
|
225
|
+
"src/LawSpec/Parser.hs": "94a5603f6831d1dad5634a766bbc02cc58d884d23b5d6606ed4a1537cfe4c813",
|
|
224
226
|
"src/LawSpec/PortableGenerator.hs": "69d457e39f539c77fc7a4c08205e75352bbecd9dfdff031a283a39bf44b22d49",
|
|
225
227
|
"src/LawSpec/PortableTestHelpers.hs": "8ba4d87183c8c11f6ef1ce21c170127335797154f29fb97ad3429f032a167fd3",
|
|
226
228
|
"src/LawSpec/Prelude.hs": "b1e155c12f58e7c346551a0c65c5d5af76d045b3f1625977ca2a5d1c97cb8c46",
|
|
227
|
-
"src/LawSpec/Public.hs": "
|
|
229
|
+
"src/LawSpec/Public.hs": "ca7f3edd3e5d623ecb1aecb2386fdcf4b1e334cf7bd7770b6a14f5690d8f5ed5",
|
|
228
230
|
"src/LawSpec/PythonData.hs": "07e9bb54a1515f3f4744b7b9bb43633cbdbf07b3f9e35efef511753f97e216d5",
|
|
229
231
|
"src/LawSpec/PythonDefinitions.hs": "30f3d2d71a378e194d62261e0fd1baade5a49ac8b5eaac97578ca015a48ce8e4",
|
|
230
232
|
"src/LawSpec/PythonExpr.hs": "23d7d24b5fb4efff9cb63d38c02a93d95744ec79b4565daf1412088fc7c4ba59",
|
|
@@ -252,8 +254,8 @@
|
|
|
252
254
|
"wasm/app/Exports.hs": "4ecbdac8faa2449e6fc61b93c82e6278fa14374154f29b4dacf0eef433f06a47",
|
|
253
255
|
"wasm/cabal.project": "021e560afdc5eb4cb7169e7119ecb8f92c9ee170245af909c94b612941bff5cc",
|
|
254
256
|
"wasm/cabal.project.freeze": "733dbed3d2ecccb26e874fd58f136296dad772184deb1658c196b5d54a0814dc",
|
|
255
|
-
"wasm/lawspec-wasm.cabal": "
|
|
256
|
-
"npm/core.wasm": "
|
|
257
|
+
"wasm/lawspec-wasm.cabal": "ee8235aa57e891fe5b1f0d798694f5db5bc6e83b4d013fd1a96e9b2d28aae613",
|
|
258
|
+
"npm/core.wasm": "f67a99e2de93750f0d4930cc086688a74ee1d309c3818bfe3125490e01b08660",
|
|
257
259
|
"npm/core_jsffi.js": "88d136efe92f7cff5758c8fec8d9b6bbc9707fe37741cebeeb415fe34ce3d72b",
|
|
258
260
|
"npm/api.mjs": "e93251db289ff0609abaff78ad67f0a9af82508c1141c6a999604f67c56b6e7d",
|
|
259
261
|
"npm/index.d.ts": "2674f8830bd20a2b9ef7dfa99e183598125b6bcc17e8d454e8f7cca1bbd2c9fe"
|
package/core.wasm
CHANGED
|
Binary file
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
unit example.ordering
|
|
2
|
+
|
|
3
|
+
-- Constrained primitives. Each wrapper is a distinct type whose constructor
|
|
4
|
+
-- checks the value, so an out-of-range quantity cannot be constructed,
|
|
5
|
+
-- generated or decoded from native code. valueOf<Name> unwraps a value.
|
|
6
|
+
wrapper UnitQuantity is Int32 where value >= 1 && value <= 1000 end
|
|
7
|
+
wrapper OrderId is Text where prelude.length value > 0 end
|
|
8
|
+
wrapper NonEmptyList (a :: Type) is List a where prelude.length value > 0 end
|
|
9
|
+
|
|
10
|
+
-- Each workflow state is its own type. A ValidatedOrder can only hold checked
|
|
11
|
+
-- values, so no later step needs to validate again.
|
|
12
|
+
type UnvalidatedOrder is UnvalidatedOrder id :: Text quantity :: Int32 end
|
|
13
|
+
type ValidatedOrder is ValidatedOrder id :: OrderId quantity :: UnitQuantity end
|
|
14
|
+
type PricedOrder is PricedOrder id :: OrderId quantity :: UnitQuantity total :: Int64 end
|
|
15
|
+
type OrderError is | InvalidOrderId | InvalidQuantity | PriceTooHigh end
|
|
16
|
+
|
|
17
|
+
-- The compiler checks that each step accepts the previous step's state and that
|
|
18
|
+
-- all failures share one error type. It adds a law that placeOrder is the
|
|
19
|
+
-- railway composition of the steps: the first Left is the workflow's result.
|
|
20
|
+
workflow placeOrder :: UnvalidatedOrder -> Either OrderError PricedOrder is
|
|
21
|
+
validateOrder :: UnvalidatedOrder -> Either OrderError ValidatedOrder
|
|
22
|
+
priceOrder :: ValidatedOrder -> Either OrderError PricedOrder
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
firstLine :: NonEmptyList Int32 -> Int32
|
|
26
|
+
|
|
27
|
+
law `validation accepts exactly the valid orders` is
|
|
28
|
+
definition is
|
|
29
|
+
`for all` (input :: UnvalidatedOrder) .
|
|
30
|
+
(match validateOrder input with
|
|
31
|
+
| Left e -> false
|
|
32
|
+
| Right v -> true
|
|
33
|
+
end) = (match input with
|
|
34
|
+
| UnvalidatedOrder id quantity ->
|
|
35
|
+
prelude.length id > 0 && quantity >= 1 && quantity <= 1000
|
|
36
|
+
end)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
law `quantities are always in range` is
|
|
41
|
+
definition is
|
|
42
|
+
`for all` (q :: UnitQuantity) .
|
|
43
|
+
valueOfUnitQuantity q >= 1 && valueOfUnitQuantity q <= 1000
|
|
44
|
+
end
|
|
45
|
+
example `largest quantity` is
|
|
46
|
+
q = UnitQuantity 1000
|
|
47
|
+
expect valueOfUnitQuantity q = 1000
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
law `a non-empty list has a first element` is
|
|
52
|
+
definition is
|
|
53
|
+
`for all` (xs :: NonEmptyList Int32) .
|
|
54
|
+
(match valueOfNonEmptyList xs with
|
|
55
|
+
| Nil -> false
|
|
56
|
+
| Cons head tail -> firstLine xs == head
|
|
57
|
+
end) = true
|
|
58
|
+
end
|
|
59
|
+
end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lawspec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "State the law once. Check it everywhere.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,6 @@
|
|
|
8
8
|
"bin": { "lawspec": "bin/lawspec.mjs" },
|
|
9
9
|
"exports": { ".": { "types": "./index.d.ts", "import": "./api.mjs" } },
|
|
10
10
|
"types": "index.d.ts",
|
|
11
|
-
"files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "NATIVE-BINDINGS.md", "PRIMITIVES.md", "REFINEMENTS.md", "RUST.md", "JAVA.md", "PYTHON.md", "GO.md", "HASKELL.md", "KOTLIN.md", "WEB.md", "LANGUAGE.md", "API-MIGRATION.md", "RELEASE-0.9.md", "RELEASE-0.10.md", "RELEASE-0.11.md", "RELEASE-0.12.md", "LICENSE", "starter.lawspec", "examples"],
|
|
11
|
+
"files": ["*.mjs", "*.json", "index.d.ts", "bin", "core.wasm", "core_jsffi.js", "README.md", "NATIVE-BINDINGS.md", "PRIMITIVES.md", "REFINEMENTS.md", "RUST.md", "JAVA.md", "PYTHON.md", "GO.md", "HASKELL.md", "KOTLIN.md", "WEB.md", "LANGUAGE.md", "API-MIGRATION.md", "RELEASE-0.9.md", "RELEASE-0.10.md", "RELEASE-0.11.md", "RELEASE-0.12.md", "RELEASE-0.13.md", "LICENSE", "starter.lawspec", "examples"],
|
|
12
12
|
"scripts": { "test": "node --test test/*.test.mjs" }
|
|
13
13
|
}
|