Build Status Dependency 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

require 'finitio'
require 'json'

# Let load a schema
schema = Finitio::DEFAULT_SYSTEM.parse <<-FIO
  {
    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:

Color = .Color <rgb> {r: Byte, g: Byte, b: Byte}

expects the following ruby class:

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:

Color = .Color <rgb> {r: Byte, g: Byte, b: Byte} .RgbContract

expected the following ruby module:

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

About representations

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

# 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<Rep(ElmType)>

# Sets are represented through ::Set.
Rep({ElmType}) = Set<Rep(ElmType)>

# Tuples are represented through ruby ::Hash. Attribute names are always
# symbolized
Rep({Ai => Ti}) = Hash<Symbol => Rep(Ti)>

# Relations are represented through ruby ::Set of ::Hash.
Rep({{Ai => Ti}}) = Set<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 <rep> ...) = Builtin

About the default system

See lib/finitio/Finitio/default.fio for the precise definition of the default system. In summary,

  • 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.