media_types 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|