strict 1.4.0 → 2.0.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.agents/resume +4 -0
  3. data/.agents/setup +105 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +14 -4
  6. data/.tool-versions +1 -1
  7. data/API.md +335 -0
  8. data/CHANGELOG.md +110 -1
  9. data/Gemfile +13 -0
  10. data/Gemfile.lock +118 -42
  11. data/README.md +278 -4
  12. data/Rakefile +13 -7
  13. data/benchmark/baseline.rb +204 -0
  14. data/lib/strict/assignment_error.rb +5 -2
  15. data/lib/strict/attribute.rb +8 -47
  16. data/lib/strict/attributes/class.rb +0 -6
  17. data/lib/strict/attributes/coercer.rb +4 -3
  18. data/lib/strict/attributes/configuration.rb +15 -0
  19. data/lib/strict/attributes/dsl.rb +18 -10
  20. data/lib/strict/attributes/generated_methods.rb +113 -0
  21. data/lib/strict/attributes/instance.rb +32 -15
  22. data/lib/strict/configuration.rb +44 -0
  23. data/lib/strict/declaration.rb +109 -0
  24. data/lib/strict/detailed_validator.rb +9 -0
  25. data/lib/strict/error.rb +8 -1
  26. data/lib/strict/initialization_error.rb +11 -2
  27. data/lib/strict/interface.rb +39 -21
  28. data/lib/strict/interfaces/conformance.rb +125 -0
  29. data/lib/strict/interfaces/instance.rb +1 -49
  30. data/lib/strict/method.rb +72 -53
  31. data/lib/strict/method_call_error.rb +12 -2
  32. data/lib/strict/method_return_error.rb +2 -2
  33. data/lib/strict/methods/dsl.rb +16 -8
  34. data/lib/strict/methods/module.rb +27 -8
  35. data/lib/strict/methods/verifiable_method.rb +234 -70
  36. data/lib/strict/object.rb +1 -1
  37. data/lib/strict/parameter.rb +3 -52
  38. data/lib/strict/return.rb +14 -10
  39. data/lib/strict/rspec.rb +159 -0
  40. data/lib/strict/union.rb +180 -0
  41. data/lib/strict/unions/coercer.rb +55 -0
  42. data/lib/strict/validation.rb +40 -0
  43. data/lib/strict/validators/all_of.rb +11 -3
  44. data/lib/strict/validators/array_of.rb +18 -3
  45. data/lib/strict/validators/hash_of.rb +23 -3
  46. data/lib/strict/value.rb +18 -4
  47. data/lib/strict/version.rb +1 -1
  48. data/lib/strict/violation.rb +9 -0
  49. data/lib/strict.rb +42 -0
  50. data/sig/strict/rspec.rbs +10 -0
  51. data/sig/strict.rbs +213 -1
  52. data/strict.gemspec +1 -9
  53. metadata +20 -120
  54. data/lib/strict/accessor/attributes.rb +0 -15
  55. data/lib/strict/accessor/module.rb +0 -45
  56. data/lib/strict/reader/attributes.rb +0 -15
  57. data/lib/strict/reader/module.rb +0 -27
data/sig/strict.rbs CHANGED
@@ -1,4 +1,216 @@
1
1
  module Strict
2
2
  VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
3
+
4
+ interface _Validator
5
+ def ===: (untyped value) -> bool
6
+ end
7
+
8
+ interface _Coercer[A, B]
9
+ def call: (A value) -> B
10
+ end
11
+
12
+ interface _CoercingValidator
13
+ include _Validator
14
+
15
+ def coercer: () -> _Coercer[untyped, untyped]
16
+ end
17
+
18
+ interface _AttributeDescriptor
19
+ def name: () -> Symbol
20
+ def validator: () -> _Validator
21
+ def optional?: () -> bool
22
+ end
23
+
24
+ interface _AttributesInstance
25
+ def to_h: () -> Hash[Symbol, untyped]
26
+ def inspect: () -> String
27
+ def pretty_print: (untyped printer) -> untyped
28
+ end
29
+
30
+ interface _ValidationDsl
31
+ def AllOf: (*_Validator validators) -> _Validator
32
+ def AnyOf: (*_Validator validators) -> _Validator
33
+ def Anything: () -> _Validator
34
+ def ArrayOf: (_Validator element_validator) -> _CoercingValidator
35
+ def Boolean: () -> _Validator
36
+ def HashOf: (Hash[_Validator, _Validator] validators) -> _CoercingValidator
37
+ def RangeOf: (_Validator element_validator) -> _Validator
38
+ end
39
+
40
+ interface _CoercionDsl
41
+ def ToArray: (?with: _Coercer[untyped, untyped]?) -> _Coercer[untyped, Array[untyped]]
42
+ def ToHash: (?with_keys: _Coercer[untyped, untyped]?, ?with_values: _Coercer[untyped, untyped]?) -> _Coercer[untyped, Hash[untyped, untyped]]
43
+ end
44
+
45
+ type attribute_coercion = bool | Symbol | _Coercer[untyped, untyped]
46
+ type method_coercion = false | _Coercer[untyped, untyped]
47
+ type violation_code = :invalid | :missing | :unexpected
48
+
49
+ interface _AttributesDsl
50
+ include _ValidationDsl
51
+ include _CoercionDsl
52
+
53
+ def strict_attribute: (
54
+ String | Symbol name,
55
+ ?_Validator validator,
56
+ ?coerce: attribute_coercion,
57
+ ?default: untyped,
58
+ ?default_value: untyped,
59
+ ?default_generator: ^() -> untyped
60
+ ) -> void
61
+
62
+ def method_missing: (Symbol name, *untyped arguments, **untyped keywords) -> void
63
+ end
64
+
65
+ interface _MethodDsl
66
+ include _ValidationDsl
67
+ include _CoercionDsl
68
+
69
+ def strict_parameter: (
70
+ String | Symbol name,
71
+ ?_Validator validator,
72
+ ?coerce: method_coercion,
73
+ ?default: untyped,
74
+ ?default_value: untyped,
75
+ ?default_generator: ^() -> untyped
76
+ ) -> void
77
+
78
+ def returns: (?_Validator validator) -> void
79
+ def method_missing: (Symbol name, *untyped arguments, **untyped keywords) -> void
80
+ end
81
+
82
+ # Extend this type-only interface in application signatures for classes that
83
+ # include Strict::Value or Strict::Object.
84
+ interface _AttributesClass
85
+ def attributes: () { () [self: _AttributesDsl] -> untyped } -> void
86
+ def strict_attributes: () -> Enumerable[_AttributeDescriptor]
87
+ def coercer: () -> _Coercer[untyped, untyped]
88
+ end
89
+
90
+ # Extend this type-only interface in application signatures for classes that
91
+ # include Strict::Method.
92
+ interface _MethodClass
93
+ def sig: () { () [self: _MethodDsl] -> untyped } -> void
94
+ end
95
+
96
+ # Extend this type-only interface in application signatures for classes that
97
+ # include Strict::Interface.
98
+ interface _InterfaceClass
99
+ include _MethodClass
100
+
101
+ def expose: (String | Symbol name) { () [self: _MethodDsl] -> untyped } -> void
102
+ def coercer: () -> _Coercer[untyped, untyped]
103
+ def implemented_by?: (untyped adapter) -> bool
104
+ def verify_implementation!: (untyped adapter) -> nil
105
+ end
106
+
107
+ interface _UnionVariantClass
108
+ def attributes: () { () [self: _AttributesDsl] -> untyped } -> void
109
+ end
110
+
111
+ # Extend this type-only interface in application signatures for classes that
112
+ # include Strict::Union.
113
+ interface _UnionClass
114
+ def attributes: () { () [self: _AttributesDsl] -> untyped } -> void
115
+ def discriminator: (String | Symbol name) -> void
116
+ def variant: (String | Symbol name, ?tag: String | Symbol) -> void
117
+ | (String | Symbol name, ?tag: String | Symbol) { () [self: _UnionVariantClass] -> untyped } -> void
118
+ def coercer: () -> _Coercer[untyped, untyped]
119
+ def ===: (untyped value) -> bool
120
+ end
121
+
122
+ def self.configuration: () -> Configuration
123
+ def self.configure: () { (Configuration configuration) -> untyped } -> void
124
+ def self.with_overrides: (
125
+ ?random: Random::Formatter,
126
+ ?sample_rate: Numeric
127
+ ) { () -> untyped } -> void
128
+
129
+ module Value
130
+ include _AttributesInstance
131
+
132
+ def with: (**untyped attributes) -> self
133
+ def deconstruct_keys: (Array[Symbol]? keys) -> Hash[Symbol, untyped]
134
+ def ==: (untyped other) -> bool
135
+ def eql?: (untyped other) -> bool
136
+ def hash: () -> Integer
137
+ end
138
+
139
+ module Object
140
+ include _AttributesInstance
141
+ end
142
+
143
+ module Method
144
+ end
145
+
146
+ module Interface
147
+ def implementation: () -> untyped
148
+ end
149
+
150
+ module Union
151
+ end
152
+
153
+ module DetailedValidator
154
+ def ===: (untyped value) -> bool
155
+ def violations: (untyped value) -> Array[Violation]
156
+ end
157
+
158
+ class Configuration
159
+ attr_accessor random: Random::Formatter
160
+ attr_accessor sample_rate: Numeric
161
+ end
162
+
163
+ class Violation
164
+ def initialize: (
165
+ path: Array[untyped],
166
+ code: violation_code,
167
+ value: untyped,
168
+ validator: _Validator?
169
+ ) -> void
170
+ def path: () -> Array[untyped]
171
+ def code: () -> violation_code
172
+ def value: () -> untyped
173
+ def validator: () -> _Validator?
174
+ end
175
+
176
+ class Error < StandardError
177
+ def violations: () -> Array[Violation]
178
+ end
179
+
180
+ class AssignmentError < Error
181
+ def value: () -> untyped
182
+ end
183
+
184
+ class InitializationError < Error
185
+ def remaining_attributes: () -> Set[Symbol]
186
+ def missing_attributes: () -> Array[Symbol]?
187
+ end
188
+
189
+ type invalid_method_definition = {
190
+ additional_parameters: Array[Symbol],
191
+ missing_parameters: Enumerable[Symbol],
192
+ non_keyword_parameters: Array[Symbol]
193
+ }
194
+
195
+ class ImplementationDoesNotConformError < Error
196
+ def interface: () -> Class
197
+ def receiver: () -> untyped
198
+ def missing_methods: () -> Array[Symbol]?
199
+ def invalid_method_definitions: () -> Hash[Symbol, invalid_method_definition]
200
+ end
201
+
202
+ class MethodCallError < Error
203
+ def remaining_args: () -> Array[untyped]
204
+ def remaining_kwargs: () -> Hash[Symbol, untyped]
205
+ def missing_parameters: () -> Array[Symbol]?
206
+ end
207
+
208
+ class MethodDefinitionError < Error
209
+ def missing_parameters: () -> Enumerable[Symbol]
210
+ def additional_parameters: () -> Enumerable[Symbol]
211
+ end
212
+
213
+ class MethodReturnError < Error
214
+ def value: () -> untyped
215
+ end
4
216
  end
data/strict.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.summary = "Strictly define a contract for your objects and methods"
12
12
  spec.homepage = "https://github.com/kylekthompson/strict"
13
13
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.0.0"
14
+ spec.required_ruby_version = ">= 3.3.0"
15
15
 
16
16
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
17
 
@@ -30,12 +30,4 @@ Gem::Specification.new do |spec|
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  spec.add_dependency "zeitwerk", "~> 2.6"
33
- spec.add_development_dependency "debug", ">= 1.0.0"
34
- spec.add_development_dependency "gem-release", "~> 2.2"
35
- spec.add_development_dependency "minitest", "~> 5.0"
36
- spec.add_development_dependency "minitest-spec-context", "~> 0.0.4"
37
- spec.add_development_dependency "rake", "~> 13.0"
38
- spec.add_development_dependency "rubocop", "~> 1.21"
39
- spec.add_development_dependency "rubocop-minitest", "~> 0.22"
40
- spec.add_development_dependency "rubocop-rake", "~> 0.6"
41
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strict
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Thompson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-02 00:00:00.000000000 Z
11
+ date: 2026-08-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -24,118 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.6'
27
- - !ruby/object:Gem::Dependency
28
- name: debug
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.0.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: 1.0.0
41
- - !ruby/object:Gem::Dependency
42
- name: gem-release
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.2'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.2'
55
- - !ruby/object:Gem::Dependency
56
- name: minitest
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '5.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '5.0'
69
- - !ruby/object:Gem::Dependency
70
- name: minitest-spec-context
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.0.4
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 0.0.4
83
- - !ruby/object:Gem::Dependency
84
- name: rake
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '13.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '13.0'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.21'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.21'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop-minitest
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '0.22'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '0.22'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop-rake
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '0.6'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '0.6'
139
27
  description:
140
28
  email:
141
29
  - me@kkt.dev
@@ -143,8 +31,12 @@ executables: []
143
31
  extensions: []
144
32
  extra_rdoc_files: []
145
33
  files:
34
+ - ".agents/resume"
35
+ - ".agents/setup"
36
+ - ".rspec"
146
37
  - ".rubocop.yml"
147
38
  - ".tool-versions"
39
+ - API.md
148
40
  - CHANGELOG.md
149
41
  - CODE_OF_CONDUCT.md
150
42
  - Gemfile
@@ -152,18 +44,21 @@ files:
152
44
  - LICENSE.txt
153
45
  - README.md
154
46
  - Rakefile
47
+ - benchmark/baseline.rb
155
48
  - lib/strict.rb
156
- - lib/strict/accessor/attributes.rb
157
- - lib/strict/accessor/module.rb
158
49
  - lib/strict/assignment_error.rb
159
50
  - lib/strict/attribute.rb
160
51
  - lib/strict/attributes/class.rb
161
52
  - lib/strict/attributes/coercer.rb
162
53
  - lib/strict/attributes/configuration.rb
163
54
  - lib/strict/attributes/dsl.rb
55
+ - lib/strict/attributes/generated_methods.rb
164
56
  - lib/strict/attributes/instance.rb
165
57
  - lib/strict/coercers/array.rb
166
58
  - lib/strict/coercers/hash.rb
59
+ - lib/strict/configuration.rb
60
+ - lib/strict/declaration.rb
61
+ - lib/strict/detailed_validator.rb
167
62
  - lib/strict/dsl/coercible.rb
168
63
  - lib/strict/dsl/validatable.rb
169
64
  - lib/strict/error.rb
@@ -171,6 +66,7 @@ files:
171
66
  - lib/strict/initialization_error.rb
172
67
  - lib/strict/interface.rb
173
68
  - lib/strict/interfaces/coercer.rb
69
+ - lib/strict/interfaces/conformance.rb
174
70
  - lib/strict/interfaces/instance.rb
175
71
  - lib/strict/method.rb
176
72
  - lib/strict/method_call_error.rb
@@ -182,9 +78,11 @@ files:
182
78
  - lib/strict/methods/verifiable_method.rb
183
79
  - lib/strict/object.rb
184
80
  - lib/strict/parameter.rb
185
- - lib/strict/reader/attributes.rb
186
- - lib/strict/reader/module.rb
187
81
  - lib/strict/return.rb
82
+ - lib/strict/rspec.rb
83
+ - lib/strict/union.rb
84
+ - lib/strict/unions/coercer.rb
85
+ - lib/strict/validation.rb
188
86
  - lib/strict/validators/all_of.rb
189
87
  - lib/strict/validators/any_of.rb
190
88
  - lib/strict/validators/anything.rb
@@ -194,7 +92,9 @@ files:
194
92
  - lib/strict/validators/range_of.rb
195
93
  - lib/strict/value.rb
196
94
  - lib/strict/version.rb
95
+ - lib/strict/violation.rb
197
96
  - sig/strict.rbs
97
+ - sig/strict/rspec.rbs
198
98
  - strict.gemspec
199
99
  homepage: https://github.com/kylekthompson/strict
200
100
  licenses:
@@ -213,14 +113,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
213
113
  requirements:
214
114
  - - ">="
215
115
  - !ruby/object:Gem::Version
216
- version: 3.0.0
116
+ version: 3.3.0
217
117
  required_rubygems_version: !ruby/object:Gem::Requirement
218
118
  requirements:
219
119
  - - ">="
220
120
  - !ruby/object:Gem::Version
221
121
  version: '0'
222
122
  requirements: []
223
- rubygems_version: 3.2.3
123
+ rubygems_version: 3.5.22
224
124
  signing_key:
225
125
  specification_version: 4
226
126
  summary: Strictly define a contract for your objects and methods
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Strict
4
- module Accessor
5
- module Attributes
6
- def attributes(&block)
7
- block ||= -> {}
8
- configuration = Strict::Attributes::Dsl.run(&block)
9
- include Module.new(configuration)
10
- include Strict::Attributes::Instance
11
- extend Strict::Attributes::Class
12
- end
13
- end
14
- end
15
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Strict
4
- module Accessor
5
- class Module < ::Module
6
- attr_reader :configuration
7
-
8
- # rubocop:disable Metrics/MethodLength
9
- def initialize(configuration)
10
- super()
11
-
12
- @configuration = configuration
13
- const_set(Strict::Attributes::Class::CONSTANT, configuration)
14
- configuration.attributes.each do |attribute|
15
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
16
- def #{attribute.name} # def name
17
- #{attribute.instance_variable} # @instance_variable
18
- end # end
19
- RUBY
20
-
21
- module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
22
- def #{attribute.name}=(value) # def name=(value)
23
- attribute = self.class.strict_attributes.named!(:#{attribute.name}) # attribute = self.class.strict_attributes.named!(:name)
24
- value = attribute.coerce(value, for_class: self.class) # value = attribute.coerce(value, for_class: self.class)
25
- if attribute.valid?(value) # if attribute.valid?(value)
26
- #{attribute.instance_variable} = value # @instance_variable = value
27
- else # else
28
- raise Strict::AssignmentError.new( # raise Strict::AssignmentError.new(
29
- assignable_class: self.class, # assignable_class: self.class,
30
- invalid_attribute: attribute, # invalid_attribute: attribute,
31
- value: value # value: value
32
- ) # )
33
- end # end
34
- end # end
35
- RUBY
36
- end
37
- end
38
- # rubocop:enable Metrics/MethodLength
39
-
40
- def inspect
41
- "#<#{self.class} (#{configuration.attributes.map(&:name).join(', ')})>"
42
- end
43
- end
44
- end
45
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Strict
4
- module Reader
5
- module Attributes
6
- def attributes(&block)
7
- block ||= -> {}
8
- configuration = Strict::Attributes::Dsl.run(&block)
9
- include Module.new(configuration)
10
- include Strict::Attributes::Instance
11
- extend Strict::Attributes::Class
12
- end
13
- end
14
- end
15
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Strict
4
- module Reader
5
- class Module < ::Module
6
- attr_reader :configuration
7
-
8
- def initialize(configuration)
9
- super()
10
-
11
- @configuration = configuration
12
- const_set(Strict::Attributes::Class::CONSTANT, configuration)
13
- configuration.attributes.each do |attribute|
14
- module_eval(
15
- "def #{attribute.name} = #{attribute.instance_variable}", # def name = @instance_variable
16
- __FILE__,
17
- __LINE__ - 2
18
- )
19
- end
20
- end
21
-
22
- def inspect
23
- "#<#{self.class} (#{configuration.attributes.map(&:name).join(', ')})>"
24
- end
25
- end
26
- end
27
- end