Skip to content

Commit aa1b685

Browse files
thexa4SleeplessByte
authored andcommitted
Small (mostly) backwards compatible quality of life changes (#65)
* Small (mostly) backwards compatible quality of life changes - Fixes an issue where multiple versions or views could map to the same identifier, which would result in them overwriting each other. - You now have to explicitly mark a version of a validation as an empty object. This solves an issue where you could succesfully validate constructables with a nil version by passing in an empty object. - view and version can now directly be called on the validator instead of having to call to_constructable. - Validating a media type which doesn't have a validation now throws an error instead of accepting an empty object. - Added available_validations function on validator which returns a list of constructable it supports. - Added name DSL function to replace media_type. name allows using self.organisation instead of having to define self.base_format. name defaults to using an opinionated media type identifier format. Name now only allows setting a default suffix as having a default version caused subtle bugs during real world usage. - Added identfier_format function to override opinionated media type identifier format. - Added identifier function to get the media type identifier
1 parent 563e3b2 commit aa1b685

22 files changed

+498
-194
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
# 1.0.0
2+
- Added the ability to do inline tests when defining validations using `assert_pass '<json>'` and `assert_fail '<json>'`.
3+
- `media_type` has been replaced with `use_name`.
4+
- It is no longer possible to set a default version. Please use `version <x> do` instead.
5+
- You no longer need to specify a custom format string. If you set an organisation with `def self.organisation` or set a module wide organisation with `MediaTypes::set_organisation <module>, '<organisation>'` the library will generate identifiers for you.
6+
- `self.base_format` has been replaced by `identifier_format do |type:, view:, version:, suffix:|`.
7+
- Added the `empty` validation to mark an empty object as valid.
8+
- Added the `identifier` function to get the [Media Type Identifier](https://en.wikipedia.org/wiki/Media_type) for the validator.
9+
- Added `version(x)` and `view(x)` functions.
10+
- Added an `available_validations` functions that returns all defined validations.
11+
- Fixed an issue where validations could accidentally merge if defined with a bad `base_format`.
12+
- Fixed an issue where undefined validations would accept an empty object.
13+
114
# 0.6.2
215

316
- Fix handling empty collections

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
media_types (0.6.2)
4+
media_types (1.0.0)
55

66
GEM
77
remote: https://rubygems.org/

README.md

Lines changed: 105 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
Media Types based on scheme, with versioning, views, suffixes and validations. Integrations available for [Rails](https://github.com/rails/rails) / ActionPack and [http.rb](https://github.com/httprb/http).
88

9+
This library makes it easy to define schemas that can be used to validate JSON objects based on their Content-Type.
10+
911
## Installation
1012

1113
Add this line to your application's Gemfile:
@@ -24,38 +26,58 @@ Or install it yourself as:
2426

2527
## Usage
2628

27-
By default there are no media types registered or defined, except for an abstract base type.
29+
Define a validation:
30+
31+
```ruby
32+
require 'media_types'
33+
34+
module Acme
35+
MediaTypes::set_organisation Acme, 'acme'
36+
37+
class FooValidator
38+
include MediaTypes::Dsl
39+
40+
use_name 'foo'
2841

29-
## Definition
30-
You can define media types by inheriting from this base type, or create your own base type with a class method `.base_format` that is used to create the final media type string by injecting formatted parameters:
42+
validations do
43+
attribute :foo, String
44+
end
45+
end
46+
end
47+
```
3148

32-
- `%<type>s`: the type `media_type` received
33-
- `%<version>s`: the version, defaults to `:current_version`
34-
- `%<view>s`: the view, defaults to <empty>
35-
- `%<suffix>s`: the suffix
49+
Validate an object:
50+
51+
```ruby
52+
Acme::FooValidator.validate!({ foo: 'bar' })
53+
```
54+
55+
## Full example
3656

3757
```Ruby
3858
require 'media_types'
3959

4060
class Venue
4161
include MediaTypes::Dsl
4262

43-
def self.base_format
44-
'application/vnd.mydomain.%<type>s.v%<version>s.%<view>s+%<suffix>s'
63+
def self.organisation
64+
'mydomain'
4565
end
4666

47-
media_type 'venue', defaults: { suffix: :json, version: 2 }
67+
media_type 'venue', defaults: { suffix: :json }
4868

4969
validations do
50-
attribute :name, String
51-
collection :location do
52-
attribute :latitude, Numeric
53-
attribute :longitude, Numeric
54-
attribute :altitude, AllowNil(Numeric)
55-
end
70+
version 2 do
71+
attribute :name, String
72+
collection :location do
73+
attribute :latitude, Numeric
74+
attribute :longitude, Numeric
75+
attribute :altitude, AllowNil(Numeric)
76+
end
5677

57-
link :self
58-
link :route, allow_nil: true
78+
link :self
79+
link :route, allow_nil: true
80+
end
5981

6082
version 1 do
6183
attribute :name, String
@@ -97,7 +119,7 @@ end
97119

98120
## Schema Definitions
99121

100-
If you define a scheme using `current_scheme { }`, you may use any of the following dsl:
122+
If you include 'MediaTypes::Dsl' in your class you can use the following functions within a `validation do` block to define your schema:
101123

102124
### `attribute`
103125

@@ -315,25 +337,25 @@ expected_object.valid?([{ foo: 'string' }])
315337
```
316338

317339
## Formatting for headers
318-
Any media type object can be coerced in valid string to be used with `Content-Type` or `Accept`:
340+
Any media type object can be converted in valid string to be used with `Content-Type` or `Accept`:
319341

320342
```Ruby
321-
Venue.mime_type.to_s
343+
Venue.mime_type.identifier
322344
# => "application/vnd.mydomain.venue.v2+json"
323345
324-
Venue.mime_type.version(1).to_s
346+
Venue.mime_type.version(1).identifier
325347
# => "application/vnd.mydomain.venue.v1+json"
326348
327-
Venue.mime_type.version(1).suffix(:xml).to_s
349+
Venue.mime_type.version(1).suffix(:xml).identifier
328350
# => "application/vnd.mydomain.venue.v1+xml"
329351
330352
Venue.mime_type.to_s(0.2)
331353
# => "application/vnd.mydomain.venue.v2+json; q=0.2"
332354
333-
Venue.mime_type.collection.to_s
355+
Venue.mime_type.collection.identifier
334356
# => "application/vnd.mydomain.venue.v2.collection+json"
335357
336-
Venue.mime_type.view('active').to_s
358+
Venue.mime_type.view('active').identifier
337359
# => "application/vnd.mydomain.venue.v2.active+json"
338360
```
339361

@@ -374,6 +396,64 @@ Load the `http` integration and call `.register` on all media types you want to
374396

375397
Currently uses `oj` under the hood and this can not be changed.
376398

399+
## API
400+
401+
A defined schema has the following functions available:
402+
403+
### `valid?`
404+
405+
Example: `Venue.valid?({ foo: 'bar' })`
406+
407+
Allows passing in validation options as a second parameter.
408+
409+
### `validate!`
410+
411+
Example: `Venue.validate!({ foo: 'bar' })`
412+
413+
Allows passing in validation options as a second parameter.
414+
415+
### `validatable?`
416+
417+
Example: `Venue.version(42).validatable?`
418+
419+
Tests wether the current configuration of the schema has a validation defined.
420+
421+
### `register`
422+
423+
Example: `Venue.register`
424+
425+
Registers the media type to the registry.
426+
427+
### `view`
428+
429+
Example: `Venue.view('create')`
430+
431+
Returns a schema validator configured with the specified view.
432+
433+
### `version`
434+
435+
Example: `Venue.version(42)`
436+
437+
Returns a schema validator configured with the specified version.
438+
439+
### `suffix`
440+
441+
Example: `Venue.suffix(:json)`
442+
443+
Returns a schema validator configured with the specified suffix.
444+
445+
### `identifier`
446+
447+
Example: `Venue.version(2).identifier` (returns `'application/vnd.application.venue.v2'`)
448+
449+
Returns the IANA compatible [Media Type Identifier](https://en.wikipedia.org/wiki/Media_type) for the configured schema.
450+
451+
### `available_validations`
452+
453+
Example: `Venue.available_validations`
454+
455+
Returns a list of all the schemas that are defined.
456+
377457
## Related
378458

379459
- [`MediaTypes::Serialization`](https://github.com/XPBytes/media_types-serialization): :cyclone: Add media types supported serialization using your favourite serializer

lib/media_types.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
require 'media_types/integrations'
1313

1414
module MediaTypes
15+
def self.set_organisation(mod, organisation)
16+
@organisation_prefixes ||= {}
17+
@organisation_prefixes[mod.name] = organisation
18+
end
19+
20+
def self.get_organisation(mod)
21+
name = mod.name
22+
prefixes = @organisation_prefixes.keys.select { |p| name.start_with? p }
23+
return nil unless prefixes.any?
24+
best = prefixes.max_by { |p| p.length }
25+
26+
@organisation_prefixes[best]
27+
end
1528
end
1629

1730

lib/media_types/.dsl.rb.swp

20 KB
Binary file not shown.

lib/media_types/constructable.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,40 +73,60 @@ def split(pattern = nil, *limit)
7373
to_str.split(pattern, *limit)
7474
end
7575

76+
def as_key
77+
[type, view, version, suffix]
78+
end
79+
7680
def hash
77-
to_str.hash
81+
as_key.hash
7882
end
7983

8084
def to_str(qualifier = nil)
81-
# TODO: remove warning by slicing out these arguments if they don't appear in the format
8285
qualified(
8386
qualifier,
84-
Formatter.call(opts)
87+
__getobj__.media_type_name_for.call(
88+
type: opts[:type],
89+
view: opts[:view],
90+
version: opts[:version],
91+
suffix: opts[:suffix],
92+
)
8593
)
8694
end
95+
96+
def available_validations
97+
return [] if !validatable?
98+
[self]
99+
end
87100

88101
def valid?(output, **validation_opts)
89-
__getobj__.valid?(
102+
raise ArgumentError, "Unable to validate #{to_s} type without a corresponding validation. Please mark objects that should be empty with 'empty'." unless validatable?
103+
104+
__getobj__.valid_unsafe?(
90105
output,
91106
self,
92107
**validation_opts
93108
)
94109
end
95110

96111
def validate!(output, **validation_opts)
97-
__getobj__.validate!(
112+
raise ArgumentError, "Unable to validate #{to_s} type without a corresponding validation. Please mark objects that should be empty with 'empty'." unless validatable?
113+
114+
__getobj__.validate_unsafe!(
98115
output,
99116
self,
100117
**validation_opts
101118
)
102119
end
103120

104121
def validatable?
122+
return false unless media_type_combinations.include? as_key
123+
105124
__getobj__.validatable?(self)
106125
end
107126

108127
alias inspect to_str
109128
alias to_s to_str
129+
alias identifier to_str
110130

111131
private
112132

0 commit comments

Comments
 (0)