Skip to content
This repository has been archived by the owner on Feb 17, 2023. It is now read-only.

Latest commit

 

History

History
289 lines (223 loc) · 9.03 KB

README.md

File metadata and controls

289 lines (223 loc) · 9.03 KB

Welcome to Respect

Respect is a DSL to concisely describe the structure of common data such as hash and array using Ruby code. It comes with a validator, a sanitizer and dumpers to generate valid json-schema.org compliant specifications. Although it was designed to specify JSON schema, it can be used for any data represented as Hash and Array. It does not require any JSON parser since it works only on data.

Respect is named after the contraction of REST and SPEC. Indeed, it was first intended to be used to specify REST API in the context of a Web application.

There is a plugin called Respect for Rails which integrate this gem in Rails.

Features

Already available:

  • Compact Ruby DSL to specify your schema.
  • Standard json-schema.org specification generator.
  • Validator for JSON document or the like.
  • Contextual validation error.
  • Object sanitizer: turn plain string and integer values into real objects.
  • Extensible API to add your custom validator and sanitizer.
  • Extensible macro definition system to factor your schema definition code.

See the RELEASE_NOTES file for detailed feature listing.

Take a Tour

Respect comes with a compact Ruby DSL to specify the structure of a hash and/or an array. Thus, it is ideal to specify JSON schema. I find it a way more concise than json-schema.org. And it is plain Ruby so you can rely on all great Ruby features to factor your specification code.

For instance, this ruby code specifies how one could structure a very simple user profile:

schema = Respect::HashSchema.define do |s|
  s.string "name"
  s.integer "age", greater_than: 18
  s.string "email", format: :email
end

You can easily convert this specification to JSON Schema specification draft v3 so that all your customers can read and understand it:

require 'json'
puts JSON.pretty_generate(schema.to_h)

prints

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
    "age": {
      "type": "integer",
      "required": true,
      "minimum": 18,
      "exclusiveMinimum": true
    },
    "email": {
      "type": "string",
      "required": true,
      "format": "email"
    }
  }
}

As you can see the Ruby specification is 4 times shorter than the JSON Schema specification...

You can also use this specification to validate JSON documents:

schema.validate?({ "name" => "My name", "age" => 20, "email" => "[email protected]" })          #=> true
schema.validate?({ "name" => "My name", "age" => 15, "email" => "[email protected]" })          #=> false

When it fails to validate a document, you get descriptive error messages with contextual information:

schema.last_error                #=> "15 is not greater than 18"
schema.last_error.message        #=> "15 is not greater than 18"
schema.last_error.context[0]     #=> "15 is not greater than 18"
schema.last_error.context[1]     #=> "in hash property `age'"

Respect does not parse JSON document by default but it is easy to do so using one of the JSON parser available in Ruby:

schema.validate?(JSON.parse('{ "name": "My name", "age": 20, "email": "[email protected]" }'))   #=> true

Once a JSON document has been validated, we often want to turn its basic strings and integers into real objects like URI for instance. Respect does that automatically for you for standard objects:

schema = Respect::HashSchema.define do |s|
  s.uri "homepage"
end
object = { "homepage" => "http://example.com" }
schema.validate!(object)                            #=> true
object["homepage"].class                            #=> URI::HTTP

You can easily extend the sanitizer with your own object type. Let's assume you have a class defined like this:

class Place
  def initialize(latitude, longitude)
    @latitude, @longitude = latitude, longitude
  end

  attr_reader :latitude, :longitude

  def ==(other)
    @latitude == other.latitude && @longitude == other.longitude
  end
end

Then you can extend the Schema class hierarchy with the new schema for your custom type. The CompositeSchema class assists you in this task so you just have to override two methods.

module Respect
  class PlaceSchema < CompositeSchema
    # This method returns the schema specification for your custom type.
    def schema_definition
      Respect::HashSchema.define do |s|
        s.float "latitude"
        s.float "longitude"
      end
    end

    # The 'sanitize' method is called with the JSON document if the validation succeed.
    # The returned value will be inserted into the JSON document.
    def sanitize(object)
      Place.new(object[:latitude], object[:longitude])
    end
  end
end

Finally, you define the structure of your JSON document as usual. Note that you have access to your custom schema via the place method.

schema = Respect::HashSchema.define do |s|
  s.place "home"
end

object = {
  "home" => {
    "latitude" => "48.846559",
    "longitude" => "2.344519",
  }
}

schema.validate!(object)                              #=> true
object["home"].class                                  #=> Place

Sometimes you just want to extend the DSL with a new statement providing higher level features than the primitives integer, string or float, etc... For instance if you specify identifier in your schema like this:

Respect::HashSchema.define do |s|
  s.integer "article_id", greater_than: 0
  s.string "title"
  s.hash "author" do |s|
    s.integer "author_id", greater_than: 0
    s.string "name"
  end
end

In such case, you don't need a custom sanitizer since an identifer is an integer after all. You just want to factor the definition of an identifier property. You can easily do it like this:

module MyMacros
  def id(name = "id", options = {})
    unless name.nil? || name == "id" || name =~ /_id$/
      name += "_id"
    end
    integer(name, { greater_than: 0 }.merge(options))
  end
end
Respect.extend_dsl_with(MyMacros)

Now you can rewrite your schema definition this way:

Respect::HashSchema.define do |s|
  s.id "article"
  s.string "title"
  s.hash "author" do |s|
    s.id "author"
    s.string "name"
  end
end

Getting started

The easiest way to install Respect is to add it to your Gemfile:

gem "respect"

Then, after running the bundle install command, you can start to validate JSON document in your program like this:

require 'respect'

schema = Respect::HashSchema.define do |s|
  s.string "name"
  s.integer "age", greater_than: 18
end

schema.validate?({ "name" => "John", "age" => 30 })

JSON Schema implementation status

Respect currently implements most of the features included in the JSON Schema specification draft v3.

See the STATUS_MATRIX file included in this package for detailed information.

Although, the semantics of the schema definition DSL available in this library may change slightly from the JSON schema standard, we have tried to keep it as close as possible. For instance the strict option of hash schema is not presented in the standard. However, when a schema is dumped to its JSON Schema version the syntax and semantic have been followed. You should note that there is no "loader" available yet in this library. In other word, you cannot instantiate a Schema class from a JSON Schema string representation.

Getting help

The easiest way to get help about how to use this library is to post your question on the Respect discussion group. I will be glade to answer. I may have already answered the same question so before you post your question take a bit of time to search the group.

You can also read these documents for further documentation:

Compatibility

Respect has been tested with:

  • Ruby 1.9.3-p392 (should be compatible with all 1.9.x family)
  • ActiveSupport 3.2.13

Note that it does not depend on any JSON parsing library. It works only on primitive Ruby data type. So, any JSON parser returning normal basic types like Hash, Array, String, Numeric, TrueClass, FalseClass, NilClass, should work.

Feedback

I would love to hear what you think about this library. Feel free to post any comments/remarks on the Respect discussion group.

Contributing patches

I spent quite a lot of time writing this gem but there is still a lot of work to do. Whether it is a bug-fix, a new feature, some code re-factoring, or documentation clarification, I will be glade to merge your pull-request on GitHub. You just have to create a branch from master and send me a pull request.

License

Respect is released under the term of the MIT License. Copyright (c) 2013 Nicolas Despres.