Git Product home page Git Product logo

maru's Introduction

Maru

REST-like API micro-framework for elixir inspired by grape.

Build Status Hex.pm Version Docs Hex.pm Downloads

Installation

To get started with Maru, add the following to mix.exs:

def deps() do
  [
    {:maru, "~> 0.14"},
    {:plug_cowboy, "~> 2.0"},

    # Optional dependency, you can also add your own json_library dependency
    # and config with `config :maru, json_library, YOUR_JSON_LIBRARY`.
    {:jason, "~> 1.1"}
  ]
end

Usage

lib/my_app/server.ex:

defmodule MyApp.Server do
  use Maru.Server, otp_app: :my_app
end

defmodule Router.User do
  use MyApp.Server

  namespace :user do
    route_param :id do
      get do
        json(conn, %{user: params[:id]})
      end

      desc "description"

      params do
        requires :age, type: Integer, values: 18..65
        requires :gender, type: Atom, values: [:male, :female], default: :female

        group :name, type: Map do
          requires :first_name
          requires :last_name
        end

        optional :intro, type: String, regexp: ~r/^[a-z]+$/
        optional :avatar, type: File
        optional :avatar_url, type: String
        exactly_one_of [:avatar, :avatar_url]
      end

      # post do
      #   ...
      # end
    end
  end
end

defmodule Router.Homepage do
  use MyApp.Server

  resources do
    get do
      json(conn, %{hello: :world})
    end

    mount Router.User
  end
end

defmodule MyApp.API do
  use MyApp.Server

  before do
    plug Plug.Logger
    plug Plug.Static, at: "/static", from: "/my/static/path/"
  end

  plug Plug.Parsers,
    pass: ["*/*"],
    json_decoder: Jason,
    parsers: [:urlencoded, :json, :multipart]

  mount Router.Homepage

  rescue_from Unauthorized, as: e do
    IO.inspect(e)

    conn
    |> put_status(401)
    |> text("Unauthorized")
  end

  rescue_from [MatchError, RuntimeError], with: :custom_error

  rescue_from :all, as: e do
    conn
    |> put_status(Plug.Exception.status(e))
    |> text("Server Error")
  end

  defp custom_error(conn, exception) do
    conn
    |> put_status(500)
    |> text(exception.message)
  end
end

In your Application module, add Server as a worker:

defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      MyApp.Server
    ]

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

Then configure maru:

# config/config.exs
config :my_app, MyApp.Server,
  adapter: Plug.Cowboy,
  plug: MyApp.API,
  scheme: :http,
  port: 8880

config :my_app,
  maru_servers: [MyApp.Server]

Or let maru works with confex :

config :my_app, MyApp.Server,
  adapter: Plug.Cowboy,
  plug: MyApp.API,
  scheme: :http,
  port: {:system, "PORT"}

defmodule MyApp.Server do
  use Maru.Server, otp_app: :my_app

  def init(_type, opts) do
    Confex.Resolver.resolve(opts)
  end
end

For more information, check out Guides and Examples

maru's People

Contributors

1ternal avatar aforward avatar brandonparsons avatar brentspell avatar cifer-y avatar danielkirch avatar dechaoqiu avatar efcasado avatar falood avatar gaku-sei avatar jalcine avatar larrylv avatar legoscia avatar lowks avatar moxley avatar oivoodoo avatar onkel-dirtus avatar optikfluffel avatar paulnicholson avatar pel-daniel avatar rrrene avatar taybin avatar wdavidw avatar whitfieldc 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

maru's Issues

How to run a maru app without `iex`?

All the documentation I've read goes iex -S mix.

If I just type mix it displays Running Elixir.MyAPP.API with Cowboy on http://127.0.0.1:8880 then promptly exits.

What if I'm eyeing to deploy this to production, do I really need to have an interactive app waiting for input?

Parsing Param Error

Hi

I have been using 0.10.1 and keep getting this error whenever I put a params block into my code.

00:16:19.004 [error] #PID<0.291.0> running Testmaru.Api terminated
Server: localhost:8880 (http)
Request: POST /
** (exit) an exception was raised:
    ** (Maru.Exceptions.InvalidFormatter) Parsing Param Error: test
        (testmaru) lib/api.ex:1: anonymous fn/1 in Testmaru.Api.route/2
        (maru) lib/maru/runtime.ex:20: Maru.Runtime.parse_params/3
        (testmaru) lib/api.ex:1: Testmaru.Api.route/2
        (testmaru) lib/api.ex:1: Testmaru.Api.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/

The Module is as follows:


defmodule Testmaru.Api do
  use Maru.Router

  params do
   requires :test, type: String
  end
  post "/" do
    conn |> text("Hello")
  end

end

Have you seen this issue ??

List parameters

I cannot for the life of me figure out how to declare a parameter that is a list of primitives.
Looking at the sample in the guide:

  optional :preferences, type: List do
    requires :key
    requires :value
  end

this will declare a list of maps with "key" and "value", won't it?
What if I want a list of integers?

Request params map permissiveness

Grape seems to allow request parameters to pass through to the params object that are not named in the params block.

Maru seems to take the other opinion, not placing request parameters in the params map that are not named in the params block.

Is there any way to have Maru allow all extra params into the params map while still enforcing the requires and optional constructs, a la Grape? I have it in my head as something like an atom passed to the params macro, maybe something like:

params :lenient do
   # constraints here
end

I'm open to trying to hack this myself, but this is my first experience with Elixir (it's really cool!), so I'm not sure really where to start (I'm guessing here).

Just curious if something like this is either possible or wanted. Thanks for your work, Maru is great!

Wrap conn(:get, "/") |> make_response

This is a little cumbersome to spell:

assert %Plug.Conn{resp_body: "It works!"} = conn(:get, "/") |> make_response

Maybe it can be more Ruby-like? We'd want to write

assert %Plug.Conn{resp_body: "It works!"} = get("/")

Now this is my first day writing Elixir, but I'd be happy to give it a shot if anyone thinks it's a good idea.

Boolean param type issue

In my API route, I have a param like below:

    params do
      requires :opt_in, type: Boolean
    end

Then I visit my api, I found a strange question:

If my param is {"opt_in": false}, it will report that I didn't pass opt_in to it.

But {"opt_in": true}, {"opt_in": "true"}, {"opt_in": "false"} all worked well.

Confused.

Using identical param keys in groups results in wrong params

For example, using the following params:

       params do                                                               
          requires :type, type: Atom, values: [:record]                         
          group :data, type: Map do                                             
            requires :name, type: String, regexp: ~r"^[a-zA-Z0-9\-\.]+$"        
            requires :type, type: String, regexp: ~r"^[a-zA-Z0-9]+$"            
          end                                                                   
        end          

Submitting this:

{data: %{name: "www", type: "a"}, type: "record"}

Will result with the following parameters:

%{data: %{name: "www", type: "record"}, type: :record}

The expected result is:

%{data: %{name: "www", type: "a"}, type: :record}

Proper way to test a File parameter?

What's the proper way to unit test a route that has a File parameter? For example, a route that would update a user's avatar on s3:

  params do
    requires :avatar, type: File
  end
  post "/info/avatar" do
    # update user's avatar on S3...
  end

In order to test this manually I have done multipart requests with HTTPoison:

name = "avatar"
filename = "avatar.png"
path = "assets/avatar.png"
body = {:multipart, [{:file, path, {["form-data"], [name: Poison.encode!(name), filename: Poison.encode!(filename)]},[]}]}
HTTPoison.post!("http://localhost:8080/info/avatar", body)

Is there some easy way to do this in a Maru test?

Top Middleware not executing during tests

Plugs mounted in a parent router aren't being run on the child routers during tests. I could work up a test case but there's one in the docs: the second code sample in this page doesn't pass.

  1) test / (API.MountedTest)
     test/random_mix_test.exs:28
     ** (KeyError) key :maru_plug_test not found in: %{maru_params: %{}, maru_test: true, maru_version: nil}
     stacktrace:
       test/random_mix_test.exs:30: (test)

Pass param options as second argument to ParamType

Right now I implement a param type like so:

defmodule Maru.ParamType.Time do
  def from(string) do
    case Timex.parse(string, "{ISO:Extended}") do
      {:ok, time} ->
        time
      {:error, _} ->
        raise Turtle.API.InvalidParamException, error: "Invalid time #{string}. Expected ISO8601 like 2016-04-21T22:26:01Z"
    end
  end
end

Two things

  • I would like to control the format like requires :time, allowed_formats: ["m/d/Y"].
  • I would like to specify the field that is invalid so I can provide more friendly errors. For example I could print The start_time parameter was an invalid time.

I think if the 2nd argument got all of the options it would solve this.

What do you think of this? Thanks!

Missing param raises an InvalidFormatter error?

A POST request to an API with requires :token yields this without a token:

  1) test requires a token (Aprb.Api.SlackTest)
     test/api/slack_test.exs:13
     ** (Maru.Exceptions.InvalidFormatter) Parsing Param Error: token
     stacktrace:

Seems like there're two problems.

  1. This is not a formatter error, it's some kind of missing or invalid parameter error.
  2. Shouldn't this be handled and turned into a 400 Bad Request error and not raise?

Example usage with umbrella app

Currently want to use Maru as our API provider for an umbrella app, but having a hard time getting it to work. Some examples would be great.

Test: make_response should not require a version

If you don't supply a version to make_response below (remove "v1")

    test "/" do
      assert %Plug.Conn {
        resp_body: "It works! port: 8800"
      } = conn(:get, "/") |> make_response("v1")
    end

you get

  1) test / (EchoServerTest)
     test/echo_server_test.exs:5
     ** (UndefinedFunctionError) undefined function EchoServer.__version__/0
     stacktrace:
       (echo_server) EchoServer.__version__()
       EchoServerTest.make_response/2
       test/echo_server_test.exs:6

Seems odd.

Phoenix.Conn tests fail when after upgrading 1.0

I'm not able to use Phoenix.Conn() when running my tests anymore if I upgrade to 1.0. The endpoints work-- I can run the server and test them in my browser. It's just that when I use ConnCase it doesn't work.

If this is expected, any ideas why this would stop working?

If not, I can dig in further to figure out why.

I prefer to use Phoenix.ConnCase to keep a consistent way of testing the endpoints as a couple API endpoints are done with controllers.

Thanks!

What's the proper way to return a 400?

      if System.get_env("SLACK_SLASH_COMMAND_TOKEN") != params[:token] do
        conn
          |> put_status(403)
          |> text("Unauthorized")

        raise("Unauthorized") <<---- this seems wrong
      end

What's the equivalent of grape's error! that lets me do

      # check that token matches, that the POST comes from our slack integration
      if System.get_env("SLACK_SLASH_COMMAND_TOKEN") != params[:token] do
        error! 403, "Unauthorized"
      end

      # this code is not excuted

Incoherance in the get started doc

Hi,

Sorry this is really nitpicking but in the get started webpage, you mention:

config :maru, MyAPP.API,
    http: [port: 8880]

and then

$ curl 127.0.0.1:4000
{"hello":"world"}

which should be 8880 ( or change config to 4000 ).

!! sorry for nitpicking !!

config :maru, test: false

Hello,

(not an elixir dev sorry if incorrect)

With the latest commits and the introduction of test_mode/0 it seems that releases built by exrm/distillery fail without forcing the test mode to false.

case Application.get_env(:maru, :test) do
 +      true  -> true
 +      false -> false
 +      nil   -> Mix.env == :test

Attempts to run Mix.env but it doesn't exists with release mode so crashes.
If this is true maybe we could document it better?

several rescue_from?

Hello, I've got a definition like this:

defmodule MyApp.API do
  use Maru.Router
  mount MyApp.API.Test
 get do
    conn |> text("It works!")
  end

  rescue_from :all, as: e do
    conn
    |> put_status(500)
    |> text("ERROR: #{inspect e}")
  end
end

and in MyApp.API.Test:

defmodule MyApp.API.Test do
  use Maru.Router
  plug Plug.Logger

  namespace :test do
    params do
      requires :Product_ID, type: String
    end 

    get do
      json(conn, %{"Result" => "yes"}
    end

    rescue_from [ Maru.Exceptions.Validation, Maru.Exceptions.InvalidFormatter ] do
    conn                                                                                                   
      |> put_status(500)
      |> json(%{"error" => %{"code" => 0, 
                      "context" => "context", 
                      "message" => "this is an error"})
    end
  end
end 

As you see, I have 2 rescue_from and the inner one doesn't work. Regarding the implementation, it's seems to be OK.
But if I remove the outer (rescue_from :all), the inner still doesn't work :(
And if I put the inner in MyApp.API then my tests are failing (the error is not catched).
Any idea how to do that?

I'm mimicking an already deployed app server so I don't really have the choice for the error handling output...

Automatic validation when using a type

Hi Falood, I have created a param type for Time in my project like so:

defmodule Maru.ParamType.Time do
  def from(string) do
    Timex.parse!(string, "{ISO:Extended}")
  end
end

If someone passes in an incorrectly formatted time, I'd like the request to fail with an error message. What's the best way to do this with maru? Should I raise an exception from within my from implementation?

Thanks!

Maru ignores input parameter if its value is null

Hello!

I wanted to give Elixir a try and tried to implement very simple JSON API using Maru. Maru is looking good for me at the moment, but I've found strange behavior recently.

Here is Maru router which describes what I've found:

defmodule API do
  before do
    plug Plug.Parsers,
      pass: ["*/*"],
      json_decoder: Poison,
      parsers: [:urlencoded, :json, :multipart]
  end

  params do
    optional :phone, type: String
  end
  post do
    conn |> json(params)
  end
end

I expect that this endpoint will return a JSON containing exact value of given phone param.
It works in most cases:

$ curl -H "Content-Type: application/json" -X POST -d '{"phone":"123"}' http://localhost:4000/
{"phone":"123"}

But if I pass "null" value, maru will discard this parameter completely:

$ curl -H "Content-Type: application/json" -X POST -d '{"phone":null}' http://localhost:4000/
{}

This behaviour restricts JSON API very much, so as an API designer I can't provide ability to use null values for certain keys (which is necessary in my case).

Is this intentional or is it a bug? Thanks :)

Support Maru::Entity

Hi,

I created maru-entity as a clone of grape-entity, to help me serialize objects to json. Is there any way you can include support for it ?

Currently I'm doing like this to serialize objects inside a maru route:

get do
   users = User.all
   UserEntity.serialize(users) |> json
end

It would be nice to have a macro like:

get do
  users = User.all
  present users, with: UserEntity
end

Maybe add support to plug any serialization module which responds to serialize ?

`mount` doesn't work on `mix test`

application code:

defmodule MyApp.Api.Mount do
    use Maru.Router
    get do
        json(conn, %{hello: :world})
    end
end

defmodule MyApp.Api do
    use Maru.Router
    mount MyApp.Api.Mount
end

test code:

defmodule MyAppTest do
    use ExUnit.Case
    use Maru.Test, for: MyApp.Api

    test "mount" do
        assert %{"hello" => "world"} = build_conn() |> get("/") |> json_response
    end
end

run mix test:

  1) test mount (MyAppTest)
     test/myapp_test.exs:5
     ** (Maru.Exceptions.NotFound) NotFound
     stacktrace:
       (maru) lib/maru/plugs/notfound.ex:11: Maru.Plugs.NotFound.call/2
       (gacct) lib/myapp/api.ex:8: MyApp.Api.call/2
       test/myapp_test.exs:6: (test)

It is caused by test ENV as far as I know.
The issue is similar to #38.

I want to test a code using mount.
Is there a way to do this?

Allow blank example

Hello I can't get the allow_blank validator to work.
Would it be possible to add an example in the doc?

For example:

params do
   requires :foo, type: :string, allow_blank: :true
end

but sending an empty string still fails the requires validation

thanks!

support desc block

like this:
desc 'Returns your public timeline.' do
detail 'more details'
params API::Entities::Status.documentation
success API::Entities::Entity
failure [[401, 'Unauthorized', 'Entities::Error']]
named 'My named route'
headers XAuthToken: {
description: 'Valdates your identity',
required: true
},
XOptionalHeader: {
description: 'Not really needed',
required: false
}

end
?? ruby grape

Unnamed parameter lists

Hi,

I was wondering if it is possible to define an unnamed parameter list. Something in the following style:

params do
	requires  type: List do
	  requires :foo, type: String
	  requires :bar, type: String
	end
  end

The idea is to send json arrays, for example;

[ {"foo" : "foobar", "bar": "foobar"}, ...]

As far as I can tell, all parameters have to be named, which usually makes sense, except for the above use case. Any help or pointers are much appreciated.

Testing POST

First off I just want to say I absolutely love this framework with all my heart.

Currently I am using this helper to test my POST requests

  use ExUnit.Case
  use Maru.Test, for: DataApi

  def post_and_respond(body, url) do
    build_conn
    |> Plug.Conn.put_req_header("content-type", "application/json")
    |> put_body_or_params(Poison.encode!(body))
    |> post(url)
    |> Map.get(:resp_body)
    |> Poison.decode
  end

This is obviously fine, but I imagine there is a better way.

How do I run a plug function in a route_param scope?

defmodule MyApp.Users do
  use Maru.Router

  alias MyApp.{Repo, User}

  defp find_user(conn, _opts) do
    case Repo.get(User, conn.params.id) do
      nil -> conn |> put_status(404) |> |> json(%{error: "Not found"}) |> halt
      user -> conn |> assign(:user, user)
    end
  end

  namespace :users do
    route_param :id do
      plug :find_user

      get do
        conn |> json(conn.assigns.user)
      end

      put do
        case conn.assigns.user |> User.changeset(params) |> Repo.update do
          {:ok, user} -> conn |> json(user)
          {:error, changeset} -> conn |> json(%{errors: changeset.errors})
        end
      end
    end
  end
end

The above Phoenix-style code doesn't work but I hope you get the idea what I'm trying to do.
What's the correct way?

Running with MIX_ENV=test results in "Server Error"

I was trying to write espec + tesla tests to exercise my maru Web service.

I'm following the guide, and so when I run iex -S mix then I curl localhost:8880, I get the expected response ({"hello":"world"}).

However, when I try to write espec tests or run MIX_ENV=test iex -S mix, then curl reports Server Error. Further poking around (I replaced text("Server Error") with text(inspect(e)) reveals:

MIX_ENV=test iex -S mix
Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe]
[kernel-poll:false] [dtrace]

20:14:08.902 [info]  Running Elixir.MyApp.API with Cowboy on http://127.0.0.1:8880
Interactive Elixir (1.3.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
$ curl localhost:8880
%Maru.Exceptions.NotFound{method: "GET", path_info: []}

I'm a bit lost why MIX_ENV=test would result in such behavior.

Testing mounted routers which depend on Route Parameters

First of all, thanks for the great framework, I'm having a blast learning Elixir with it! I've bumped into a limitation I think: I want to test that a nested router is correctly getting its params from the parent's route_params. An example:

defmodule Users do
  use Maru.Router

  namespace :users do
    route_param :id do
      params do
        require :id, type: Integer
      end

      namespace :posts, do: mount Posts
    end
  end
end

defmodule Posts do
  use Maru.Router

  params do
    # should be coming from the route_param
    require :id, type: Integer
  end
  
  get do
    # render user's posts
  end
end

In this case I'd get the path /users/:id/posts where the nested router grabs the :id param from the route. However, during unit tests of the Posts route, I've no way (afaik) of testing that it's grabbing the id from the route correctly, since all paths are relative to that router (i.e.: /).

I guess I could test this from the Users router, but that doesn't make a lot of sense since the functionality doesn't really belong to it.

I think we should be able to test this, perhaps with Routing unit tests?

Testing not with ExUnit and Maru.Test is sending unhandled data

Anyway I can suppress Maru.Test from doing that or would it be better to test with straight Plug?

defmodule CustomersSpec do
  use ESpec
  use Maru.Test, for: Auberge.API.V1.Customers
  alias Auberge.Customer

  describe "Customers API" do
    it "can create a new customer" do
      customer = %Customer{first_name: "John",
                           last_name: "Doe",
                           email: "[email protected]",
                           phone_num: "5555555555"}

      response = build_conn()
                  |> Plug.Conn.put_req_header("content-type", "application/json")
                  |> put_body_or_params(customer |> Poison.encode!)
                  |> post("/customers")

      expected_customer = customer |> Map.put(:id, 2) |> Poison.encode!

      expect response.status |> to(eq 201)
      expect response.resp_body |> to(eq expected_customer)
    end
  end
end

Output Log

00:09:50.226 [warn]  ESpec.Runner ESpec.Runner received unexpected message in handle_info/2: {#Reference<0.0.2.559>,
 {201,
  [{"cache-control", "max-age=0, private, must-revalidate"},
   {"content-type", "application/json; charset=utf-8"}],
  "{\"phone_num\":\"5555555555\",\"last_name\":\"Doe\",\"id\":2,\"first_name\":\"John\",\"email\":\"[email protected]\"}"}}

Can't get basic tests to work

I am sure this is a problem between the computer and the chair since I am an Elixir noob, but I would appreciate some help.

https://github.com/dblock/aprb/tree/tests

A very simple endpoint:

defmodule Aprb.Api.Ping do
  use Maru.Router

  namespace :ping do
    desc "Ping which returns pong."
    get do
      json(conn, %{ ping: :pong })
    end
  end
end

A very simple test:

defmodule Aprb.ApiTest do
  use ExUnit.Case, async: true
  use Maru.Test, for: Aprb.Api.Ping

  test "/ping" do
    assert "pong" = get("/ping") |> text_response
  end
end

Running mix test

** (CompileError) test/api/ping_test.exs:6: undefined function get/1
    (stdlib) lists.erl:1338: :lists.foreach/2
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (elixir) lib/code.ex:363: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:56: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

Halp?

`rescue_from` blocks are not as documented

Documentation suggests you can write something like this:

rescue_from :all do
  status 500
  "Oh noes."
end

However an exception is raised, expressing that the Cowboy adapter was expecting to receive a plug. In contrast the following works fine.

rescue_from :all do
  conn
  |> send_resp(500, "Oh noes.")
end

Is the documentation out of date? Or is this a bug?

Json params raises as InvalidFormat exception

Hi there!
I need to pass some unstructured json document as content_type param like this:

{
	"content_type": {
		"a": 1
	}
}

content_type can be any json with any fields. When Im using Map as param type

    params do
      requires :content_type, type: Map
    end

Then params parses as empty map %{}. But if I set param type to Json, %Maru.Exceptions.InvalidFormat I'm getting %Maru.Exceptions.InvalidFormat.
Here is the code:

    params do
      requires :content_type, type: Json
    end

    post do
      Logger.info(inspect(params))

      case Geronimo.ContentType.create(params[:content_type]) do
        {:ok, resp} -> respond_with(conn, resp)
        {:err, err} -> respond_with(conn, err, 422)
      end
    end

The main question is: how can I allow to push any json document as content_type parameter?
Thanks in advance!

Auto-generated API docs?

Does maru have the option to generate API docs (e.g. HTML output) or is there an external tool that works with maru to do so?

Problems with multiple Maru apps in one umbrella project

I'm not sure if this is just a undocumented config setting or an actual bug but I'm having problems with several Maru apps in one umbrella project. I'm building a REST api as an umbrella project so I can seperate out the components into microservices. Before I added Maru to provide the routing for each module I was able to run tests either as a whole or for each individual app. Once I've added Maru I now can't start one of the apps by itself unless it has the others as a dependecy, because it tries to start the Maru routers for all sub apps, rather than just the one I'm in, which obviously it can't find.

Failing to implement POST macro

Hi,

I am failing to implement a 'post' macro. Please can you guide me as to what I am doing wrong? And there is very little documentation around testing a post.

defmodule Skynet.Person.API do
  use Maru.Router
  alias Skynet.Person
  alias Skynet.Repo
  version "v1"

  resource :person do
    # desc "description"
    get ":id" do
      entry = Repo.get!(Person, params[:id])
      present entry, with: Person.Entity
    end

    # desc "description"
    params do
      requires :name, type: String
      requires :phone, type: String, regexp: ~r/^27[0-9]{9}$/
    end
    post do
      %Person{name: params[:name], phone: params[:phone]}
      |> Repo.insert!
      status 200
    end
  end
end
defmodule Skynet.API do
  use Maru.Router

  plug Plug.RequestId
  plug Plug.Logger
  resources do
    mount Skynet.Person.API
  end

  rescue_from Unauthorized, as: e do
    IO.inspect e
    status 401
    "Unauthorized"
  end

  rescue_from :all, as: e do
    IO.inspect e
    status 500
    "Server Error"
  end
end
@ anthony-mac> curl -H "Accept-Version:v1" -H "Content-Type: application/json" --request POST --data '{"name": "anthony", "phone": "27745765000"}' http://127.0.0.1:8000/person
Server Error
2015-10-23 13:32:48.495 [info] POST /person
%Maru.Exceptions.NotFound{method: "POST", path_info: ["person"]}
iex(2)> Skynet.Person.API.__endpoints__
[%Maru.Router.Endpoint{block: {:__block__, [line: 23],
   [{:|>, [line: 37],
     [{:%, [line: 36],
       [{:__aliases__, [counter: 0, line: 36], [:Person]},
        {:%{}, [line: 36],
         [name: {{:., [line: 36], [Access, :get]}, [line: 36],
           [{:params, [line: 36], nil}, :name]},
          phone: {{:., [line: 36], [Access, :get]}, [line: 36],
           [{:params, [line: 36], nil}, :phone]}]}]},
      {{:., [line: 37],
        [{:__aliases__, [counter: 0, line: 37], [:Repo]}, :insert!]},
       [line: 37], []}]}, {:status, [line: 38], [200]}]}, desc: nil,
  helpers: [], method: "POST",
  param_context: [%Maru.Router.Param{attr_name: :name, default: nil, desc: nil,
    group: [], nested: false, parser: Maru.ParamType.String, required: true,
    validators: []},
   %Maru.Router.Param{attr_name: :phone, default: nil, desc: nil, group: [],
    nested: false, parser: Maru.ParamType.String, required: true,
    validators: [regexp: ~r/^27[0-9]{9}$/]}], path: ["person"], version: "v1"},
 %Maru.Router.Endpoint{block: {:__block__, [line: 23],
   [{:=, [line: 26],
     [{:entry, [line: 26], nil},
      {{:., [line: 26],
        [{:__aliases__, [counter: 0, line: 26], [:Repo]}, :get!]}, [line: 26],
       [{:__aliases__, [counter: 0, line: 26], [:Person]},
        {{:., [line: 26], [Access, :get]}, [line: 26],
         [{:params, [line: 26], nil}, :id]}]}]},
    {:present, [line: 27],
     [{:entry, [line: 27], nil},
      [with: {:__aliases__, [line: 27, counter: 189], [:Person, :Entity]}]]}]},
  desc: nil, helpers: [], method: "GET", param_context: [],
  path: ["person", :id], version: "v1"}]

Note: I am using Maru version 0.7.1, because anything above results in the following error during tests:

  1) test v1 post /person (Skynet.Person.Test)
     test/person_test.exs:29
     ** (UndefinedFunctionError) undefined function: :urlencoded.parse/5 (module :urlencoded is not available)
     stacktrace:
       :urlencoded.parse(%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{}, before_send: [], body_params: %Plug.Conn.Unfetched{aspect: :body_params}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "www.example.com", method: "POST", owner: #PID<0.643.0>, params: %{}, path_info: ["person"], peer: {{127, 0, 0, 1}, 111317}, port: 80, private: %{}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"content-type", "application/json"}, {"accept-version", "v1"}], request_path: "/person", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}], scheme: :http, script_name: [], secret_key_base: nil, state: :unset, status: nil}, "application", "json", %{}, [parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Poison])
       (plug) lib/plug/parsers.ex:186: Plug.Parsers.reduce/6
       test/person_test.exs:3: Skynet.Person.Test.make_response/2
       test/person_test.exs:37

could not compile dependency :maru

run this command after deps.get : mix deps.compile maru
==> maru
Compiling 59 files (.ex)

== Compilation error in file lib/maru/plugs/version.ex ==
** (CompileError) lib/maru/plugs/version.ex:32: expected -> clauses for :else in "with"

could not compile dependency :maru, "mix compile" failed. You can recompile this dependency with "mix deps.compile maru", update it with "mix deps.update maru" or clean it with "mix deps.clean maru"

Maru might not allow for dynamic configuration

Don't shoot me if I'm wrong, but I think this is an issue with Maru. The context is rather simple: I modified the configuration using Application.put_env before starting my dependencies such as Maru, but for some reason Maru does not respect this new configuration, even though when I use Application.get_env it reports the new configuration just as you would expect.

I would love to fix it myself, but unfortunately I have no clue about what is going on, because the soruce suggests Maru does load the configuration after it is started. Is there any chance you could look into it or tell me that it doesn't have anything to do with Maru? That would be really great. For more information, see samvv/elixir-takeoff#1.

Add documentation / example for lists.

It would be great if the documentation included an example like:

params do
  requires :tags, List[String]
end

as I had to do a bit of code spelunking to figure out how to allow for a list of strings.

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.