media_types-serialization 1.0.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -3
- data/.prettierrc +1 -0
- data/CHANGELOG.md +31 -7
- data/CODE_OF_CONDUCT.md +11 -11
- data/Gemfile.lock +64 -70
- data/README.md +98 -53
- data/lib/media_types/serialization.rb +84 -11
- data/lib/media_types/serialization/base.rb +8 -4
- data/lib/media_types/serialization/error.rb +11 -3
- data/lib/media_types/serialization/fake_validator.rb +1 -1
- data/lib/media_types/serialization/serialization_registration.rb +19 -9
- data/lib/media_types/serialization/serializers/problem_serializer.rb +23 -10
- data/lib/media_types/serialization/utils/accept_header.rb +77 -0
- data/lib/media_types/serialization/utils/accept_language_header.rb +82 -0
- data/lib/media_types/serialization/utils/header_list.rb +89 -0
- data/lib/media_types/serialization/version.rb +1 -1
- data/media_types-serialization.gemspec +3 -5
- metadata +14 -42
@@ -15,8 +15,7 @@ require 'active_support/concern'
|
|
15
15
|
require 'active_support/core_ext/module/attribute_accessors'
|
16
16
|
require 'active_support/core_ext/object/blank'
|
17
17
|
|
18
|
-
require '
|
19
|
-
|
18
|
+
require 'media_types/serialization/utils/accept_header'
|
20
19
|
require 'media_types/serialization/base'
|
21
20
|
require 'media_types/serialization/error'
|
22
21
|
require 'media_types/serialization/serialization_dsl'
|
@@ -44,7 +43,8 @@ end
|
|
44
43
|
module MediaTypes
|
45
44
|
module Serialization
|
46
45
|
|
47
|
-
HEADER_ACCEPT = 'HTTP_ACCEPT'
|
46
|
+
HEADER_ACCEPT = 'HTTP_ACCEPT'.freeze
|
47
|
+
HEADER_ACCEPT_LANGUAGE = 'HTTP_ACCEPT_LANGUAGE'.freeze
|
48
48
|
|
49
49
|
mattr_accessor :json_encoder, :json_decoder
|
50
50
|
if defined?(::Oj)
|
@@ -67,7 +67,21 @@ module MediaTypes
|
|
67
67
|
quirks_mode: false
|
68
68
|
)
|
69
69
|
}
|
70
|
-
self.json_decoder =
|
70
|
+
self.json_decoder = ->(obj) {
|
71
|
+
Oj.load(obj,
|
72
|
+
mode: :compat,
|
73
|
+
ascii_only: false,
|
74
|
+
allow_nan: false,
|
75
|
+
symbol_keys: true,
|
76
|
+
allow_nil: false,
|
77
|
+
allow_invalid_unicode: false,
|
78
|
+
array_class: ::Array,
|
79
|
+
create_additions: false,
|
80
|
+
hash_class: ::Hash,
|
81
|
+
nilnil: false,
|
82
|
+
quirks_mode: false
|
83
|
+
)
|
84
|
+
}
|
71
85
|
else
|
72
86
|
require 'json'
|
73
87
|
self.json_encoder = JSON.method(:pretty_generate)
|
@@ -169,7 +183,66 @@ module MediaTypes
|
|
169
183
|
@serialization_output_registrations = @serialization_output_registrations.merge(mergeable_outputs)
|
170
184
|
end
|
171
185
|
end
|
172
|
-
|
186
|
+
|
187
|
+
def allow_output_html(as: nil, view: nil, layout: nil, **filter_opts)
|
188
|
+
before_action(**filter_opts) do
|
189
|
+
raise SerializersAlreadyFrozenError if defined? @serialization_frozen
|
190
|
+
|
191
|
+
@serialization_output_registrations ||= SerializationRegistration.new(:output)
|
192
|
+
|
193
|
+
html_registration = SerializationRegistration.new(:output)
|
194
|
+
output_identifier = 'text/html'
|
195
|
+
output_identifier += "; variant=#{as}" unless as.nil?
|
196
|
+
|
197
|
+
validator = FakeValidator.new(as.nil? ? 'text/html' : as)
|
198
|
+
|
199
|
+
block = lambda { |_, _, controller|
|
200
|
+
if layout.nil?
|
201
|
+
if view.nil?
|
202
|
+
controller.render_to_string
|
203
|
+
else
|
204
|
+
controller.render_to_string(template: view)
|
205
|
+
end
|
206
|
+
else
|
207
|
+
if view.nil?
|
208
|
+
controller.render_to_string(layout: layout)
|
209
|
+
else
|
210
|
+
controller.render_to_string(template: view, layout: layout)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
}
|
214
|
+
|
215
|
+
html_registration.register_block(nil, validator, nil, block, true, wildcards: true)
|
216
|
+
html_registration.registrations[validator.identifier].display_identifier = output_identifier
|
217
|
+
html_registration.registrations["#{validator.identifier.split('/')[0]}/*"].display_identifier = output_identifier
|
218
|
+
html_registration.registrations['*/*'].display_identifier = output_identifier
|
219
|
+
|
220
|
+
@serialization_output_registrations = @serialization_output_registrations.merge(html_registration)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def allow_output_docs(description, **filter_opts)
|
225
|
+
before_action(**filter_opts) do
|
226
|
+
raise SerializersAlreadyFrozenError if defined? @serialization_frozen
|
227
|
+
|
228
|
+
@serialization_output_registrations ||= SerializationRegistration.new(:output)
|
229
|
+
|
230
|
+
docs_registration = SerializationRegistration.new(:output)
|
231
|
+
validator = FakeValidator.new('text/vnd.delftsolutions.docs')
|
232
|
+
|
233
|
+
block = lambda { |_, _, _|
|
234
|
+
description
|
235
|
+
}
|
236
|
+
|
237
|
+
docs_registration.register_block(nil, validator, nil, block, true, wildcards: true)
|
238
|
+
docs_registration.registrations['text/vnd.delftsolutions.docs'].display_identifier = 'text/plain; charset=utf-8'
|
239
|
+
docs_registration.registrations['text/*'].display_identifier = 'text/plain; charset=utf-8'
|
240
|
+
docs_registration.registrations['*/*'].display_identifier = 'text/plain; charset=utf-8'
|
241
|
+
|
242
|
+
@serialization_output_registrations = @serialization_output_registrations.merge(docs_registration)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
173
246
|
def allow_api_viewer(serializer: MediaTypes::Serialization::Serializers::ApiViewer, **filter_opts)
|
174
247
|
before_action do
|
175
248
|
@serialization_api_viewer_enabled ||= {}
|
@@ -202,7 +275,7 @@ module MediaTypes
|
|
202
275
|
raise ArrayInViewParameterError, :allow_input_serializer if view.is_a? Array
|
203
276
|
views = [view] if views.nil?
|
204
277
|
raise ViewsNotAnArrayError unless views.is_a? Array
|
205
|
-
|
278
|
+
|
206
279
|
before_action do
|
207
280
|
@serialization_available_serializers ||= {}
|
208
281
|
@serialization_available_serializers[:input] ||= {}
|
@@ -245,8 +318,8 @@ module MediaTypes
|
|
245
318
|
##
|
246
319
|
# Freezes additions to the serializes and notifies the controller what it will be able to respond to.
|
247
320
|
#
|
248
|
-
def freeze_io!
|
249
|
-
before_action :serializer_freeze_io_internal
|
321
|
+
def freeze_io!(**filter_opts)
|
322
|
+
before_action :serializer_freeze_io_internal, **filter_opts
|
250
323
|
|
251
324
|
output_error MediaTypes::Serialization::NoInputReceivedError do |p, error|
|
252
325
|
p.title 'Providing input is mandatory. Please set a Content-Type', lang: 'en'
|
@@ -352,7 +425,7 @@ module MediaTypes
|
|
352
425
|
return nil if identifier.nil?
|
353
426
|
|
354
427
|
registration = registration.registrations[identifier]
|
355
|
-
|
428
|
+
|
356
429
|
raise 'Assertion failed, inconsistent answer from resolve_media_type' if registration.nil?
|
357
430
|
registration.serializer
|
358
431
|
end
|
@@ -372,7 +445,7 @@ module MediaTypes
|
|
372
445
|
#
|
373
446
|
#
|
374
447
|
|
375
|
-
accept_header =
|
448
|
+
accept_header = Utils::AcceptHeader.new(request.get_header(HEADER_ACCEPT)) || ''
|
376
449
|
accept_header.each do |mime_type|
|
377
450
|
stripped = mime_type.to_s.split(';')[0]
|
378
451
|
next unless registration.has? stripped
|
@@ -389,7 +462,7 @@ module MediaTypes
|
|
389
462
|
identifier = serializer.validator.identifier
|
390
463
|
obj = { request: request, registrations: registrations }
|
391
464
|
new_registrations = serializer.outputs_for(views: [nil])
|
392
|
-
|
465
|
+
|
393
466
|
serialization_render_resolved(obj: obj, serializer: serializer, identifier: identifier, registrations: new_registrations, options: {})
|
394
467
|
response.status = :not_acceptable
|
395
468
|
end
|
@@ -30,6 +30,10 @@ module MediaTypes
|
|
30
30
|
self.serializer_disable_wildcards = true
|
31
31
|
end
|
32
32
|
|
33
|
+
def enable_wildcards
|
34
|
+
self.serializer_disable_wildcards = false
|
35
|
+
end
|
36
|
+
|
33
37
|
def output(view: nil, version: nil, versions: nil, &block)
|
34
38
|
versions = [version] if versions.nil?
|
35
39
|
raise VersionsNotAnArrayError unless versions.is_a? Array
|
@@ -57,18 +61,18 @@ module MediaTypes
|
|
57
61
|
end
|
58
62
|
end
|
59
63
|
|
60
|
-
def output_alias(media_type_identifier, view: nil)
|
64
|
+
def output_alias(media_type_identifier, view: nil, hide_variant: false)
|
61
65
|
validator = serializer_validator.view(view)
|
62
66
|
victim_identifier = validator.identifier
|
63
67
|
|
64
|
-
serializer_output_registration.register_alias(self, media_type_identifier, victim_identifier, false, wildcards: !self.serializer_disable_wildcards)
|
68
|
+
serializer_output_registration.register_alias(self, media_type_identifier, victim_identifier, false, hide_variant, wildcards: !self.serializer_disable_wildcards)
|
65
69
|
end
|
66
70
|
|
67
|
-
def output_alias_optional(media_type_identifier, view: nil)
|
71
|
+
def output_alias_optional(media_type_identifier, view: nil, hide_variant: false)
|
68
72
|
validator = serializer_validator.view(view)
|
69
73
|
victim_identifier = validator.identifier
|
70
74
|
|
71
|
-
serializer_output_registration.register_alias(self, media_type_identifier, victim_identifier, true, wildcards: !self.serializer_disable_wildcards)
|
75
|
+
serializer_output_registration.register_alias(self, media_type_identifier, victim_identifier, true, hide_variant, wildcards: !self.serializer_disable_wildcards)
|
72
76
|
end
|
73
77
|
|
74
78
|
def input(view: nil, version: nil, versions: nil, &block)
|
@@ -73,21 +73,29 @@ module MediaTypes
|
|
73
73
|
def initialize(identifier, inout)
|
74
74
|
super(
|
75
75
|
"Serializer tried to define an #{inout}_alias that points to the media type identifier #{identifier} but no such #{inout} has been defined yet. Please move the #{inout} definition above the alias.\n\n" \
|
76
|
-
"Move the
|
76
|
+
"Move the #{inout} definition above the alias:\n" \
|
77
77
|
"\n" \
|
78
78
|
"class MySerializer < MediaTypes::Serialization::Base\n" \
|
79
79
|
"#...\n" \
|
80
|
-
"
|
80
|
+
"#{inout} do\n" \
|
81
81
|
" # ...\n" \
|
82
82
|
"end\n" \
|
83
83
|
"\n" \
|
84
|
-
"
|
84
|
+
"#{inout}_alias 'text/html'\n" \
|
85
85
|
"# ^----- move here\n" \
|
86
86
|
'end'
|
87
87
|
)
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
class VersionedAliasDefinitionError < ConfigurationError
|
92
|
+
def initialize(identifier, inout, prefix_match)
|
93
|
+
super(
|
94
|
+
"Serializer tried to define an #{inout}_alias that points to the media type identifier #{identifier} but no such #{inout} has been defined yet. An #{inout} named #{prefix_match} was found. Often this can be fixed by providing an #{inout} with a nil version."
|
95
|
+
)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
91
99
|
class DuplicateDefinitionError < ConfigurationError
|
92
100
|
def initialize(identifier, inout)
|
93
101
|
super("Serializer tried to define an #{inout} using the media type identifier #{identifier}, but another #{inout} was already defined with that identifier. Please remove one of the two.")
|
@@ -27,8 +27,8 @@ class FakeValidator
|
|
27
27
|
def identifier
|
28
28
|
suffix = suffixes[[internal_view, internal_version]] || ''
|
29
29
|
result = prefix
|
30
|
-
result += '.' + internal_view.to_s unless internal_view.nil?
|
31
30
|
result += '.v' + internal_version.to_s unless internal_version.nil?
|
31
|
+
result += '.' + internal_view.to_s unless internal_view.nil?
|
32
32
|
result += '+' + suffix.to_s unless suffix.empty?
|
33
33
|
|
34
34
|
result
|
@@ -31,14 +31,23 @@ module MediaTypes
|
|
31
31
|
register_wildcards(identifier, registration) if wildcards && inout == :output
|
32
32
|
end
|
33
33
|
|
34
|
-
def register_alias(serializer, alias_identifier, target_identifier, optional, wildcards: true)
|
35
|
-
raise DuplicateDefinitionError.new(
|
34
|
+
def register_alias(serializer, alias_identifier, target_identifier, optional, hide_variant, wildcards: true)
|
35
|
+
raise DuplicateDefinitionError.new(alias_identifier, inout) if registrations.key? alias_identifier
|
36
36
|
|
37
|
-
|
37
|
+
unless registrations.key? target_identifier
|
38
|
+
potential_match = registrations.keys.find do |k|
|
39
|
+
k.starts_with? target_identifier
|
40
|
+
end
|
41
|
+
raise VersionedAliasDefinitionError.new(target_identifier, inout, potential_match) unless potential_match.nil?
|
42
|
+
raise UnbackedAliasDefinitionError.new(target_identifier, inout)
|
43
|
+
end
|
38
44
|
|
39
45
|
target = registrations[target_identifier]
|
40
46
|
|
41
|
-
|
47
|
+
result_content_type = alias_identifier
|
48
|
+
result_content_type += "; variant=#{target_identifier}" unless hide_variant
|
49
|
+
|
50
|
+
registration = SerializationAliasRegistration.new serializer, inout, target.validator, result_content_type, target, optional, hide_variant
|
42
51
|
registrations[alias_identifier] = registration
|
43
52
|
|
44
53
|
register_wildcards(alias_identifier, registration) if wildcards && inout == :output
|
@@ -103,7 +112,7 @@ module MediaTypes
|
|
103
112
|
private
|
104
113
|
|
105
114
|
def register_wildcards(identifier, registration)
|
106
|
-
new_alias = SerializationAliasRegistration.new registration.serializer, registration.inout, registration.validator, identifier, registration, true
|
115
|
+
new_alias = SerializationAliasRegistration.new registration.serializer, registration.inout, registration.validator, identifier, registration, true, true
|
107
116
|
|
108
117
|
registrations['*/*'] = new_alias unless has? '*/*'
|
109
118
|
|
@@ -160,14 +169,14 @@ module MediaTypes
|
|
160
169
|
begin
|
161
170
|
victim = MediaTypes::Serialization.json_decoder.call(victim)
|
162
171
|
validator.validate!(victim)
|
163
|
-
rescue MediaTypes::Scheme::ValidationError, Oj::ParseError, JSON::ParserError => inner
|
172
|
+
rescue MediaTypes::Scheme::ValidationError, Oj::ParseError, JSON::ParserError, EncodingError => inner
|
164
173
|
raise InputValidationFailedError, inner
|
165
174
|
end
|
166
175
|
else
|
167
176
|
begin
|
168
177
|
victim = MediaTypes::Serialization.json_decoder.call(victim)
|
169
178
|
validator.validate!(victim)
|
170
|
-
rescue MediaTypes::Scheme::ValidationError, JSON::ParserError => inner
|
179
|
+
rescue MediaTypes::Scheme::ValidationError, JSON::ParserError, EncodingError => inner
|
171
180
|
raise InputValidationFailedError, inner
|
172
181
|
end
|
173
182
|
end
|
@@ -204,9 +213,10 @@ module MediaTypes
|
|
204
213
|
|
205
214
|
# A registration that calls another registration when called.
|
206
215
|
class SerializationAliasRegistration < SerializationBaseRegistration
|
207
|
-
def initialize(serializer, inout, validator, display_identifier, target, optional)
|
216
|
+
def initialize(serializer, inout, validator, display_identifier, target, optional, hide_variant)
|
208
217
|
self.target = target
|
209
218
|
self.optional = optional
|
219
|
+
self.hide_variant = hide_variant
|
210
220
|
super(serializer, inout, validator, display_identifier)
|
211
221
|
end
|
212
222
|
|
@@ -229,7 +239,7 @@ module MediaTypes
|
|
229
239
|
target.call(victim, context, dsl: dsl, raw: raw)
|
230
240
|
end
|
231
241
|
|
232
|
-
attr_accessor :target, :optional
|
242
|
+
attr_accessor :target, :optional, :hide_variant
|
233
243
|
end
|
234
244
|
end
|
235
245
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'erb'
|
4
4
|
require 'media_types/serialization/base'
|
5
|
+
require 'media_types/serialization/utils/accept_language_header'
|
5
6
|
|
6
7
|
module MediaTypes
|
7
8
|
module Serialization
|
@@ -9,13 +10,19 @@ module MediaTypes
|
|
9
10
|
class ProblemSerializer < MediaTypes::Serialization::Base
|
10
11
|
|
11
12
|
unvalidated 'application/vnd.delftsolutions.problem'
|
13
|
+
disable_wildcards
|
12
14
|
|
13
15
|
output do |problem, _, context|
|
14
16
|
raise 'No translations defined, add at least one title' unless problem.translations.keys.any?
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
accept_language_header = Utils::AcceptLanguageHeader.new(context.request.get_header(HEADER_ACCEPT_LANGUAGE) || '')
|
19
|
+
translation_entry = accept_language_header.map do |locale|
|
20
|
+
problem.translations.keys.find do |l|
|
21
|
+
l.start_with? locale.locale
|
22
|
+
end
|
23
|
+
end.compact.first || problem.translations.keys.first
|
24
|
+
translation = problem.translations[translation_entry]
|
25
|
+
|
19
26
|
title = translation[:title]
|
20
27
|
detail = translation[:detail] || problem.error.message
|
21
28
|
|
@@ -33,12 +40,19 @@ module MediaTypes
|
|
33
40
|
output_alias 'application/problem+json'
|
34
41
|
|
35
42
|
output_raw view: :html do |problem, _, context|
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
accept_language_header = Utils::AcceptLanguageHeader.new(context.request.get_header(HEADER_ACCEPT_LANGUAGE) || '')
|
44
|
+
translation_entry = accept_language_header.map do |locale|
|
45
|
+
problem.translations.keys.find do |l|
|
46
|
+
l.starts_with? locale.locale
|
47
|
+
end
|
48
|
+
end.compact.first || problem.translations.keys.first
|
49
|
+
translation = problem.translations[translation_entry]
|
50
|
+
|
39
51
|
title = translation[:title]
|
40
52
|
detail = translation[:detail] || problem.error.message
|
41
53
|
|
54
|
+
detail_lang = translation[:detail].nil? ? 'en' : translation_entry
|
55
|
+
|
42
56
|
input = OpenStruct.new(
|
43
57
|
title: title,
|
44
58
|
detail: detail,
|
@@ -61,11 +75,11 @@ module MediaTypes
|
|
61
75
|
</header>
|
62
76
|
<section id="content">
|
63
77
|
<nav>
|
64
|
-
<section id="description">
|
78
|
+
<section id="description" lang="#{translation_entry}">
|
65
79
|
<h2><a href="<%= help_url %>"><%= CGI::escapeHTML(title) %></a></h2>
|
66
80
|
</section>
|
67
81
|
</nav>
|
68
|
-
<main>
|
82
|
+
<main lang="#{detail_lang}">
|
69
83
|
<p><%= detail %>
|
70
84
|
</main>
|
71
85
|
</section>
|
@@ -76,8 +90,7 @@ module MediaTypes
|
|
76
90
|
template.result(input.instance_eval { binding })
|
77
91
|
end
|
78
92
|
|
79
|
-
|
80
|
-
self.serializer_output_registration.registrations.delete('*/*')
|
93
|
+
enable_wildcards
|
81
94
|
|
82
95
|
output_alias_optional 'text/html', view: :html
|
83
96
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
=begin
|
2
|
+
The MIT License (MIT)
|
3
|
+
|
4
|
+
Copyright (c) 2019 Derk-Jan Karrenbeld
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
THE SOFTWARE.
|
23
|
+
=end
|
24
|
+
|
25
|
+
require 'media_types/serialization/utils/header_list'
|
26
|
+
|
27
|
+
module MediaTypes
|
28
|
+
module Serialization
|
29
|
+
module Utils
|
30
|
+
class AcceptHeader < DelegateClass(Array)
|
31
|
+
def initialize(value)
|
32
|
+
__setobj__ HeaderList.new(value, entry_klazz: AcceptHeader::Entry)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Entry
|
36
|
+
def initialize(media_type, index:, parameters:)
|
37
|
+
self.media_type = media_type
|
38
|
+
self.parameters = parameters
|
39
|
+
self.index = index
|
40
|
+
|
41
|
+
freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :media_type
|
45
|
+
|
46
|
+
# noinspection RubyInstanceMethodNamingConvention
|
47
|
+
def q
|
48
|
+
parameters.fetch(:q) { 1.0 }.to_f
|
49
|
+
end
|
50
|
+
|
51
|
+
def <=>(other)
|
52
|
+
quality = other.q <=> q
|
53
|
+
return quality unless quality.zero?
|
54
|
+
index <=> other.send(:index)
|
55
|
+
end
|
56
|
+
|
57
|
+
def [](parameter)
|
58
|
+
parameters.fetch(String(parameter).to_sym)
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_header
|
62
|
+
to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
[media_type].concat(parameters.map { |k, v| "#{k}=#{v}" }).compact.reject(&:empty?).join('; ')
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_writer :media_type
|
72
|
+
attr_accessor :parameters, :index
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|