media_types 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27062ffb1d9b17404a0032f220478b120aa41089
4
- data.tar.gz: ba0dab9025a4e53aef10e8b9de7edd3ef121c2dc
3
+ metadata.gz: 3399faaeae9e47d89ee2efe7a327dd8b1ceae7ae
4
+ data.tar.gz: 2519f99721a48e5aa5f7cf7b014e821717276d8f
5
5
  SHA512:
6
- metadata.gz: f18dd51f3f86ba10a7c8deb68ce21dc5f2fe1f07bafe2a2fbdf6ed2a30caba04c92df54b1468adce18512641742c593d433b974304db28345804ae6020e79fe4
7
- data.tar.gz: b05ed05756b9af5512eca5a10dec3244a75462d8ea3a7869ccd9b6e0fe2dc76b2c4fab5d5d89f39b760f606ec496590b9469d8a82fc2b96f8ea4d288f4576ba4
6
+ metadata.gz: 55f29a6d81758d3c0fa71153e63677d35d138b22e64afae8ae4e76b349219915f0e826a86d51f83c51c0ff573555abbaa807669766f413f6297ee12a9609ae4c
7
+ data.tar.gz: c4017a0fc061f4ecbde0f66a3274e59982f6e608847e4654e431f3c3644464481da56c0bd5364c88525d7da1e2f6ea45bb0a69534e37abc86340992ef29f674a
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # 0.2.0
2
+
3
+ Breaking changes to update public API and usage
4
+
5
+ - Remove `Base` class (use `MediaTypes::Dsl` instead)
6
+ - Remove a lot of configuration options as they are deemed unneeded
7
+ - Remove `active_support` dependency
8
+ - Rename `ConstructableMimeType` to `Constructable`
9
+ - Moved global scheme types to `Scheme` as subtype
10
+ - Add `MediaTypes::Dsl`
11
+ - Add `validations` block to capture schemes
12
+ - Add `registrations` block to capture register intent
13
+ - Add `defaults` block to capture mime type defaults
14
+ - Add `MediaTypes.register` class method to call `Mime::Type.register`
15
+ - Add `Registerable` capture class
16
+ - Add type / base setting for `Constructable`
17
+ - Add versioned validations
18
+ - Add forced types of `collection`s
19
+ - Add `attribute` with block
20
+ - Add `EnumerationOfType` for schema typed arrays
21
+ - Add `AnyOf` for scheme enum types
22
+ - Add non-block calls for `Scheme` dsl
23
+ - Add yard documentation to `/docs`
24
+
25
+ # 0.1.0
26
+
27
+ :baby: initial release
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- media_types (0.1.3)
4
+ media_types (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ansi (1.5.0)
10
+ awesome_print (1.8.0)
10
11
  builder (3.2.3)
11
12
  docile (1.3.1)
12
13
  json (2.1.0)
@@ -30,6 +31,7 @@ PLATFORMS
30
31
  x64-mingw32
31
32
 
32
33
  DEPENDENCIES
34
+ awesome_print
33
35
  bundler (~> 1.16)
34
36
  media_types!
35
37
  minitest (~> 5.0)
data/README.md CHANGED
@@ -24,7 +24,7 @@ Or install it yourself as:
24
24
 
25
25
  By default there are no media types registered or defined, except for an abstract base type.
26
26
 
27
- ### Definition
27
+ ## Definition
28
28
  You can define media types by inheriting from this base type, or create your own base type with a class method
29
29
  `.base_format` that is used to create the final media type string by injecting formatted parameters:
30
30
 
@@ -37,9 +37,9 @@ You can define media types by inheriting from this base type, or create your own
37
37
  require 'media_types'
38
38
 
39
39
  class Venue < MediaTypes::Base
40
- media_type 'venue', suffix: :json, current_version: 2
40
+ media_type 'venue', defaults: { suffix: :json, version: 2 }
41
41
 
42
- current_scheme do
42
+ validations do
43
43
  attribute :name, String
44
44
  collection :location do
45
45
  attribute :latitude, Numeric
@@ -49,22 +49,41 @@ class Venue < MediaTypes::Base
49
49
 
50
50
  link :self
51
51
  link :route, allow_nil: true
52
- end
53
-
54
- register_types :venue_json do
55
- create :create_venue_json
56
- index :venue_urls_json
57
- collection :venue_collection_json
58
- end
59
-
60
- register_additional_versions do
52
+
61
53
  version 1 do
62
54
  attribute :name, String
63
55
  attribute :coords, String
64
56
  attribute :updated_at, String
65
-
57
+
66
58
  link :self
67
59
  end
60
+
61
+ view 'create' do
62
+ collection :location do
63
+ attribute :latitude, Numeric
64
+ attribute :longitude, Numeric
65
+ attribute :altitude, AllowNil(Numeric)
66
+ end
67
+
68
+ version 1 do
69
+ collection :location do
70
+ attribute :latitude, Numeric
71
+ attribute :longitude, Numeric
72
+ attribute :altitude, AllowNil(Numeric)
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ registrations :venue_json do
79
+ view 'create', :create_venue
80
+ view 'index', :venue_urls
81
+ view 'collection', :venue_collection
82
+
83
+ versions [1,2]
84
+
85
+ suffix :json
86
+ suffix :xml
68
87
  end
69
88
 
70
89
  def self.base_format
@@ -73,26 +92,201 @@ class Venue < MediaTypes::Base
73
92
  end
74
93
  ```
75
94
 
76
- ### Schema Definitions
95
+ ## Schema Definitions
77
96
 
78
97
  If you define a scheme using `current_scheme { }`, you may use any of the following dsl:
79
98
 
80
- - `attribute(string, klazz)`: Adds an attribute to the scheme, with the type `klazz`
81
- - `any(&block)`: Allow for any key, which then is validated against the block (which is a scheme).
82
- - `collection(string, &block)`: Expect a collection such as an array or hash. If it's an array, each item is validated
83
- against the block (which is a scheme). If it's a hash, the hash is validated against the block. If you want to force an
84
- array or an object, prepend the collection by `attribute(string, Hash)` or `attribute(string, Array)`.
85
- - `no_strict`: Can be added to a `scheme` such as the root, block inside `any` or block inside `collection` to allow for
86
- undefined keys. If `no_strict` is not added, the block will not be valid if there are extra keys.
87
- - `link(string)`: Example of a domain type. Each link is actually added to a scheme for `_links` on the current scheme.
99
+ ### `attribute`
100
+
101
+ Adds an attribute to the schema, if a +block+ is given, uses that to test against instead of +type+
102
+
103
+ | param | type | description |
104
+ |-------|------|-------------|
105
+ | key | `Symbol` | the attribute name |
106
+ | opts | `Hash` | options to pass to `Scheme` or `Attribute` |
107
+ | type | `Class`, `===`, Scheme | The type of the value, can be anything that responds to `===`, or scheme to use if no `&block` is given. Defaults to `String` without a `&block` and to Hash with a `&block`. |
108
+ | &block | `Block` | defines the scheme of the value of this attribute |
109
+
110
+ #### Add an attribute named foo, expecting a string
111
+ ```Ruby
112
+ require 'media_types'
113
+
114
+ class MyMedia
115
+ include MediaTypes::Dsl
116
+
117
+ validations do
118
+ attribute :foo, String
119
+ end
120
+ end
121
+
122
+ MyMedia.valid?({ foo: 'my-string' })
123
+ # => true
124
+ ```
125
+
126
+ #### Add an attribute named foo, expecting nested scheme
88
127
 
89
- If you want to compose types, you can wrap a klazz in `AllowNil(klazz)` to allow for nil values. This makes a validation
90
- expected that klass, or nil.
128
+ ```Ruby
129
+ class MyMedia
130
+ include MediaTypes::Dsl
131
+
132
+ validations do
133
+ attribute :foo do
134
+ attribute :bar, String
135
+ end
136
+ end
137
+ end
138
+
139
+ MyMedia.valid?({ foo: { bar: 'my-string' }})
140
+ # => true
141
+ ```
91
142
 
92
- You an add your own DSL by inspecting the `lib/media_types/scheme/<klazz>` classes.
143
+ ### `any`
144
+ Allow for any key. The `&block` defines the Schema for each value.
93
145
 
94
- ### Validation
95
- If your type has a schema, you can now use this media type for validation:
146
+ | param | type | description |
147
+ |-------|------|-------------|
148
+ | scheme | `Scheme`, `NilClass` | scheme to use if no `&block` is given |
149
+ | allow_empty: | `TrueClass`, `FalsClass` | if true, empty (no key/value present) is allowed |
150
+ | force: | `Class`, | forces the validated value to have this type, defaults to `Hash` |
151
+ | &block | `Block` | defines the scheme of the value of this attribute |
152
+
153
+ #### Add a collection named foo, expecting any key with a defined value
154
+ ```Ruby
155
+ class MyMedia
156
+ include MediaTypes::Dsl
157
+
158
+ validations do
159
+ collection :foo do
160
+ any do
161
+ attribute :bar, String
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ MyMedia.valid?({ foo: [{ anything: { bar: 'my-string' }, other_thing: { bar: 'other-string' } }] })
168
+ # => true
169
+ ````
170
+
171
+ ### `not_strict`
172
+ Allow for extra keys in the schema/collection even when passing `strict: true` to `#validate!`
173
+
174
+ #### Allow for extra keys in collection
175
+
176
+ ```Ruby
177
+ class MyMedia
178
+ include MediaTypes::Dsl
179
+
180
+ validations do
181
+ collection :foo do
182
+ attribute :required, String
183
+ not_strict
184
+ end
185
+ end
186
+ end
187
+
188
+ MyMedia.valid?({ foo: [{ required: 'test', bar: 42 }] })
189
+ # => true
190
+ ```
191
+
192
+ ### `collection`
193
+ Expect a collection such as an array or hash. The `&block` defines the Schema for each item in that collection.
194
+
195
+ | param | type | description |
196
+ |-------|------|-------------|
197
+ | key | `Symbol` | key of the collection (same as `#attribute`) |
198
+ | scheme | `Scheme`, `NilClass`, `Class` | scheme to use if no `&block` is given or `Class` of each item in the |
199
+ | allow_empty: | `TrueClass`, `FalsClass` | if true, empty (no key/value present) is allowed |
200
+ | force: | `Class`, | forces the validated value to have this type, defaults to `Array` |
201
+ | &block | `Block` | defines the scheme of the value of this attribute |
202
+
203
+
204
+ #### Collection with an array of string
205
+ ```Ruby
206
+ class MyMedia
207
+ include MediaTypes::Dsl
208
+
209
+ validations do
210
+ collection :foo, String
211
+ end
212
+ end
213
+
214
+ MyMedia.valid?({ collection: ['foo', 'bar'] })
215
+ # => true
216
+ ```
217
+
218
+ #### Collection with defined scheme
219
+
220
+ ```Ruby
221
+ class MyMedia
222
+ include MediaTypes::Dsl
223
+
224
+ validations do
225
+ collection :foo do
226
+ attribute :required, String
227
+ attribute :number, Numeric
228
+ end
229
+ end
230
+ end
231
+
232
+ MyMedia.valid?({ foo: [{ required: 'test', number: 42 }, { required: 'other', number: 0 }] })
233
+ # => true
234
+ ```
235
+
236
+ ### `link`
237
+
238
+ Expect a link
239
+
240
+ #### Links as defined in HAL, JSON-Links and other specs
241
+ ```Ruby
242
+ class MyMedia
243
+ include MediaTypes::Dsl
244
+
245
+ validations do
246
+ link :_self
247
+ link :image
248
+ end
249
+ end
250
+
251
+ MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
252
+ # => true
253
+ ```
254
+
255
+
256
+ #### Link with extra attributes
257
+ ```Ruby
258
+ class MyMedia
259
+ include MediaTypes::Dsl
260
+
261
+ validations do
262
+ link :image do
263
+ attribute :templated, TrueClass
264
+ end
265
+ end
266
+ end
267
+
268
+ MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
269
+ # => true
270
+ ```
271
+
272
+ #### Link with extra attributes
273
+ ```Ruby
274
+ class MyMedia
275
+ include MediaTypes::Dsl
276
+
277
+ validations do
278
+ link :image do
279
+ attribute :templated, TrueClass
280
+ end
281
+ end
282
+ end
283
+
284
+ MyMedia.valid?({ _links: { image: { href: 'https://image.org/{md5}', templated: true }} })
285
+ # => true
286
+ ```
287
+
288
+ ## Validation
289
+ If your type has a validations, you can now use this media type for validation:
96
290
 
97
291
  ```Ruby
98
292
  Venue.valid?({ ... })
@@ -102,11 +296,10 @@ Venue.validate!({ ... })
102
296
  # => raises if it's not valid
103
297
  ```
104
298
 
105
- ### Formatting for headers
299
+ ## Formatting for headers
106
300
  Any media type object can be coerced in valid string to be used with `Content-Type` or `Accept`:
107
301
 
108
302
  ```Ruby
109
-
110
303
  Venue.mime_type.to_s
111
304
  # => "application/vnd.mydomain.venue.v2+json"
112
305
 
@@ -126,7 +319,11 @@ Venue.mime_type.view('active').to_s
126
319
  # => "application/vnd.mydomain.venue.v2.active+json"
127
320
  ```
128
321
 
129
- ### Register in Rails or Rack
322
+ ## Register in Rails or Rack
323
+ Define a `registrations` block on your media type, indicating the symbol for the base type (`registrations :symbol do`)
324
+ and inside use the registrations dsl to define which media types to register. `versions array_of_numbers` determines which versions,
325
+ `suffix name` adds a suffix, `type_alias name` adds an alias and `view name, symbol` adds a view.
326
+
130
327
  As long as `action_dispatch` is available, you can register the mime type with `action_dispatch/http/mime_type`:
131
328
  ```Ruby
132
329
  Venue.register
@@ -147,4 +344,4 @@ push the `.gem` file to rubygems.org.
147
344
 
148
345
  ## Contributing
149
346
 
150
- Bug reports and pull requests are welcome on GitHub at [SleeplessByte/media_types-ruby](https://github.com/SleeplessByte/media_types-ruby)
347
+ Bug reports and pull requests are welcome on GitHub at [SleeplessByte/media-types-ruby](https://github.com/SleeplessByte/media-types-ruby)
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
- require "bundler/gem_tasks"
2
- require "rake/testtask"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
3
5
 
4
6
  Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList["test/**/*_test.rb"]
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
8
10
  end
9
11
 
10
- task :default => :test
12
+ task default: :test
@@ -4,26 +4,31 @@ require 'delegate'
4
4
  require 'singleton'
5
5
 
6
6
  module MediaTypes
7
- class ConstructableMimeType < SimpleDelegator
7
+ class Constructable < SimpleDelegator
8
8
 
9
9
  def initialize(klazz, **opts)
10
10
  super klazz
11
11
  self.opts = opts
12
12
  end
13
13
 
14
+ def type(name = NO_ARG)
15
+ return opts[:type] if name == NO_ARG
16
+ Constructable.new(__getobj__, **with(type: name))
17
+ end
18
+
14
19
  def version(version = NO_ARG)
15
20
  return opts[:version] if version == NO_ARG
16
- ConstructableMimeType.new(__getobj__, **with(version: version))
21
+ Constructable.new(__getobj__, **with(version: version))
17
22
  end
18
23
 
19
24
  def view(view = NO_ARG)
20
25
  return opts[:view] if view == NO_ARG
21
- ConstructableMimeType.new(__getobj__, **with(view: view))
26
+ Constructable.new(__getobj__, **with(view: view))
22
27
  end
23
28
 
24
29
  def suffix(suffix = NO_ARG)
25
30
  return opts[:suffix] if suffix == NO_ARG
26
- ConstructableMimeType.new(__getobj__, **with(suffix: suffix))
31
+ Constructable.new(__getobj__, **with(suffix: suffix))
27
32
  end
28
33
 
29
34
  def collection
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Defaults
5
+ def initialize(media_type, &block)
6
+ self.media_type = media_type
7
+
8
+ instance_exec(&block) if block_given?
9
+ end
10
+
11
+ def method_missing(method_name, *arguments, &block)
12
+ if media_type.respond_to?(method_name)
13
+ return self.media_type = media_type.send(method_name, *arguments, &block)
14
+ end
15
+
16
+ super
17
+ end
18
+
19
+ def respond_to_missing?(method_name, include_private = false)
20
+ media_type.respond_to?(method_name) || super
21
+ end
22
+
23
+ def to_constructable
24
+ media_type
25
+ end
26
+
27
+ private
28
+
29
+ attr_accessor :media_type
30
+ end
31
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/constructable'
4
+ require 'media_types/defaults'
5
+ require 'media_types/registrar'
6
+ require 'media_types/validations'
7
+
8
+ module MediaTypes
9
+ module Dsl
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ base.class_eval do
13
+ class << self
14
+ private
15
+
16
+ attr_accessor :media_type_constructable, :symbol_base, :media_type_registrar, :media_type_validations
17
+ end
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ def to_constructable
24
+ media_type_constructable.dup
25
+ end
26
+
27
+ def valid?(output, media_type = to_constructable, **opts)
28
+ validations.find(String(media_type)).valid?(output, backtrace: ['.'], **opts)
29
+ end
30
+
31
+ def validate!(output, media_type = to_constructable, **opts)
32
+ validations.find(String(media_type)).validate(output, backtrace: ['.'], **opts)
33
+ end
34
+
35
+ def register
36
+ registrations.to_a.map do |registerable|
37
+ MediaTypes.register(registerable)
38
+ registerable
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def media_type(name, defaults: {})
45
+ self.media_type_constructable =
46
+ Constructable.new(self, format: base_format, type: name)
47
+ .version(defaults.fetch(:version) { nil })
48
+ .suffix(defaults.fetch(:suffix) { nil })
49
+ .view(defaults.fetch(:view) { nil })
50
+ end
51
+
52
+ def defaults(&block)
53
+ self.media_type_constructable = Defaults.new(to_constructable, &block).to_constructable
54
+ end
55
+
56
+ def registrations(symbol = nil, &block)
57
+ self.media_type_registrar = media_type_registrar || Registrar.new(self, symbol: symbol, &block)
58
+ end
59
+
60
+ def validations(&block)
61
+ self.media_type_validations = media_type_validations || Validations.new(to_constructable, &block)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ module MediaTypes
2
+ class Hash < SimpleDelegator
3
+ def class
4
+ __getobj__.class
5
+ end
6
+
7
+ def ===(other)
8
+ __getobj__ === other # rubocop:disable Style/CaseEquality
9
+ end
10
+
11
+ def slice(*keep_keys)
12
+ if __getobj__.respond_to?(:slice)
13
+ return __getobj__.slice(*keep_keys)
14
+ end
15
+
16
+ h = {}
17
+ keep_keys.each { |key| h[key] = fetch(key) if key?(key) }
18
+ h
19
+ end
20
+ end
21
+ end
@@ -106,10 +106,10 @@ module MediaTypes
106
106
  uncalled = expected_types_hash.dup
107
107
 
108
108
  uncalled.length.times do
109
- mock.expect(:call, nil) do |arguments|
110
- type = arguments.fetch(:mime_type)
111
- symbol = arguments.fetch(:symbol)
112
- synonyms = arguments.fetch(:synonyms)
109
+ mock.expect(:call, nil) do |registerable|
110
+ type = registerable.to_s
111
+ symbol = registerable.to_sym
112
+ synonyms = registerable.synonyms
113
113
 
114
114
  options = uncalled.delete(type)
115
115
  options && options == [symbol, synonyms] || raise(
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MediaTypes
4
+ class Object < SimpleDelegator
5
+ def class
6
+ __getobj__.class
7
+ end
8
+
9
+ def ===(other)
10
+ __getobj__ === other # rubocop:disable Style/CaseEquality
11
+ end
12
+
13
+ def blank?
14
+ if __getobj__.respond_to?(:blank?)
15
+ return __getobj__.blank?
16
+ end
17
+
18
+ if __getobj__.respond_to?(:empty?)
19
+ return __getobj__.empty?
20
+ end
21
+
22
+ if __getobj__.respond_to?(:length)
23
+ return __getobj__.length.zero?
24
+ end
25
+
26
+ !__getobj__
27
+ end
28
+
29
+ alias empty? blank?
30
+
31
+ def present?
32
+ !blank?
33
+ end
34
+ end
35
+ end