media_types 0.1.3 → 0.2.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 +27 -0
- data/Gemfile.lock +3 -1
- data/README.md +228 -31
- data/Rakefile +8 -6
- data/lib/media_types/{constructable_mime_type.rb → constructable.rb} +9 -4
- data/lib/media_types/defaults.rb +31 -0
- data/lib/media_types/dsl.rb +65 -0
- data/lib/media_types/hash.rb +21 -0
- data/lib/media_types/minitest/assert_media_types_registered.rb +4 -4
- data/lib/media_types/object.rb +35 -0
- data/lib/media_types/registrar.rb +148 -0
- data/lib/media_types/scheme/allow_nil.rb +8 -1
- data/lib/media_types/scheme/any_of.rb +25 -0
- data/lib/media_types/scheme/attribute.rb +10 -0
- data/lib/media_types/scheme/enumeration_context.rb +16 -0
- data/lib/media_types/scheme/enumeration_of_type.rb +70 -0
- data/lib/media_types/scheme/validation_options.rb +27 -0
- data/lib/media_types/scheme.rb +262 -73
- data/lib/media_types/validations.rb +79 -0
- data/lib/media_types/version.rb +1 -1
- data/lib/media_types.rb +18 -46
- data/media_types.gemspec +1 -0
- metadata +28 -5
- data/lib/media_types/base/collector.rb +0 -44
- data/lib/media_types/base.rb +0 -193
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
class Registerable
|
5
|
+
def initialize(media_type, symbol:, aliases: [])
|
6
|
+
self.media_type = media_type
|
7
|
+
self.symbol = symbol
|
8
|
+
self.aliases = aliases
|
9
|
+
|
10
|
+
freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
String(media_type)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_sym
|
18
|
+
symbol
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :media_type, :symbol, :aliases
|
22
|
+
|
23
|
+
alias synonyms aliases
|
24
|
+
alias mime_type to_s
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Holds all the configuration options in order to call {MediaTypes.register} exactly enough times to register the
|
30
|
+
# media type in its entirety.
|
31
|
+
#
|
32
|
+
# @see Registerable
|
33
|
+
#
|
34
|
+
class Registrar
|
35
|
+
|
36
|
+
##
|
37
|
+
# Creates a new registry with default values
|
38
|
+
#
|
39
|
+
# @param [Class] klazz the class that has {MediaTypes::Dsl} included
|
40
|
+
# @param [Symbol, NilClass] symbol the symbol the base view (nil view) should be registered under
|
41
|
+
#
|
42
|
+
def initialize(klazz, symbol: nil, &block)
|
43
|
+
self.base_media_type = klazz.to_constructable
|
44
|
+
|
45
|
+
self.registered_views = symbol ? { nil => { symbol: String(symbol).to_sym } } : {}
|
46
|
+
self.registered_versions = [base_media_type.version]
|
47
|
+
self.registered_aliases = []
|
48
|
+
self.registered_suffixes = [String(base_media_type.suffix).to_sym]
|
49
|
+
|
50
|
+
instance_exec(&block) if block_given?
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Resolves all the permutations and returns a list of {Registerable}
|
55
|
+
#
|
56
|
+
# @return [Array<Registerable>] the registerables based on all the permutations of +self+
|
57
|
+
#
|
58
|
+
def to_a
|
59
|
+
result = []
|
60
|
+
|
61
|
+
each_resolved do |media_type, symbol|
|
62
|
+
result << Registerable.new(media_type, symbol: symbol, aliases: aliases(media_type))
|
63
|
+
end
|
64
|
+
|
65
|
+
result
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_accessor :registered_views, :registered_versions, :registered_aliases, :registered_suffixes,
|
71
|
+
:symbol_base, :base_media_type
|
72
|
+
|
73
|
+
##
|
74
|
+
# Registers a +view+ with a +symbol+
|
75
|
+
#
|
76
|
+
# @param [String, Symbol] view the view
|
77
|
+
# @param [String, Symbol] symbol the symbol base for this view
|
78
|
+
#
|
79
|
+
def view(view, symbol)
|
80
|
+
registered_views[String(view)] = { symbol: String(symbol).to_sym }
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Registers +versions+
|
86
|
+
#
|
87
|
+
# @param [*Numeric, Array<Numeric>] versions the versions to register
|
88
|
+
#
|
89
|
+
def versions(*versions)
|
90
|
+
registered_versions.push(*Array(versions))
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# Registers a type alias. This will become a synonym when registering the media type
|
96
|
+
#
|
97
|
+
# @param [String] alias_name the name of the alias
|
98
|
+
#
|
99
|
+
def type_alias(alias_name)
|
100
|
+
registered_aliases.push(String(alias_name))
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Registers a suffix
|
106
|
+
#
|
107
|
+
# @param [String, Symbol] suffix the suffix to register
|
108
|
+
#
|
109
|
+
def suffix(suffix)
|
110
|
+
registered_suffixes.push(String(suffix).to_sym)
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
##
|
115
|
+
# Calculates the aliased media types for a media type
|
116
|
+
#
|
117
|
+
# @param [Constructable] media_type the media type constructable
|
118
|
+
# @return [Array<String>] the resolved aliases
|
119
|
+
#
|
120
|
+
def aliases(media_type)
|
121
|
+
registered_aliases.map { |a| media_type.type(a).to_s }
|
122
|
+
end
|
123
|
+
|
124
|
+
def each_combination
|
125
|
+
iterable(registered_versions).each do |version|
|
126
|
+
iterable(registered_views).each do |view, view_opts|
|
127
|
+
iterable(registered_suffixes).each do |suffix|
|
128
|
+
opts = { view: view_opts }
|
129
|
+
yield version, view, suffix, opts
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def iterable(source)
|
136
|
+
source.compact.uniq
|
137
|
+
end
|
138
|
+
|
139
|
+
def each_resolved
|
140
|
+
each_combination do |version, view, suffix, opts|
|
141
|
+
media_type = base_media_type.version(version).view(view).suffix(suffix)
|
142
|
+
symbol = iterable([opts[:view][:symbol], "v#{version}", suffix]).join('_').to_sym
|
143
|
+
|
144
|
+
yield media_type, symbol
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -5,14 +5,21 @@ require 'delegate'
|
|
5
5
|
module MediaTypes
|
6
6
|
class Scheme
|
7
7
|
class CaseEqualityWithNil < SimpleDelegator
|
8
|
+
|
9
|
+
# Same as the wrapped {Object#===}, but also allows for NilCLass
|
8
10
|
def ===(other)
|
9
11
|
other.nil? || super
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
15
|
# noinspection RubyInstanceMethodNamingConvention
|
16
|
+
##
|
17
|
+
# Allows the wrapped +klazz+ to be nil
|
18
|
+
#
|
19
|
+
# @param [Class] klazz the class that +it+ must be the if +it+ is not NilClass
|
20
|
+
# @return [CaseEqualityWithNil]
|
14
21
|
def AllowNil(klazz) # rubocop:disable Naming/MethodName
|
15
|
-
|
22
|
+
CaseEqualityWithNil.new(klazz)
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
module MediaTypes
|
6
|
+
class Scheme
|
7
|
+
class CaseEqualityWithList < SimpleDelegator
|
8
|
+
|
9
|
+
# True for Enumerable#any? {Object#===}
|
10
|
+
def ===(other)
|
11
|
+
any? { |it| it === other } # rubocop:disable Style/CaseEquality
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# noinspection RubyInstanceMethodNamingConvention
|
16
|
+
##
|
17
|
+
# Allows +it+ to be any of the wrapped +klazzes+
|
18
|
+
#
|
19
|
+
# @param [Array<Class>] klazzes the classes that are valid for +it+
|
20
|
+
# @return [CaseEqualityWithList]
|
21
|
+
def AnyOf(*klazzes) # rubocop:disable Naming/MethodName
|
22
|
+
CaseEqualityWithList.new(klazzes)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -3,6 +3,16 @@
|
|
3
3
|
module MediaTypes
|
4
4
|
class Scheme
|
5
5
|
class Attribute
|
6
|
+
|
7
|
+
##
|
8
|
+
# An attribute that expects a value of type +type+
|
9
|
+
#
|
10
|
+
# @see AllowNil
|
11
|
+
# @see AnyOf
|
12
|
+
#
|
13
|
+
# @param [Class] type the class +it+ must be
|
14
|
+
# @param [TrueClass, FalseClass] allow_nil if true, nil? is allowed
|
15
|
+
#
|
6
16
|
def initialize(type, allow_nil: false)
|
7
17
|
self.type = type
|
8
18
|
self.allow_nil = allow_nil
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
class Scheme
|
5
|
+
class EnumerationOfType
|
6
|
+
|
7
|
+
##
|
8
|
+
# An attribute that expects a value of type +enumeration_type+ and each item of type +item_type+
|
9
|
+
#
|
10
|
+
# @param [Class] item_type the type of each item
|
11
|
+
# @param [Class] enumeration_type the type of the enumeration as a whole
|
12
|
+
# @param [TrueClass, FalseClass] allow_empty if true, an empty instance of +enumeration_type+ is valid
|
13
|
+
#
|
14
|
+
def initialize(item_type, enumeration_type: Array, allow_empty: false)
|
15
|
+
self.item_type = item_type
|
16
|
+
self.enumeration_type = enumeration_type
|
17
|
+
self.allow_empty = allow_empty
|
18
|
+
|
19
|
+
freeze
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate!(output, options, **_opts)
|
23
|
+
validate_enumeration!(output, options) &&
|
24
|
+
validate_not_empty!(output, options) &&
|
25
|
+
validate_items!(output, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_accessor :allow_empty, :enumeration_type, :item_type
|
31
|
+
|
32
|
+
def validate_enumeration!(output, options)
|
33
|
+
return true if enumeration_type === output # rubocop:disable Style/CaseEquality
|
34
|
+
raise ValidationError,
|
35
|
+
format(
|
36
|
+
'Expected collection as %<type>s, got %<actual>s at [%<backtrace>s]',
|
37
|
+
type: enumeration_type,
|
38
|
+
actual: output.inspect,
|
39
|
+
backtrace: options.backtrace.join('->')
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_not_empty!(output, options)
|
44
|
+
return true if allow_empty
|
45
|
+
return true if output.respond_to?(:length) && output.length.positive?
|
46
|
+
return true if output.respond_to?(:empty?) && !output.empty?
|
47
|
+
|
48
|
+
raise EmptyOutputError,
|
49
|
+
format(
|
50
|
+
'Expected collection to be not empty, got empty at [%<backtrace>s]',
|
51
|
+
backtrace: options.backtrace.join('->')
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_items!(output, options)
|
56
|
+
output.all? do |item|
|
57
|
+
next true if item_type === item # rubocop:disable Style/CaseEquality
|
58
|
+
raise ValidationError,
|
59
|
+
format(
|
60
|
+
'Expected collection item as %<type>s, got %<actual>s at [%<backtrace>s]',
|
61
|
+
type: item_type,
|
62
|
+
actual: item.inspect,
|
63
|
+
backtrace: options.backtrace.join('->')
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MediaTypes
|
4
|
+
class Scheme
|
5
|
+
class ValidationOptions
|
6
|
+
attr_accessor :exhaustive, :strict, :backtrace
|
7
|
+
|
8
|
+
def initialize(exhaustive: true, strict: true, backtrace: [])
|
9
|
+
self.exhaustive = exhaustive
|
10
|
+
self.strict = strict
|
11
|
+
self.backtrace = backtrace
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_backtrace(backtrace)
|
15
|
+
ValidationOptions.new(exhaustive: exhaustive, strict: strict, backtrace: backtrace)
|
16
|
+
end
|
17
|
+
|
18
|
+
def trace(*traces)
|
19
|
+
with_backtrace(backtrace.dup.concat(traces))
|
20
|
+
end
|
21
|
+
|
22
|
+
def exhaustive!
|
23
|
+
ValidationOptions.new(exhaustive: true, strict: strict, backtrace: backtrace)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|