├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── config └── config.exs ├── lib └── trailing_format_plug.ex ├── mix.exs ├── mix.lock └── test ├── test_helper.exs └── trailing_format_plug_test.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | /doc 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | elixir: 1.3.4 3 | otp_release: 4 | - 19.1 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.0.7 4 | * Enhancements 5 | * Remove cowboy dependency 6 | * Fix Elixir 1.4 warnings 7 | 8 | ## v0.0.6 9 | 10 | * Enhancements 11 | * Strip format from param when using `:wildcard`-style routes (thanks 12 | @msmykowski) 13 | 14 | ## v0.0.5 15 | 16 | * Fixes 17 | * Allow empty `path_info` 18 | * Use `_format` param 19 | 20 | Many thanks to @smpallen99. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TrailingFormatPlug 2 | ================== 3 | [![Build Status](https://travis-ci.org/mschae/trailing_format_plug.svg?branch=master)](https://travis-ci.org/mschae/trailing_format_plug) 4 | 5 | ## Usage 6 | 7 | Add the `trailing_format_plug` dependency to your `mix.exs` as follows: 8 | 9 | ```elixir 10 | def deps do 11 | # ... 12 | {:trailing_format_plug, "~> 0.0.5"} 13 | # ... 14 | end 15 | ``` 16 | 17 | If you are using phoenix: 18 | 19 | Add the plug to your endpoint.ex file: 20 | 21 | ```elixir 22 | defmodule MyProject.Endpoint do 23 | # ... 24 | plug TrailingFormatPlug # add this 25 | plug Plug.RequestId 26 | plug Plug.Logger 27 | # ... 28 | end 29 | ``` 30 | 31 | ## License 32 | 33 | Copyright 2014 Michael Schaefermeyer 34 | 35 | Licensed under the Apache License, Version 2.0 (the "License"); 36 | you may not use this file except in compliance with the License. 37 | You may obtain a copy of the License at 38 | 39 | http://www.apache.org/licenses/LICENSE-2.0 40 | 41 | Unless required by applicable law or agreed to in writing, software 42 | distributed under the License is distributed on an "AS IS" BASIS, 43 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | See the License for the specific language governing permissions and 45 | limitations under the License. 46 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /lib/trailing_format_plug.ex: -------------------------------------------------------------------------------- 1 | defmodule TrailingFormatPlug do 2 | @behaviour Plug 3 | 4 | def init(options), do: options 5 | 6 | def call(%{path_info: []} = conn, _opts), do: conn 7 | def call(conn, _opts) do 8 | path = conn.path_info |> List.last() |> String.split(".") |> Enum.reverse() 9 | 10 | case path do 11 | [ _ ] -> 12 | conn 13 | 14 | [ format | fragments ] -> 15 | new_path = fragments |> Enum.reverse() |> Enum.join(".") 16 | path_fragments = List.replace_at conn.path_info, -1, new_path 17 | params = 18 | Plug.Conn.fetch_query_params(conn).params 19 | |> update_params(new_path, format) 20 | |> Map.put("_format", format) 21 | 22 | %{ 23 | conn | 24 | path_info: path_fragments, 25 | query_params: params, 26 | params: params 27 | } 28 | end 29 | end 30 | 31 | defp update_params(params, new_path, format) do 32 | wildcard = Enum.find params, fn {_, v} -> v == "#{new_path}.#{format}" end 33 | 34 | case wildcard do 35 | {key, _} -> 36 | Map.put(params, key, new_path) 37 | 38 | _ -> 39 | params 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule TrailingFormatPlug.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :trailing_format_plug, 7 | version: "0.0.7", 8 | elixir: ">= 1.0.0", 9 | deps: deps(), 10 | package: [ 11 | maintainers: ["Michael Schaefermeyer"], 12 | licenses: ["Apache 2.0"], 13 | links: %{"Github" => "http://github.com/mschae/trailing_format_plug"} 14 | ], 15 | description: description() 16 | ] 17 | end 18 | 19 | def application do 20 | [applications: [:logger]] 21 | end 22 | 23 | defp deps do 24 | [ 25 | {:plug, "> 0.12.0"}, 26 | 27 | {:ex_doc, "~> 0.14.3", only: [:dev]} 28 | ] 29 | end 30 | 31 | defp description do 32 | """ 33 | An elixir plug to support legacy APIs that use a rails-like trailing 34 | format: http://api.dev/resources.format 35 | """ 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [:make, :rebar], [{:cowlib, "~> 1.0.0", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, optional: false]}]}, 2 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []}, 3 | "earmark": {:hex, :earmark, "1.0.3", "89bdbaf2aca8bbb5c97d8b3b55c5dd0cff517ecc78d417e87f1d0982e514557b", [:mix], []}, 4 | "ex_doc": {:hex, :ex_doc, "0.14.3", "e61cec6cf9731d7d23d254266ab06ac1decbb7651c3d1568402ec535d387b6f7", [:mix], [{:earmark, "~> 1.0", [hex: :earmark, optional: false]}]}, 5 | "mime": {:hex, :mime, "1.0.1", "05c393850524767d13a53627df71beeebb016205eb43bfbd92d14d24ec7a1b51", [:mix], []}, 6 | "plug": {:hex, :plug, "1.2.2", "cfbda521b54c92ab8ddffb173fbaabed8d8fc94bec07cd9bb58a84c1c501b0bd", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]}, 7 | "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}} 8 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/trailing_format_plug_test.exs: -------------------------------------------------------------------------------- 1 | defmodule TrailingFormatPlugTest do 2 | use ExUnit.Case, async: true 3 | use Plug.Test 4 | 5 | @opts TrailingFormatPlug.init([]) 6 | 7 | test "plug removes trailing format" do 8 | conn = conn(:get, "/foo/bar.json") 9 | 10 | conn = TrailingFormatPlug.call(conn, @opts) 11 | 12 | assert conn.path_info == ["foo", "bar"] 13 | end 14 | 15 | test "plug does nothing without trailing format" do 16 | conn = conn(:get, "/foo/bar") 17 | 18 | conn = TrailingFormatPlug.call(conn, @opts) 19 | 20 | assert conn.path_info == ["foo", "bar"] 21 | end 22 | 23 | test "plug adds format to conn.params" do 24 | conn = 25 | conn(:get, "/foo/bar.json") 26 | |> Plug.Conn.fetch_query_params 27 | 28 | conn = TrailingFormatPlug.call(conn, @opts) 29 | assert conn.params["_format"] == "json" 30 | end 31 | 32 | test "plug removes .json from param" do 33 | params = Map.put(%{}, "sport", "hockey.json") 34 | 35 | conn = 36 | conn(:get, "/api/hockey.json") 37 | |> Plug.Conn.fetch_query_params 38 | |> Map.put(:params, params) 39 | 40 | conn = TrailingFormatPlug.call(conn, @opts) 41 | assert conn.params["_format"] == "json" 42 | assert conn.params["sport"] == "hockey" 43 | end 44 | 45 | test "plug supports empty path_info" do 46 | conn = 47 | conn(:get, "/") 48 | |> Plug.Conn.fetch_query_params 49 | 50 | conn = TrailingFormatPlug.call(conn, @opts) 51 | 52 | assert conn.path_info == [] 53 | refute conn.params["_format"] 54 | end 55 | end 56 | --------------------------------------------------------------------------------