Skip to content

vladfaust/http-params-serializable

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 

HTTP::Params::Serializable

Built with Crystal Build status API Docs Releases Awesome vladfaust.com Patrons count Gitter chat

The HTTP params serialization module for Crystal.

Supporters

Thanks to all my patrons, I can build and support beautiful Open Source Software! 🙏

Lauri Jutila, Alexander Maslov, Dainel Vera

You can become a patron too in exchange of prioritized support and other perks

Become Patron

About

This module is intended to provide a simple and convenient way to make an object to safely initialize from an HTTP params query (be it an URL query or "application/x-www-form-urlencoded" body). It tries to have an API almost the same as existing JSON::Serializable and YAML::Serializable modules, thus allowing to serialize infinitely-nested structures, including Arrays and Hashes.

Installation

  1. Add the dependency to your shard.yml:
dependencies:
  http-params-serializable:
    github: vladfaust/http-params-serializable
    version: ~> 0.4.0
  1. Run shards install

Usage

Simple example:

require "http-params-serializable"

struct Params
  include HTTP::Params::Serializable
  getter id : Int32
end

params = Params.from_query("id=42")
pp params.id.class # => Int32
pp params.to_query # => "id=42"

Params.from_query("")
# HTTP::Params::Serializable::ParamMissingError: Parameter "id" is missing

Params.from_query("id=foo")
# HTTP::Params::Serializable::ParamTypeCastError: Parameter "id" cannot be cast from "foo" to Int32

As you may expect, unions work as well:

struct Params
  include HTTP::Params::Serializable
  getter id : Int32 | Nil
end

params = Params.from_query("id=")
pp params.id # => nil

Arrays are supported too:

struct Params
  include HTTP::Params::Serializable
  getter foo : Array(Float32)
end

params = Params.from_query("foo[]=42.0&foo[]=43.5")
pp params.foo[1] # => 43.5
pp params.to_query # => "foo[]=42.0&foo[]=43.5"

Nested params are supported:

struct Params
  include HTTP::Params::Serializable
  getter nested : Nested

  struct Nested
    include HTTP::Params::Serializable
    getter foo : Bool
  end
end

params = Params.from_query("nested[foo]=true")
pp params.nested.foo # => true
pp params.to_query # => "nested[foo]=true"

Nested arrays are supported as well:

struct Params
  include HTTP::Params::Serializable
  getter nested : Array(Nested)

  struct Nested
    include HTTP::Params::Serializable
    getter foo : Array(Int32)
  end
end

params = Params.from_query("nested[0][foo][]=1&nested[0][foo][]=2")
pp params.nested.first.foo.first # => [1, 2]
pp params.to_query # ditto

It is also possible to alter the serialization behaviour with @[HTTP::Param] annotation. It currently supports two options: :key and :converter. Read more in docs.

struct Params
  include HTTP::Params::Serializable

  @[HTTP::Param(key: "the___Time", converter: Time::EpochConverter)]
  getter time : Time
end

params = Params.from_query("the___Time=1544958806")
pp params.time # => 2018-12-16 11:13:26.0 UTC
pp params.to_query # => "the___Time=1544958806"

Custom serialization rules

If you want to know-how to make custom objects serializable, read the Custom serialization rules Wiki page.

Usage with Onyx::REST

Onyx::REST comes with an Action module, which implements common param sources parsing, which uses this shard under the hood:

require "onyx/rest"

struct UpdateUser
  include Onyx::REST::Action

  params do
    # Path params ("/users/:id")
    path do
      type id : Int32
    end

    # Query params ("/users/42?password=secret")
    # Nesting and arrays natively supported
    query do
      type password : String
      type foo do
        type bar : Int32?
      end
    end

    # Full support for form-data payloads
    form do
      type username : String?
      type email : String?
    end
  end

  errors do
    type Forbidden(403)
  end

  def call
    user = Onyx.query(User.where(id: params.path.id))
    raise Forbidden.new unless user.password == params.query.password
  end
end

Onyx.put "/users/:id", UpdateUser
Onyx.listen

Usage with Kemal

It's pretty simple to make your Kemal applications more safe:

require "kemal"
require "http-params-serializable"

struct Params
  include HTTP::Params::Serializable
  getter id : Int32
end

get "/" do |env|
  if query = env.request.query
    query_params = Params.from_query(query)

    if query_params.id > 0
      "#{query_params.id} is positive\n"
    else
      "#{query_params.id} is negative or zero\n"
    end
  else
    "Empty query\n"
  end
rescue ex : HTTP::Params::Serializable::Error
  ex.message.not_nil! + "\n"
end

Kemal.run
$ curl http://localhost:3000?id=42
42 is positive
$ curl http://localhost:3000?id=-1
-1 is negative or zero
$ curl http://localhost:3000?id=foo
Parameter "id" cannot be cast from "foo" to Int32

Development

crystal spec and you're good to go.

Contributing

  1. Fork it (https://github.com/vladfaust/http-params-serializable/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

Contributors