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.
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