media_types 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40c245e8d88896a28fc047f4c05f1991c59a95ba
4
+ data.tar.gz: 007f468d119afe894a615e4acd28a16a9175cfa4
5
+ SHA512:
6
+ metadata.gz: 38e8ac6b119157c3ac12ac84f0cf495c797c9878df4f60cc24570ac7baf890941d95a62a540281a924eedff94f21acdccfd598f07a169380490b9e4946da2aac
7
+ data.tar.gz: 7243579da6b1819058856ebcfe85262cf48f509983e06e288fba8d5ac10ded2880fd3ed997b21f823ff83e80a2f4c6cec106bc33e2417e7a6290144d8fefdedb
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ .idea/
data/.rubocop.yml ADDED
@@ -0,0 +1,29 @@
1
+ AllCops:
2
+ Include:
3
+ - '**/Rakefile'
4
+ - 'lib/**/*.rb'
5
+ Exclude:
6
+ - 'Gemfile'
7
+ - 'bin/**/*'
8
+ TargetRubyVersion: 2.4
9
+
10
+ Layout/EmptyLinesAroundClassBody:
11
+ Enabled: false
12
+
13
+ Layout/EndOfLine:
14
+ Enabled: false
15
+
16
+ Metrics/LineLength:
17
+ Max: 120
18
+
19
+ Metrics/MethodLength:
20
+ Max: 15
21
+
22
+ Style/Documentation:
23
+ Enabled: false
24
+
25
+ Style/EmptyMethod:
26
+ EnforcedStyle: expanded
27
+
28
+ Style/IfUnlessModifier:
29
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ sudo: false
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.0
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
9
+ - rbx-3
10
+ - ruby-head
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
14
+ - rvm: rbx-3
15
+ - rvm: 2.6
16
+ before_install:
17
+ - gem update --system
18
+ - gem --version
19
+ install:
20
+ - bundle install --with development --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in media_types.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ media_types (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ builder (3.2.3)
11
+ docile (1.3.1)
12
+ json (2.1.0)
13
+ minitest (5.11.3)
14
+ minitest-ci (3.4.0)
15
+ minitest (>= 5.0.6)
16
+ minitest-reporters (1.3.4)
17
+ ansi
18
+ builder
19
+ minitest (>= 5.0)
20
+ ruby-progressbar
21
+ rake (10.5.0)
22
+ ruby-progressbar (1.10.0)
23
+ simplecov (0.16.1)
24
+ docile (~> 1.1)
25
+ json (>= 1.8, < 3)
26
+ simplecov-html (~> 0.10.0)
27
+ simplecov-html (0.10.2)
28
+
29
+ PLATFORMS
30
+ x64-mingw32
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.16)
34
+ media_types!
35
+ minitest (~> 5.0)
36
+ minitest-ci
37
+ minitest-reporters
38
+ rake (~> 10.0)
39
+ simplecov
40
+
41
+ BUNDLED WITH
42
+ 1.16.4
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # MediaTypes
2
+ [![Build Status](https://travis-ci.com/SleeplessByte/media-types-ruby.svg?branch=master)](https://travis-ci.com/SleeplessByte/media-types-ruby)
3
+ [![Gem Version](https://badge.fury.io/rb/media-types-ruby.svg)](https://badge.fury.io/rb/media-types-ruby)
4
+ [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/6f2dc1fb37ecb98c4363/maintainability)](https://codeclimate.com/github/SleeplessByte/media-types-ruby/maintainability)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'media_types'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install media_types
22
+
23
+ ## Usage
24
+
25
+ By default there are no media types registered or defined, except for an abstract base type.
26
+
27
+ ### Definition
28
+ You can define media types by inheriting from this base type, or create your own base type with a class method
29
+ `.base_format` that is used to create the final media type string by injecting formatted parameters:
30
+
31
+ - `%<type>s`: the type `media_type` received
32
+ - `%<version>s`: the version, defaults to `:current_version`
33
+ - `%<view>s`: the view, defaults to <empty>
34
+ - `%<suffix>s`: the suffix
35
+
36
+ ```Ruby
37
+ require 'media_types'
38
+
39
+ class Venue < MediaTypes::Base
40
+ media_type 'venue', suffix: :json, current_version: 2
41
+
42
+ current_scheme do
43
+ attribute :name, String
44
+ collection :location do
45
+ attribute :latitude, Numeric
46
+ attribute :longitude, Numeric
47
+ attribute :altitude, AllowNil(Numeric)
48
+ end
49
+
50
+ link :self
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
61
+ version 1 do
62
+ attribute :name, String
63
+ attribute :coords, String
64
+ attribute :updated_at, String
65
+
66
+ link :self
67
+ end
68
+ end
69
+
70
+ def self.base_format
71
+ 'application/vnd.mydomain.%<type>s.v%<version>s%<view>s+%<suffix>s'
72
+ end
73
+ end
74
+ ```
75
+
76
+ ### Schema Definitions
77
+
78
+ If you define a scheme using `current_scheme { }`, you may use any of the following dsl:
79
+
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.
88
+
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.
91
+
92
+ You an add your own DSL by inspecting the `lib/media_types/scheme/<klazz>` classes.
93
+
94
+ ### Validation
95
+ If your type has a schema, you can now use this media type for validation:
96
+
97
+ ```Ruby
98
+ Venue.valid?({ ... })
99
+ # => true if valid, false otherwise
100
+
101
+ Venue.validate!({ ... })
102
+ # => raises if it's not valid
103
+ ```
104
+
105
+ ### Formatting for headers
106
+ Any media type object can be coerced in valid string to be used with `Content-Type` or `Accept`:
107
+
108
+ ```Ruby
109
+
110
+ Venue.mime_type.to_s
111
+ # => "application/vnd.mydomain.venue.v2+json"
112
+
113
+ Venue.mime_type.version(1).to_s
114
+ # => "application/vnd.mydomain.venue.v1+json"
115
+
116
+ Venue.mime_type.version(1).suffix(:xml).to_s
117
+ # => "application/vnd.mydomain.venue.v1+xml"
118
+
119
+ Venue.mime_type.to_s(0.2)
120
+ # => "application/vnd.mydomain.venue.v2+json; q=0.2"
121
+
122
+ Venue.mime_type.collection.to_s
123
+ # => "application/vnd.mydomain.venue.v2.collection+json"
124
+
125
+ Venue.mime_type.view('active').to_s
126
+ # => "application/vnd.mydomain.venue.v2.active+json"
127
+ ```
128
+
129
+ ### Register in Rails or Rack
130
+ As long as `action_dispatch` is available, you can register the mime type with `action_dispatch/http/mime_type`:
131
+ ```Ruby
132
+ Venue.register
133
+ # => Mime type is now available using the symbol, or lookup the actual mimetype
134
+ ```
135
+
136
+ You can do this in the `mime_types` initializer, or anywhere before your controllers are instantiated. Yes, the symbol
137
+ (by default `<type>_v<version>_<suffix>`) can now be used in your `format` blocks, or as extension in the url.
138
+
139
+ ## Development
140
+
141
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can
142
+ also run `bin/console` for an interactive prompt that will allow you to experiment.
143
+
144
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
145
+ version number in `version.rb`, call `bundle exec rake release` to create a new git tag, push git commits and tags, and
146
+ push the `.gem` file to rubygems.org.
147
+
148
+ ## Contributing
149
+
150
+ Bug reports and pull requests are welcome on GitHub at [SleeplessByte/media_types-ruby](https://github.com/SleeplessByte/media_types-ruby)
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'media_types'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/version'
4
+ require 'media_types/base'
5
+ require 'media_types/scheme'
6
+
7
+ require 'delegate'
8
+
9
+ module MediaTypes
10
+ COLLECTION_VIEW = 'collection'
11
+ INDEX_VIEW = 'index'
12
+ CREATE_VIEW = 'create'
13
+
14
+ module_function
15
+
16
+ def register(mime_type:, symbol: nil, synonyms: [])
17
+ require 'action_dispatch/http/mime_type'
18
+ Mime::Type.register(mime_type, symbol, synonyms)
19
+ end
20
+
21
+ class Object < SimpleDelegator
22
+ def class
23
+ __getobj__.class
24
+ end
25
+
26
+ def ===(other)
27
+ __getobj__ === other # rubocop:disable Style/CaseEquality
28
+ end
29
+
30
+ def blank?
31
+ if __getobj__.respond_to?(:blank?)
32
+ return __getobj__.blank?
33
+ end
34
+
35
+ # noinspection RubySimplifyBooleanInspection
36
+ __getobj__.respond_to?(:empty?) ? !!__getobj__.empty? : !__getobj__ # rubocop:disable Style/DoubleNegation
37
+ end
38
+
39
+ def present?
40
+ !blank?
41
+ end
42
+ end
43
+
44
+ class Hash < SimpleDelegator
45
+ def class
46
+ __getobj__.class
47
+ end
48
+
49
+ def ===(other)
50
+ __getobj__ === other # rubocop:disable Style/CaseEquality
51
+ end
52
+
53
+ def slice(*keep_keys)
54
+ if __getobj__.respond_to?(:slice)
55
+ return __getobj__.slice(*keep_keys)
56
+ end
57
+
58
+ h = {}
59
+ keep_keys.each { |key| h[key] = fetch(key) if key?(key) }
60
+ h
61
+ end
62
+ end
63
+ end
64
+
65
+
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'media_types/base/collector'
4
+ require 'media_types/constructable_mime_type'
5
+ require 'media_types/scheme'
6
+
7
+ module MediaTypes
8
+ class Base
9
+
10
+ class << self
11
+ ##
12
+ # Registers all configured mime types
13
+ #
14
+ def register(**overrides)
15
+ override_version = overrides.delete(:version)
16
+ if override_version
17
+ self.versions_ = { override_version => { scheme: (overrides.delete(:scheme) || current_scheme_) } }
18
+ end
19
+
20
+ Array(registers_).map do |registered|
21
+ resolvable = registered.merge(Hash(overrides))
22
+ mime_type = construct_mime_type(resolvable)
23
+
24
+ resolve_versions(resolvable) do |version, symbol:|
25
+ MediaTypes.register(
26
+ mime_type: mime_type.version(version).to_s,
27
+ symbol: symbol,
28
+ synonyms: versioned_synonyms(version, resolvable)
29
+ )
30
+ symbol
31
+ end
32
+ end.flatten.compact
33
+ end
34
+
35
+ ##
36
+ # Get the constructable mime type for this class
37
+ #
38
+ # @return [ConstructableMimeType]
39
+ #
40
+ def mime_type(type: type_, version: current_version_, suffix: suffix_, view: nil)
41
+ ConstructableMimeType.new(self, format: base_format, version: version, suffix: suffix, type: type, view: view)
42
+ end
43
+
44
+ def current_version
45
+ current_version_
46
+ end
47
+
48
+ def valid?(output, version: current_version, **opts)
49
+ scheme = version_scheme(version: version)
50
+ !scheme || scheme.valid?(output, backtrace: ['.'], **opts)
51
+ end
52
+
53
+ def validate!(output, version: current_version, **opts)
54
+ scheme = version_scheme(version: version)
55
+ scheme.validate(output, backtrace: ['.'], **opts)
56
+ end
57
+
58
+ protected
59
+
60
+ attr_accessor :type_, :suffix_, :registers_, :versions_, :type_aliases_, :current_version_, :current_scheme_
61
+
62
+ def base_format
63
+ NotImplementedError.new('Implementors of MediaType::Base must override base_format')
64
+ end
65
+
66
+ ##
67
+ # Configure the media type
68
+ #
69
+ # @param [String] media_type the +type+ part of the media type
70
+ # @param [Symbol, String, NilClass] suffix the +suffix+ part of the media type
71
+ # @param [Array<String>] aliases the aliases of +media_type+
72
+ # @param [Number, String] version the latest version
73
+ #
74
+ def media_type(media_type, suffix: nil, aliases: [], current_version: nil, version: current_version, &block)
75
+ self.type_ = media_type
76
+ self.type_aliases_ = aliases
77
+ self.suffix_ = suffix
78
+
79
+ self.current_version_ = version
80
+ self.current_scheme_ = current_scheme(&block)
81
+ end
82
+
83
+ def current_scheme(&block)
84
+ scheme = Scheme.new
85
+ scheme.instance_exec(&block) if block_given?
86
+
87
+ self.current_scheme_ = scheme
88
+ end
89
+
90
+ ##
91
+ # Start registering media types
92
+ #
93
+ # @param [Symbol] opts optional symbol
94
+ # @option options [Symbol] symbol the symbol to register as if not given by +opts+
95
+ # @option options [Number] version the version to register for defaults to +current_version_+
96
+ # @option options [String, NilClass] view the view of the registered type
97
+ # @option options [String] synonyms synonyms to resolve to the same type
98
+ #
99
+ # @yieldparam [Collector] the collector to collect views for the media type
100
+ #
101
+ def register_types(*opts, **options, &block)
102
+ version = options.delete(:version)
103
+ symbol_suffix = options.delete(:symbol_suffix)
104
+
105
+ register_type(*opts, **options)
106
+ register_version(version || current_version_, symbol_suffix: symbol_suffix, scheme: current_scheme_)
107
+
108
+ return unless block_given?
109
+ block_collector(&block)
110
+ end
111
+
112
+ def register_additional_versions(&block)
113
+ block_collector(&block)
114
+ end
115
+
116
+ private
117
+
118
+ def block_collector(&block)
119
+ collector = Collector.new(self)
120
+
121
+ case block.arity
122
+ when 1, -1
123
+ collector.instance_exec(collector, &block)
124
+ else
125
+ collector.instance_exec(&block)
126
+ end
127
+ end
128
+
129
+ # @param [Symbol] opts optional symbol
130
+ # @param [Symbol] symbol the symbol to register as if not given by +opts+
131
+ # @param [String, NilClass] view the view of the registered type
132
+ # @param [String] synonyms synonyms to resolve to the same type
133
+ # rubocop:disable Metrics/ParameterLists
134
+ def register_type(*opts, suffix: suffix_, view: nil, symbol: nil, synonyms: [], version: nil)
135
+ symbol = opts&.first || symbol
136
+ self.registers_ = Array(registers_).push(
137
+ symbol: symbol,
138
+ view: view,
139
+ synonyms: synonyms,
140
+ suffix: suffix,
141
+ pinned_version: version
142
+ )
143
+ end
144
+ # rubocop:enable Metrics/ParameterLists
145
+
146
+ def construct_mime_type(type: type_, **resolvable)
147
+ mime_type(type: type, **MediaTypes::Hash.new(resolvable).slice(:version, :view, :suffix))
148
+ end
149
+
150
+ def synonyms(resolvable)
151
+ aliases = type_aliases_.map { |type_alias| construct_mime_type(type: type_alias, **resolvable) }
152
+
153
+ Array(resolvable[:synonyms]).concat(aliases)
154
+ end
155
+
156
+ def register_version(version, symbol_suffix: :"_v#{version}", scheme: Scheme.new, &block)
157
+ scheme.instance_exec(&block) if block_given?
158
+
159
+ self.versions_ = Hash(versions_).merge(
160
+ version => {
161
+ symbol_suffix: symbol_suffix,
162
+ scheme: scheme
163
+ }
164
+ )
165
+ end
166
+
167
+ def resolve_versions(**resolvable)
168
+ pinned_version = resolvable[:pinned_version]
169
+
170
+ versions.map do |version, opts|
171
+ next if pinned_version && pinned_version != version
172
+ yield version, symbol: :"#{resolvable.fetch(:symbol)}#{opts[:symbol_suffix]}"
173
+ end
174
+ end
175
+
176
+ def versioned_synonyms(version, resolvable)
177
+ synonyms(resolvable).map do |synonym|
178
+ synonym.is_a?(String) ? synonym : synonym.version(version).to_s
179
+ end.uniq
180
+ end
181
+
182
+ def versions
183
+ { current_version => { scheme: current_scheme_ } }.merge(Hash(versions_))
184
+ end
185
+
186
+ def version_scheme(version:)
187
+ version_data = versions[version]
188
+ return nil unless version_data
189
+ version_data[:scheme] || nil
190
+ end
191
+ end
192
+ end
193
+ end