lawspec 0.8.0 → 0.10.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.
Files changed (61) hide show
  1. package/API-MIGRATION.md +180 -3
  2. package/GO.md +144 -0
  3. package/HASKELL.md +171 -0
  4. package/JAVA.md +113 -0
  5. package/KOTLIN.md +214 -0
  6. package/LANGUAGE.md +240 -12
  7. package/NATIVE-BINDINGS.md +1046 -0
  8. package/PRIMITIVES.md +1 -1
  9. package/PYTHON.md +152 -0
  10. package/README.md +86 -23
  11. package/REFINEMENTS.md +289 -3
  12. package/RELEASE-0.10.md +60 -0
  13. package/RELEASE-0.9.md +61 -0
  14. package/RUST.md +115 -1
  15. package/WEB.md +144 -0
  16. package/api.mjs +28 -2
  17. package/bin/lawspec.mjs +23 -7
  18. package/build.json +199 -37
  19. package/core.wasm +0 -0
  20. package/examples/native-payments/go/example/payments/domain.go +38 -0
  21. package/examples/native-payments/go/example/payments/native_generators_test.go +13 -0
  22. package/examples/native-payments/go/lawspec.json +136 -0
  23. package/examples/native-payments/haskell/lawspec.json +149 -0
  24. package/examples/native-payments/haskell/src/PaymentsDomain.hs +21 -0
  25. package/examples/native-payments/haskell/test/PaymentGenerators.hs +12 -0
  26. package/examples/native-payments/java/lawspec.json +165 -0
  27. package/examples/native-payments/java/src/main/java/domain/PaymentsDomain.java +32 -0
  28. package/examples/native-payments/java/src/test/java/domain/PaymentGenerators.java +16 -0
  29. package/examples/native-payments/javascript/lawspec.json +149 -0
  30. package/examples/native-payments/javascript/src/payments_domain.mjs +44 -0
  31. package/examples/native-payments/javascript/test/lawspec_generators.mjs +6 -0
  32. package/examples/native-payments/kotlin/lawspec.json +165 -0
  33. package/examples/native-payments/kotlin/src/main/kotlin/domain/PaymentsDomain.kt +17 -0
  34. package/examples/native-payments/kotlin/src/test/kotlin/domain/PaymentGenerators.kt +12 -0
  35. package/examples/native-payments/python/lawspec.json +149 -0
  36. package/examples/native-payments/python/src/payments_domain.py +60 -0
  37. package/examples/native-payments/python/tests/lawspec_generators.py +15 -0
  38. package/examples/native-payments/rust/lawspec.json +167 -0
  39. package/examples/native-payments/rust/src/domain.rs +37 -0
  40. package/examples/native-payments/rust/src/lib.rs +2 -0
  41. package/examples/native-payments/rust/tests/support/lawspec_generators.rs +11 -0
  42. package/examples/native-payments/typescript/lawspec.json +149 -0
  43. package/examples/native-payments/typescript/src/payments_domain.ts +42 -0
  44. package/examples/native-payments/typescript/test/lawspec_generators.ts +8 -0
  45. package/examples/specs/collections.lawspec +67 -0
  46. package/examples/specs/data_types.lawspec +66 -0
  47. package/examples/specs/finite_data.lawspec +37 -0
  48. package/examples/specs/list_contracts.lawspec +136 -0
  49. package/examples/specs/list_refinements.lawspec +51 -0
  50. package/examples/specs/matching.lawspec +49 -0
  51. package/examples/specs/payments.lawspec +76 -0
  52. package/examples/specs/recursive_refinements.lawspec +41 -0
  53. package/examples/specs/refined_definitions.lawspec +44 -0
  54. package/examples/specs/sum_refinements.lawspec +49 -0
  55. package/examples/specs/total_functions.lawspec +101 -0
  56. package/examples-command.mjs +11 -1
  57. package/files.mjs +16 -7
  58. package/index.d.ts +297 -27
  59. package/native-examples.mjs +81 -0
  60. package/package.json +2 -2
  61. package/templates.mjs +107 -17
@@ -0,0 +1,149 @@
1
+ {
2
+ "version": 1,
3
+ "sources": [
4
+ "laws"
5
+ ],
6
+ "machineBits": 64,
7
+ "targets": [
8
+ {
9
+ "language": "typescript",
10
+ "root": ".",
11
+ "nativeBindings": {
12
+ "types": [
13
+ {
14
+ "type": "example.payments::type::Currency",
15
+ "native": [
16
+ "payments_domain",
17
+ "CurrencyCode"
18
+ ],
19
+ "constructors": [
20
+ {
21
+ "constructor": "USD",
22
+ "native": [
23
+ "payments_domain",
24
+ "Dollars"
25
+ ],
26
+ "style": "unit",
27
+ "fields": []
28
+ },
29
+ {
30
+ "constructor": "EUR",
31
+ "native": [
32
+ "payments_domain",
33
+ "Euros"
34
+ ],
35
+ "style": "unit",
36
+ "fields": []
37
+ },
38
+ {
39
+ "constructor": "GBP",
40
+ "native": [
41
+ "payments_domain",
42
+ "Pounds"
43
+ ],
44
+ "style": "unit",
45
+ "fields": []
46
+ }
47
+ ]
48
+ },
49
+ {
50
+ "type": "example.payments::type::Money",
51
+ "native": [
52
+ "payments_domain",
53
+ "Price"
54
+ ],
55
+ "constructors": [
56
+ {
57
+ "constructor": "Money",
58
+ "native": [
59
+ "payments_domain",
60
+ "Price"
61
+ ],
62
+ "style": "record",
63
+ "fields": [
64
+ {
65
+ "field": "amount",
66
+ "native": "major"
67
+ },
68
+ {
69
+ "field": "currency",
70
+ "native": "unit"
71
+ }
72
+ ]
73
+ }
74
+ ]
75
+ },
76
+ {
77
+ "type": "example.payments::type::Payment",
78
+ "native": [
79
+ "payments_domain",
80
+ "PaymentStatus"
81
+ ],
82
+ "constructors": [
83
+ {
84
+ "constructor": "Paid",
85
+ "native": [
86
+ "payments_domain",
87
+ "Settled"
88
+ ],
89
+ "style": "variant",
90
+ "fields": [
91
+ {
92
+ "field": "value",
93
+ "native": "price"
94
+ }
95
+ ]
96
+ },
97
+ {
98
+ "constructor": "Declined",
99
+ "native": [
100
+ "payments_domain",
101
+ "Rejected"
102
+ ],
103
+ "style": "variant",
104
+ "fields": [
105
+ {
106
+ "field": "reason",
107
+ "native": "explanation"
108
+ }
109
+ ]
110
+ }
111
+ ]
112
+ }
113
+ ],
114
+ "functions": [
115
+ {
116
+ "declaration": "example.payments::addFee",
117
+ "native": [
118
+ "payments_domain",
119
+ "apply_fee"
120
+ ]
121
+ },
122
+ {
123
+ "declaration": "example.payments::roundTrip",
124
+ "native": [
125
+ "payments_domain",
126
+ "restore"
127
+ ]
128
+ },
129
+ {
130
+ "declaration": "example.payments::archive",
131
+ "native": [
132
+ "payments_domain",
133
+ "store"
134
+ ]
135
+ }
136
+ ],
137
+ "generators": [
138
+ {
139
+ "type": "example.payments::type::Money",
140
+ "factory": [
141
+ "lawspec_generators",
142
+ "prices"
143
+ ]
144
+ }
145
+ ]
146
+ }
147
+ }
148
+ ]
149
+ }
@@ -0,0 +1,42 @@
1
+ // Application-owned domain types; no generated domain declarations.
2
+ import * as ls from './lawspec_runtime.js';
3
+
4
+ export class CurrencyCode {}
5
+ export class Dollars extends CurrencyCode {}
6
+ export class Euros extends CurrencyCode {}
7
+ export class Pounds extends CurrencyCode {}
8
+
9
+ export class Price {
10
+ major: ls.Decimal;
11
+ unit: CurrencyCode;
12
+ constructor({unit, major}: {unit: CurrencyCode; major: ls.Decimal}) {
13
+ this.unit = unit;
14
+ this.major = major;
15
+ }
16
+ }
17
+ export class PaymentStatus {}
18
+ export class Settled extends PaymentStatus {
19
+ price: Price;
20
+ constructor({price}: {price: Price}) {
21
+ super();
22
+ this.price = price;
23
+ }
24
+ }
25
+ export class Rejected extends PaymentStatus {
26
+ explanation: string;
27
+ constructor({explanation}: {explanation: string}) {
28
+ super();
29
+ this.explanation = explanation;
30
+ }
31
+ }
32
+ export function apply_fee(price: Price): Price {
33
+ const major = ls.binary('+', price.major, new ls.Decimal(2n, -1n),
34
+ 'Decimal', 'Decimal') as ls.Decimal;
35
+ return new Price({major, unit: price.unit});
36
+ }
37
+ export function restore<T>(payment: T): T {
38
+ return payment;
39
+ }
40
+ export function store<T>(payments: T[]): T[] {
41
+ return payments;
42
+ }
@@ -0,0 +1,8 @@
1
+ import * as fc from 'fast-check';
2
+ import * as ls from '../src/lawspec_runtime.js';
3
+ import {Price, Euros} from '../src/payments_domain.js';
4
+
5
+ export function prices(): fc.Arbitrary<Price> {
6
+ return fc.integer({min:100, max:200}).map(cents =>
7
+ new Price({major:new ls.Decimal(BigInt(cents), -2n), unit:new Euros()}));
8
+ }
@@ -0,0 +1,67 @@
1
+ unit example.collections
2
+
3
+ reverse :: List Int32 -> List Int32
4
+ sort :: List Int32 -> List Int32
5
+ sorted :: List Int32 -> Bool
6
+ permutation :: List Int32 -> List Int32 -> Bool
7
+ echoMaybe :: Maybe (Maybe Bool) -> Maybe (Maybe Bool)
8
+ echoEither :: Either (List Int32) (Maybe Bool) -> Either (List Int32) (Maybe Bool)
9
+ echoNested :: List (Maybe (Either CodeUnit16 Bytes)) -> List (Maybe (Either CodeUnit16 Bytes))
10
+
11
+ law `list involution` (f :: List a -> List a) requires Eq a is
12
+ definition is `involution` f end
13
+ end
14
+
15
+ law `reverse twice restores the list` is
16
+ definition is `list involution` reverse end
17
+ example `ordered input` is
18
+ x = [3, 1, 2]
19
+ expect reverse x = [2, 1, 3]
20
+ end
21
+ example `empty input` is x = [] expect reverse x = [] end
22
+ end
23
+
24
+ law `sorting is idempotent` is
25
+ definition is `idempotent` sort end
26
+ example `duplicates` is x = [3, 1, 3] expect sort x = [1, 3, 3] end
27
+ end
28
+
29
+ law `sort produces sorted output` is
30
+ definition is `for all` (xs :: List Int32) . sorted (sort xs) end
31
+ end
32
+
33
+ law `sort preserves length` is
34
+ definition is `for all` (xs :: List Int32) . prelude.length (sort xs) = prelude.length xs end
35
+ end
36
+
37
+ law `sort preserves elements` is
38
+ definition is `for all` (xs :: List Int32) . permutation (sort xs) xs end
39
+ end
40
+
41
+ law `nonempty reversal preserves length` is
42
+ definition is
43
+ `for all` (xs :: List Int32 where prelude.length xs > 0) .
44
+ prelude.length (reverse xs) = prelude.length xs
45
+ end
46
+ end
47
+
48
+ law `Maybe preserves nested absence` is
49
+ definition is `for all` (x :: Maybe (Maybe Bool)) . echoMaybe x = x end
50
+ example `outer absence` is x = Nothing expect echoMaybe x = Nothing end
51
+ example `inner absence` is x = Just Nothing expect echoMaybe x = Just Nothing end
52
+ example `present` is x = Just (Just true) expect echoMaybe x = Just (Just true) end
53
+ end
54
+
55
+ law `Either preserves branches` is
56
+ definition is `for all` (x :: Either (List Int32) (Maybe Bool)) . echoEither x = x end
57
+ example `left list` is x = Left [3, 1, 3] expect echoEither x = Left [3, 1, 3] end
58
+ example `right absence` is x = Right Nothing expect echoEither x = Right Nothing end
59
+ end
60
+
61
+ law `nested raw values survive` is
62
+ definition is `for all` (xs :: List (Maybe (Either CodeUnit16 Bytes))) . echoNested xs = xs end
63
+ example `surrogate and octets` is
64
+ xs = [Nothing, Just (Left codeUnit16(55296)), Just (Right bytes([0, 255]))]
65
+ expect echoNested xs = [Nothing, Just (Left codeUnit16(55296)), Just (Right bytes([0, 255]))]
66
+ end
67
+ end
@@ -0,0 +1,66 @@
1
+ unit example.data_types
2
+
3
+ type Pair (a :: Type) (b :: Type) is
4
+ Pair
5
+ first :: a
6
+ second :: b
7
+ end
8
+
9
+ type Tree (a :: Type) is
10
+ Leaf value :: a
11
+ Branch children :: List (Tree a)
12
+ end
13
+
14
+ law `products preserve every field` is
15
+ definition is
16
+ `for all` (pair :: Pair Int8 Bool) .
17
+ (match pair with
18
+ | Pair first second -> Pair first second
19
+ end) = pair
20
+ end
21
+ example `maximum integer and false` is
22
+ pair = Pair 127 false
23
+ expect pair = Pair 127 false
24
+ end
25
+ end
26
+
27
+ law `sums preserve constructor identity and payloads` is
28
+ definition is
29
+ `for all` (tree :: Tree Int8) .
30
+ (match tree with
31
+ | Leaf value -> Leaf value
32
+ | Branch children -> Branch children
33
+ end) = tree
34
+ end
35
+ example `nested branches` is
36
+ tree = Branch [Leaf 127, Branch [], Leaf -128]
37
+ expect tree = Branch [Leaf 127, Branch [], Leaf -128]
38
+ end
39
+ end
40
+
41
+
42
+ -- A refined type argument constrains the corresponding stored fields. This
43
+ -- predicate becomes a scoped Core traversal, shared by all eight backends.
44
+ refinement Positive is (value :: Int8 where value > 0) end
45
+
46
+ definition positivePair (value :: Positive) :: Pair Positive Bool is
47
+ Pair value true
48
+ end
49
+
50
+ definition reciprocalFirst (pair :: Pair Positive Bool) :: Rational is
51
+ match pair with
52
+ | Pair first second -> 1 / first
53
+ end
54
+ end
55
+
56
+ law `named payload contracts justify exact division` is
57
+ definition is
58
+ `for all` (value :: Positive) .
59
+ reciprocalFirst (positivePair value) = 1 / value
60
+ end
61
+ example `one half` is
62
+ value = 2
63
+ expect positivePair value = Pair 2 true
64
+ expect reciprocalFirst (positivePair value) = rational(1, 2)
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ unit example.finite_data
2
+
3
+ type Choice is Yes | No end
4
+
5
+ type Pair (a :: Type) (b :: Type) is
6
+ Pair first :: a second :: b
7
+ end
8
+
9
+ law `sum constructors remain distinct` is
10
+ definition is
11
+ `for all` (choice :: Choice) .
12
+ (match choice with
13
+ | Yes -> Yes
14
+ | No -> No
15
+ end) = choice
16
+ end
17
+ example `yes` is choice = Yes expect choice = Yes end
18
+ end
19
+
20
+ law `products preserve both fields` is
21
+ definition is
22
+ `for all` (pair :: Pair Bool Bool) .
23
+ (match pair with
24
+ | Pair first second -> Pair first second
25
+ end) = pair
26
+ end
27
+ example `different fields` is
28
+ pair = Pair true false
29
+ expect pair = Pair true false
30
+ end
31
+ end
32
+
33
+ law `nested optional products keep presence states` is
34
+ definition is
35
+ `for all` (value :: Maybe (Pair Choice (Nullable Bool))) . value = value
36
+ end
37
+ end
@@ -0,0 +1,136 @@
1
+ unit example.list_contracts
2
+
3
+ definition keep (xs :: List (value :: Int8 where value > 0))
4
+ :: List (value :: Int8 where value > 0)
5
+ is xs end
6
+
7
+ definition stronger (xs :: List (value :: Int8 where value > 10)) :: List Int8 is
8
+ keep (copypositive xs)
9
+ end
10
+
11
+ definition positive (value :: a) :: Bool requires Integer a is value > 0 end
12
+
13
+ definition keephelper (xs :: List (value :: a where positive value))
14
+ :: List a requires Integer a
15
+ is xs end
16
+
17
+ definition reuse (xs :: List (value :: Int8 where positive value)) :: List Int8 is
18
+ keephelper xs
19
+ end
20
+
21
+ definition empty (value :: Int8) :: List (element :: Int8 where false) is [] end
22
+
23
+ definition singleton (value :: Int8 where value > 0)
24
+ :: List (element :: Int8 where element > 0)
25
+ is [value] end
26
+
27
+
28
+ law `stronger domains satisfy the callee` is
29
+ definition is `for all` (xs :: List (value :: Int8 where value > 10)) .
30
+ stronger xs = xs
31
+ end
32
+ example `positive input` is xs = [11, 127] expect stronger xs = [11, 127] end
33
+ end
34
+
35
+ law `generic predicates keep their scope` is
36
+ definition is `for all` (xs :: List (value :: Int8 where value > 0)) .
37
+ reuse xs = xs
38
+ end
39
+ example `empty` is xs = [] expect reuse xs = [] end
40
+ example `nonempty` is xs = [1, 2] expect reuse xs = [1, 2] end
41
+ end
42
+
43
+ law `empty output satisfies an impossible element domain` is
44
+ definition is `for all` (value :: Int8) . empty value = [] end
45
+ example `zero` is value = 0 expect empty value = [] end
46
+ end
47
+
48
+ law `constructed output satisfies its contract` is
49
+ definition is `for all` (value :: Int8 where value > 0) . singleton value = [value] end
50
+ example `maximum` is value = 127 expect singleton value = [127] end
51
+ end
52
+
53
+
54
+ definition sumreciprocal (xs :: List (value :: Int8 where value != 0)) :: Rational is
55
+ match xs with
56
+ | Nil -> 0
57
+ | Cons first rest -> unwrapnonzero (wrapnonzero (nonzero first)) + sumreciprocal rest
58
+ end
59
+ end
60
+
61
+ definition sumrows (rows :: List (List (value :: Int8 where value != 0))) :: Rational is
62
+ match rows with
63
+ | Nil -> 0
64
+ | Cons first rest -> sumreciprocal first + sumrows rest
65
+ end
66
+ end
67
+
68
+ definition positivetail (xs :: List (value :: Int8 where value > 0))
69
+ :: List (value :: Int8 where value > 0)
70
+ is
71
+ match xs with | Nil -> [] | Cons first rest -> rest end
72
+ end
73
+
74
+ definition positivefirst (xs :: List (value :: Int8 where value > 0))
75
+ :: (result :: Int8 where result > 0)
76
+ is
77
+ match xs with | Nil -> 1 | Cons first rest -> first end
78
+ end
79
+
80
+
81
+ law `reciprocal sum uses each nonzero head` is
82
+ definition is `for all` (value :: Int8 where value != 0) .
83
+ sumreciprocal [value] = 1 / value
84
+ end
85
+ example `half` is value = 2 expect sumreciprocal [value] = rational(1, 2) end
86
+ end
87
+
88
+ law `nested membership keeps row contracts` is
89
+ definition is `for all` (value :: Int8 where value != 0) .
90
+ sumrows [[], [value]] = 1 / value
91
+ end
92
+ example `negative half` is value = -2 expect sumrows [[], [value]] = rational(-1, 2) end
93
+ end
94
+
95
+ law `tail preserves its positive elements` is
96
+ definition is `for all` (first :: Int8 where first > 0)
97
+ (second :: Int8 where second > 0) . positivetail [first, second] = [second]
98
+ end
99
+ example `two values` is first = 1 second = 2 expect positivetail [first, second] = [2] end
100
+ end
101
+
102
+ -- Recursive calls supply their verified result contract by structural induction.
103
+ definition copypositive (xs :: List (value :: Int8 where value > 0))
104
+ :: List (value :: Int8 where value > 0)
105
+ is
106
+ match xs with
107
+ | Nil -> []
108
+ | Cons first rest -> Cons first (copypositive rest)
109
+ end
110
+ end
111
+
112
+ definition nonzero (value :: Int8 where value != 0)
113
+ :: (result :: Int8 where result != 0)
114
+ is value end
115
+
116
+ definition wrapnonzero (value :: Int8 where value != 0)
117
+ :: Maybe (result :: Int8 where result != 0)
118
+ is Just value end
119
+
120
+ definition unwrapnonzero (value :: Maybe (element :: Int8 where element != 0))
121
+ :: Rational
122
+ is
123
+ match value with
124
+ | Nothing -> 0
125
+ | Just element -> selectnonzero (Left element)
126
+ end
127
+ end
128
+
129
+ definition selectnonzero (value :: Either (element :: Int8 where element != 0) Bool)
130
+ :: Rational
131
+ is
132
+ match value with
133
+ | Left element -> 1 / element
134
+ | Right ignored -> 0
135
+ end
136
+ end
@@ -0,0 +1,51 @@
1
+ unit example.list_refinements
2
+
3
+ definition above (floor :: Int8) (xs :: List Int8) :: Bool is
4
+ match xs with
5
+ | Nil -> true
6
+ | Cons first rest -> first > floor && above floor rest
7
+ end
8
+ end
9
+
10
+ definition positiveRows (rows :: List (List Int8)) :: Bool is
11
+ match rows with
12
+ | Nil -> true
13
+ | Cons first rest -> above 0 first && positiveRows rest
14
+ end
15
+ end
16
+
17
+ law `each element is positive` is
18
+ definition is `for all` (xs :: List (value :: Int8 where value > 0)) .
19
+ above 0 xs = true
20
+ end
21
+ example `empty` is xs = [] expect above 0 xs = true end
22
+ example `positive values` is xs = [1, 127] expect above 0 xs = true end
23
+ end
24
+
25
+ law `nested element domains` is
26
+ definition is `for all` (rows :: List (List (value :: Int8 where value > 0))) .
27
+ positiveRows rows = true
28
+ end
29
+ example `empty rows` is rows = [[], [1, 2]] expect positiveRows rows = true end
30
+ end
31
+
32
+ law `dependent elements` is
33
+ definition is `for all` (lawspecElement :: Int8)
34
+ (xs :: List (value :: Int8 where value > lawspecElement)) .
35
+ above lawspecElement xs = true
36
+ end
37
+ example `maximum prefix still permits empty` is
38
+ lawspecElement = 127 xs = [] expect above lawspecElement xs = true
39
+ end
40
+ example `nonempty continuation` is
41
+ lawspecElement = 0 xs = [1, 2] expect above lawspecElement xs = true
42
+ end
43
+ end
44
+
45
+ law `division stays behind its guard` is
46
+ definition is `for all`
47
+ (xs :: List (value :: Int8 where value != 0 && 1 / value > 0)) .
48
+ above 0 xs = true
49
+ end
50
+ example `positive denominator` is xs = [1] expect above 0 xs = true end
51
+ end
@@ -0,0 +1,49 @@
1
+ unit example.matching
2
+
3
+ -- Rebuilding values checks both constructor identity and every payload field.
4
+ law `list patterns preserve the head and tail` is
5
+ definition is
6
+ `for all` (xs :: List Int8) .
7
+ (match xs with
8
+ | Nil -> []
9
+ | Cons head tail -> Cons head tail
10
+ end) = xs
11
+ end
12
+ end
13
+
14
+ law `nested Maybe patterns preserve each absence state` is
15
+ definition is
16
+ `for all` (value :: Maybe (Maybe Bool)) .
17
+ (match value with
18
+ | Nothing -> Nothing
19
+ | Just inner -> Just (match inner with
20
+ | Nothing -> Nothing
21
+ | Just payload -> Just payload
22
+ end)
23
+ end) = value
24
+ end
25
+ end
26
+
27
+ law `Either patterns preserve their constructor` is
28
+ definition is
29
+ `for all` (value :: Either (List Int8) (Maybe Bool)) .
30
+ (match value with
31
+ | Left items -> Left items
32
+ | Right optional -> Right optional
33
+ end) = value
34
+ end
35
+ end
36
+
37
+ -- The annotation gives the empty-case literal the same primitive type as head.
38
+ law `matching can provide a default value` is
39
+ definition is
40
+ `for all` (value :: Maybe Int8) .
41
+ ((match value with
42
+ | Nothing -> 0
43
+ | Just payload -> payload
44
+ end) :: Int8) = ((match value with
45
+ | Just payload -> payload
46
+ | Nothing -> 0
47
+ end) :: Int8)
48
+ end
49
+ end
@@ -0,0 +1,76 @@
1
+ unit example.payments
2
+
3
+ -- The executable acceptance model for native domain bindings in 0.10.
4
+ -- Applications may call Money "Price", amount "major", and currency "unit";
5
+ -- those representation choices must not alter these laws.
6
+ type Currency is USD | EUR | GBP end
7
+
8
+ type Money is
9
+ Money amount :: Decimal currency :: Currency
10
+ end
11
+
12
+ type Payment is
13
+ Paid value :: Money
14
+ Declined reason :: Text
15
+ end
16
+
17
+ -- These are adapter functions supplied by the application.
18
+ addFee :: Money -> Money
19
+ roundTrip :: Payment -> Payment
20
+ archive :: List (Maybe Payment) -> List (Maybe Payment)
21
+
22
+ -- The model is exact, independent of host decimal rounding settings.
23
+ definition feeModel (money :: Money) :: Money is
24
+ match money with
25
+ | Money amount currency -> Money (amount + 0.2) currency
26
+ end
27
+ end
28
+
29
+ law `fees preserve currency and exact decimal value` is
30
+ definition is
31
+ `for all` (money :: Money) . addFee money = feeModel money
32
+ end
33
+ example `decimal tenths` is
34
+ money = Money 0.1 USD
35
+ expect addFee money = Money 0.3 USD
36
+ end
37
+ example `small amount is not rounded away` is
38
+ money = Money 0.000000000000000000000000000001 EUR
39
+ expect addFee money = Money 0.200000000000000000000000000001 EUR
40
+ end
41
+ example `beyond binary floating precision` is
42
+ money = Money 9007199254740993.1 GBP
43
+ expect addFee money = Money 9007199254740993.3 GBP
44
+ end
45
+ end
46
+
47
+ law `payment variants and payloads survive native conversion` is
48
+ definition is
49
+ `for all` (payment :: Payment) . roundTrip payment = payment
50
+ end
51
+ example `successful payment` is
52
+ payment = Paid (Money 12.34 EUR)
53
+ expect roundTrip payment = Paid (Money 12.34 EUR)
54
+ end
55
+ example `declined payment` is
56
+ payment = Declined "card declined"
57
+ expect roundTrip payment = Declined "card declined"
58
+ end
59
+ end
60
+
61
+ law `archives preserve order duplicates and absence` is
62
+ definition is
63
+ `for all` (payments :: List (Maybe Payment)) . archive payments = payments
64
+ end
65
+ example `empty archive` is
66
+ payments = []
67
+ expect archive payments = []
68
+ end
69
+ example `missing is different from a declined payment` is
70
+ payments = [Nothing, Just (Declined ""), Just (Paid (Money 0.1 USD)),
71
+ Just (Paid (Money 0.1 USD))]
72
+ expect archive payments = [Nothing, Just (Declined ""),
73
+ Just (Paid (Money 0.1 USD)),
74
+ Just (Paid (Money 0.1 USD))]
75
+ end
76
+ end