Skip to content

πŸ’Ž Library to create media type definitions, schemes and validations

Notifications You must be signed in to change notification settings

TrailerVote/media-types-ruby

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

71 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MediaTypes

Build Status Gem Version MIT license Maintainability

Media Types based on scheme, with versioning, views, suffixes and validations. Integrations available for Rails / ActionPack and http.rb.

Installation

Add this line to your application's Gemfile:

gem 'media_types'

And then execute:

$ bundle

Or install it yourself as:

$ gem install media_types

Usage

By default there are no media types registered or defined, except for an abstract base type.

Definition

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:

  • %<type>s: the type media_type received
  • %<version>s: the version, defaults to :current_version
  • %<view>s: the view, defaults to
  • %<suffix>s: the suffix
require 'media_types'

class Venue
  include MediaTypes::Dsl
  
  def self.base_format
    'application/vnd.mydomain.%<type>s.v%<version>s.%<view>s+%<suffix>s'
  end
  
  media_type 'venue', defaults: { suffix: :json, version: 2 }

  validations do
    attribute :name, String
    collection :location do
      attribute :latitude, Numeric
      attribute :longitude, Numeric
      attribute :altitude, AllowNil(Numeric)
    end

    link :self
    link :route, allow_nil: true
    
    version 1 do
      attribute :name, String
      attribute :coords, String
      attribute :updated_at, String
    
      link :self
    end
    
    view 'create' do
      collection :location do
        attribute :latitude, Numeric
        attribute :longitude, Numeric
        attribute :altitude, AllowNil(Numeric)
      end
      
      version 1 do
        collection :location do
          attribute :latitude, Numeric
          attribute :longitude, Numeric
          attribute :altitude, AllowNil(Numeric)
        end
      end
    end
  end

  registrations :venue_json do
    view 'create', :create_venue
    view 'index', :venue_urls
    view 'collection', :venue_collection
    
    versions [1,2]
    
    suffix :json
    suffix :xml
  end
end

Schema Definitions

If you define a scheme using current_scheme { }, you may use any of the following dsl:

attribute

Adds an attribute to the schema, if a +block+ is given, uses that to test against instead of +type+

param type description
key Symbol the attribute name
opts Hash options to pass to Scheme or Attribute
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 Object without a &block and to Hash with a &block.
optional: TrueClass, FalseClass if true, key may be absent, defaults to false
&block Block defines the scheme of the value of this attribute

Add an attribute named foo, expecting a string

require 'media_types'

class MyMedia
  include MediaTypes::Dsl

  validations do
    attribute :foo, String
  end
end

MyMedia.valid?({ foo: 'my-string' })
# => true

Add an attribute named foo, expecting nested scheme

class MyMedia
 include MediaTypes::Dsl

 validations do
   attribute :foo do
     attribute :bar, String
   end
 end
end

MyMedia.valid?({ foo: { bar: 'my-string' }})
# => true

any

Allow for any key. The &block defines the Schema for each value.

param type description
scheme Scheme, NilClass scheme to use if no &block is given
allow_empty: TrueClass, FalsClass if true, empty (no key/value present) is allowed
expected_type: Class, forces the validated value to have this type, defaults to Hash. Use Object if either Hash or Array is fine
&block Block defines the scheme of the value of this attribute

Add a collection named foo, expecting any key with a defined value

class MyMedia
 include MediaTypes::Dsl

 validations do
   collection :foo do
     any do
       attribute :bar, String
     end
   end
 end
end

MyMedia.valid?({ foo: [{ anything: { bar: 'my-string' }, other_thing: { bar: 'other-string' } }] })
# => true

not_strict

Allow for extra keys in the schema/collection even when passing strict: true to #validate!

Allow for extra keys in collection

class MyMedia
 include MediaTypes::Dsl

 validations do
   collection :foo do
     attribute :required, String
     not_strict
   end
 end
end

MyMedia.valid?({ foo: [{ required: 'test', bar: 42 }] })
# => true

collection

Expect a collection such as an array or hash. The &block defines the Schema for each item in that collection.

param type description
key Symbol key of the collection (same as #attribute)
scheme Scheme, NilClass, Class scheme to use if no &block is given or Class of each item in the
allow_empty: TrueClass, FalseClass if true, empty (no key/value present) is allowed
expected_type: Class, forces the validated value to have this type, defaults to Array. Use Object if either Array or Hash is fine.
optional: TrueClass, FalseClass if true, key may be absent, defaults to false
&block Block defines the scheme of the value of this attribute

Collection with an array of string

class MyMedia
 include MediaTypes::Dsl

 validations do
   collection :foo, String
 end
end

MyMedia.valid?({ collection: ['foo', 'bar'] })
# => true

Collection with defined scheme

class MyMedia
 include MediaTypes::Dsl

 validations do
   collection :foo do
     attribute :required, String
     attribute :number, Numeric
   end
 end
end

MyMedia.valid?({ foo: [{ required: 'test', number: 42 }, { required: 'other', number: 0 }] })
# => true

link

Expect a link with a required href: String attribute

param type description
key Symbol key of the link (same as #attribute)
allow_nil: TrueClass, FalseClass if true, value may be nil
optional: TrueClass, FalseClass if true, key may be absent, defaults to false
&block Block defines the scheme of the value of this attribute, in addition to the href attribute

Links as defined in HAL, JSON-Links and other specs

class MyMedia
  include MediaTypes::Dsl

  validations do
    link :_self
    link :image
  end
end

MyMedia.valid?({ _links: { self: { href: 'https://example.org/s' }, image: { href: 'https://image.org/i' }} })
# => true

Link with extra attributes

class MyMedia
 include MediaTypes::Dsl

 validations do
   link :image do
     attribute :templated, TrueClass
   end
 end
end

MyMedia.valid?({ _links: { image: { href: 'https://image.org/{md5}', templated: true }} })
# => true

Validation

If your type has a validations, you can now use this media type for validation:

Venue.valid?({
  #...
})
# => true if valid, false otherwise

Venue.validate!({
  # /*...*/ 
})
# => raises if it's not valid

If an array is passed, check the scheme for each value, unless the scheme is defined as expecting a hash:

expected_hash = Scheme.new(expected_type: Hash) { attribute(:foo) }
expected_object = Scheme.new { attribute(:foo) } 

expected_hash.valid?({ foo: 'string' })
# => true

expected_hash.valid?([{ foo: 'string' }])
# => false


expected_object.valid?({ foo: 'string' })
# => true

expected_object.valid?([{ foo: 'string' }])
# => true

Formatting for headers

Any media type object can be coerced in valid string to be used with Content-Type or Accept:

Venue.mime_type.to_s
# => "application/vnd.mydomain.venue.v2+json"

Venue.mime_type.version(1).to_s
# => "application/vnd.mydomain.venue.v1+json"

Venue.mime_type.version(1).suffix(:xml).to_s
# => "application/vnd.mydomain.venue.v1+xml"

Venue.mime_type.to_s(0.2)
# => "application/vnd.mydomain.venue.v2+json; q=0.2"

Venue.mime_type.collection.to_s
# => "application/vnd.mydomain.venue.v2.collection+json"

Venue.mime_type.view('active').to_s
# => "application/vnd.mydomain.venue.v2.active+json"

Integrations

The integrations are not loaded by default, so you need to require them:

# For Rails / ActionPack
require 'media_types/integrations/actionpack'

# For HTTP.rb
require 'media_types/integrations/http' 

Define a registrations block on your media type, indicating the symbol for the base type (registrations :symbol do) and inside use the registrations dsl to define which media types to register. versions array_of_numbers determines which versions, suffix name adds a suffix, type_alias name adds an alias and view name, symbol adds a view.

Venue.register

Rails

Load the actionpack integration and call .register on all the media types you want to be available in Rails. You can do this in the mime_types initializer, or anywhere before your controllers are instantiated. Yes, the symbol (by default <type>_v<version>_<suffix>) can now be used in your format blocks, or as extension in the url.

Rails only has a default serializer for application/json, and content with your +json media types (or different once) will not be deserialized by default. A way to overcome this is to set the JSON parameter parser for all new symbols. .register gives you back an array of Registerable objects that responds to #to_sym to get that symbol.

symbols = Venue.register.map(&:to_sym)

original_parsers = ActionDispatch::Request.parameter_parsers
new_parser = original_parsers[Mime[:json].symbol]
new_parsers = original_parsers.merge(Hash[*symbols.map { |s| [s, new_parser] }])
ActionDispatch::Request.parameter_parsers = new_parsers

If you want to validate the content-type and not have your errors be Rack::Error but be handled by your controllers, leave this out and add a before_action to your controller that deserializes + validates for you.

HTTP.rb

Load the http integration and call .register on all media types you want to be able to serialize and deserialize. The media type validations will run both before serialization and after deserialization.

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

Related

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, call bundle exec rake release to create a new git tag, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at SleeplessByte/media-types-ruby

About

πŸ’Ž Library to create media type definitions, schemes and validations

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Ruby 99.9%
  • Shell 0.1%