media_types 2.3.0 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/debian.yml +43 -43
  3. data/.github/workflows/publish-bookworm.yml +34 -33
  4. data/.github/workflows/publish-sid.yml +34 -33
  5. data/.github/workflows/ruby.yml +22 -22
  6. data/.gitignore +20 -20
  7. data/.rubocop.yml +29 -29
  8. data/CHANGELOG.md +183 -175
  9. data/Gemfile +6 -6
  10. data/Gemfile.lock +43 -43
  11. data/LICENSE +21 -21
  12. data/README.md +666 -666
  13. data/Rakefile +12 -12
  14. data/bin/console +15 -15
  15. data/bin/setup +8 -8
  16. data/lib/media_types/constructable.rb +161 -161
  17. data/lib/media_types/dsl/errors.rb +18 -18
  18. data/lib/media_types/dsl.rb +172 -172
  19. data/lib/media_types/errors.rb +25 -25
  20. data/lib/media_types/formatter.rb +56 -56
  21. data/lib/media_types/hash.rb +21 -21
  22. data/lib/media_types/object.rb +35 -35
  23. data/lib/media_types/scheme/allow_nil.rb +30 -30
  24. data/lib/media_types/scheme/any_of.rb +41 -41
  25. data/lib/media_types/scheme/attribute.rb +46 -46
  26. data/lib/media_types/scheme/enumeration_context.rb +18 -18
  27. data/lib/media_types/scheme/enumeration_of_type.rb +80 -80
  28. data/lib/media_types/scheme/errors.rb +87 -87
  29. data/lib/media_types/scheme/links.rb +54 -54
  30. data/lib/media_types/scheme/missing_validation.rb +41 -41
  31. data/lib/media_types/scheme/not_strict.rb +15 -15
  32. data/lib/media_types/scheme/output_empty_guard.rb +45 -45
  33. data/lib/media_types/scheme/output_iterator_with_predicate.rb +66 -66
  34. data/lib/media_types/scheme/output_type_guard.rb +39 -39
  35. data/lib/media_types/scheme/rules.rb +186 -186
  36. data/lib/media_types/scheme/rules_exhausted_guard.rb +75 -73
  37. data/lib/media_types/scheme/validation_options.rb +44 -44
  38. data/lib/media_types/scheme.rb +535 -535
  39. data/lib/media_types/testing/assertions.rb +20 -20
  40. data/lib/media_types/validations.rb +118 -118
  41. data/lib/media_types/version.rb +5 -5
  42. data/lib/media_types/views.rb +12 -12
  43. data/lib/media_types.rb +73 -73
  44. data/media_types.gemspec +33 -33
  45. metadata +6 -6
@@ -1,186 +1,186 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/missing_validation'
4
-
5
- module MediaTypes
6
- class Scheme
7
- class Rules < DelegateClass(::Hash)
8
-
9
- attr_reader :expected_type
10
-
11
- def initialize(allow_empty:, expected_type:)
12
- super({})
13
-
14
- self.allow_empty = allow_empty
15
- self.expected_type = expected_type
16
- self.optional_keys = []
17
- self.strict_keys = []
18
- self.original_key_type = {}
19
-
20
- self.default = MissingValidation.new
21
- end
22
-
23
- def allow_empty?
24
- allow_empty
25
- end
26
-
27
- def [](key)
28
- __getobj__[normalize_key(key)]
29
- end
30
-
31
- def add(key, val, optional: false)
32
- validate_input(key, val)
33
-
34
- normalized_key = normalize_key(key)
35
- __getobj__[normalized_key] = val
36
- if optional == :loose
37
- strict_keys << normalized_key
38
- else
39
- optional_keys << normalized_key if optional
40
- end
41
- original_key_type[normalized_key] = key.class
42
-
43
- self
44
- end
45
-
46
- def validate_input(key, val)
47
- raise KeyTypeError, "Unexpected key type #{key.class.name}, please use either a symbol or string." unless key.is_a?(String) || key.is_a?(Symbol)
48
-
49
- validate_key_name(key, val)
50
- end
51
-
52
- def validate_key_name(key, val)
53
- return unless has_key?(key)
54
-
55
- if key.is_a?(Symbol)
56
- duplicate_symbol_key_name(key, val)
57
- else
58
- duplicate_string_key_name(key, val)
59
- end
60
- end
61
-
62
- def duplicate_symbol_key_name(key, val)
63
- raise DuplicateSymbolKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == Symbol
64
-
65
- raise SymbolOverwritingStringError.new(val.class.name.split('::').last, key)
66
- end
67
-
68
- def duplicate_string_key_name(key, val)
69
- raise DuplicateStringKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == String
70
-
71
- raise StringOverwritingSymbolError.new(val.class.name.split('::').last, key)
72
- end
73
-
74
- def []=(key, val)
75
- add(key, val, optional: false)
76
- end
77
-
78
- def fetch(key, &block)
79
- __getobj__.fetch(normalize_key(key), &block)
80
- end
81
-
82
- def delete(key)
83
- __getobj__.delete(normalize_key(key))
84
- self
85
- end
86
-
87
- ##
88
- # Returns the keys that are not options
89
- #
90
- # @see #add
91
- # #see #merge
92
- #
93
- # @return [Array<Symbol>] required keys
94
- #
95
- def required(loose:)
96
- clone.tap do |cloned|
97
- optional_keys.each do |key|
98
- cloned.delete(key)
99
- end
100
- if loose
101
- strict_keys.each do |key|
102
- cloned.delete(key)
103
- end
104
- end
105
- end
106
- end
107
-
108
- def clone
109
- super.tap do |cloned|
110
- cloned.__setobj__(__getobj__.clone)
111
- end
112
- end
113
-
114
- ##
115
- # Merges another set of rules into a clone of this one
116
- #
117
- # @param [Rules, ::Hash] the other rules
118
- # @return [Rules] a clone
119
- #
120
- def merge(rules)
121
- clone.instance_exec do
122
- __setobj__(__getobj__.merge(rules))
123
- if rules.respond_to?(:optional_keys, true)
124
- optional_keys.push(*rules.send(:optional_keys))
125
- end
126
- if rules.respond_to?(:strict_keys, true)
127
- strict_keys.push(*rules.send(:strict_keys))
128
- end
129
-
130
- self
131
- end
132
- end
133
-
134
- def inspect(indent = 0)
135
- prefix = ' ' * indent
136
- return "#{prefix}[Error]Depth limit reached[/Error]" if indent > 5_000
137
-
138
- [
139
- "#{prefix}[Rules n=#{keys.length} optional=#{optional_keys.length} strict=#{strict_keys.length} allow_empty=#{allow_empty?}]",
140
- "#{prefix} #{inspect_format_attribute(indent, '*', default)}",
141
- *keys.map { |key| "#{prefix} #{inspect_format_attribute(indent, key)}" },
142
- "#{prefix}[/Rules]"
143
- ].join("\n")
144
- end
145
-
146
- def inspect_format_attribute(indent, key, value = self[key])
147
- [
148
- [key.to_s, optional_keys.include?(key) && '(optional)' || nil].compact.join(' '),
149
- value.is_a?(Scheme) || value.is_a?(Rules) ? "\n#{value.inspect(indent + 2)}" : value.inspect
150
- ].join(': ')
151
- end
152
-
153
- def has_key?(key)
154
- __getobj__.key?(normalize_key(key))
155
- end
156
-
157
- def get_original_key_type(key)
158
- raise format('Key %<key>s does not exist', key: key) unless has_key?(key)
159
-
160
- original_key_type[normalize_key(key)]
161
- end
162
-
163
- def default=(input_default)
164
- unless default.nil?
165
- raise DuplicateAnyRuleError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
166
- raise DuplicateNotStrictRuleError if default.is_a?(NotStrict) && input_default.is_a?(NotStrict)
167
- raise NotStrictOverwritingAnyError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && input_default.is_a?(NotStrict)
168
- raise AnyOverwritingNotStrictError if default.is_a?(NotStrict) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
169
- end
170
- super(input_default)
171
- end
172
-
173
- alias get []
174
- alias remove delete
175
-
176
- private
177
-
178
- attr_accessor :allow_empty, :strict_keys, :optional_keys, :original_key_type
179
- attr_writer :expected_type
180
-
181
- def normalize_key(key)
182
- String(key).to_sym
183
- end
184
- end
185
- end
186
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/missing_validation'
4
+
5
+ module MediaTypes
6
+ class Scheme
7
+ class Rules < DelegateClass(::Hash)
8
+
9
+ attr_reader :expected_type
10
+
11
+ def initialize(allow_empty:, expected_type:)
12
+ super({})
13
+
14
+ self.allow_empty = allow_empty
15
+ self.expected_type = expected_type
16
+ self.optional_keys = []
17
+ self.strict_keys = []
18
+ self.original_key_type = {}
19
+
20
+ self.default = MissingValidation.new
21
+ end
22
+
23
+ def allow_empty?
24
+ allow_empty
25
+ end
26
+
27
+ def [](key)
28
+ __getobj__[normalize_key(key)]
29
+ end
30
+
31
+ def add(key, val, optional: false)
32
+ validate_input(key, val)
33
+
34
+ normalized_key = normalize_key(key)
35
+ __getobj__[normalized_key] = val
36
+ if optional == :loose
37
+ strict_keys << normalized_key
38
+ else
39
+ optional_keys << normalized_key if optional
40
+ end
41
+ original_key_type[normalized_key] = key.class
42
+
43
+ self
44
+ end
45
+
46
+ def validate_input(key, val)
47
+ raise KeyTypeError, "Unexpected key type #{key.class.name}, please use either a symbol or string." unless key.is_a?(String) || key.is_a?(Symbol)
48
+
49
+ validate_key_name(key, val)
50
+ end
51
+
52
+ def validate_key_name(key, val)
53
+ return unless has_key?(key)
54
+
55
+ if key.is_a?(Symbol)
56
+ duplicate_symbol_key_name(key, val)
57
+ else
58
+ duplicate_string_key_name(key, val)
59
+ end
60
+ end
61
+
62
+ def duplicate_symbol_key_name(key, val)
63
+ raise DuplicateSymbolKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == Symbol
64
+
65
+ raise SymbolOverwritingStringError.new(val.class.name.split('::').last, key)
66
+ end
67
+
68
+ def duplicate_string_key_name(key, val)
69
+ raise DuplicateStringKeyError.new(val.class.name.split('::').last, key) if get_original_key_type(key) == String
70
+
71
+ raise StringOverwritingSymbolError.new(val.class.name.split('::').last, key)
72
+ end
73
+
74
+ def []=(key, val)
75
+ add(key, val, optional: false)
76
+ end
77
+
78
+ def fetch(key, &block)
79
+ __getobj__.fetch(normalize_key(key), &block)
80
+ end
81
+
82
+ def delete(key)
83
+ __getobj__.delete(normalize_key(key))
84
+ self
85
+ end
86
+
87
+ ##
88
+ # Returns the keys that are not options
89
+ #
90
+ # @see #add
91
+ # #see #merge
92
+ #
93
+ # @return [Array<Symbol>] required keys
94
+ #
95
+ def required(loose:)
96
+ clone.tap do |cloned|
97
+ optional_keys.each do |key|
98
+ cloned.delete(key)
99
+ end
100
+ if loose
101
+ strict_keys.each do |key|
102
+ cloned.delete(key)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ def clone
109
+ super.tap do |cloned|
110
+ cloned.__setobj__(__getobj__.clone)
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Merges another set of rules into a clone of this one
116
+ #
117
+ # @param [Rules, ::Hash] the other rules
118
+ # @return [Rules] a clone
119
+ #
120
+ def merge(rules)
121
+ clone.instance_exec do
122
+ __setobj__(__getobj__.merge(rules))
123
+ if rules.respond_to?(:optional_keys, true)
124
+ optional_keys.push(*rules.send(:optional_keys))
125
+ end
126
+ if rules.respond_to?(:strict_keys, true)
127
+ strict_keys.push(*rules.send(:strict_keys))
128
+ end
129
+
130
+ self
131
+ end
132
+ end
133
+
134
+ def inspect(indent = 0)
135
+ prefix = ' ' * indent
136
+ return "#{prefix}[Error]Depth limit reached[/Error]" if indent > 5_000
137
+
138
+ [
139
+ "#{prefix}[Rules n=#{keys.length} optional=#{optional_keys.length} strict=#{strict_keys.length} allow_empty=#{allow_empty?}]",
140
+ "#{prefix} #{inspect_format_attribute(indent, '*', default)}",
141
+ *keys.map { |key| "#{prefix} #{inspect_format_attribute(indent, key)}" },
142
+ "#{prefix}[/Rules]"
143
+ ].join("\n")
144
+ end
145
+
146
+ def inspect_format_attribute(indent, key, value = self[key])
147
+ [
148
+ [key.to_s, optional_keys.include?(key) && '(optional)' || nil].compact.join(' '),
149
+ value.is_a?(Scheme) || value.is_a?(Rules) ? "\n#{value.inspect(indent + 2)}" : value.inspect
150
+ ].join(': ')
151
+ end
152
+
153
+ def has_key?(key)
154
+ __getobj__.key?(normalize_key(key))
155
+ end
156
+
157
+ def get_original_key_type(key)
158
+ raise format('Key %<key>s does not exist', key: key) unless has_key?(key)
159
+
160
+ original_key_type[normalize_key(key)]
161
+ end
162
+
163
+ def default=(input_default)
164
+ unless default.nil?
165
+ raise DuplicateAnyRuleError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
166
+ raise DuplicateNotStrictRuleError if default.is_a?(NotStrict) && input_default.is_a?(NotStrict)
167
+ raise NotStrictOverwritingAnyError if !(default.is_a?(MissingValidation) || default.is_a?(NotStrict)) && input_default.is_a?(NotStrict)
168
+ raise AnyOverwritingNotStrictError if default.is_a?(NotStrict) && !(input_default.is_a?(MissingValidation) || input_default.is_a?(NotStrict))
169
+ end
170
+ super(input_default)
171
+ end
172
+
173
+ alias get []
174
+ alias remove delete
175
+
176
+ private
177
+
178
+ attr_accessor :allow_empty, :strict_keys, :optional_keys, :original_key_type
179
+ attr_writer :expected_type
180
+
181
+ def normalize_key(key)
182
+ String(key).to_sym
183
+ end
184
+ end
185
+ end
186
+ end
@@ -1,73 +1,75 @@
1
- # frozen_string_literal: true
2
-
3
- require 'media_types/scheme/errors'
4
- require 'media_types/scheme/output_iterator_with_predicate'
5
-
6
- module MediaTypes
7
- class Scheme
8
- class RulesExhaustedGuard
9
-
10
- EMPTY_MARK = ->(_) {}
11
-
12
- class << self
13
- def call(*args, **opts, &block)
14
- new(*args, **opts).call(&block)
15
- end
16
- end
17
-
18
- def initialize(output, options, rules:)
19
- self.rules = rules
20
- self.output = output
21
- self.options = options
22
- end
23
-
24
- def call
25
- unless options.exhaustive
26
- return iterate(EMPTY_MARK)
27
- end
28
-
29
- required_rules = rules.required(loose: options.loose)
30
- # noinspection RubyScope
31
- result = iterate(->(key) { required_rules.remove(key) })
32
- return result if required_rules.empty?
33
-
34
- raise_exhausted!(missing_keys: required_rules.keys, backtrace: options.backtrace, found: output)
35
- end
36
-
37
- def iterate(mark)
38
- OutputIteratorWithPredicate.call(output, options, rules: rules) do |key, value, options:, context:|
39
- unless key.class == options.expected_key_type || rules.get(key).class == NotStrict
40
- raise ValidationError,
41
- format(
42
- 'Expected key as %<type>s, got %<actual>s at [%<backtrace>s]',
43
- type: options.expected_key_type,
44
- actual: key.class,
45
- backtrace: options.trace(key).backtrace.join('->')
46
- )
47
- end
48
-
49
- mark.call(key)
50
-
51
- rules.get(key).validate!(
52
- value,
53
- options.trace(key),
54
- context: context
55
- )
56
- end
57
- end
58
-
59
- private
60
-
61
- attr_accessor :rules, :options, :output
62
-
63
- def raise_exhausted!(missing_keys:, backtrace:, found:)
64
- raise ExhaustedOutputError, format(
65
- 'Missing keys in output: %<missing_keys>s at [%<backtrace>s]. I did find: %<found>s',
66
- missing_keys: missing_keys,
67
- backtrace: backtrace.join('->'),
68
- found: (found.is_a? Hash) ? found.keys : found.class.name,
69
- )
70
- end
71
- end
72
- end
73
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/scheme/errors'
4
+ require 'media_types/scheme/output_iterator_with_predicate'
5
+
6
+ module MediaTypes
7
+ class Scheme
8
+ class RulesExhaustedGuard
9
+
10
+ EMPTY_MARK = ->(_) {}
11
+
12
+ class << self
13
+ def call(*args, **opts, &block)
14
+ new(*args, **opts).call(&block)
15
+ end
16
+ end
17
+
18
+ def initialize(output, options, rules:)
19
+ self.rules = rules
20
+ self.output = output
21
+ self.options = options
22
+ end
23
+
24
+ def call
25
+ unless options.exhaustive
26
+ return iterate(EMPTY_MARK)
27
+ end
28
+
29
+ required_rules = rules.required(loose: options.loose)
30
+ # noinspection RubyScope
31
+ result = iterate(->(key) { required_rules.remove(key) })
32
+ return result if required_rules.empty?
33
+
34
+ raise_exhausted!(missing_keys: required_rules.keys, backtrace: options.backtrace, found: output)
35
+ end
36
+
37
+ def iterate(mark)
38
+ OutputIteratorWithPredicate.call(output, options, rules: rules) do |key, value, options:, context:|
39
+ unless key.class == options.expected_key_type || rules.get(key).class == NotStrict
40
+ raise ValidationError,
41
+ format(
42
+ 'Expected key as %<type>s, got %<actual>s at [%<backtrace>s]',
43
+ type: options.expected_key_type,
44
+ actual: key.class,
45
+ backtrace: options.trace(key).backtrace.join('->')
46
+ )
47
+ end
48
+
49
+ mark.call(key)
50
+
51
+ catch(:end) do
52
+ rules.get(key).validate!(
53
+ value,
54
+ options.trace(key),
55
+ context: context
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ attr_accessor :rules, :options, :output
64
+
65
+ def raise_exhausted!(missing_keys:, backtrace:, found:)
66
+ raise ExhaustedOutputError, format(
67
+ 'Missing keys in output: %<missing_keys>s at [%<backtrace>s]. I did find: %<found>s',
68
+ missing_keys: missing_keys,
69
+ backtrace: backtrace.join('->'),
70
+ found: (found.is_a? Hash) ? found.keys : found.class.name,
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -1,44 +1,44 @@
1
- # frozen_string_literal: true
2
-
3
- module MediaTypes
4
- class Scheme
5
- class ValidationOptions
6
- attr_accessor :exhaustive, :strict, :backtrace, :context, :expected_key_type, :loose
7
-
8
- def initialize(context = {}, exhaustive: true, strict: true, backtrace: [], loose: false, expected_key_type:)
9
- self.exhaustive = exhaustive
10
- self.strict = strict
11
- self.backtrace = backtrace
12
- self.context = context
13
- self.expected_key_type = expected_key_type
14
- self.loose = loose
15
- end
16
-
17
- def inspect
18
- "backtrack: #{backtrace.inspect}, strict: #{strict.inspect}, loose: #{loose}, exhaustive: #{exhaustive}, current_obj: #{scoped_output.to_json}"
19
- end
20
-
21
- def scoped_output
22
- current = context
23
-
24
- backtrace.drop(1).first([0, backtrace.size - 2].max).each do |e|
25
- current = current[e] unless current.nil?
26
- end
27
-
28
- current
29
- end
30
-
31
- def with_backtrace(backtrace)
32
- ValidationOptions.new(context, exhaustive: exhaustive, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
33
- end
34
-
35
- def trace(*traces)
36
- with_backtrace(backtrace.dup.concat(traces))
37
- end
38
-
39
- def exhaustive!
40
- ValidationOptions.new(context, exhaustive: true, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
41
- end
42
- end
43
- end
44
- end
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Scheme
5
+ class ValidationOptions
6
+ attr_accessor :exhaustive, :strict, :backtrace, :context, :expected_key_type, :loose
7
+
8
+ def initialize(context = {}, exhaustive: true, strict: true, backtrace: [], loose: false, expected_key_type:)
9
+ self.exhaustive = exhaustive
10
+ self.strict = strict
11
+ self.backtrace = backtrace
12
+ self.context = context
13
+ self.expected_key_type = expected_key_type
14
+ self.loose = loose
15
+ end
16
+
17
+ def inspect
18
+ "backtrack: #{backtrace.inspect}, strict: #{strict.inspect}, loose: #{loose}, exhaustive: #{exhaustive}, current_obj: #{scoped_output.to_json}"
19
+ end
20
+
21
+ def scoped_output
22
+ current = context
23
+
24
+ backtrace.drop(1).first([0, backtrace.size - 2].max).each do |e|
25
+ current = current[e] unless current.nil?
26
+ end
27
+
28
+ current
29
+ end
30
+
31
+ def with_backtrace(backtrace)
32
+ ValidationOptions.new(context, exhaustive: exhaustive, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
33
+ end
34
+
35
+ def trace(*traces)
36
+ with_backtrace(backtrace.dup.concat(traces))
37
+ end
38
+
39
+ def exhaustive!
40
+ ValidationOptions.new(context, exhaustive: true, strict: strict, backtrace: backtrace, expected_key_type: expected_key_type, loose: loose)
41
+ end
42
+ end
43
+ end
44
+ end