Skip to content

JavaScriptBach/stone

Repository files navigation

Babel

Define an API once in Babel. Use code generators to translate your specification into objects and functions in the programming languages of your choice.

Currently, only Python is supported as a generation target. Swift is being actively worked on, and the intention is to support several other languages.

Motivation

Being an API designer is tough. There are an innumerable number of protocols and serialization formats that two hosts can use to communicate. Today, JSON over HTTP is gaining popularity, but just a few years ago, XML was the standard. To compound the issue, developers need to support an increasing number of language-specific SDKs to gain wide adoption.

Babel seeks to:

  1. Define API endpoints in terms of input and output data types that can be consistently implemented in different protocols and languages.
  2. Offer structs (product types) and tagged unions (sum types) as fundamental data types for modeling APIs flexibly, but strictly.
  3. Improve the visibility developers have into their APIs by centralizing specification and documentation.

If we only had one protocol and one language Babel API wouldn't be needed, but unfortunately humanity was handicapped for good reason. See Why do we have multiple programming languages?

Assumptions

Babel makes no assumptions about the protocol layer being used to make API requests and return responses; its first use case is the Dropbox v2 API which operates over HTTP. Babel does not come with nor enforce any particular RPC framework.

Babel makes some assumptions about the data types supported in the serialization format and target programming language. It's assumed that there is a capacity for representing dictionaries (unordered string keys -> value), lists, numeric types, and strings. The intention is for Babel to map to a multitude of serialization formats from JSON to more space-efficient representations.

Babel assumes that a route (or API endpoint) can have its request and response types defined without relation to each other. In other words, the type of response does not change based on the input to the endpoint. An exception to this rule is afforded for error responses.

Installation

Download or clone BabelAPI, and run the following in its root directory:

$ sudo python setup.py install

This will install a script babelapi to your PATH that can be run from the command line:

$ babelapi -h

Alternative

If you choose not to install babelapi using the method above, you will need to ensure that you have the Python packages ply and six, which can be installed through pip:

$ pip install ply>=3.4 six>=1.3.0

If the babelapi package is in your PYTHONPATH, you can replace babelapi with python -m babelapi.cli as follows:

$ python -m babelapi.cli -h

If you have the babelapi package on your machine, but did not install it or add its location to your PYTHONPATH, you can use the following:

$ PYTOHNPATH=path/to/babelapi python -m babelapi.cli -h

A Taste of Babel

Here we define a hypothetical route that shows up in some form or another in APIs for web services: querying the account information for a user of a service. Our hypothetical spec lives in a file called users.babel:

# We put this in the "users" namespace in anticipation that
# there would be many user-account-related routes.
namespace users

# We define an AccountId as being a 10-character string
# once here to avoid declaring it each time.
alias AccountId = String(min_length=10, max_length=10)

union Status
    active
        "The account is active."
    inactive Timestamp("%a, %d %b %Y %H:%M:%S")
        "The account is inactive. The value is when the account was
        deactivated."

struct Account
    "Information about a user's account."

    account_id AccountId
        "A unique identifier for the user's account."
    email String(pattern="^[^@]+@[^@]+\.[^@]+$")
        "The e-mail address of the user."
    name String(min_length=1)?
        "The user's full name. :val:`null` if no name was provided."
    status Status
        "The status of the account."

    example default "A regular user"
        account_id="id-48sa2f0"
        email="alex@example.org"
        name="Alexander the Great"
        status=active

# This struct represents the input data to the route.
struct GetAccountReq
    account_id AccountId

# This union represents the possible errors that might be returned.
union GetAccountErr
    no_account
        "No account with the requested id could be found."
    perm_denied
        "Insufficient privileges to query account information."
    unknown*

route get_account (GetAccountReq, Account, GetAccountErr)
    "Get information about a specified user's account."

Using the Python generator, we can generate a Python module that mirrors this specification using the command-line interface. From the top-level of the babelapi folder, try:

$ babelapi generator/python/python.babelg.py users.babel .
INFO:babelapi.idl:Parsing spec users.babel
INFO:babelapi.compiler:Found generator at ...
INFO:babelapi.compiler:Running generator ...
INFO:bablesdk.generator.PythonGenerator:Copying babel_data_types.py to output folder
INFO:bablesdk.generator.PythonGenerator:Copying babel_serializers.py to output folder
INFO:bablesdk.generator.PythonGenerator:Generating ./users.py

Now we can interact with the specification in Python:

$ python -i users.py
>>> a = Account()
>>> a.account_id = 1234 # fails type validation
Traceback (most recent call last):
  ...
babel_data_types.ValidationError: '1234' expected to be a string, got integer

>>> a.account_id = '1234' # fails length validation
Traceback (most recent call last):
  ...
babel_data_types.ValidationError: '1234' must be at least 10 characters, got 4

>>> a.account_id = 'id-48sa2f0' # passes validation

>>> # Now we use the included JSON serializer
>>> from babel_serializers import json_encode
>>> a2 = Account(account_id='id-48sa2f0', name='Alexander the Great',
...              email='alex@example.org', status=Status.active)
>>> json_encode(GetAccountRoute.response_data_type, a2)
'{"status": "active", "account_id": "id-48sa2f0", "name": "Alexander the Great", "email": "alex@example.org"}'

About

Write your APIs in stone.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 95.8%
  • Objective-C 2.4%
  • Swift 1.8%