Masterplan

Masterplan is a library for comparing Ruby data structures against predefined templates.

At Travel IQ, this is used to define a canonical definition of the structure and format of the requests and responses of our APIs. In short, you get something that is similar to a XML Scheme - a template against which your data can be compared, to ensure its correctness programmatically. Only without the XML part.

Examples make this easier to explain. Say you have a webservice or class that produces a Ruby data structure like this:

{
  :airports => [
    {
      :name => "Tegel Airport",
      :code => "TXL",
      :latitude => 2.35454,
      :longitude => 54.67867
    },
    {
      :name => "Schönefeld Airport",
      :code => "SXF",
      :latitude => 5.35454,
      :longitude => 34.67867
    }
  ]
}

and so on and so forth. In your tests of this service, you want to make sure that this structure follows certain rules:

In a more XML-centric world, you would define an XML (or Relax-NG etc.) Scheme that defines all these rules and then use that to validate your output. But we also want to deliver JSON…so we'll define the rules as a Masterplan::Document, and validate the data while it's still Ruby, and say that as long as the source data is correct, the representation in JSON or XML will also be correct:

include Masterplan::DefineRules

doc = Masterplan::Document.new(
  :airports => [
    {
      :name => "Tegel Airport",
      :code => rule("TXL", :matches => /[A-Z]{3}/),
      :latitude => 2.35454,
      :longitude => 54.67867
    },
    {
      :name => "Schönefeld Airport",
      :code => "SXF",
      :latitude => 5.35454,
      :longitude => 34.67867
    }
  ]
)

It doesn't look much different from the example, but there are a lot of rules built-in. You can now use the doc object to check your data against the template:

Masterplan.compare(:scheme => doc, :to => [{:example => :data}])

And it will throw a Masterplan::FailedError exception, and print out debugging data:

>> Masterplan.compare(:scheme => doc, :to => {:example => :data})
Masterplan::FailedError: keys don't match in 'root':
expected:   airports
received:   example

Expected:
{"airports"=>
  [{:latitude=>2.35454,
    :longitude=>54.67867,
    :code=>
     #<Masterplan::Rule:0x6d0bd70
      @example_value="TXL",
      @options=
       {"compare_each"=>false,
        "allow_nil"=>false,
        "included_in"=>false,
        "matches"=>/[A-Z]{3}/}>,
    :name=>"Tegel Airport"},
   {:latitude=>5.35454,
    :longitude=>34.67867,
    :code=>"SXF",
    :name=>"Schönefeld Airport"}]}

but was:
{"example"=>:data}

Another example:

>> Masterplan.compare(:scheme => doc, :to => {:airports => [{:name => "Bla", :latitude => 1.1, :longitude => 2.3, :code => "XXx"}]})
Masterplan::FailedError: value at 'root'=>'airports'=>'0'=>'code' "XXx" (String) does not match /[A-Z]{3}/ !

Expected:
{"name"=>"Tegel Airport",
 "latitude"=>2.35454,
 "code"=>
  #<Masterplan::Rule:0x6d0bd70
   @example_value="TXL",
   @options=
    {"compare_each"=>false,
     "allow_nil"=>false,
     "included_in"=>false,
     "matches"=>/[A-Z]{3}/}>,
 "longitude"=>54.67867}

but was:
{"name"=>"Bla", "latitude"=>1.1, "code"=>"XXx", "longitude"=>2.3}

The implicit rules are:

You can add extra rules with the rule method - see Masterplan::DefineRules#rule for details.

You can control the verbosity of the output with the :format option:

>> Masterplan.compare(:scheme => doc, :to => {:example => :data}, :format => :mini)

Valid values are :full (the default), or :mini. :mini produces only a one-line output (leaving out the “Expected…but was” part).

There is also an added assertion for unit tests or specs:

assert_masterplan(doc, [{:example => :data})

Use schemes as examples in documentation

A problem with webservices is that you need to keep the documentation up to date - something that is easily forgotten. If you have a masterplan document, you can use it not only as the template, but also as an example in, say, online documentation:

<pre>
  <%= JSON.dump(doc.to_hash) %>
</pre>

The to_hash method removes the Masterplan::Rule objects for clean output.

Caveat

Note that for the moment, schemes, i.e. the outermost object, can only be hashes.

Installation

(sudo) gem install masterplan

(Note that the latest version is probably not available as a gem directly).

Or, install latest version from Github with bundler by adding this to your Gemfile:

gem 'masterplan', :git => 'git://github.com/traveliq/masterplan.git'

Authors

Martin Tepper (monogreen.de), Holger Pillmann (holger.pillmann@gmail.com), Dr. Florian Odronitz (odo@mac.com)

Contact

For questions, contact the authors or developer@traveliq.net

Contributing to masterplan

Copyright © 2011 www.travel-iq.com. See LICENSE.txt for further details.