dry-types 1.2.2 → 1.3.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +306 -225
  3. data/LICENSE +1 -1
  4. data/README.md +14 -12
  5. data/dry-types.gemspec +27 -31
  6. data/lib/dry/types.rb +0 -9
  7. data/lib/dry/types/builder.rb +4 -0
  8. data/lib/dry/types/constructor/function.rb +17 -31
  9. data/lib/dry/types/core.rb +3 -1
  10. data/lib/dry/types/decorator.rb +0 -7
  11. data/lib/dry/types/extensions/maybe.rb +14 -14
  12. data/lib/dry/types/lax.rb +1 -4
  13. data/lib/dry/types/meta.rb +2 -2
  14. data/lib/dry/types/params.rb +1 -0
  15. data/lib/dry/types/result.rb +2 -2
  16. data/lib/dry/types/schema.rb +23 -2
  17. data/lib/dry/types/schema/key.rb +11 -2
  18. data/lib/dry/types/spec/types.rb +11 -0
  19. data/lib/dry/types/sum.rb +2 -2
  20. data/lib/dry/types/version.rb +1 -1
  21. metadata +21 -59
  22. data/.codeclimate.yml +0 -12
  23. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  24. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
  25. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  26. data/.github/workflows/custom_ci.yml +0 -76
  27. data/.github/workflows/docsite.yml +0 -34
  28. data/.github/workflows/sync_configs.yml +0 -34
  29. data/.gitignore +0 -11
  30. data/.rspec +0 -4
  31. data/.rubocop.yml +0 -92
  32. data/.yardopts +0 -9
  33. data/CODE_OF_CONDUCT.md +0 -13
  34. data/CONTRIBUTING.md +0 -29
  35. data/Gemfile +0 -34
  36. data/Rakefile +0 -22
  37. data/benchmarks/hash_schemas.rb +0 -55
  38. data/benchmarks/lax_schema.rb +0 -15
  39. data/benchmarks/profile_invalid_input.rb +0 -15
  40. data/benchmarks/profile_lax_schema_valid.rb +0 -16
  41. data/benchmarks/profile_valid_input.rb +0 -15
  42. data/benchmarks/schema_valid_vs_invalid.rb +0 -21
  43. data/benchmarks/setup.rb +0 -17
  44. data/docsite/source/array-with-member.html.md +0 -13
  45. data/docsite/source/built-in-types.html.md +0 -116
  46. data/docsite/source/constraints.html.md +0 -31
  47. data/docsite/source/custom-types.html.md +0 -93
  48. data/docsite/source/default-values.html.md +0 -91
  49. data/docsite/source/enum.html.md +0 -69
  50. data/docsite/source/extensions.html.md +0 -15
  51. data/docsite/source/extensions/maybe.html.md +0 -57
  52. data/docsite/source/extensions/monads.html.md +0 -61
  53. data/docsite/source/getting-started.html.md +0 -57
  54. data/docsite/source/hash-schemas.html.md +0 -169
  55. data/docsite/source/index.html.md +0 -156
  56. data/docsite/source/map.html.md +0 -17
  57. data/docsite/source/optional-values.html.md +0 -35
  58. data/docsite/source/sum.html.md +0 -21
@@ -1,31 +0,0 @@
1
- ---
2
- title: Constraints
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- You can create constrained types that will use validation rules to check that the input is not violating any of the configured constraints. You can treat it as a lower level guarantee that you're not instantiating objects that are broken.
8
-
9
- All types support the constraints API, but not all constraints are suitable for a particular primitive, it's up to you to set up constraints that make sense.
10
-
11
- Under the hood it uses [`dry-logic`](/gems/dry-logic) and all of its predicates are supported.
12
-
13
- ``` ruby
14
- string = Types::String.constrained(min_size: 3)
15
-
16
- string['foo']
17
- # => "foo"
18
-
19
- string['fo']
20
- # => Dry::Types::ConstraintError: "fo" violates constraints
21
-
22
- email = Types::String.constrained(
23
- format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
24
- )
25
-
26
- email["jane@doe.org"]
27
- # => "jane@doe.org"
28
-
29
- email["jane"]
30
- # => Dry::Types::ConstraintError: "jane" violates constraints
31
- ```
@@ -1,93 +0,0 @@
1
- ---
2
- title: Custom Types
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- There are a bunch of helpers for building your own types based on existing classes and values. These helpers are automatically defined if you're imported types in a module.
8
-
9
- ### `Types.Instance`
10
-
11
- `Types.Instance` builds a type that checks if a value has the given class.
12
-
13
- ```ruby
14
- range_type = Types.Instance(Range)
15
- range_type[1..2] # => 1..2
16
- ```
17
-
18
- ### `Types.Value`
19
-
20
- `Types.Value` builds a type that checks a value for equality (using `==`).
21
-
22
- ```ruby
23
- valid = Types.Value('valid')
24
- valid['valid'] # => 'valid'
25
- valid['invalid']
26
- # => Dry::Types::ConstraintError: "invalid" violates constraints (eql?("valid", "invalid") failed)
27
- ```
28
-
29
- ### `Types.Constant`
30
-
31
- `Types.Constant` builds a type that checks a value for identity (using `equal?`).
32
-
33
- ```ruby
34
- valid = Types.Constant(:valid)
35
- valid[:valid] # => :valid
36
- valid[:invalid]
37
- # => Dry::Types::ConstraintError: :invalid violates constraints (is?(:valid, :invalid) failed)
38
- ```
39
-
40
- ### `Types.Constructor`
41
-
42
- `Types.Constructor` builds a new constructor type for the given class. By default uses the `new` method as a constructor.
43
-
44
- ```ruby
45
- user_type = Types.Constructor(User)
46
-
47
- # It is equivalent to User.new(name: 'John')
48
- user_type[name: 'John']
49
-
50
- # Using a block
51
- user_type = Types.Constructor(User) { |values| User.new(values) }
52
- ```
53
-
54
- ### `Types.Nominal`
55
-
56
- `Types.Nominal` wraps the given class with a simple definition without any behavior attached.
57
-
58
- ```ruby
59
- int = Types.Nominal(Integer)
60
- int[1] # => 1
61
-
62
- # The type doesn't have any checks
63
- int['one'] # => 'one'
64
- ```
65
-
66
- ### `Types.Hash`
67
-
68
- `Types.Hash` builds a new hash schema.
69
-
70
- ```ruby
71
- # In the full form
72
- Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer)
73
-
74
- # Using Types.Hash()
75
- Types.Hash(:permissive, name: Types::String, age: Types::Coercible::Integer)
76
- ```
77
-
78
- ### `Types.Array`
79
-
80
- `Types.Array` is a shortcut for `Types::Array.of`
81
-
82
- ```ruby
83
- ListOfStrings = Types.Array(Types::String)
84
- ```
85
-
86
- ### `Types.Interface`
87
-
88
- `Types.Interface` builds a type that checks a value responds to given methods.
89
-
90
- ```ruby
91
- Callable = Types.Interface(:call)
92
- Contact = Types.Interface(:name, :phone)
93
- ```
@@ -1,91 +0,0 @@
1
- ---
2
- title: Default Values
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- A type with a default value will return the configured value when the input is not defined:
8
-
9
- ``` ruby
10
- PostStatus = Types::String.default('draft')
11
-
12
- PostStatus[] # "draft"
13
- PostStatus["published"] # "published"
14
- PostStatus[true] # raises ConstraintError
15
- ```
16
-
17
- It works with a callable value:
18
-
19
- ``` ruby
20
- CallableDateTime = Types::DateTime.default { DateTime.now }
21
-
22
- CallableDateTime[]
23
- # => #<DateTime: 2017-05-06T00:43:06+03:00 ((2457879j,78186s,649279000n),+10800s,2299161j)>
24
- CallableDateTime[]
25
- # => #<DateTime: 2017-05-06T00:43:07+03:00 ((2457879j,78187s,635494000n),+10800s,2299161j)>
26
- ```
27
-
28
- `Dry::Types::Undefined` can be passed explicitly as a missing value:
29
-
30
- ```ruby
31
- PostStatus = Types::String.default('draft')
32
-
33
- PostStatus[Dry::Types::Undefined] # "draft"
34
- ```
35
-
36
- It also receives the type constructor as an argument:
37
-
38
- ```ruby
39
- CallableDateTime = Types::DateTime.constructor(&:to_datetime).default { |type| type[Time.now] }
40
-
41
- CallableDateTime[Time.now]
42
- # => #<DateTime: 2017-05-06T01:13:06+03:00 ((2457879j,79986s,63464000n),+10800s,2299161j)>
43
- CallableDateTime[Date.today]
44
- # => #<DateTime: 2017-05-06T00:00:00+00:00 ((2457880j,0s,0n),+0s,2299161j)>
45
- CallableDateTime[]
46
- # => #<DateTime: 2017-05-06T01:13:06+03:00 ((2457879j,79986s,63503000n),+10800s,2299161j)>
47
- ```
48
-
49
- **Be careful:** types will return the **same instance** of the default value every time. This may cause problems if you mutate the returned value after receiving it:
50
-
51
- ```ruby
52
- default_0 = PostStatus.()
53
- # => "draft"
54
- default_1 = PostStatus.()
55
- # => "draft"
56
-
57
- # Both variables point to the same string:
58
- default_0.object_id == default_1.object_id
59
- # => true
60
-
61
- # Mutating the string will change the default value of type:
62
- default_0 << '_mutated'
63
- PostStatus.(nil)
64
- # => "draft_mutated" # not "draft"
65
- ```
66
-
67
- You can guard against these kind of errors by calling `freeze` when setting the default:
68
-
69
- ```ruby
70
- PostStatus = Types::Params::String.default('draft'.freeze)
71
- default = PostStatus.()
72
- default << 'attempt to mutate default'
73
- # => RuntimeError: can't modify frozen string
74
-
75
- # If you really want to mutate it, call `dup` on it first:
76
- default = default.dup
77
- default << "this time it'll work"
78
- ```
79
-
80
- **Warning on using with constrained types**: If the value passed to the `.default` block does not match the type constraints, this will not throw an exception, because it is not passed to the constructor and will be used as is.
81
-
82
- ```ruby
83
- CallableDateTime = Types::DateTime.constructor(&:to_datetime).default { Time.now }
84
-
85
- CallableDateTime[Time.now]
86
- # => #<DateTime: 2017-05-06T00:50:09+03:00 ((2457879j,78609s,839588000n),+10800s,2299161j)>
87
- CallableDateTime[Date.today]
88
- # => #<DateTime: 2017-05-06T00:00:00+00:00 ((2457880j,0s,0n),+0s,2299161j)>
89
- CallableDateTime[]
90
- # => 2017-05-06 00:50:15 +0300
91
- ```
@@ -1,69 +0,0 @@
1
- ---
2
- title: Enum
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- In many cases you may want to define an enum. For example, in a blog application a post may have a finite list of statuses. Apart from accessing the current status value, it is useful to have all possible values accessible too. Furthermore, an enum can be a map from, e.g., strings to integers. This is useful for mapping externally-provided integer values to human-readable strings without explicit conversions, see examples.
8
-
9
- ``` ruby
10
- require 'dry-types'
11
- require 'dry-struct'
12
-
13
- module Types
14
- include Dry.Types()
15
- end
16
-
17
- class Post < Dry::Struct
18
- Statuses = Types::String.enum('draft', 'published', 'archived')
19
-
20
- attribute :title, Types::String
21
- attribute :body, Types::String
22
- attribute :status, Statuses
23
- end
24
-
25
- # enum values are frozen, let's be paranoid, doesn't hurt and have potential to
26
- # eliminate silly bugs
27
- Post::Statuses.values.frozen? # => true
28
- Post::Statuses.values.all?(&:frozen?) # => true
29
-
30
- Post::Statuses['draft'] # => "draft"
31
-
32
- # it'll raise if something silly was passed in
33
- Post::Statuses['something silly']
34
- # => Dry::Types::ConstraintError: "something silly" violates constraints
35
-
36
- # nil is considered as something silly too
37
- Post::Statuses[nil]
38
- # => Dry::Types::ConstraintError: nil violates constraints
39
- ```
40
-
41
- Note that if you want to define an enum type with a default, you must call `.default` *before* calling `.enum`, not the other way around:
42
-
43
- ```ruby
44
- # this is the correct usage:
45
- Dry::Types::String.default('red').enum('blue', 'green', 'red')
46
-
47
- # this will raise an error:
48
- Dry::Types::String.enum('blue', 'green', 'red').default('red')
49
- ```
50
-
51
- ### Mappings
52
-
53
- A classic example is mapping integers coming from somewhere (API/database/etc) to something more understandable:
54
-
55
- ```ruby
56
- class Cell < Dry::Struct
57
- attribute :state, Types::String.enum('locked' => 0, 'open' => 1)
58
- end
59
-
60
-
61
- Cell.new(state: 'locked')
62
- # => #<Cell state="locked">
63
-
64
- # Integers are accepted too
65
- Cell.new(state: 0)
66
- # => #<Cell state="locked">
67
- Cell.new(state: 1)
68
- # => #<Cell state="open">
69
- ```
@@ -1,15 +0,0 @@
1
- ---
2
- title: Extensions
3
- layout: gem-single
4
- name: dry-types
5
- sections:
6
- - maybe
7
- - monads
8
- ---
9
-
10
- `dry-types` can be extended with extension. Those extensions are loaded with `Dry::Types.load_extensions`.
11
-
12
- Available extensions:
13
-
14
- - [Maybe](docs::extensions/maybe)
15
- - [Monads](docs::extensions/monads)
@@ -1,57 +0,0 @@
1
- ---
2
- title: Maybe
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- The [dry-monads gem](/gems/dry-monads/) provides approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object. This allows you to pass your type to a `Maybe(x)` block that only executes if `x` returns `Some` or `None`.
8
-
9
- > NOTE: Requires the [dry-monads gem](/gems/dry-monads/) to be loaded.
10
- 1. Load the `:maybe` extension in your application.
11
-
12
- ```ruby
13
- require 'dry-types'
14
-
15
- Dry::Types.load_extensions(:maybe)
16
- module Types
17
- include Dry.Types()
18
- end
19
- ```
20
-
21
- 2. Append `.maybe` to a _Type_ to return a _Monad_ object
22
-
23
- ```ruby
24
- x = Types::Maybe::Strict::Integer[nil]
25
- Maybe(x) { puts(x) }
26
- x = Types::Maybe::Coercible::String[nil]
27
- Maybe(x) { puts(x) }
28
- x = Types::Maybe::Strict::Integer[123]
29
- Maybe(x) { puts(x) }
30
- x = Types::Maybe::Strict::String[123]
31
- Maybe(x) { puts(x) }
32
- ```
33
-
34
- ```ruby
35
- Types::Maybe::Strict::Integer[nil] # None
36
- Types::Maybe::Strict::Integer[123] # Some(123)
37
- Types::Maybe::Coercible::Float[nil] # None
38
- Types::Maybe::Coercible::Float['12.3'] # Some(12.3)
39
- # 'Maybe' types can also accessed by calling '.maybe' on a regular type:
40
- Types::Strict::Integer.maybe # equivalent to Types::Maybe::Strict::Integer
41
- ```
42
-
43
- You can define your own optional types:
44
-
45
- ``` ruby
46
- maybe_string = Types::Strict::String.maybe
47
- maybe_string[nil]
48
- # => None
49
- maybe_string[nil].fmap(&:upcase)
50
- # => None
51
- maybe_string['something']
52
- # => Some('something')
53
- maybe_string['something'].fmap(&:upcase)
54
- # => Some('SOMETHING')
55
- maybe_string['something'].fmap(&:upcase).value_or('NOTHING')
56
- # => "SOMETHING"
57
- ```
@@ -1,61 +0,0 @@
1
- ---
2
- title: Monads
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- The monads extension makes `Dry::Types::Result` objects compatible with `dry-monads`.
8
-
9
- To enable the extension:
10
-
11
- ```ruby
12
- require 'dry/types'
13
- Dry::Types.load_extensions(:monads)
14
- ```
15
-
16
- After loading the extension, you can leverage monad API:
17
-
18
- ```ruby
19
- Types = Dry.Types()
20
-
21
- result = Types::String.try('Jane')
22
- result.class #=> Dry::Types::Result::Success
23
- monad = result.to_monad
24
- monad.class #=> Dry::Monads::Result::Success
25
- monad.value! # => 'Jane'
26
- result = Types::String.try(nil)
27
- result.class #=> Dry::Types::Result::Failure
28
- monad = result.to_monad
29
- monad.class #=> Dry::Monads::Result::Failure
30
- monad.failure # => [#<Dry::Types::ConstraintError>, nil]
31
- Types::String.try(nil)
32
- .to_monad
33
- .fmap { |result| puts "passed: #{result.inspect}" }
34
- .or { |error, input| puts "input '#{input.inspect}' failed with error: #{error.to_s}" }
35
- ```
36
-
37
- This can be useful when used with `dry-monads` and the [`do` notation](/gems/dry-monads/1.0/do-notation/):
38
-
39
- ```ruby
40
- require 'dry/types'
41
- Types = Dry.Types()
42
- Dry::Types.load_extensions(:monads)
43
-
44
- class AddTen
45
- include Dry::Monads[:result, :do]
46
-
47
- def call(input)
48
- integer = yield Types::Coercible::Integer.try(input)
49
-
50
- Success(integer + 10)
51
- end
52
- end
53
-
54
- add_ten = AddTen.new
55
-
56
- add_ten.call(10)
57
- # => Success(20)
58
-
59
- add_ten.call('integer')
60
- # => Failure([#<Dry::Types::CoercionError: invalid value for Integer(): "integer">, "integer"])
61
- ```
@@ -1,57 +0,0 @@
1
- ---
2
- title: Getting Started
3
- layout: gem-single
4
- name: dry-types
5
- ---
6
-
7
- ### Using `Dry::Types` in Your Application
8
-
9
- 1. Make `Dry::Types` available to the application by creating a namespace that includes `Dry::Types`:
10
-
11
- ```ruby
12
- module Types
13
- include Dry.Types()
14
- end
15
- ```
16
-
17
- 2. Reload the environment, & type `Types::Coercible::String` in the ruby console to confirm it worked:
18
-
19
- ``` ruby
20
- Types::Coercible::String
21
- # => #<Dry::Types::Constructor type=#<Dry::Types::Definition primitive=String options={}>>
22
- ```
23
-
24
- ### Creating Your First Type
25
-
26
- 1. Define a struct's types by passing the name & type to the `attribute` method:
27
-
28
- ```ruby
29
- class User < Dry::Struct
30
- attribute :name, Types::String
31
- end
32
- ```
33
-
34
- 2. Define [Custom Types](docs::custom-types) in the `Types` module, then pass the name & type to `attribute`:
35
-
36
- ```ruby
37
- module Types
38
- include Dry.Types()
39
-
40
- Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
41
- Age = Integer.constrained(gt: 18)
42
- end
43
- class User < Dry::Struct
44
- attribute :name, Types::String
45
- attribute :email, Types::Email
46
- attribute :age, Types::Age
47
- end
48
- ```
49
-
50
- 3. Use a `Dry::Struct` as a type:
51
-
52
- ```ruby
53
- class Message < Dry::Struct
54
- attribute :body, Types::String
55
- attribute :to, User
56
- end
57
- ```