Class: Praxis::MediaTypeIdentifier
- Inherits:
-
Attributor::Model
- Object
- Attributor::Model
- Praxis::MediaTypeIdentifier
- Defined in:
- lib/praxis/media_type_identifier.rb
Overview
Ruby object representation of an Internet Media Type Identifier as defined by RFC6838; also known as MIME types due to their origin in RFC2046 (the MIME specification).
Constant Summary collapse
- VALID_TYPE =
Postel’s principle encourages us to accept anything that MIGHT be an identifier, although the syntax for type identifiers is substantially narrower than what we accept there.
Note that this ONLY matches type, subtype and suffix; we handle options differently.
%r{^\s*(?<type>[^/]+)/(?<subtype>[^+]+)(?<nothing>\+(?<suffix>[^; ]+))?\s*$}x.freeze
- PARAMETER_SEPARATOR =
Pattern that separates parameters of a media type from each other, and from the base identifier.
/\s*;\s*/x.freeze
- WORD_SEPARATOR =
Pattern used to identify the first “word” when we encounter a malformed type identifier, so we can apply a heuristic and assume the user meant “application/XYZ”.
/[^a-z0-9-]/i.freeze
- QUOTED_STRING =
Pattern that lets us strip quotes from parameter values.
/^".*"$/.freeze
- WILDCARD =
Token that indicates a media-type component that matches anything.
'*'
- Parameters =
Inner type representing semicolon-delimited parameters.
Attributor::Hash.of(key: String)
Class Method Summary collapse
-
.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options) ⇒ MediaTypeIdentifier
Parse a media type identifier from a String, or load it from a Hash or Model.
Instance Method Summary collapse
-
#+(other) ⇒ MediaTypeIdentifier
Extend this type identifier by adding a suffix or parameter(s); return a new type identifier.
-
#=~(other) ⇒ Boolean
Determine whether this type is compatible with (i.e. is a subset or specialization of) another identifier.
-
#handler_name ⇒ String
Make an educated guess about the structured-syntax encoding implied by this media type, which in turn governs which handler should be used to parse and generate media of this type.
-
#match(other) ⇒ Boolean
Determine whether another identifier is compatible with (i.e. is a subset or specialization of) this identifier.
- #redundant_suffix(suffix) ⇒ Object
-
#to_s ⇒ String
(also: #to_str)
Canonicalized representation of the media type including all suffixes and parameters.
-
#without_parameters ⇒ MediaTypeIdentifier
If parameters are empty, return self; otherwise return a new object consisting of this type minus parameters.
Class Method Details
.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options) ⇒ MediaTypeIdentifier
Parse a media type identifier from a String, or load it from a Hash or Model. Assume malformed types represent a subtype, e.g. “nachos” => application/nachos“
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/praxis/media_type_identifier.rb', line 46 def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **) case value when ::String return nil if value.blank? base, *parameters = value.split(PARAMETER_SEPARATOR) match = VALID_TYPE.match(base) obj = new if match parameters = parameters.each_with_object({}) do |e, h| k, v = e.split('=', 2) v = v[1...-1] if v =~ QUOTED_STRING h[k] = v end obj.type = match[:type] obj.subtype = match[:subtype] obj.suffix = match[:suffix] obj.parameters = parameters else obj.type = 'application' obj.subtype = base.split(WORD_SEPARATOR, 2).first obj.suffix = ::String.new obj.parameters = {} end obj when nil nil else super end end |
Instance Method Details
#+(other) ⇒ MediaTypeIdentifier
Extend this type identifier by adding a suffix or parameter(s); return a new type identifier.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/praxis/media_type_identifier.rb', line 184 def +(other) parameters = other.split(PARAMETER_SEPARATOR) # remove useless initial '; ' parameters.shift if parameters.first && parameters.first.empty? raise ArgumentError, 'Must pass a type identifier suffix and/or parameters' if parameters.empty? suffix = parameters.shift unless parameters.first.include?('=') # remove redundant '+' suffix = suffix[1..] if suffix && suffix[0] == '+' parameters = parameters.each_with_object({}) do |e, h| k, v = e.split('=', 2) v = v[1...-1] if v =~ /^".*"$/ h[k] = v h end parameters = Parameters.load(parameters) obj = self.class.new obj.type = type obj.subtype = subtype target_suffix = suffix || self.suffix obj.suffix = redundant_suffix(target_suffix) ? ::String.new : target_suffix obj.parameters = self.parameters.merge(parameters) obj end |
#=~(other) ⇒ Boolean
Determine whether this type is compatible with (i.e. is a subset or specialization of) another identifier. This is the same operation as #match, but with the position of the operands switched – analogous to “Regexp#match(String)” vs “String =~ Regexp”.
122 123 124 |
# File 'lib/praxis/media_type_identifier.rb', line 122 def =~(other) other.match(self) end |
#handler_name ⇒ String
Make an educated guess about the structured-syntax encoding implied by this media type, which in turn governs which handler should be used to parse and generate media of this type.
If a suffix e.g. “+json” is present, return it. Otherwise, return the subtype.
168 169 170 |
# File 'lib/praxis/media_type_identifier.rb', line 168 def handler_name suffix.empty? ? subtype : suffix end |
#match(other) ⇒ Boolean
Determine whether another identifier is compatible with (i.e. is a subset or specialization of) this identifier.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/praxis/media_type_identifier.rb', line 98 def match(other) other = MediaTypeIdentifier.load(other) return false if other.nil? return false unless type == other.type || type == WILDCARD return false unless subtype == other.subtype || subtype == WILDCARD return false unless suffix.empty? || suffix == other.suffix parameters.each_pair do |k, v| return false unless other.parameters[k] == v end true end |
#redundant_suffix(suffix) ⇒ Object
213 214 215 216 217 218 219 |
# File 'lib/praxis/media_type_identifier.rb', line 213 def redundant_suffix(suffix) # application/json does not need to be suffixed with +json (same for application/xml) # we're supporting text/json and text/xml for older formats as well return true if (type == 'application' || type == 'text') && subtype == suffix false end |
#to_s ⇒ String Also known as: to_str
Returns canonicalized representation of the media type including all suffixes and parameters.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/praxis/media_type_identifier.rb', line 127 def to_s # Our handcrafted media types consist of a rich chocolatey center s = ::String.new("#{type}/#{subtype}") # coated in a hard candy shell s << '+' << suffix unless suffix.empty? # and encrusted with lexically-ordered sprinkles unless parameters.empty? s << '; ' s << parameters.keys.sort.map { |k| "#{k}=#{parameters[k]}" }.join('; ') end # May contain peanuts, tree nuts, soy, dairy, sawdust or glue s end |
#without_parameters ⇒ MediaTypeIdentifier
If parameters are empty, return self; otherwise return a new object consisting of this type minus parameters.
150 151 152 153 154 155 156 157 |
# File 'lib/praxis/media_type_identifier.rb', line 150 def without_parameters if parameters.empty? self else val = { type: type, subtype: subtype, suffix: suffix } MediaTypeIdentifier.load(val) end end |