media_types 0.4.1 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +44 -44
- data/README.md +379 -347
- data/lib/media_types/constructable.rb +12 -16
- data/lib/media_types/dsl.rb +7 -0
- data/lib/media_types/formatter.rb +55 -0
- data/lib/media_types/scheme.rb +59 -131
- data/lib/media_types/scheme/allow_nil.rb +2 -10
- data/lib/media_types/scheme/any_of.rb +4 -0
- data/lib/media_types/scheme/attribute.rb +4 -0
- data/lib/media_types/scheme/enumeration_context.rb +5 -3
- data/lib/media_types/scheme/enumeration_of_type.rb +4 -0
- data/lib/media_types/scheme/errors.rb +21 -0
- data/lib/media_types/scheme/links.rb +18 -12
- data/lib/media_types/scheme/missing_validation.rb +1 -1
- data/lib/media_types/scheme/not_strict.rb +4 -0
- data/lib/media_types/scheme/output_empty_guard.rb +40 -0
- data/lib/media_types/scheme/output_iterator_with_predicate.rb +55 -0
- data/lib/media_types/scheme/output_type_guard.rb +39 -0
- data/lib/media_types/scheme/rules.rb +84 -0
- data/lib/media_types/scheme/rules_exhausted_guard.rb +62 -0
- data/lib/media_types/version.rb +5 -5
- metadata +9 -2
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module MediaTypes
|
2
4
|
class Scheme
|
3
5
|
class EnumerationContext
|
4
|
-
def initialize(
|
5
|
-
self.
|
6
|
+
def initialize(rules:)
|
7
|
+
self.rules = rules
|
6
8
|
end
|
7
9
|
|
8
10
|
def enumerate(val)
|
@@ -10,7 +12,7 @@ module MediaTypes
|
|
10
12
|
self
|
11
13
|
end
|
12
14
|
|
13
|
-
attr_accessor :
|
15
|
+
attr_accessor :rules, :key
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -25,6 +25,10 @@ module MediaTypes
|
|
25
25
|
validate_items!(output, options)
|
26
26
|
end
|
27
27
|
|
28
|
+
def inspect
|
29
|
+
"[Scheme::EnumerationOfType #{item_type} collection=#{enumeration_type} empty=#{allow_empty}]"
|
30
|
+
end
|
31
|
+
|
28
32
|
private
|
29
33
|
|
30
34
|
attr_accessor :allow_empty, :enumeration_type, :item_type
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
class Scheme
|
5
|
+
|
6
|
+
# Base class for all validations errors
|
7
|
+
class ValidationError < ArgumentError; end
|
8
|
+
|
9
|
+
# Raised when it did not expect more data, but there was more left
|
10
|
+
class StrictValidationError < ValidationError; end
|
11
|
+
|
12
|
+
# Raised when it expected not to be empty, but it was
|
13
|
+
class EmptyOutputError < ValidationError; end
|
14
|
+
|
15
|
+
# Raised when a value did not have the expected type
|
16
|
+
class OutputTypeMismatch < ValidationError; end
|
17
|
+
|
18
|
+
# Raised when it expected more data but there wasn't any left
|
19
|
+
class ExhaustedOutputError < ValidationError; end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,27 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'media_types/scheme/rules'
|
4
|
+
|
3
5
|
module MediaTypes
|
4
6
|
class Scheme
|
5
7
|
class Links
|
6
8
|
def initialize
|
7
|
-
self.links =
|
9
|
+
self.links = Rules.new(allow_empty: false, expected_type: ::Hash)
|
8
10
|
end
|
9
11
|
|
10
|
-
def link(key, allow_nil: false, &block)
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
def link(key, allow_nil: false, optional: false, &block)
|
13
|
+
links.add(
|
14
|
+
key,
|
15
|
+
Scheme.new do
|
16
|
+
attribute :href, String, allow_nil: allow_nil
|
17
|
+
instance_exec(&block) if block_given?
|
18
|
+
end,
|
19
|
+
optional: optional
|
20
|
+
)
|
14
21
|
|
15
|
-
|
22
|
+
self
|
16
23
|
end
|
17
24
|
|
18
25
|
def validate!(output, options, **_opts)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
26
|
+
RulesExhaustedGuard.call(output, options, rules: links)
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"[Scheme::Links #{links.keys}]"
|
25
31
|
end
|
26
32
|
|
27
33
|
private
|
@@ -7,7 +7,7 @@ module MediaTypes
|
|
7
7
|
def validate!(_output, options, context:, **_opts)
|
8
8
|
# Check that no unknown keys are present
|
9
9
|
return true unless options.strict
|
10
|
-
raise_strict!(key: context.key, strict_keys: context.
|
10
|
+
raise_strict!(key: context.key, strict_keys: context.rules, backtrace: options.backtrace)
|
11
11
|
end
|
12
12
|
|
13
13
|
def raise_strict!(key:, backtrace:, strict_keys:)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'media_types/scheme/errors'
|
4
|
+
require 'media_types/object'
|
5
|
+
|
6
|
+
module MediaTypes
|
7
|
+
class Scheme
|
8
|
+
class OutputEmptyGuard
|
9
|
+
class << self
|
10
|
+
def call(*args, **opts, &block)
|
11
|
+
new(*args, **opts).call(&block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(output, options, rules:)
|
16
|
+
self.output = output
|
17
|
+
self.options = options
|
18
|
+
self.rules = rules
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
return unless MediaTypes::Object.new(output).empty?
|
23
|
+
throw(:end, true) if allow_empty?
|
24
|
+
raise_empty!(backtrace: options.backtrace)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_accessor :output, :options, :rules
|
30
|
+
|
31
|
+
def allow_empty?
|
32
|
+
rules.allow_empty? || rules.required.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def raise_empty!(backtrace:)
|
36
|
+
raise EmptyOutputError, format('Expected output, got empty at %<backtrace>s', backtrace: backtrace.join('->'))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'media_types/scheme/enumeration_context'
|
4
|
+
|
5
|
+
module MediaTypes
|
6
|
+
class Scheme
|
7
|
+
class OutputIteratorWithPredicate
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def call(*args, **opts, &block)
|
11
|
+
new(*args, **opts).call(&block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(enumerable, options, rules:)
|
16
|
+
self.enumerable = enumerable
|
17
|
+
self.options = options
|
18
|
+
self.rules = rules
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Mimics Enumerable#all? with mandatory +&block+
|
23
|
+
#
|
24
|
+
def call
|
25
|
+
if hash?
|
26
|
+
return iterate_hash { |*args, **opts| yield(*args, **opts) }
|
27
|
+
end
|
28
|
+
|
29
|
+
iterate { |*args, **opts| yield(*args, **opts) }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_accessor :enumerable, :options, :rules
|
35
|
+
|
36
|
+
def hash?
|
37
|
+
enumerable.is_a?(::Hash) || enumerable.respond_to?(:key)
|
38
|
+
end
|
39
|
+
|
40
|
+
def iterate_hash
|
41
|
+
context = EnumerationContext.new(rules: rules)
|
42
|
+
|
43
|
+
enumerable.all? do |key, value|
|
44
|
+
yield key, value, options: options, context: context.enumerate(key)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def iterate(&block)
|
49
|
+
Array(enumerable).each_with_index.all? do |array_like_element, i|
|
50
|
+
OutputIteratorWithPredicate.call(array_like_element, options.trace(i), rules: rules, &block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'media_types/scheme/errors'
|
4
|
+
|
5
|
+
module MediaTypes
|
6
|
+
class Scheme
|
7
|
+
class OutputTypeGuard
|
8
|
+
class << self
|
9
|
+
def call(*args, **opts, &block)
|
10
|
+
new(*args, **opts).call(&block)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(output, options, rules:)
|
15
|
+
self.output = output
|
16
|
+
self.options = options
|
17
|
+
self.expected_type = rules.expected_type
|
18
|
+
end
|
19
|
+
|
20
|
+
def call
|
21
|
+
return unless expected_type && !(expected_type === output) # rubocop:disable Style/CaseEquality
|
22
|
+
raise_type_error!(type: output.class, backtrace: options.backtrace)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_accessor :output, :options, :expected_type
|
28
|
+
|
29
|
+
def raise_type_error!(type:, backtrace:)
|
30
|
+
raise OutputTypeMismatch, format(
|
31
|
+
'Expected a %<expected>s, got a %<actual>s at %<backtrace>s',
|
32
|
+
expected: expected_type,
|
33
|
+
actual: type,
|
34
|
+
backtrace: backtrace.join('->')
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
class Scheme
|
5
|
+
class Rules < DelegateClass(::Hash)
|
6
|
+
|
7
|
+
attr_reader :expected_type
|
8
|
+
|
9
|
+
def initialize(allow_empty:, expected_type:)
|
10
|
+
super({})
|
11
|
+
|
12
|
+
self.allow_empty = allow_empty
|
13
|
+
self.expected_type = expected_type
|
14
|
+
self.optional_keys = []
|
15
|
+
|
16
|
+
self.default = MissingValidation.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def allow_empty?
|
20
|
+
allow_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
__getobj__[normalize_key(key)]
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(key, val, optional: false)
|
28
|
+
normalized_key = normalize_key(key)
|
29
|
+
__getobj__[normalized_key] = val
|
30
|
+
optional_keys << normalized_key if optional
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def []=(key, val)
|
36
|
+
add(key, val, optional: false)
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch(key, &block)
|
40
|
+
__getobj__.fetch(normalize_key(key), &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(key)
|
44
|
+
__getobj__.delete(normalize_key(key))
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def required
|
49
|
+
clone.tap do |cloned|
|
50
|
+
optional_keys.each do |key|
|
51
|
+
cloned.delete(key)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def clone
|
57
|
+
super.tap do |cloned|
|
58
|
+
cloned.__setobj__(__getobj__.clone)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge(rules)
|
63
|
+
__getobj__.merge!(rules)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def inspect
|
68
|
+
"[Scheme::Rules n=#{keys.length} default=#{default}]"
|
69
|
+
end
|
70
|
+
|
71
|
+
alias get []
|
72
|
+
alias remove delete
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
attr_accessor :allow_empty, :optional_keys
|
77
|
+
attr_writer :expected_type
|
78
|
+
|
79
|
+
def normalize_key(key)
|
80
|
+
String(key).to_sym
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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
|
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)
|
35
|
+
end
|
36
|
+
|
37
|
+
def iterate(mark)
|
38
|
+
OutputIteratorWithPredicate.call(output, options, rules: rules) do |key, value, options:, context:|
|
39
|
+
mark.call(key)
|
40
|
+
|
41
|
+
rules.get(key).validate!(
|
42
|
+
value,
|
43
|
+
options.trace(key),
|
44
|
+
context: context
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_accessor :rules, :options, :output
|
52
|
+
|
53
|
+
def raise_exhausted!(missing_keys:, backtrace:)
|
54
|
+
raise ExhaustedOutputError, format(
|
55
|
+
'Missing keys in output: %<missing_keys>s at [%<backtrace>s]',
|
56
|
+
missing_keys: missing_keys,
|
57
|
+
backtrace: backtrace.join('->')
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/media_types/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module MediaTypes
|
4
|
-
VERSION = '0.
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
VERSION = '0.5.0'
|
5
|
+
end
|