Coder Social home page Coder Social logo

poncho's Introduction

Poncho

Poncho is an API to build APIs or, in other words, a DSL to build REST interfaces.

It'll validate input and output, coerce values and is easily extendable with custom data types.

It's compatible with any rack-based framework, such as Rails or Sinatra.

Installation

Add this line to your application's Gemfile:

gem 'poncho'

And then execute:

$ bundle

Or install it yourself as:

$ gem install poncho

TLDR Usage

class ChargeResource < Poncho::Resource
  param :amount, :type => :integer
  param :currency

  def currency
    super || 'USD'
  end
end

class ChargeCreateMethod < Poncho::JSONMethod
  param :amount, :type => :integer, :required => true
  param :currency, :in => ['USD', 'GBP']

  def invoke
    charge = Charge.new
    charge.amount = param(:amount)
    charge.currency = param(:currency)
    charge.save

    ChargeResource.new(charge)
  end
end

post '/charges', &ChargeCreateMethod

Getting started with Methods

Methods inherit from Poncho::Method and override invoke, where they perform any necessary logic.

In a similar vein to Sinatra, anything returned from invoke is sent right back to the user. You can return a http status code, a body string, or even a Rack response array.

class ChargeListMethod < Poncho::Method
  def invoke
    # Some DB shizzle

    200
  end
end

To invoke the method just add it to your routes.

Using Rails:

match '/users' => UsersListMethod, :via => :get

Using Sinatra:

get '/users', &UsersListMethod

Or invoke manually:

UsersListMethod.call(rack_env)

If you're writing a JSON API, you'll probably want to inherit the Method from Poncho::JSONMethod instead of Poncho::Method, but we'll cover that later.

Params

You can get access to the request params, via the params or param(name) methods.

Before you can use a param though, you need to define it:

param :param_name

By default, param are of type 'string'. you can choose a different type via the :type option:

param :amount, :type => :integer

There are a bunch of predefined types, such as :integer, :array, :boolean_string etc, but you can also easily define your own custom ones (covered later).

Poncho will automatically validate that if a paramter is provided it is in a valid format. Poncho will also handle type conversion for you.

So for example, in the case above, Poncho will automatically validate that the amount param is indeed an Integer or an Integer string, and will coerce the parameter into an integer when you try to access it.

Validation

As well as the default type validation, Poncho lets you validate presence, format, length and much more!

For example, to validate that a :currency parameter is provided, pass in the `:presence' option:

param :currency, :presence => true

To validate that a currency is either 'USD' or 'GBP', use the :in option.

param :currency, :in => ['USD', 'GBP']

The other supported validations out of the box are :format, :not_in, and :length:

param :email, :format => /@/
param :password, :length => 5..20

Custom Validation

You can use a custom validator via the validate method, passing in a block:

validate do
  unless param(:customer_id) ~= /\Acus_/
    errors.add(:customer_id, :invalid_customer)
  end
end

# Or

validates :customer_id, :customer_validate

Alternatively, if your validation is being used in multiple places, you can wrap it up in a class and pass it to the validates_with method.

validates_with CustomValidator

For a good example of how to build validations, see the existing ones.

Custom Params

As your API grows you'll probably start to need custom parameter types. These can be useful to ensure parameters are both valid and converted into suitable values.

To define a custom parameter, simply inherit from Poncho::Param. For example, let's define a new param called CardHashParam. It needs to validate input via overriding the validate_each method, and convert input via overriding the convert method.

module Poncho
  module Params
    class CardHashParam < Param
      def validate_each(method, attribute, value)
        value = convert(value)

        unless value.is_a?(Hash) && value.keys == [:number, :exp_month, :exp_year, :cvc]
          method.errors.add(attribute, :invalid_card_hash, options.merge(:value => value))
        end
      end

      def convert(value)
        value && value.symbolize_keys
      end
    end
  end
end

You can use custom parameters via the :type option.

param :card, :type => Poncho::Params::CardHashParam

# Or the shortcut
param :card, :type => :card_hash

Request & Response

You can gain access to the rack request via the request method, for example:

def invoke
 accept = request.headers['Accept']
 200
end

The same goes for the response object:

def invoke
  response.body = ['Fee-fi-fo-fum']
  200
end

There are some helper methods to set such things as the HTTP status response codes and body.

def invoke
  status 201
  body 'Created!'
end

Method filters

There are various filters you can apply to the request, for example:

class MyMethod < Poncho::Method
  before_validation do
    # Before validation
  end

  before do
    # Before invoke
    p params
  end

  after do
    # After invocation
  end
end

Error responses

You can provide custom responses to exceptions via the error class method.

Pass error a exception type or status code.

class MyMethod < Poncho::Method
  error MyCustomClass do
    'Sorry, something went wrong.'
  end

  error 403 do
    'Not authorized.'
  end
end

JSON APIs

If your API only returns JSON then Poncho has a convenient JSONMethod class which will ensure that all response bodies are converted into JSON and that the correct content type header is set.

class TokenCreateMethod < Poncho::JSONMethod
  param :number, :required => true

  def invoke
    {:token => '123'}
  end
end

JSONMethod also ensures that there's valid JSON error responses to 404s and 500s, as well as returning a JSON error hash for validation errors.

$ curl http://localhost:4567/tokens -d number=
  {"error":{"param":"number","type":"presence"}}

Resources

Resources are wrappers around other classes, such as models, providing a view representation of them.

You can specify attributes to be returned to the client using the same param syntax as documented above.

class Card
  attr_reader :number

  def initialize(number)
    @number = number
  end
end

class CardResource < Poncho::Resource
  param :number
  param :description

  def number
    super[-4..-1]
  end
end

As you can see in the example above, you can override params and return a custom response.

When the Resource instance is converted into JSON the appropriate params will be used and serialized.

class ChargeResource < Poncho::Resource
  param :amount, :type => :integer
  param :currency
  param :card, :resource => CardResource

  def currency
    super || 'USD'
  end
end

class ChargeListMethod < Poncho::JSONMethod
  def invoke
    [
      ChargeResource.new(Charge.new(1000, 'USD')),
      ChargeResource.new(Charge.new(50, 'USD'))
    ]
  end
end

If a particular param points to another resource, you can use the :type => :resource option as demonstrated above.

poncho's People

Contributors

alex-stripe avatar andrew-stripe avatar brian-stripe avatar dclausen avatar flype avatar maccman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

poncho's Issues

`.to_json` throws an error when just the Poncho::Resource is used and not the whole library

Poncho::Resource is a neat and small wrapper that can be used on its own for wrapping a model instance and provide .to_json, to_hash and other methods for free. Unfortunately, since it doesn't load the JSON library, using it standalone would throw an error:

require "poncho"

class A
  def a
    20
  end

  def b
    "Poncho is fun"
   end
end

class AR < Poncho::Resource
  param :a
  param :b
end      



AR.new(A.new).to_json
# => NoMethodError: undefined method `to_json' for {:a=>"20", :b=>"Poncho is fun"}:Hash

I can understand that adding a require 'json' line to every file is not clean. We need to load the entire JSON library just to get access to one method :/

Aside:

Is there a reason why Poncho does not support multiple JSON libraries? Since .to_json is not present in, say, Oj gem.

Ambiguous error message when a POST request with a json string body is made and no Content-Type is set

This can be reproducible with the same example in the examples directory, and with the following command:
# let us not include the explicit header setting and send the data as a url-encoded
# body instead of application/json which is what we really want/expect

curl http://localhost:4567/charges -X POST -d "{\"amount\" : 120 }"

results in this error:

{"error":{"param":"{\"amount\":120}","type":"invalid_param","message":null}}

This is not really an error with Poncho but a problem with the user forgetting to set the wrong content type.

However, Poncho seems to run the basic sanity checks on params and throws this error. Guarding against wrong user input (in this case, pure idiocy on the part of the user) may not be the goal of a framework/abstraction and I really don't know what would be a better way to solve this for the same reason. Should there be a better error message? (I know it's impossible to cover all the edge-cases). Or perhaps, a setting to make sure Poncho won't sanity check the params unless the developer wants them?

Inheritable params & filters

Loving the current direction of Poncho. I've run into a few repeat issues, mainly stemming from the fact that params and filters are not inherited in method classes.

We typically setup a BaseMethod class for our custom exception handling, shared methods etc, which all other api method classes inherit from. This works fine until we try to share a before filter or param... Filters and params are not inherited... BOOM fullstop.

To get around this limitation, we've been wrapping our shared code (exceptions, filters, params) into modules, and including the module into each... and... every... method class. Arg.

Any chance the stripe team could make filters and params inheritable?

How to implement security?

I really like where Poncho is going and I'm excited to use it in my projects, but I'm running into a roadblock. How should one implement security?

So far, I've tried implementing security via a shared base method, like so:

class BaseMethod < Poncho::JSONMethod
  param :api_key

  validate do
    # Validation here for api key
  end
end

Then all my methods inherit from that BaseMethod:

class ResourceListMethod < BaseMethod
  # Method specific stuff here
end

At first glance, it seemed like this would work. However, the api_key validations don't get run inside the subclassed methods. The same goes if I use before. Any suggestions?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.