Skip to content

Commit

Permalink
Merge pull request #8 from inaka/cabol.4.to_json_function
Browse files Browse the repository at this point in the history
Implemented cowboy_swagger:to_json/1 function.
  • Loading branch information
igaray committed Aug 3, 2015
2 parents 76c7fdb + ac5d06a commit 0c951f4
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
PROJECT = cowboy_swagger

DEPS = trails
DEPS = jiffy trails

dep_jiffy = git https://github.com/davisp/jiffy.git 0.14.2
dep_trails = git https://github.com/inaka/cowboy-trails.git 0.0.1

SHELL_DEPS = sync
Expand Down
2 changes: 2 additions & 0 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
{deps_dir, "deps"}.
{deps,
[
{jiffy, ".*",
{git, "https://github.com/davisp/jiffy.git", "0.14.2"}},
{trails, ".*",
{git, "https://github.com/inaka/cowboy-trails.git", "0.0.1"}}
]
Expand Down
97 changes: 93 additions & 4 deletions src/cowboy_swagger.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,96 @@
%% API
-export([to_json/1]).

-spec to_json(Trails :: [trails:trail()]) -> binary().
to_json(_Trails) ->
%% @todo implement this function
<<>>.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Types.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-opaque swagger_parameters() ::
#{ name => binary()
, in => binary()
, description => binary()
, required => boolean()
, type => binary()
}.
-export_type([swagger_parameters/0]).

-opaque response_obj() ::
#{ description => binary()
}.
-type swagger_response() :: #{binary() => response_obj()}.
-export_type([response_obj/0, swagger_response/0]).

%% Swagger map spec
-opaque swagger_map() ::
#{ description => binary()
, summary => binary()
, parameters => [swagger_parameters()]
, tags => [binary()]
, consumes => [binary()]
, produces => [binary()]
, responses => swagger_response()
}.
-type metadata() :: trails:metadata(swagger_map()).
-export_type([swagger_map/0, metadata/0]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Public API.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @doc Returns the swagger json specification from given `trails'.
-spec to_json(Trails :: [trails:trail()]) -> iolist().
to_json(Trails) ->
SwaggerSpec = #{paths => swagger_paths(Trails)},
jiffy:encode(SwaggerSpec, [uescape]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Private API.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @private
swagger_paths(Trails) ->
swagger_paths(Trails, #{}).

%% @private
swagger_paths([], Acc) ->
Acc;
swagger_paths([Trail | T], Acc) ->
Path = normalize_path(trails:path_match(Trail)),
Metadata = normalize_map_values(trails:metadata(Trail)),
swagger_paths(T, maps:put(Path, Metadata, Acc)).

%% @private
normalize_path(Path) ->
re:replace(
re:replace(Path, "\\:\\w+", "\\{&\\}", [global]),
"\\[|\\]|\\:", "", [{return, binary}, global]).

%% @private
normalize_map_values(Map) when is_map(Map) ->
normalize_map_values(maps:to_list(Map));
normalize_map_values(Proplist) ->
F = fun({K, V}, Acc) when is_list(V) ->
case io_lib:printable_list(V) of
true -> maps:put(K, list_to_binary(V), Acc);
false -> maps:put(K, normalize_list_values(V), Acc)
end;
({K, V}, Acc) when is_map(V) ->
maps:put(K, normalize_map_values(V), Acc);
({K, V}, Acc) ->
maps:put(K, V, Acc)
end,
lists:foldl(F, #{}, Proplist).

%% @private
normalize_list_values(List) ->
F = fun(V, Acc) when is_list(V) ->
case io_lib:printable_list(V) of
true -> [list_to_binary(V) | Acc];
false -> [normalize_list_values(V) | Acc]
end;
(V, Acc) when is_map(V) ->
[normalize_map_values(V) | Acc];
(V, Acc) ->
[V | Acc]
end,
lists:foldl(F, [], List).
175 changes: 175 additions & 0 deletions test/cowboy_swagger_SUITE.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
-module(cowboy_swagger_SUITE).

%% CT
-export([all/0, init_per_suite/1, end_per_suite/1]).

%% Test cases
-export([to_json_test/1]).

-type config() :: [{atom(), term()}].

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Common test
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec all() -> [atom()].
all() ->
Exports = ?MODULE:module_info(exports),
[F || {F, 1} <- Exports, F /= module_info].

-spec init_per_suite(config()) -> config().
init_per_suite(Config) ->
Config.

-spec end_per_suite(config()) -> config().
end_per_suite(Config) ->
Config.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Test Cases
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-spec to_json_test(config()) -> {atom(), string()}.
to_json_test(_Config) ->
Trails = test_trails(),
SwaggerJson = cowboy_swagger:to_json(Trails),
#{<<"paths">> :=
#{<<"/a/{b}/{c}">> :=
#{<<"delete">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"b">>,
<<"required">> := false,
<<"type">> := <<"string">>}
]
},
<<"get">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"produces">> := [<<"application/json">>],
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"d">>,
<<"required">> := false,
<<"type">> := <<"string">>},
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"c">>,
<<"required">> := false,
<<"type">> := <<"string">>},
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"b">>,
<<"required">> := false,
<<"type">> := <<"string">>}
]
},
<<"post">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"body">>,
<<"name">> := <<"Request Body">>,
<<"required">> := true,
<<"type">> := <<"string">>}
]
}
},
<<"/a/{b}/{c}/{d}">> :=
#{<<"delete">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"b">>,
<<"required">> := false,
<<"type">> := <<"string">>}
]
},
<<"get">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"produces">> := [<<"application/json">>],
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"d">>,
<<"required">> := false,
<<"type">> := <<"string">>},
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"c">>,
<<"required">> := false,
<<"type">> := <<"string">>},
#{<<"description">> := <<"bla">>,
<<"in">> := <<"path">>,
<<"name">> := <<"b">>,
<<"required">> := false,
<<"type">> := <<"string">>}
]
},
<<"post">> :=
#{<<"description">> := <<"bla bla bla">>,
<<"parameters">> := [
#{<<"description">> := <<"bla">>,
<<"in">> := <<"body">>,
<<"name">> := <<"Request Body">>,
<<"required">> := true,
<<"type">> := <<"string">>}
]
}
}
}
} = jiffy:decode(SwaggerJson, [return_maps]),
{comment, ""}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Internal functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% @private
test_trails() ->
Metadata =
#{get => #{description => <<"bla bla bla">>,
produces => ["application/json"],
parameters => [
#{name => "b",
in => "path",
description => "bla",
required => false,
type => "string"},
#{name => "c",
in => "path",
description => "bla",
required => false,
type => "string"},
#{name => "d",
in => "path",
description => "bla",
required => false,
type => "string"}
]
},
delete => #{description => <<"bla bla bla">>,
parameters => [
#{name => <<"b">>,
in => <<"path">>,
description => <<"bla">>,
required => false,
type => <<"string">>}
]
},
post => #{description => <<"bla bla bla">>,
parameters => [
#{name => <<"Request Body">>,
in => <<"body">>,
description => <<"bla">>,
required => true,
type => <<"string">>}
]
}
},
[trails:trail("/a/[:b/[:c/[:d]]]", handler1, [], Metadata),
trails:trail("/a/:b/[:c]", handler2, [], Metadata),
trails:trail("/a/[:b]/:c/[:d]", handler3, [], Metadata)].

0 comments on commit 0c951f4

Please sign in to comment.