ur 0.0.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85e941c9b4dad6807b7635acc90b762e4f096f38f8f981511be120e42effc7f9
4
- data.tar.gz: cbca689430e05b3865b8572dacbc114201c4af217b0e893fade40ae2a74f3b12
3
+ metadata.gz: '08e517b90ad97d890764bc228d8836e7e48d7f8250ed7c3a4878368b77b2deb2'
4
+ data.tar.gz: e87aedb3a622a5606bff0370cc1a06f43d98be53488389c1902e989226760cd1
5
5
  SHA512:
6
- metadata.gz: e45319855396792763e515a96d21ad3e56475461c9b8733b4d0dfb97dee9a2e3efac7107b0f58a510003ab70a17e64c367dde594ea8d0e3acd5a771be6a47044
7
- data.tar.gz: b7d0f97850df360137870edefe21f321b9f919f84d1ec9be65c45e30461297b6e725d949e9051de6c7f87bd3d9bef25be1cb7479fc20356f8dfb2fd7cf79d58f
6
+ metadata.gz: '069f50e6768b41beb13830308fd3a3d18845c32fc38f6e95755b8f929ba88e20efcfce8f84abf50ed393a93f9c2971bb7b280e5793d43098c06dc034f136487c'
7
+ data.tar.gz: 42c005bf4853f4bb946a2bd143696c84fa84c6f933b71a6425505722aab150f290ab21265ac2b6e9fdaa313e0207becb46f5a14ff20a7cd2c18e4daa53d12084
@@ -1,3 +1,20 @@
1
+ # v0.2.0
2
+
3
+ - Ur uses JSI schema modules instead of classes
4
+
5
+ # v0.1.1
6
+ - minor fixes
7
+
8
+ # v0.1.0
9
+ - rename processing to metadata
10
+ - Ur::ContentType
11
+
12
+ # v0.0.4
13
+ - bump JSI v0.2.0
14
+
15
+ # v0.0.3
16
+ - bump JSI v0.1.0
17
+
1
18
  # v0.0.2
2
19
 
3
20
  - module SubUr common to Ur::Request, Response, Processing
@@ -0,0 +1,169 @@
1
+ Copright © [Ethan](https://github.com/notEthan/)
2
+
3
+ [<img align="right" src="https://github.com/notEthan/ur/raw/master/resources/icons/LGPL-3.0.png">](https://www.gnu.org/licenses/lgpl-3.0.html)
4
+
5
+ Ur is Open Source Software licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/lgpl-3.0.html).
6
+
7
+ GNU Lesser General Public License
8
+ =================================
9
+
10
+ _Version 3, 29 June 2007_
11
+ _Copyright © 2007 Free Software Foundation, Inc. &lt;<https://fsf.org/>&gt;_
12
+
13
+ Everyone is permitted to copy and distribute verbatim copies
14
+ of this license document, but changing it is not allowed.
15
+
16
+
17
+ This version of the GNU Lesser General Public License incorporates
18
+ the terms and conditions of version 3 of the GNU General Public
19
+ License, supplemented by the additional permissions listed below.
20
+
21
+ ### 0. Additional Definitions
22
+
23
+ As used herein, “this License” refers to version 3 of the GNU Lesser
24
+ General Public License, and the “GNU GPL” refers to version 3 of the GNU
25
+ General Public License.
26
+
27
+ “The Library” refers to a covered work governed by this License,
28
+ other than an Application or a Combined Work as defined below.
29
+
30
+ An “Application” is any work that makes use of an interface provided
31
+ by the Library, but which is not otherwise based on the Library.
32
+ Defining a subclass of a class defined by the Library is deemed a mode
33
+ of using an interface provided by the Library.
34
+
35
+ A “Combined Work” is a work produced by combining or linking an
36
+ Application with the Library. The particular version of the Library
37
+ with which the Combined Work was made is also called the “Linked
38
+ Version”.
39
+
40
+ The “Minimal Corresponding Source” for a Combined Work means the
41
+ Corresponding Source for the Combined Work, excluding any source code
42
+ for portions of the Combined Work that, considered in isolation, are
43
+ based on the Application, and not on the Linked Version.
44
+
45
+ The “Corresponding Application Code” for a Combined Work means the
46
+ object code and/or source code for the Application, including any data
47
+ and utility programs needed for reproducing the Combined Work from the
48
+ Application, but excluding the System Libraries of the Combined Work.
49
+
50
+ ### 1. Exception to Section 3 of the GNU GPL
51
+
52
+ You may convey a covered work under sections 3 and 4 of this License
53
+ without being bound by section 3 of the GNU GPL.
54
+
55
+ ### 2. Conveying Modified Versions
56
+
57
+ If you modify a copy of the Library, and, in your modifications, a
58
+ facility refers to a function or data to be supplied by an Application
59
+ that uses the facility (other than as an argument passed when the
60
+ facility is invoked), then you may convey a copy of the modified
61
+ version:
62
+
63
+ * **a)** under this License, provided that you make a good faith effort to
64
+ ensure that, in the event an Application does not supply the
65
+ function or data, the facility still operates, and performs
66
+ whatever part of its purpose remains meaningful, or
67
+
68
+ * **b)** under the GNU GPL, with none of the additional permissions of
69
+ this License applicable to that copy.
70
+
71
+ ### 3. Object Code Incorporating Material from Library Header Files
72
+
73
+ The object code form of an Application may incorporate material from
74
+ a header file that is part of the Library. You may convey such object
75
+ code under terms of your choice, provided that, if the incorporated
76
+ material is not limited to numerical parameters, data structure
77
+ layouts and accessors, or small macros, inline functions and templates
78
+ (ten or fewer lines in length), you do both of the following:
79
+
80
+ * **a)** Give prominent notice with each copy of the object code that the
81
+ Library is used in it and that the Library and its use are
82
+ covered by this License.
83
+ * **b)** Accompany the object code with a copy of the GNU GPL and this license
84
+ document.
85
+
86
+ ### 4. Combined Works
87
+
88
+ You may convey a Combined Work under terms of your choice that,
89
+ taken together, effectively do not restrict modification of the
90
+ portions of the Library contained in the Combined Work and reverse
91
+ engineering for debugging such modifications, if you also do each of
92
+ the following:
93
+
94
+ * **a)** Give prominent notice with each copy of the Combined Work that
95
+ the Library is used in it and that the Library and its use are
96
+ covered by this License.
97
+
98
+ * **b)** Accompany the Combined Work with a copy of the GNU GPL and this license
99
+ document.
100
+
101
+ * **c)** For a Combined Work that displays copyright notices during
102
+ execution, include the copyright notice for the Library among
103
+ these notices, as well as a reference directing the user to the
104
+ copies of the GNU GPL and this license document.
105
+
106
+ * **d)** Do one of the following:
107
+ - **0)** Convey the Minimal Corresponding Source under the terms of this
108
+ License, and the Corresponding Application Code in a form
109
+ suitable for, and under terms that permit, the user to
110
+ recombine or relink the Application with a modified version of
111
+ the Linked Version to produce a modified Combined Work, in the
112
+ manner specified by section 6 of the GNU GPL for conveying
113
+ Corresponding Source.
114
+ - **1)** Use a suitable shared library mechanism for linking with the
115
+ Library. A suitable mechanism is one that **(a)** uses at run time
116
+ a copy of the Library already present on the user's computer
117
+ system, and **(b)** will operate properly with a modified version
118
+ of the Library that is interface-compatible with the Linked
119
+ Version.
120
+
121
+ * **e)** Provide Installation Information, but only if you would otherwise
122
+ be required to provide such information under section 6 of the
123
+ GNU GPL, and only to the extent that such information is
124
+ necessary to install and execute a modified version of the
125
+ Combined Work produced by recombining or relinking the
126
+ Application with a modified version of the Linked Version. (If
127
+ you use option **4d0**, the Installation Information must accompany
128
+ the Minimal Corresponding Source and Corresponding Application
129
+ Code. If you use option **4d1**, you must provide the Installation
130
+ Information in the manner specified by section 6 of the GNU GPL
131
+ for conveying Corresponding Source.)
132
+
133
+ ### 5. Combined Libraries
134
+
135
+ You may place library facilities that are a work based on the
136
+ Library side by side in a single library together with other library
137
+ facilities that are not Applications and are not covered by this
138
+ License, and convey such a combined library under terms of your
139
+ choice, if you do both of the following:
140
+
141
+ * **a)** Accompany the combined library with a copy of the same work based
142
+ on the Library, uncombined with any other library facilities,
143
+ conveyed under the terms of this License.
144
+ * **b)** Give prominent notice with the combined library that part of it
145
+ is a work based on the Library, and explaining where to find the
146
+ accompanying uncombined form of the same work.
147
+
148
+ ### 6. Revised Versions of the GNU Lesser General Public License
149
+
150
+ The Free Software Foundation may publish revised and/or new versions
151
+ of the GNU Lesser General Public License from time to time. Such new
152
+ versions will be similar in spirit to the present version, but may
153
+ differ in detail to address new problems or concerns.
154
+
155
+ Each version is given a distinguishing version number. If the
156
+ Library as you received it specifies that a certain numbered version
157
+ of the GNU Lesser General Public License “or any later version”
158
+ applies to it, you have the option of following the terms and
159
+ conditions either of that published version or of any later version
160
+ published by the Free Software Foundation. If the Library as you
161
+ received it does not specify a version number of the GNU Lesser
162
+ General Public License, you may choose any version of the GNU Lesser
163
+ General Public License ever published by the Free Software Foundation.
164
+
165
+ If the Library as you received it specifies that a proxy can decide
166
+ whether future versions of the GNU Lesser General Public License shall
167
+ apply, that proxy's public statement of acceptance of any version is
168
+ permanent authorization for you to choose that version for the
169
+ Library.
data/README.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Ur: Unified Request/Response Representation in Ruby
4
4
 
5
+ ## Properties
6
+
7
+ An ur primarily consists of a request, a response, and additional metadata.
8
+
9
+ The request consists of the request method, uri, headers, and body.
10
+
11
+ The response consists of the response status, headers, and body.
12
+
13
+ The metadata consist of the time the request began, the duration of the request, or tag strings. This is optional.
14
+
15
+ Other attributes may be present, and are ignored by this library.
16
+
5
17
  ## Usage with middleware
6
18
 
7
19
  Rack middleware:
@@ -18,7 +30,7 @@ class MyRackMiddleware
18
30
  ur = Ur.from_rack_request(env)
19
31
 
20
32
  # set additional properties of the ur, for example:
21
- ur.logger = my_logger
33
+ ur.logger_tags(my_logger)
22
34
 
23
35
  rack_response = ur.with_rack_response(@app, env) do
24
36
  # do things after the response
@@ -38,7 +50,7 @@ class MyFaradayMiddleware < ::Faraday::Middleware
38
50
  ur = Ur.from_faraday_request(request_env)
39
51
 
40
52
  # set additional properties of the ur, for example:
41
- ur.logger = my_logger
53
+ ur.logger_tags(my_logger)
42
54
 
43
55
  ur.faraday_on_complete(@app, request_env) do |response_env|
44
56
  # do things after the response
@@ -49,4 +61,6 @@ end
49
61
 
50
62
  ## License
51
63
 
52
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
64
+ [<img align="right" src="https://github.com/notEthan/ur/raw/master/resources/icons/LGPL-3.0.png">](https://www.gnu.org/licenses/lgpl-3.0.html)
65
+
66
+ Ur is Open Source Software licensed under the terms of the [GNU Lesser General Public License version 3](https://www.gnu.org/licenses/lgpl-3.0.html).
@@ -1,4 +1,3 @@
1
- require "bundler/gem_tasks"
2
1
  require "rake/testtask"
3
2
 
4
3
  Rake::TestTask.new(:test) do |t|
data/lib/ur.rb CHANGED
@@ -1,46 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "ur/version"
2
4
 
3
5
  require 'jsi'
4
6
  require 'time'
5
7
  require 'addressable/uri'
8
+ require 'pathname'
6
9
 
7
- Ur = JSI.class_for_schema({
8
- id: 'https://schemas.ur.unth.net/ur',
9
- type: 'object',
10
- properties: {
11
- bound: {
12
- type: 'string',
13
- description: %q([rfc2616] Inbound and outbound refer to the request and response paths for messages: "inbound" means "traveling toward the origin server", and "outbound" means "traveling toward the user agent"),
14
- enum: ['inbound', 'outbound'],
15
- },
16
- request: {
17
- type: 'object',
18
- properties: {
19
- method: {type: 'string', description: 'HTTP ', example: 'POST'},
20
- uri: {type: 'string', example: 'https://example.com/foo?bar=baz'},
21
- headers: {type: 'object'},
22
- body: {type: 'string'},
23
- },
24
- },
25
- response: {
26
- type: 'object',
27
- properties: {
28
- status: {type: 'integer', example: 200},
29
- headers: {type: 'object'},
30
- body: {type: 'string'},
31
- },
32
- },
33
- processing: {
34
- type: 'object',
35
- properties: {
36
- began_at_s: {type: 'string'},
37
- duration: {type: 'number'},
38
- tags: {type: 'array', items: {type: 'string'}}
39
- },
40
- },
41
- },
42
- })
43
- class Ur
10
+ UR_ROOT = Pathname.new(__FILE__).dirname.parent.expand_path
11
+ Ur = JSI::Schema.new(YAML.load_file(UR_ROOT.join('resources/ur.schema.yml'))).jsi_schema_module
12
+ module Ur
44
13
  VERSION = UR_VERSION
45
14
 
46
15
  autoload :SubUr, 'ur/sub_ur'
@@ -50,17 +19,30 @@ class Ur
50
19
  autoload :RackMiddleware, 'ur/middleware'
51
20
  autoload :Faraday, 'ur/faraday'
52
21
 
53
- Request = JSI.class_for_schema(self.schema['properties']['request'])
54
- Response = JSI.class_for_schema(self.schema['properties']['response'])
55
- Processing = JSI.class_for_schema(self.schema['properties']['processing'])
22
+ Request = self.schema.properties['request'].jsi_schema_module
23
+ Response = self.schema.properties['response'].jsi_schema_module
24
+ Metadata = self.schema.properties['metadata'].jsi_schema_module
56
25
  require 'ur/request'
57
26
  require 'ur/response'
58
- require 'ur/processing'
27
+ require 'ur/metadata'
59
28
 
60
- autoload :ContentTypeAttrs, 'ur/content_type_attrs'
29
+ autoload :ContentType, 'ur/content_type'
61
30
 
62
31
  class << self
63
- def from_rack_request(request_env)
32
+ def new(instance = {}, schemas: Set[], **options)
33
+ unless instance.respond_to?(:to_hash)
34
+ raise(TypeError, "expected hash for ur instance. got: #{instance.pretty_inspect.chomp}")
35
+ end
36
+
37
+ ur_class = JSI.class_for_schemas(Set[schema] + schemas)
38
+ ur_class.new(instance, options).tap do |ur|
39
+ ur.request = {} if ur.request.nil?
40
+ ur.response = {} if ur.response.nil?
41
+ ur.metadata = {} if ur.metadata.nil?
42
+ end
43
+ end
44
+
45
+ def from_rack_request(request_env, **options)
64
46
  if request_env.is_a?(Rack::Request)
65
47
  rack_request = request_env
66
48
  env = request_env.env
@@ -69,9 +51,17 @@ class Ur
69
51
  env = request_env
70
52
  end
71
53
 
72
- new({'bound' => 'inbound'}).tap do |ur|
73
- ur.processing.begin!
54
+ new({'bound' => 'inbound'}, options).tap do |ur|
74
55
  ur.request['method'] = rack_request.request_method
56
+
57
+ ur.request.addressable_uri = Addressable::URI.new(
58
+ :scheme => rack_request.scheme,
59
+ :host => rack_request.host,
60
+ :port => rack_request.port,
61
+ :path => rack_request.path,
62
+ :query => (rack_request.query_string unless rack_request.query_string.empty?)
63
+ )
64
+
75
65
  ur.request.headers = env.map do |(key, value)|
76
66
  http_match = key.match(/\AHTTP_/)
77
67
  if http_match
@@ -84,44 +74,30 @@ class Ur
84
74
  end
85
75
  end
86
76
  end.compact.inject({}, &:update)
87
- ur.request.addressable_uri = Addressable::URI.new(
88
- :scheme => rack_request.scheme,
89
- :host => rack_request.host,
90
- :port => rack_request.port,
91
- :path => rack_request.path,
92
- :query => (rack_request.query_string unless rack_request.query_string.empty?)
93
- )
77
+
94
78
  env["rack.input"].rewind
95
79
  ur.request.body = env["rack.input"].read
96
80
  env["rack.input"].rewind
97
81
  end
98
82
  end
99
83
 
100
- def from_faraday_request(request_env, logger: nil)
101
- new({'bound' => 'outbound'}).tap do |ur|
102
- ur.processing.begin!
84
+ def from_faraday_request(request_env, **options)
85
+ new({'bound' => 'outbound'}, options).tap do |ur|
103
86
  ur.request['method'] = request_env[:method].to_s
104
- ur.request.headers = request_env[:request_headers]
105
87
  ur.request.uri = request_env[:url].normalize.to_s
88
+ ur.request.headers = request_env[:request_headers]
106
89
  ur.request.set_body_from_faraday(request_env)
107
90
  end
108
91
  end
109
92
  end
110
93
 
111
- def initialize(ur = {}, **opt, &b)
112
- ur = JSI::JSON::Node.new_doc(ur) unless ur.is_a?(JSI::JSON::Node)
113
- super(ur, **opt, &b)
114
- unless instance.respond_to?(:to_hash)
115
- raise(TypeError, "expected hash argument. got: #{ur.pretty_inspect.chomp}")
116
- end
117
- self.request = {} if self.request.nil?
118
- self.response = {} if self.response.nil?
119
- self.processing = {} if self.processing.nil?
120
- end
121
-
122
- def logger=(logger)
94
+ # Ur#logger_tags applies tags from a tagged logger to this ur's metadata.
95
+ # note: ur does not log anything and this logger is not stored.
96
+ # @param [logger] a tagged logger
97
+ # @return [void]
98
+ def logger_tags(logger)
123
99
  if logger && logger.formatter.respond_to?(:current_tags)
124
- processing.tags = logger.formatter.current_tags.dup
100
+ metadata.tags = logger.formatter.current_tags.dup
125
101
  end
126
102
  end
127
103
 
@@ -133,8 +109,6 @@ class Ur
133
109
  response.body = response_body.to_enum.to_a.join('')
134
110
 
135
111
  response_body_proxy = ::Rack::BodyProxy.new(response_body) do
136
- processing.finish!
137
-
138
112
  yield
139
113
  end
140
114
  [status, response_headers, response_body_proxy]
@@ -145,7 +119,6 @@ class Ur
145
119
  response.status = response_env[:status]
146
120
  response.headers = response_env[:response_headers]
147
121
  response.set_body_from_faraday(response_env)
148
- processing.finish!
149
122
 
150
123
  yield(response_env)
151
124
  end
@@ -158,6 +131,7 @@ class Ur
158
131
  schema['properties'].each do |property_name, property_schema|
159
132
  if property_schema['type'] == 'object' && property_schema['properties']
160
133
  property_schema['properties'].each_key do |property_property_name|
134
+ # ur.request_method => ur['request']['method']
161
135
  define_method("#{property_name}_#{property_property_name}") do
162
136
  self[property_name][property_property_name]
163
137
  end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ur' unless Object.const_defined?(:Ur)
4
+
5
+ module Ur
6
+ # Ur::ContentType represents a Content-Type header field.
7
+ # it parses the media type and its components, as well as any parameters.
8
+ #
9
+ # this class aims to be permissive in what it will parse. it will not raise any
10
+ # error when given a malformed or syntactically invalid Content-Type string.
11
+ # fields and parameters parsed from invalid Content-Type strings are undefined,
12
+ # but this class generally tries to make the most sense of what it's given.
13
+ #
14
+ # this class is based on RFCs:
15
+ # - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
16
+ # Section 3.1.1.1. Media Type
17
+ # https://tools.ietf.org/html/rfc7231#section-3.1.1.1
18
+ # - Media Type Specifications and Registration Procedures https://tools.ietf.org/html/rfc6838
19
+ # - Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies.
20
+ # Section 5.1. Syntax of the Content-Type Header Field
21
+ # https://tools.ietf.org/html/rfc2045#section-5.1
22
+ # - Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types
23
+ # https://tools.ietf.org/html/rfc2046
24
+ class ContentType < String
25
+ # the character ranges in this SHOULD be significantly more restrictive,
26
+ # and the /<subtype> construct should not be optional. however, we'll aim
27
+ # to match whatever media type we are given.
28
+ #
29
+ # example:
30
+ # MEDIA_TYPE_REGEXP.match('application/vnd.github+json').named_captures
31
+ # =>
32
+ # {
33
+ # "media_type" => "application/vnd.github+json",
34
+ # "type" => "application",
35
+ # "subtype" => "vnd.github+json",
36
+ # "facet" => "vnd",
37
+ # "suffix" => "json",
38
+ # }
39
+ #
40
+ # example of being more permissive than the spec allows:
41
+ # MEDIA_TYPE_REGEXP.match('where the %$*! am I').named_captures
42
+ # =>
43
+ # {
44
+ # "media_type" => "where the %$*! am I",
45
+ # "type" => "where the %$*! am I",
46
+ # "subtype" => nil,
47
+ # "facet" => nil,
48
+ # "suffix" => nil
49
+ # }
50
+ MEDIA_TYPE_REGEXP = %r{
51
+ (?<media_type> # the media type includes the type and subtype
52
+ (?<type>[^\/;\"]*) # the type precedes the first slash
53
+ (?:\/ # slash
54
+ (?<subtype> # the subtype includes the facet, the suffix, and bits in between
55
+ (?:
56
+ (?<facet>[^.+;\"]*) # the facet name comes before the first . in the subtype
57
+ \. # dot
58
+ )?
59
+ [^\+;\"]* # anything between facet and suffix
60
+ (?:\+ # plus
61
+ (?<suffix>[^;\"]*) # optional suffix
62
+ )?
63
+ )
64
+ )? # the subtype should not be optional, but we will match a type without subtype anyway
65
+ )
66
+ }x
67
+
68
+ def initialize(*a)
69
+ super
70
+
71
+ scanner = StringScanner.new(self)
72
+
73
+ if scanner.scan(MEDIA_TYPE_REGEXP)
74
+ @media_type = scanner[:media_type].strip.freeze if scanner[:media_type]
75
+ @type = scanner[:type].strip.freeze if scanner[:type]
76
+ @subtype = scanner[:subtype].strip.freeze if scanner[:subtype]
77
+ @facet = scanner[:facet].strip.freeze if scanner[:facet]
78
+ @suffix = scanner[:suffix].strip.freeze if scanner[:suffix]
79
+ end
80
+
81
+ @parameters = Hash.new do |h, k|
82
+ if k.respond_to?(:downcase) && k != k.downcase
83
+ h[k.downcase]
84
+ else
85
+ nil
86
+ end
87
+ end
88
+
89
+ while scanner.scan(/(;\s*)+/)
90
+ key = scanner.scan(/[^;=\"]*/)
91
+ if key && scanner.scan(/=/)
92
+ value = String.new
93
+ until scanner.eos? || scanner.check(/;/)
94
+ if scanner.scan(/\s+/)
95
+ ws = scanner[0]
96
+ # discard trailing whitespace.
97
+ # other whitespace isn't technically valid but we are permissive so we put it in the value.
98
+ value << ws unless scanner.eos? || scanner.check(/;/)
99
+ elsif scanner.scan(/"/)
100
+ until scanner.eos? || scanner.scan(/"/)
101
+ if scanner.scan(/\\/)
102
+ value << scanner.getch unless scanner.eos?
103
+ end
104
+ value << scanner.scan(/[^\"\\]*/)
105
+ end
106
+ else
107
+ value << scanner.scan(/[^\s;\"]*/)
108
+ end
109
+ end
110
+ @parameters[key.downcase.freeze] = value.freeze
111
+ end
112
+ end
113
+
114
+ @parameters.freeze
115
+
116
+ freeze
117
+ end
118
+
119
+ # @return [String, nil] the media type of this content type.
120
+ # e.g. "application/vnd.github+json" in content-type: application/vnd.github+json; charset="utf-8"
121
+ attr_reader :media_type
122
+
123
+ # @return [String, nil] the 'type' portion of our media type.
124
+ # e.g. "application" in content-type: application/vnd.github+json; charset="utf-8"
125
+ attr_reader :type
126
+
127
+ # @return [String, nil] the 'subtype' portion of our media type.
128
+ # e.g. "vnd.github+json" in content-type: application/vnd.github+json; charset="utf-8"
129
+ attr_reader :subtype
130
+
131
+ # @return [String, nil] the 'facet' portion of our media type.
132
+ # e.g. "vnd" in content-type: application/vnd.github+json; charset="utf-8"
133
+ attr_reader :facet
134
+
135
+ # @return [String, nil] the 'suffix' portion of our media type.
136
+ # e.g. "json" in content-type: application/vnd.github+json; charset="utf-8"
137
+ attr_reader :suffix
138
+
139
+ # @return [Hash<String, String>] parameters of this content type.
140
+ # e.g. {"charset" => "utf-8"} in content-type: application/vnd.github+json; charset="utf-8"
141
+ attr_reader :parameters
142
+
143
+ # @param other_type
144
+ # @return [Boolean] is the 'type' portion of our media type equal (case-insensitive) to the given other_type
145
+ def type?(other_type)
146
+ type && type.casecmp?(other_type)
147
+ end
148
+
149
+ # @param other_subtype
150
+ # @return [Boolean] is the 'subtype' portion of our media type equal (case-insensitive) to the given other_subtype
151
+ def subtype?(other_subtype)
152
+ subtype && subtype.casecmp?(other_subtype)
153
+ end
154
+
155
+ # @param other_suffix
156
+ # @return [Boolean] is the 'suffix' portion of our media type equal (case-insensitive) to the given other_suffix
157
+ def suffix?(other_suffix)
158
+ suffix && suffix.casecmp?(other_suffix)
159
+ end
160
+
161
+ SOME_TEXT_SUBTYPES = %w(
162
+ x-www-form-urlencoded
163
+ json
164
+ json-seq
165
+ jwt
166
+ jose
167
+ yaml
168
+ x-yaml
169
+ xml
170
+ html
171
+ css
172
+ javascript
173
+ ecmascript
174
+ ).map(&:freeze).freeze
175
+
176
+ # @param unknown [Boolean] return this value when we have no idea whether
177
+ # our media type is binary or text.
178
+ # @return [Boolean] does this content type appear to be binary?
179
+ # this library makes its best guess based on a very incomplete knowledge
180
+ # of which media types indicate binary or text.
181
+ def binary?(unknown: true)
182
+ return false if type_text?
183
+
184
+ SOME_TEXT_SUBTYPES.each do |cmpsubtype|
185
+ return false if (suffix ? suffix.casecmp?(cmpsubtype) : subtype ? subtype.casecmp?(cmpsubtype) : false)
186
+ end
187
+
188
+ # these are generally binary
189
+ return true if type_image? || type_audio? || type_video?
190
+
191
+ # we're out of ideas
192
+ return unknown
193
+ end
194
+
195
+ # @return [Boolean] is this a JSON content type?
196
+ def json?
197
+ suffix ? suffix.casecmp?('json') : subtype ? subtype.casecmp?('json') : false
198
+ end
199
+
200
+ # @return [Boolean] is this an XML content type?
201
+ def xml?
202
+ suffix ? suffix.casecmp?('xml'): subtype ? subtype.casecmp?('xml') : false
203
+ end
204
+
205
+ # @return [Boolean] is this a x-www-form-urlencoded content type?
206
+ def form_urlencoded?
207
+ suffix ? suffix.casecmp?('x-www-form-urlencoded'): subtype ? subtype.casecmp?('x-www-form-urlencoded') : false
208
+ end
209
+
210
+ # @return [Boolean] is the 'type' portion of our media type 'text'
211
+ def type_text?
212
+ type && type.casecmp?('text')
213
+ end
214
+
215
+ # @return [Boolean] is the 'type' portion of our media type 'image'
216
+ def type_image?
217
+ type && type.casecmp?('image')
218
+ end
219
+
220
+ # @return [Boolean] is the 'type' portion of our media type 'audio'
221
+ def type_audio?
222
+ type && type.casecmp?('audio')
223
+ end
224
+
225
+ # @return [Boolean] is the 'type' portion of our media type 'video'
226
+ def type_video?
227
+ type && type.casecmp?('video')
228
+ end
229
+
230
+ # @return [Boolean] is the 'type' portion of our media type 'application'
231
+ def type_application?
232
+ type && type.casecmp?('application')
233
+ end
234
+
235
+ # @return [Boolean] is the 'type' portion of our media type 'message'
236
+ def type_message?
237
+ type && type.casecmp?('message')
238
+ end
239
+
240
+ # @return [Boolean] is the 'type' portion of our media type 'multipart'
241
+ def type_multipart?
242
+ type && type.casecmp?('multipart')
243
+ end
244
+ end
245
+ end