Xenon

Gem Version Build Status Code Climate Test Coverage YARD Docs

An HTTP framework for building RESTful APIs. As you can probably tell from the very low version number, this is in very early stages of development so I wouldn't use it anywhere close to production yet. But have a play around and let me know what you think, I'm very open to feedback.

The xenon gem is a top-level gem that simply pulls in the other gems for convenience.

xenon-http

A set of model objects for the HTTP protocol which can parse and format the strings you typically get into proper objects you can work with. At the moment this covers key things like media types and the most common headers, but it will expand to cover the whole protocol. You can use the model by itself without any other parts of the library.

This is how things tend to look:

accept = Xenon::Headers::Accept.parse('application/json, application/*; q=0.5')
accept.media_ranges.first.media_type.json? #=> true
accept.media_ranges.last.q #=> 0.5
# etc.

Yeah, it's not exciting and it's not glamorous, but if you need to parse parts of the HTTP protocol that other frameworks just don't reach, Xenon is here for you.

xenon-routing

A tree-based routing approach using "directives" which gives you great flexibility in building APIs and without the need to write extensions, helpers, etc. because everything is a directive and you extend it by simply writing more directives!

A really simple example with a custom authentication directive is shown below, assuming an ActiveRecord-style Salutation model which supports from_json and to_json. You can run this from the examples directory!

class HelloWorld < Xenon::API
  path '/' do
    hello_auth do |user|
      get do
        params greeting: { type: String, default: 'hello' } do |greeting|
          complete :ok, Salutation.new(greeting: greeting, username: user.username)
        end
      end
      post do
        body as: Salutation do |salutation|
          salutation.username = user.username
          complete :ok, salutation
        end
      end
    end
  end

  private

  def hello_auth
    authenticate authenticator do |user|
      authorize user.username == 'greg' do
        yield user
      end
    end
  end

  def authenticator
    @authenticator ||= Xenon::BasicAuth.new realm: 'hello world' do |credentials|
      OpenStruct.new(username: credentials.username) # should actually auth here!
    end
  end
end

Authentication and authorisation are split so if you don't pass an auth token, or pass a badly formed token that can't be read as Basic credentials, you'll be unauthorised:

{
  "status": 401,
  "developer_message": "Unauthorized"
}

Whereas if you pass well a well-formed token with a username that isn't "greg" and you'll be forbidden:

{
  "status": 403,
  "developer_message": "Forbidden"
}

And, of course, it does all the things you'd expect from a decent API library like content negotiation and returning the correct status codes when paths or methods aren't found. For example, if you try to PUT you'll see the error:

{
  "status": 405,
  "developer_message": "Supported methods: GET, HEAD, POST"
}

Or if you send it an Accept header that doesn't allow JSON (the only supported format by default) you'll see:

{
  "status": 406,
  "developer_message": "Supported media types: application/json"
}

Spray

Xenon is inspired by Spray, an awesome Scala framework for building RESTful APIs, and which I sorely miss while working in Ruby. However although it's inspired by it, there are some key differences.

Firsly Xenon is synchronous rather than asynchronous, as that is a much more common approach to writing code in Ruby, and fits better with commonly used frameworks such as Rack and ActiveRecord. It's also much easier to write and reason about synchronous code, and you can still scale it pretty well using the process model.

Secondly the directives are just methods which are composed using Ruby's usual yield mechanism rather than being monads composed with flat map as in Spray. This is primarily to make the framework feel natural for Ruby users where the general desire is for simplicity and "it just works". This does limit composability of directives, but for most real-world situations this doesn't seem to be a problem so I think it's the right trade-off.

Installation

Add this line to your application's Gemfile:

gem 'xenon'

And then execute:

$ bundle

Or install it yourself as:

$ gem install xenon

Contributing

  1. Fork it ( https://github.com/gregbeech/xenon/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request