Build Status Code Climate Coverage Status

Finitio(-rb)

Finitio is a language for capturing information structure. Think “JSON/XML schema” but the right way. For more information about Finitio itself, see www.finitio.io

finitio-rb is the ruby binding of Finitio. It allows defining data schemas and validating and coercing data against them in an idiomatic ruby way.

Example

“by require ‘finitio’ require ‘json’

Let load a schema

schema = Finitio.system «-FIO @import finitio/data

{ name: String( s | s.strip.size > 0 ), at: DateTime } FIO

Let load some JSON document

data = JSON.parse «-JSON { “name”: “Finitio”, “at”: “20142-03-01” } JSON

And try dressing that data

puts schema.dress(data)

ADTs with internal contracts

finitio-rb tries to provide an idiomatic binding for ruby developers. In particular, it uses a simple convention-over-configuration protocol for information contracts. This protocol is easily described through an example. The following ADT definition:

“by Color = .Color

Maruku could not parse this XML/HTML: 
<rgb> {r: Byte, g: Byte, b: Byte}

expects the following ruby class:

“by class Color

# Constructor & internal representation def initialize(r, g, b) @r, @g, @b = r, g, b end attr_reader :r, :g, :b

# Public dresser for the RGB information contract on the class def self.rgb(tuple) new(tuple[:r], tuple[:g], tuple[:b]) end

# Public undresser on the instance def to_rgb { r: @r, g: @g, b: @b } end

# …

end

ADTs with external contracts

When the scenario above is not possible or not wanted (would require core extensions for instance), finitio-rb allows defining ADTs with external contracts. The following ADT definition:

“by Color = .Color

Maruku could not parse this XML/HTML: 
<rgb> {r: Byte, g: Byte, b: Byte} .RgbContract

expected the following ruby module:

“by module RgbContract

def self.dress(tuple) Color.new(tuple[:r], tuple[:g], tuple[:b]) end

def self.undress(color) { r: color.r, g: color.g, b: color.b } end

end

Decompose complex system with imports

It is useful to decompose complex systems in many files using the import feature. The latter works with relative file paths like this:

child.fio

Posint = .Integer(i | i >= 0)

parent.fio

@import ./child.fio

Child’s types are available inside the system, but not outside it, that

is, imported types are not themselves exported

Byte = Posint(i | i <= 255 )

“import ./parent.fio

This will work

HalfByte = Byte(i | i <= 128)

But this will not: Posint is not defined

Posint(i | i <= 128)

See the next section about the standard library if you need to share types without relying on relative paths.

Standard library

Usual type definitions are already defined for simple data types, forming Finitio’s default system:

  • Most ruby native (data) classes are already aliased to avoid explicit use of builtins. In particular, Integer, String, etc.

  • A Boolean union type also hides the TrueClass and FalseClass distinction.

  • Date, Time and DateTime ADTs are also provided that perform common conversions from JSON strings, through iso8601.

This system is best used through Finitio’s so-called “standard library”, e.g.

“import finitio/data

String, Integer, Boolean, etc. are now available in this system

See lib/finitio/stdlib/*.fio for the precise definition of the standard library.

Contributing to the standard library

Ruby gems may contribute to the standard library by specifying resolve paths. We suggest the following system file structure inside your gem source code:

“ib myrubygem myrubygem.rb finitio myrubygem base.fio advanced.fio

Registering the standard library path can then be done as follows:

inside myrubygem.rb

Finitio.stdlib_path(File.expand_path(‘../../finitio’, FILE))

Then, a Finitio schema will have access to the types defined in your extension:

“import myrubygem/base @import myrubygem/advanced

About representations

The Rep representation function mapping Finitio types to ruby classes is as follows:

“by

Any type is represented by Ruby’s Object class

Rep(.) = Object

Builtins are represented by the corresponding ruby class

Rep(.Builtin) = Builtin

Sub types are represented by the same representation as the super type

Rep(SuperType( s | … )) = Rep(SuperType)

Unions are represented by the corresponding classes. The guaranteed result

class is thus the least common super class (^) of the corresponding

representations of candidate types

Rep(T1 | … | Tn) = Rep(T1) ^ … ^ Rep(Tn)

Sequences are represented through ::Array.

Rep([ElmType]) = Array

Maruku could not parse this XML/HTML: 
<Rep(ElmType)>

Sets are represented through ::Set.

Rep(ElmType) = Set

Maruku could not parse this XML/HTML: 
<Rep(ElmType)>

Tuples are represented through ruby ::Hash. Attribute names are always

symbolized

Rep(=> Ti) = Hash

Maruku could not parse this XML/HTML: 
<Symbol => Rep(Ti)>

Relations are represented through ruby ::Set of ::Hash.

Rep(=> Ti}) = Set

Maruku could not parse this XML/HTML: 
<Hash<Symbol => Rep(Ti)>>

Abstract data types are represented through the corresponding class when

specified. ADTs behave as Union types if no class is bound.

Rep(.Builtin

Maruku could not parse this XML/HTML: 
<rep> ...) = Builtin