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.
@@ -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
- CaseEqualityWithNil.new(klazz)
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,16 @@
1
+ module MediaTypes
2
+ class Scheme
3
+ class EnumerationContext
4
+ def initialize(validations:)
5
+ self.validations = validations
6
+ end
7
+
8
+ def enumerate(val)
9
+ self.key = val
10
+ self
11
+ end
12
+
13
+ attr_accessor :validations, :key
14
+ end
15
+ end
16
+ end
@@ -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