bcdd-contract 0.1.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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +128 -0
  3. data/CHANGELOG.md +45 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +964 -0
  7. data/Rakefile +18 -0
  8. data/Steepfile +32 -0
  9. data/examples/README.md +11 -0
  10. data/examples/anti_corruption_layer/README.md +212 -0
  11. data/examples/anti_corruption_layer/Rakefile +30 -0
  12. data/examples/anti_corruption_layer/app/models/payment/charge_credit_card.rb +36 -0
  13. data/examples/anti_corruption_layer/config.rb +20 -0
  14. data/examples/anti_corruption_layer/lib/payment_gateways/adapters/circle_up.rb +19 -0
  15. data/examples/anti_corruption_layer/lib/payment_gateways/adapters/pay_friend.rb +19 -0
  16. data/examples/anti_corruption_layer/lib/payment_gateways/contract.rb +15 -0
  17. data/examples/anti_corruption_layer/lib/payment_gateways/response.rb +5 -0
  18. data/examples/anti_corruption_layer/lib/payment_gateways.rb +11 -0
  19. data/examples/anti_corruption_layer/vendor/circle_up/client.rb +11 -0
  20. data/examples/anti_corruption_layer/vendor/pay_friend/client.rb +11 -0
  21. data/examples/business_processes/README.md +245 -0
  22. data/examples/business_processes/Rakefile +50 -0
  23. data/examples/business_processes/config.rb +14 -0
  24. data/examples/business_processes/lib/division.rb +58 -0
  25. data/examples/design_by_contract/README.md +227 -0
  26. data/examples/design_by_contract/Rakefile +60 -0
  27. data/examples/design_by_contract/config.rb +13 -0
  28. data/examples/design_by_contract/lib/shopping_cart.rb +62 -0
  29. data/examples/ports_and_adapters/README.md +246 -0
  30. data/examples/ports_and_adapters/Rakefile +68 -0
  31. data/examples/ports_and_adapters/app/models/user/record/repository.rb +13 -0
  32. data/examples/ports_and_adapters/app/models/user/record.rb +7 -0
  33. data/examples/ports_and_adapters/config.rb +28 -0
  34. data/examples/ports_and_adapters/db/setup.rb +16 -0
  35. data/examples/ports_and_adapters/lib/user/creation.rb +19 -0
  36. data/examples/ports_and_adapters/lib/user/data.rb +5 -0
  37. data/examples/ports_and_adapters/lib/user/repository.rb +24 -0
  38. data/examples/ports_and_adapters/test/user_test/repository.rb +21 -0
  39. data/lib/bcdd/contract/assertions.rb +21 -0
  40. data/lib/bcdd/contract/config.rb +25 -0
  41. data/lib/bcdd/contract/core/checker.rb +37 -0
  42. data/lib/bcdd/contract/core/checking.rb +38 -0
  43. data/lib/bcdd/contract/core/factory.rb +32 -0
  44. data/lib/bcdd/contract/core/proxy.rb +19 -0
  45. data/lib/bcdd/contract/core.rb +12 -0
  46. data/lib/bcdd/contract/interface.rb +25 -0
  47. data/lib/bcdd/contract/list.rb +45 -0
  48. data/lib/bcdd/contract/map/pairs.rb +47 -0
  49. data/lib/bcdd/contract/map/schema.rb +50 -0
  50. data/lib/bcdd/contract/map.rb +10 -0
  51. data/lib/bcdd/contract/proxy.rb +40 -0
  52. data/lib/bcdd/contract/registry.rb +67 -0
  53. data/lib/bcdd/contract/unit/checker.rb +51 -0
  54. data/lib/bcdd/contract/unit/factory.rb +53 -0
  55. data/lib/bcdd/contract/unit.rb +40 -0
  56. data/lib/bcdd/contract/version.rb +7 -0
  57. data/lib/bcdd/contract.rb +118 -0
  58. data/lib/bcdd-contract.rb +3 -0
  59. data/sig/bcdd/contract/assertions.rbs +7 -0
  60. data/sig/bcdd/contract/config.rbs +15 -0
  61. data/sig/bcdd/contract/core.rbs +60 -0
  62. data/sig/bcdd/contract/interface.rbs +12 -0
  63. data/sig/bcdd/contract/list.rbs +21 -0
  64. data/sig/bcdd/contract/map.rbs +45 -0
  65. data/sig/bcdd/contract/proxy.rbs +8 -0
  66. data/sig/bcdd/contract/registry.rbs +25 -0
  67. data/sig/bcdd/contract/unit.rbs +39 -0
  68. data/sig/bcdd/contract.rbs +31 -0
  69. metadata +116 -0
data/README.md ADDED
@@ -0,0 +1,964 @@
1
+ <p align="center">
2
+ <h1 align="center" id="-bcddcontract">🚦 BCDD::Contract</h1>
3
+ <p align="center"><i>Reliable contract definition, data validation, and type checking for Ruby.</i></p>
4
+ <p align="center">
5
+ <img src="https://img.shields.io/badge/ruby->%3D%202.7.0-ruby.svg?colorA=99004d&colorB=cc0066" alt="Ruby">
6
+ <a href="https://rubygems.org/gems/bcdd-contract"><img src="https://badge.fury.io/rb/bcdd-contract.svg" alt="bcdd-contract gem version" height="18"></a>
7
+ <a href="https://codeclimate.com/github/B-CDD/contract/maintainability"><img src="https://api.codeclimate.com/v1/badges/14e87347cd2b660ae3cf/maintainability" /></a>
8
+ <a href="https://codeclimate.com/github/B-CDD/contract/test_coverage"><img src="https://api.codeclimate.com/v1/badges/14e87347cd2b660ae3cf/test_coverage" /></a>
9
+ </p>
10
+ </p>
11
+
12
+ - [Introduction](#introduction)
13
+ - [Features](#features)
14
+ - [Motivation](#motivation)
15
+ - [Examples](#examples)
16
+ - [Installation](#installation)
17
+ - [Usage](#usage)
18
+ - [Contract Units](#contract-units)
19
+ - [Lambda Based](#lambda-based)
20
+ - [Type Based](#type-based)
21
+ - [Union Based](#union-based)
22
+ - [Using `nil` to define optional checkers](#using-nil-to-define-optional-checkers)
23
+ - [Data Structure Checkers](#data-structure-checkers)
24
+ - [List Schema](#list-schema)
25
+ - [Hash Schema](#hash-schema)
26
+ - [Hash key/value Pairs Schema](#hash-keyvalue-pairs-schema)
27
+ - [Registered Checkers](#registered-checkers)
28
+ - [Defining Interfaces](#defining-interfaces)
29
+ - [`BCDD::Contract::Interface`](#bcddcontractinterface)
30
+ - [`BCDD::Contract::Proxy`](#bcddcontractproxy)
31
+ - [Assertions](#assertions)
32
+ - [Configuration](#configuration)
33
+ - [Switchable features](#switchable-features)
34
+ - [Non-switchable features](#non-switchable-features)
35
+ - [Reference](#reference)
36
+ - [The Contract Checker API](#the-contract-checker-api)
37
+ - [`.===`](#)
38
+ - [`.to_proc`](#to_proc)
39
+ - [`.invariant`](#invariant)
40
+ - [The Contract Checking API](#the-contract-checking-api)
41
+ - [Unary operators](#unary-operators)
42
+ - [`BCDD::Contract` methods](#bcddcontract-methods)
43
+ - [`BCDD::Contract::Assertions`](#bcddcontractassertions)
44
+ - [About](#about)
45
+ - [Development](#development)
46
+ - [Contributing](#contributing)
47
+ - [License](#license)
48
+ - [Code of Conduct](#code-of-conduct)
49
+
50
+ ## Introduction
51
+
52
+ `bcdd-contract` is a library for implementing contracts in Ruby. It provides abstractions to validate data structures, perform type checks, and define contracts inlined or through proxies.
53
+
54
+ ## Features
55
+
56
+ - Strict **type checking**.
57
+ - Value validation with **error messages**.
58
+ - **Data structure validation**: Hashes, Arrays, Sets.
59
+ - **Interface** mechanisms.
60
+ - **Configurable** features.
61
+ - **Pattern matching** integration.
62
+ - **More Ruby** and less DSL.
63
+ - **Simple** and easy **to use**.
64
+
65
+ ## Motivation
66
+
67
+ Due to the addition of pattern matching, Ruby now has an excellent tool for doing type checks.
68
+
69
+ ```ruby
70
+ def divide(a, b)
71
+ a => Float | Integer
72
+ b => Float | Integer
73
+
74
+ outcome = a / b => Float | Integer
75
+ outcome
76
+ end
77
+
78
+ divide('4', 2) # Integer === "4" does not return true (NoMatchingPatternError)
79
+ divide(4, '2') # Integer === "2" does not return true (NoMatchingPatternError)
80
+ divide(4, 2r) # Integer === (2/1) does not return true (NoMatchingPatternError)
81
+
82
+ divide(4, 2.0) # 2.0
83
+ ```
84
+
85
+ However, more is needed to implement contracts. Often, the object is of the expected type but does not have a valid state.
86
+
87
+ ```ruby
88
+ # Examples of floats that are undesirable (invalid state)
89
+
90
+ divide(0.0, 0.0) # NaN
91
+ divide(0.0, 1.0) # Infinity
92
+
93
+ divide(Float::NAN, 2) # NaN
94
+ divide(Float::INFINITY, 2) # Infinity
95
+ ```
96
+
97
+ Let's see how we can use `bcdd-contract` can be used to implement contracts that will work with and without pattern matching.
98
+
99
+ ```ruby
100
+ module FloatOrInt
101
+ is_finite = ->(val) { val.finite? or "%p must be finite" }
102
+
103
+ extend (BCDD::Contract[Float] & is_finite) | Integer
104
+ end
105
+
106
+ def divide(a, b)
107
+ a => FloatOrInt
108
+ b => FloatOrInt
109
+
110
+ outcome = a / b => FloatOrInt
111
+ outcome
112
+ end
113
+
114
+ divide('4', 2) # FloatOrInt === "4" does not return true (NoMatchingPatternError)
115
+ divide(4, '2') # FloatOrInt === "2" does not return true (NoMatchingPatternError)
116
+ divide(4, 2r) # FloatOrInt === (2/1) does not return true (NoMatchingPatternError)
117
+ divide(0.0, 0.0) # FloatOrInt === NaN does not return true (NoMatchingPatternError)
118
+ divide(0.0, 1.0) # FloatOrInt === Infinity does not return true (NoMatchingPatternError)
119
+ divide(Float::NAN, 2) # FloatOrInt === NaN does not return true (NoMatchingPatternError)
120
+ divide(Float::INFINITY, 2) # FloatOrInt === Infinity does not return true (NoMatchingPatternError)
121
+
122
+ divide(4, 2.0) # 2.0
123
+
124
+ # The contract can be used to validate values
125
+
126
+ FloatOrInt['1'].valid? # false
127
+ FloatOrInt['2'].invalid? # true
128
+ FloatOrInt['3'].errors # ['"3" must be a Float OR "3" must be a Integer']
129
+ FloatOrInt['4'].value # "4"
130
+ FloatOrInt['5'].value! # "5" must be a Float OR "4" must be a Integer (BCDD::Contract::Error)
131
+ ```
132
+
133
+ Although all of this, the idea of contracts goes far beyond type checking or value validation. They are a way to define an expected behavior (method's inputs and outputs) and ensure pre-conditions, post-conditions, and invariants (Design by Contract concepts).
134
+
135
+ It looks good? So, let's see what more `bcdd-contract` can do.
136
+
137
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
138
+
139
+ ## Examples
140
+
141
+ Check the [examples](examples) directory to see different applications of `bcdd-contract`.
142
+
143
+ > **Attention:** Each example has its own **README** with more details.
144
+
145
+ 1. [Ports and Adapters](examples/ports_and_adapters) - Implements the Ports and Adapters pattern. It uses [**`BCDD::Contract::Interface`**](#bcddcontractinterface) to provide an interface from the application's core to other layers.
146
+
147
+ 2. [Anti-Corruption Layer](examples/anti_corruption_layer) - Implements the Anti-Corruption Layer pattern. It uses the [**`BCDD::Contract::Proxy`**](#bcddcontractproxy) to define an inteface for a set of adapters, which will be used to translate an external interface (`vendors`) to the application's core interface.
148
+
149
+ 3. [Business Processes](examples/business_processes) - Implements a business process using the [`bcdd-result`](https://github.com/B-CDD/result) gem and uses the `bcdd-contract` to define its contract.
150
+
151
+ 4. [Design by Contract](examples/design_by_contract) - Shows how the `bcdd-contract` can be used to establish pre-conditions, post-conditions, and invariants in a class.
152
+
153
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
154
+
155
+ ## Installation
156
+
157
+ Add this line to your application's Gemfile:
158
+
159
+ ```ruby
160
+ gem 'bcdd-contract'
161
+ ```
162
+
163
+ And then execute:
164
+
165
+ $ bundle install
166
+
167
+ Or install it yourself as:
168
+
169
+ $ gem install bcdd-contract
170
+
171
+ And require it:
172
+
173
+ ```ruby
174
+ require 'bcdd/contract' # or require 'bcdd-contract'
175
+ ```
176
+
177
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
178
+
179
+ ## Usage
180
+
181
+ ### Contract Units
182
+
183
+ A unit can be used to check any object, use it when you need to check the type of an object or validate its value.
184
+
185
+ #### Lambda Based
186
+
187
+ There are two ways to create a unit checker using a Ruby lambda.
188
+
189
+ The difference between them is the number of arguments that the lambda receive.
190
+
191
+ **One argument**
192
+
193
+ When the lambda receives only one argument, it will be considered an error when it returns a string. Otherwise, it will be valid.
194
+
195
+ ```ruby
196
+ # Using and, or keywords
197
+
198
+ BCDD::Contract[->(val) { val.empty? or "%p must be empty" }]
199
+ BCDD::Contract[->(val) { val.empty? and "%p must be filled" }]
200
+
201
+ # The same as above, but using if/unless + return
202
+
203
+ BCDD::Contract[->(val) { "%p must be empty" unless val.empty? }]
204
+ BCDD::Contract[->(val) { "%p must be filled" if val.empty? }]
205
+ ```
206
+
207
+ You can also use numbered parameters to make the code more concise.
208
+
209
+ ```ruby
210
+ BCDD::Contract[-> { _1.empty? or "%p must be empty" }]
211
+ BCDD::Contract[-> { _1.empty? and "%p must be filled" }]
212
+
213
+ BCDD::Contract[-> { "%p must be empty" unless _1.empty? }]
214
+ BCDD::Contract[-> { "%p must be filled" if _1.empty? }]
215
+ ```
216
+
217
+ **Two arguments**
218
+
219
+ When the lambda receives two arguments, the first will be the value to be checked, and the second will be an array of errors. If the value is invalid, the lambda must add an error message to the array.
220
+
221
+ ```ruby
222
+ MustBeFilled = BCDD::Contract[->(val, err) { err << "%p must be filled" if val.empty? }]
223
+
224
+ MustBeFilled[''].valid? # false
225
+ MustBeFilled[[]].valid? # false
226
+ MustBeFilled[{}].valid? # false
227
+
228
+ MustBeFilled['4'].valid? # true
229
+ MustBeFilled[[5]].valid? # true
230
+ MustBeFilled[{six: 6}].valid? # true
231
+
232
+ [] => MustBeFilled # MustBeFilled === [] does not return true (NoMatchingPatternError)
233
+ {} => MustBeFilled # MustBeFilled === {} does not return true (NoMatchingPatternError)
234
+
235
+ checking = MustBeFilled[[]]
236
+
237
+ checking.errors # ["[] must be filled"]
238
+ ```
239
+
240
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
241
+
242
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
243
+
244
+ #### Type Based
245
+
246
+ Pass a Ruby module or class to `BCDD::Contract[]` to create a type checker.
247
+
248
+ ```ruby
249
+ IsEnumerable = BCDD::Contract[Enumerable]
250
+
251
+ IsEnumerable[[]].valid? # true
252
+ IsEnumerable[{}].valid? # true
253
+ IsEnumerable[1].valid? # false
254
+
255
+ {} => IsEnumerable # nil
256
+ [] => IsEnumerable # nil
257
+ 1 => IsEnumerable # IsEnumerable === 1 does not return true (NoMatchingPatternError)
258
+
259
+ checking = IsEnumerable[1]
260
+
261
+ checking.errors # ["1 must be a Enumerable"]
262
+ ```
263
+
264
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
265
+
266
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
267
+
268
+ #### Union Based
269
+
270
+ After creating a unit checker, you can use the methods `|` (OR) and `&` (AND) to create union/intersection checkers.
271
+
272
+ ```ruby
273
+ is_filled = -> { _1.empty? and "%p must be filled" }
274
+
275
+ FilledArrayOrHash = (BCDD::Contract[Array] | Hash) & is_filled
276
+
277
+ FilledArrayOrHash[[]].valid? # false
278
+ FilledArrayOrHash[{}].valid? # false
279
+
280
+ FilledArrayOrHash[['1']].valid? # true
281
+ FilledArrayOrHash[{one: '1'}].valid? # true
282
+
283
+ [] => FilledArrayOrHash # FilledArrayOrHash === [] does not return true (NoMatchingPatternError)
284
+ {} => FilledArrayOrHash # FilledArrayOrHash === {} does not return true (NoMatchingPatternError)
285
+
286
+ checking = FilledArrayOrHash[[]]
287
+
288
+ checking.errors # ["[] must be filled"]
289
+ ```
290
+
291
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
292
+
293
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
294
+
295
+ ##### Using `nil` to define optional checkers
296
+
297
+ You can use `nil` to create optional contract checkers.
298
+
299
+ ```ruby
300
+ IsStringOrNil = BCDD::Contract[String] | nil
301
+ ```
302
+
303
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
304
+
305
+ ### Data Structure Checkers
306
+
307
+ #### List Schema
308
+
309
+ Use an array to define a schema. Only one element is allowed. Use the union checker to allow multiple types.
310
+
311
+ If the element is not a checker, it will be transformed into one.
312
+
313
+ The checker only accept arrays and sets.
314
+
315
+ ```ruby
316
+ ListOfString = ::BCDD::Contract([String])
317
+
318
+ ListOfString[[]].valid? # false
319
+ ListOfString[{}].valid? # false
320
+
321
+ ListOfString[['1', '2', '3']].valid? # true
322
+ ListOfString[Set['1', '2', '3']].valid? # true
323
+
324
+ ['1', '2', 3] => ListOfString # ListOfString === ["1", "2", 3] does not return true (NoMatchingPatternError)
325
+
326
+ Set['1', '2', 3] => ListOfString # ListOfString === #<Set: {"1", "2", 3}> does not return true (NoMatchingPatternError)
327
+
328
+ checking = ListOfString[[1, '2', 3]]
329
+
330
+ checking.errors
331
+ # [
332
+ # "0: 1 must be a String",
333
+ # "2: 3 must be a String"
334
+ # ]
335
+ ```
336
+
337
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
338
+
339
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
340
+
341
+ #### Hash Schema
342
+
343
+ Use a hash to define a schema. The keys will be used to match the keys, and the values will be transformed into checkers (if they are not). You can use any kind of checker, including other hash schemas.
344
+
345
+ ```ruby
346
+ PersonParams = ::BCDD::Contract[{
347
+ name: String,
348
+ age: Integer,
349
+ address: {
350
+ street: String,
351
+ number: Integer,
352
+ city: String,
353
+ state: String,
354
+ country: String
355
+ },
356
+ phone_numbers: ::BCDD::Contract([String])
357
+ }]
358
+
359
+ PersonParams[{}].valid? # => false
360
+
361
+ PersonParams[{
362
+ name: 'John',
363
+ age: 30,
364
+ address: {
365
+ street: 'Main Street',
366
+ number: 123,
367
+ city: 'New York',
368
+ state: 'NY',
369
+ country: 'USA'
370
+ },
371
+ phone_numbers: ['+1 555 123 4567']
372
+ }].valid? # => true
373
+
374
+ params_checking = PersonParams[{
375
+ name: 'John',
376
+ age: '30',
377
+ address: {
378
+ street: 'Main Street',
379
+ number: 123,
380
+ city: nil,
381
+ state: :NY,
382
+ country: 'USA'
383
+ },
384
+ phone_numbers: ['+1 555 123 4567']
385
+ }]
386
+
387
+ params_checking.errors
388
+ # {
389
+ # :age => ["\"30\" must be a Integer"],
390
+ # :address => {
391
+ # :city => ["is missing"],
392
+ # :state => [":NY must be a String"]
393
+ # }
394
+ # }
395
+ ```
396
+
397
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
398
+
399
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
400
+
401
+ #### Hash key/value Pairs Schema
402
+
403
+ Use a hash to define a schema. The key and value will be transformed into checkers (if they are not).
404
+
405
+ ```ruby
406
+ is_int_str = -> { _1.is_a?(String) && _1.match?(/\A\d+\z/) or "%p must be a Integer String" }
407
+
408
+ PlayerRankings = ::BCDD::Contract.pairs(is_int_str => { name: String, username: String })
409
+
410
+ PlayerRankings[{}].valid? # => false
411
+
412
+ PlayerRankings[{
413
+ '1' => { name: 'John', username: 'john' },
414
+ '2' => { name: 'Mary', username: 'mary' },
415
+ '3' => { name: 'Paul', username: 'paul' }
416
+ }].valid? # => true
417
+
418
+ checking = PlayerRankings[{
419
+ '1' => { name: :John, username: 'john' },
420
+ 'two' => { name: 'Mary', username: 'mary' },
421
+ '3' => { name: 'Paul', username: 3 }
422
+ }]
423
+
424
+ checking.errors
425
+ # [
426
+ # "1: (name: :John must be a String)",
427
+ # "key: \"two\" must be a Integer String",
428
+ # "3: (username: 3 must be a String)"
429
+ # ]
430
+ ```
431
+
432
+ > Check out the [Registered Contract Checkers](#registered-contract-checkers) section to see how to avoid duplication of checker definitions.
433
+
434
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
435
+
436
+ ### Registered Checkers
437
+
438
+ Sometimes you need to use the same checker in different places. To avoid code duplication, you can register a checker and use it later.
439
+
440
+ Use the `BCDD::Contract.register` method to give a name and register a checker.
441
+
442
+ You can register any kind of checker:
443
+ - **Unit**
444
+ - [Lambda based](#lambda-based)
445
+ - [Type based](#type-based)
446
+ - [Union based](#union-based)
447
+ - **Data Structure**
448
+ - [List schema](#list-schema)
449
+ - [Hash schema](#hash-schema)
450
+ - [Hash key/value pairs schema](#hash-keyvalue-pairs-schema)
451
+
452
+ ```ruby
453
+ is_string = ::BCDD::Contract[::String]
454
+ is_filled = -> { _1.empty? and "%p must be filled" }
455
+
456
+ uuid_format = -> { _1.match?(/\A[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}\z/) or "%p must be a valid UUID" }
457
+ email_format = -> { _1.match?(/\A[^@\s]+@[^@\s]+\z/) or "%p must be a valid email" }
458
+
459
+ ::BCDD::Contract.register(
460
+ is_uuid: is_string & uuid_format,
461
+ is_email: is_string & email_format,
462
+ is_filled: is_filled
463
+ )
464
+ ```
465
+
466
+ To use them, use a symbol with `BCDD::Contract[]` or a method that transforms a value into a checker.
467
+
468
+ ```ruby
469
+ str_filled = ::BCDD::Contract[:is_str] & :is_filled
470
+
471
+ PersonParams = ::BCDD::Contract[{
472
+ uuid: :is_uuid,
473
+ name: str_filled,
474
+ email: :is_email,
475
+ tags: [str_filled]
476
+ }]
477
+ ```
478
+
479
+ You can use registered checkers with unions and intersections.
480
+
481
+ ```ruby
482
+ BCDD::Contract.register(
483
+ is_hash: Hash,
484
+ is_array: Array,
485
+ is_filled: -> { _1.empty? and "%p must be filled" }
486
+ )
487
+
488
+ filled_array_or_hash = (BCDD::Contract[:is_array] | :is_hash) & :is_filled
489
+ ```
490
+
491
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
492
+
493
+ ### Defining Interfaces
494
+
495
+ #### `BCDD::Contract::Interface`
496
+
497
+ This feature allows the creation of a module that will be used as an interface.
498
+
499
+ It will check if the class that includes it or the object that extends it implements all the expected methods.
500
+
501
+ ```ruby
502
+ module User::Repository
503
+ include ::BCDD::Contract::Interface
504
+
505
+ module Methods
506
+ IsString = ::BCDD::Contract[String]
507
+
508
+ def create(name:, email:)
509
+ output = super(name: +IsString[name], email: +IsString[email])
510
+
511
+ output => ::User::Data[id: Integer, name: IsString, email: IsString]
512
+
513
+ output
514
+ end
515
+ end
516
+ end
517
+ ```
518
+
519
+ Let's break down the example above.
520
+
521
+ 1. The `User::Repository` module includes `BCDD::Contract::Interface`.
522
+ 2. Defines the `Methods` module. It is mandatory, as these will be the methods to be implemented.
523
+ 3. The `create` method is defined inside the `Method`s' module.
524
+ 1. This method receives two arguments: `name` and `email`.
525
+ 2. The arguments are checked using the `IsString` checker.
526
+ * The `+` operator performs a strict check. An error will be raised if the value is invalid. Otherwise, the value will be returned.
527
+ 3. `super` is called to invoke the `create` method of the superclass. Which will be the class/object that includes/extends the `User::Repository` module.
528
+ 4. The `output` is checked using pattern matching.
529
+ * The `=>` operator performs strict checks. If the value is invalid, a `NoMatchingPatternError` will be raised.
530
+ 5. The `output` is returned.
531
+
532
+ Now, let's see how to use it in a class.
533
+
534
+ ```ruby
535
+ class User::Record::Repository
536
+ include User::Repository
537
+
538
+ def create(name:, email:)
539
+ record = Record.create(name:, email:)
540
+
541
+ ::User::Data.new(id: record.id, name: record.name, email: record.email)
542
+ end
543
+ end
544
+ ```
545
+
546
+ And how to use it in a module with singleton methods.
547
+
548
+ ```ruby
549
+ module User::Record::Repository
550
+ extend User::Repository
551
+
552
+ def self.create(name:, email:)
553
+ record = Record.create(name:, email:)
554
+
555
+ ::User::Data.new(id: record.id, name: record.name, email: record.email)
556
+ end
557
+ end
558
+ ```
559
+
560
+ **What happend when an interface module is included/extended?**
561
+
562
+ 1. An instance of the class will be a `User::Repository`.
563
+ 2. The module, class, object, that extended the interface will be a `User::Repository`.
564
+
565
+ ```ruby
566
+ class User::Record::Repository
567
+ include User::Repository
568
+ end
569
+
570
+ module UserTest::RepositoryInMemory
571
+ extend User::Repository
572
+ # ...
573
+ end
574
+
575
+ User::Record::Repository.new.is_a?(User::Repository) # true
576
+
577
+ UserTest::RepositoryInMemory.is_a?(User::Repository) # true
578
+ ```
579
+
580
+ **Why this is useful?**
581
+
582
+ Use `is_a?` to ensure that the class/object implements the expected methods.
583
+
584
+ ```ruby
585
+ class User::Creation
586
+ def initialize(repository)
587
+ repository => User::Repository
588
+
589
+ @repository = repository
590
+ end
591
+
592
+ # ...
593
+ end
594
+ ```
595
+
596
+ > Access the [**Ports and Adapters example**](examples/ports_and_adapters) to see, test, and run something that uses the `BCDD::Contract::Interface`
597
+
598
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
599
+
600
+ #### `BCDD::Contract::Proxy`
601
+
602
+ This feature allows the creation of a class that will be used as a proxy for another objects.
603
+
604
+ The idea is to define an interface for the object that will be proxied.
605
+
606
+ Let's implement the example from the [previous section](#bcddcontractinterface) using a proxy.
607
+
608
+ ```ruby
609
+ class User::Repository < BCDD::Contract::Proxy
610
+ IsString = ::BCDD::Contract[String]
611
+
612
+ def create(name:, email:)
613
+ output = object.create(name: +IsString[name], email: +IsString[email])
614
+
615
+ output => ::User::Data[id: Integer, name: IsString, email: IsString]
616
+
617
+ output
618
+ end
619
+ end
620
+ ```
621
+
622
+ **How to use it?**
623
+
624
+ Inside the proxy you will use `object` to access the proxied object. This means the proxy must be initialized with an object. And the object must implement the methods defined in the proxy.
625
+
626
+ ```ruby
627
+ class User::Record::Repository
628
+ # ...
629
+ end
630
+
631
+ module UserTest::RepositoryInMemory
632
+ extend self
633
+ # ...
634
+ end
635
+
636
+ # The proxy must be initialized with an object that implements the expected methods
637
+
638
+ memory_repository = User::Repository.new(UserTest::RepositoryInMemory)
639
+
640
+ record_repository = User::Repository.new(User::Record::Repository.new)
641
+ ```
642
+
643
+ > Access the [**Anti-Corruption Layer**](examples/anti_corruption_layer) to see, test, and run something that uses the `BCDD::Contract::Proxy`
644
+
645
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
646
+
647
+ ### Assertions
648
+
649
+ Use the `BCDD::Contract.assert` method to check if a value is truthy or use the `BCDD::Contract.refute` method to check if a value is falsey.
650
+
651
+ Both methods always expect a value and a message. The third argument is optional and can be used to perform a more complex check.
652
+
653
+ If the value is falsey for an assertion or truthy for a refutation, an error will be raised with the message.
654
+
655
+ **Assertions withouth a block**
656
+
657
+ ```ruby
658
+ item_name1 = nil
659
+ item_name2 = 'Item 2'
660
+
661
+ BCDD::Contract.assert!(item_name1, 'item (%p) not found')
662
+ # item (nil) not found (BCDD::Contract::Error)
663
+
664
+ BCDD::Contract.assert!(item_name2, 'item (%p) not found')
665
+ # "Item 2"
666
+ ```
667
+
668
+ **Refutations withouth a block**
669
+
670
+ ```ruby
671
+ allowed_quantity = 10
672
+
673
+ BCDD::Contract.refute!(20 > allowed_quantity, 'quantity is greater than allowed')
674
+ # quantity is greater than allowed (BCDD::Contract::Error)
675
+
676
+ BCDD::Contract.refute!(5 > allowed_quantity, 'quantity is greater than allowed')
677
+ # false
678
+ ```
679
+
680
+ **Assertions/Refutations with a block**
681
+
682
+ You can use a block to perform a more complex check. The value passed to `assert`/`refute` will be yielded to the block.
683
+
684
+ ```ruby
685
+ item_name = 'Item 1'
686
+ item_quantity = 10
687
+
688
+ # ---
689
+
690
+ quantity_to_remove = 11
691
+
692
+ BCDD::Contract.assert(item_name, 'item (%p) not enough quantity to remove') { quantity_to_remove <= item_quantity }
693
+ # item ("Item 1") not enough quantity to remove (BCDD::Contract::Error)
694
+
695
+ BCDD::Contract.refute(item_name, 'item (%p) not enough quantity to remove') { quantity_to_remove > item_quantity }
696
+ # item ("Item 1") not enough quantity to remove (BCDD::Contract::Error)
697
+
698
+
699
+ quantity_to_remove = 10
700
+
701
+ BCDD::Contract.assert(item_name, 'item (%p) not enough quantity to remove') { quantity_to_remove <= item_quantity }
702
+ # "Item 1"
703
+
704
+ BCDD::Contract.refute(item_name, 'item (%p) not enough quantity to remove') { quantity_to_remove > item_quantity }
705
+ # "Item 1"
706
+ ```
707
+
708
+ > Access the [**Design by Contract**](examples/design_by_contract) to see, test, and run something that uses the `BCDD::Contract` assertions.
709
+
710
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
711
+
712
+ ## Configuration
713
+
714
+ By default, the `BCDD::Contract` enables all its features. You can disable them by setting the configuration.
715
+
716
+ ### Switchable features
717
+
718
+ ```ruby
719
+ BCDD::Contract.configuration do |config|
720
+ dev_or_test = ::Rails.env.local?
721
+
722
+ config.proxy_enabled = dev_or_test
723
+ config.interface_enabled = dev_or_test
724
+ config.assertions_enabled = dev_or_test
725
+ end
726
+ ```
727
+
728
+ In the example above, the `BCDD::Contract::Proxy`, `BCDD::Contract::Interface`, and `BCDD::Contract.assert`/`BCDD::Contract.refute` will be disabled in production.
729
+
730
+ ### Non-switchable features
731
+
732
+ The following variants are always enabled. You cannot disable them through the configuration.
733
+
734
+ - [`BCDD::Contract::Proxy::AlwaysEnabled`](#bcddcontractproxy).
735
+ - [`BCDD::Contract::Interface::AlwaysEnabled`](#bcddcontractinterface).
736
+ - [`BCDD::Contract.assert!`](#assertions).
737
+ - [`BCDD::Contract.refute!`](#assertions).
738
+
739
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
740
+
741
+ ## Reference
742
+
743
+ ### The Contract Checker API
744
+
745
+ This section describes the common API for all contract checkers:
746
+ - **Unit**
747
+ - [Lambda based](#lambda-based)
748
+ - [Type based](#type-based)
749
+ - [Union based](#union-based)
750
+ - **Data Structure**
751
+ - [List schema](#list-schema)
752
+ - [Hash schema](#hash-schema)
753
+ - [Hash key/value pairs schema](#hash-keyvalue-pairs-schema)
754
+
755
+ Let's the following contract checker to illustrate the API.
756
+
757
+ ```ruby
758
+ IsFilled = BCDD::Contract[-> { _1.empty? and "%p must be filled" }]
759
+ ```
760
+
761
+ #### `.===`
762
+
763
+ You can use the `===` operator to check if a value is valid. This operator is also used by the `case` statement and `pattern matching` operators.
764
+
765
+ ```ruby
766
+ # ===
767
+
768
+ IsFilled === '' # false
769
+ IsFilled === [] # false
770
+
771
+ IsFilled === '1' # true
772
+
773
+ # case statement
774
+
775
+ case {}
776
+ when IsFilled
777
+ # ...
778
+ end
779
+
780
+ # pattern matching
781
+
782
+ case []
783
+ in IsFilled
784
+ # ...
785
+ end
786
+
787
+ '' in IsFilled # false
788
+
789
+ Set.new => IsFilled # is_filled === #<Set: {}> does not return true (NoMatchingPatternError)
790
+ ```
791
+
792
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
793
+
794
+ #### `.to_proc`
795
+
796
+ You can use the `to_proc` method to transform a value into a checking object.
797
+
798
+ ```ruby
799
+ [
800
+ '',
801
+ [],
802
+ {}
803
+ ].map(&IsFilled).all?(&:valid?) # false
804
+
805
+ [
806
+ '1',
807
+ '2',
808
+ '3'
809
+ ].map(&IsFilled).all?(&:valid?) # true
810
+ ```
811
+
812
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
813
+
814
+ #### `.invariant`
815
+
816
+ Use the `invariant` to perform an strict check before and after the block execution.
817
+
818
+ ```ruby
819
+ IsFilled.invariant([1]) { |numbers| numbers.pop }
820
+ # [] must be filled (BCDD::Contract::Error)
821
+ ```
822
+
823
+ > Access the [**Design by Contract**](examples/design_by_contract) a better example of how to use `invariant`.
824
+
825
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
826
+
827
+ ### The Contract Checking API
828
+
829
+ This section describes the common API for all contract checking objects. Objects that are created by a contract checker.
830
+
831
+ ```ruby
832
+ IsFilled = BCDD::Contract[-> { _1.empty? and "%p must be filled" }]
833
+
834
+ checking = IsFilled['']
835
+
836
+ checking.valid? # false
837
+ checking.invalid? # true
838
+ checking.errors? # true
839
+ checking.errors # ['"" must be filled']
840
+ checking.errors_message # '"" must be filled'
841
+
842
+ checking.value # ""
843
+
844
+ +checking # "" must be filled (BCDD::Contract::Error)
845
+ !checking # "" must be filled (BCDD::Contract::Error)
846
+ checking.value! # "" must be filled (BCDD::Contract::Error)
847
+ checking.assert! # "" must be filled (BCDD::Contract::Error)
848
+
849
+ # ---
850
+
851
+ checking = IsFilled['John']
852
+
853
+ +checking # "John"
854
+ !checking # "John"
855
+ checking.value! # "John"
856
+ checking.assert! # "John"
857
+ ```
858
+
859
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
860
+
861
+ #### Unary operators
862
+
863
+ You can use the unary operators `+` and `!` to perform a strict check. If the value is invalid, an error will be raised. Otherwise, the value will be returned.
864
+
865
+ ```ruby
866
+ +IsFilled[''] # "" must be filled (BCDD::Contract::Error)
867
+ !IsFilled[''] # "" must be filled (BCDD::Contract::Error)
868
+
869
+ +IsFilled['John'] # "John"
870
+ !IsFilled['John'] # "John"
871
+ ```
872
+
873
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
874
+
875
+ ### `BCDD::Contract` methods
876
+
877
+ ```ruby
878
+ BCDD::Contract[lambda] # returns a unit checker
879
+ BCDD::Contract[module] # returns a type checker
880
+ BCDD::Contract[<Array>] # returns a list schema checker
881
+ BCDD::Contract[<Hash>] # returns a hash schema checker
882
+
883
+ BCDD::Contract(<Object>) # alias for BCDD::Contract[<Object>]
884
+ BCDD::Contract.new(<Object>) # alias for BCDD::Contract[<Object>]
885
+
886
+ BCDD::Contract.to_proc # returns a proc that transforms a value into a checker
887
+
888
+ BCDD::Contract.pairs(<Hash>) # returns a hash key/value pairs schema checker
889
+ BCDD::Contract.schema(<Hash>) # returns a hash schema checker
890
+ BCDD::Contract.list(<Object>) # returns a list schema checker
891
+ BCDD::Contract.unit(<Object>) # returns a unit checker (lambda/type based)
892
+
893
+ BCDD::Contract.error!(<String>) # raises a BCDD::Contract::Error
894
+
895
+ BCDD::Contract.assert(value, message, &block) # raises a BCDD::Contract::Error if the value/block is falsey
896
+ BCDD::Contract.refute(value, message, &block) # raises a BCDD::Contract::Error if the value/block is truthy
897
+
898
+ BCDD::Contract.assert!(value, message, &block) # same as BCDD::Contract.assert but cannot be disabled
899
+ BCDD::Contract.refute!(value, message, &block) # same as BCDD::Contract.refute but cannot be disabled
900
+
901
+ # Produces a BCDD::Contract::Proxy class
902
+ BCDD::Contract.proxy do
903
+ # ...
904
+ end
905
+
906
+ # Produces a BCDD::Contract::Proxy::AlwaysEnabled class
907
+ BCDD::Contract.proxy(always_enabled: true) do
908
+ # ...
909
+ end
910
+ ```
911
+
912
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
913
+
914
+ ### `BCDD::Contract::Assertions`
915
+
916
+ Use this module to include/extend the `BCDD::Contract` assertions (`#assert`/`#assert!` and `#refute`/`#refute!`).
917
+
918
+ The methods without bang (`#assert` and `#refute`) can be disabled through the assertions configuration.
919
+
920
+ ```ruby
921
+ class User::Creation
922
+ include BCDD::Contract::Assertions
923
+
924
+ def initialize(repository)
925
+ assert!(repository, '%p must be a User::Repository') { _1.repository.is_a?(User::Repository) }
926
+
927
+ @repository = repository
928
+ end
929
+
930
+ # ...
931
+ end
932
+ ```
933
+
934
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
935
+
936
+ ## About
937
+
938
+ [Rodrigo Serradura](https://github.com/serradura) created this project. He is the B/CDD process/method creator and has already made similar gems like the [u-case](https://github.com/serradura/u-case) and [kind](https://github.com/serradura/kind/blob/main/lib/kind/result.rb). This gem can be used independently, but it also contains essential features that facilitate the adoption of B/CDD in code.
939
+
940
+ ## Development
941
+
942
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
943
+
944
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
945
+
946
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
947
+
948
+ ## Contributing
949
+
950
+ Bug reports and pull requests are welcome on GitHub at https://github.com/B-CDD/contract. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/B-CDD/contract/blob/main/CODE_OF_CONDUCT.md).
951
+
952
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
953
+
954
+ ## License
955
+
956
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
957
+
958
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>
959
+
960
+ ## Code of Conduct
961
+
962
+ Everyone interacting in the `BCDD::Contract` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/B-CDD/contract/blob/main/CODE_OF_CONDUCT.md).
963
+
964
+ <p align="right"><a href="#-bcddcontract">⬆️ &nbsp;back to top</a></p>