Fundamentally Barrister is simply a way to describe meta information about a system. You express this information in an interface defintion language (IDL) file. Barrister takes this file and generates a JSON representation of the file that the runtime language bindings use. Static languages may also provide a code generator so that clients and servers may benefit from compile time type checking.
Here's a simple IDL example that uses all the primitive types and entities available.
// Structs contain fields. One per line.
struct Response {
// fields have an identifier and a type
elapsedTime int
}
// Structs may extend other structs
// In this case they inherit the fields of their ancestors
struct HelloResponse extends Response {
hello string
}
// Enums are a constrained set of identifiers.
enum Language {
java
csharp
python
ruby
javascript
}
struct GitHubProject {
id int
owner string
name string
// the [optional] flag means this field
// can be ommitted from requests/responses,
// or may be null
//
// by default all struct fields and return types
// are required and may not be null
//
description string [optional]
// Structs may reference structs or enums
// But you cannot have cyclical references
language Language
isPrivate bool
}
//
// Interfaces are collections of functions
//
interface ExampleService {
// returns the word "hello"
sayHello() HelloResponse
// adds all the numbers together
addInts(nums []int) int
addFloats(nums []float) float
// stores this project in a db
// returns the generated ID
storeProject(project GitHubProject) int
// loads a project by id
// returns null if no project found
// the [optional] flag tells Barrister that
// this return type is nullable
getProjectById(id int) GitHubProject [optional]
}
Barrister supports four primitive types:
You may define your own types by declaring a struct
struct Person {
firstName string
email string
}
Notice that the type comes after the identifier (like in SQL or Go).
Structs can extend each other. The child struct inherits all fields from its ancestors.
struct A {
x string
}
// has fields x and y
struct B extends A {
y string
}
// has fields x, y, and z
struct C extends B {
z string
}
But you cannot redeclare a field that one of your ancestors defined:
struct Animal {
type string
}
// INVALID!!
struct Cat extends Animal {
type int
}
Structs may use other structs as field types, but circular references are not allowed:
struct Animal {
color Color
}
struct Color {
name string
// INVALID!
animalsWithThisColor []Animal
}
By default all struct fields are required and may not be null. However, you may
mark a field as [optional]
. Optional fields may be omitted from requests and responses,
or may be null. For example:
struct Person {
// These are required:
firstName string
lastName string
// Email is not required and may be null
email string [optional]
}
Note that top level function parameters are always required. See below.
Enums are marshaled as string values, so choose good names. Enums may be used in structs, function parameters, and return types.
enum Colors {
blue
black
red
}
Types may be declared as arrays. Simply put []
before the type.
struct Book {
categoryIds []int
}
Each IDL file may define one or more interfaces. An interface is a collection of functions.
Functions have a name, zero or more parameters, and a return type. Parameters have a name, and a type. Parameters and return types may optionally be declared as arrays.
All function parameters are required. For example, if you have this interface:
interface Calculator {
add(a int, b int) int
}
You may not call it like this:
# Invalid! Barrister bindings will reject this call
result = client.Calculator.add(3, None)
If you wish to make parameters optional, define a struct to hold the request fields and mark
the fields [optional]
as desired. For example:
struct AddRequest {
a int
b int [optional]
}
interface Calculator {
add(req AddRequest) int
}
Then you can call it like this:
# Contrived example of course!
result = client.Calculator.add({"a": 2})
# or:
result = client.Calculator.add({"a": 2, "b": None})
Comments are preceeded by double slashes //
. Comments immediately above an interface, struct,
enum, or function are included in the generated IDL JSON so that clients can expose them at
runtime.
Comments may contain Markdown. When the HTML documentation is generated, the Markdown will be converted to HTML.
Use a blank line to separate sections in the generated HTML output.
Use the barrister
tool to convert your IDL into JSON and HTML representations.
First download and install barrister using pip
Then run Barrister. A common invocation looks like this:
barrister -t "My Awesome Interface" -d awesome.html -j awesome.json awesome.idl
This will take awesome.idl
as input and write awesome.html
and awesome.json
using
"My Awesome Interface" as the title and H1 tag in the HTML file.
If your IDL has a syntax error, the error message will be displayed.
To get a complete list of options, run: barrister -h
That's honestly about all there is to it. Once you have the JSON file, you're ready to start writing some clients and servers.
barrister
can optionally use Graphviz to generate a diagram based on an
IDL file.
First, install Graphviz. I tested this using Graphviz 2.28, installed on my Mac using Homebrew. Make
sure dot
is in your PATH, then use the -p
option to generate a PNG from your .idl
file.
For example:
barrister -d foo.html -p foo.png -j foo.json foo.idl
This will generate a diagram foo.png
If you want to override the parameters passed to dot
, use the -z
flag. You probably want to quote
this parameter. For example, in bash:
barrister -z "-Gsize=8,5 -Glayout=twopi" -d foo.html -p foo.png -j foo.json foo.idl
Barrister uses JSON-RPC 2.0 messages, which defines a standard way to express errors.
JSON-RPC errors have 3 elements:
In practice, each Barrister language binding will provide a way to raise errors. For example,
the Python binding provides a RpcException
class:
def myFunction(self, a, b):
if a < 1:
raise barrister.RpcException(100, "myFunction.a must be >= 1")
Please view the documentation for your language binding for more information on how to send errors (in servers) or catch errors (in clients).