/turbine

Bootstrap your type declarations

Primary LanguageRustGNU Affero General Public License v3.0AGPL-3.0

Turbine

Turbine is a toy CLI app for converting Rails schema declarations into equivalent type declarations in other languages.

It’s described as a toy because the parsing of schema files is less than robust, and the conversion into type declarations is somewhat limited. Basically, use it at your own risk sort of thing.

USAGE:
    turbine [OPTIONS] <SCHEMA>

ARGS:
    <SCHEMA>    Specifies the location of the Rails schema file

FLAGS:
    -h, --help       Print help information
    -V, --version    Print version information

OPTIONS:
    -f, --format <FORMAT>    Specifies type definition format to convert the schema file into
                             [default: spec] [possible values: spec, rust, typescript, go]
    -o, --output <OUTPUT>    Where to save the output. If no name is specified it defaults to stdout

Example Output

Given a rails schema of

ActiveRecord::Schema.define(version: 2021_09_16_202951) do
  create_table "sample_schema", id: :serial, force: :cascade do |t|
    t.primary_key "a"
    t.string "b"
    t.text "c"
    t.integer "d"
    t.bigint "e"
    t.float "f"
    t.decimal "g"
    t.numeric "h"
    t.datetime "i"
    t.time "j"
    t.date "k"
    t.binary "l"
    t.boolean "m"
    t.hstore "n"
    t.jsonb "o"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

Clojure Spec

(spec/def sample_schema
  {:a int?
   :b string?
   :c string?
   :d int?
   :e int?
   :f float?
   :g float?
   :h int?
   :i string?
   :j string?
   :k string?
   :l string?
   :m boolean?
   :n map?
   :o map?
   :created_at string?
   :updated_at string?})

Rust

struct SampleSchema {
   a: usize,
   b: String,
   c: String,
   d: i64,
   e: i128,
   f: f64,
   g: f64,
   h: i64,
   i: String,
   j: String,
   k: String,
   l: Vec<u8>,
   m: bool,
   n: std::collections::HashMap<String,String>,
   o: std::collections::HashMap<String,String>,
   created_at: String,
   updated_at: String,
}

TypeScript

type SampleSchema {
  a: number;
  b: string;
  c: string;
  d: number;
  e: number;
  f: number;
  g: number;
  h: number;
  i: string;
  j: string;
  k: string;
  l: string;
  m: bool;
  n: any;
  o: any;
  created_at: string;
  updated_at: string;
}

Go

type SampleSchema struct {
    a: *int64,
    b: *string,
    c: *string,
    d: *int64,
    e: *int128,
    f: *float64,
    g: *float64,
    h: *int64,
    i: *time.Time,
    j: *time.Time,
    k: *time.Time,
    l: *[]uint8,
    m: *bool,
    n: map[string]interface{},
    o: map[string]interface{},
    created_at: *time.Time,
    updated_at: *time.Time,
}

Building

I have not set up, like, any CI/CD for this. So if you want a copy you’ll have to build it yourself. Luckily, Rust makes this pretty easy.

$ cargo build --release
 # or if you want to install it onto your path
$ cargo install --path .

Where laziness won

For example, when turbine encounters a JsonB column or HStore column, it will type out these values as the safest possible types for their language. IE: any in TypeScript, map? in Clojure Spec and HashMap<String, String> in Rust. Additionally, all date/time types are represented as strings. I did this because of laziness, the possibility of date/times not represented in the base language, and when interacting with an API, it’s probably a string already.

The Rails schema parser is also the simplest form I could build. It looks for create_table declarations, captures the next word as the name for the type and then looks for a word like “t.integer” to describe the type of the column. If the column declaration doesn’t start with “t.” or is surrounded by strings, things will break and break badly. So, don’t do that.

Finally, it doesn’t handle where columns can be nullable… yet.