Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactor dispatcher and improve routes handling.

  • Loading branch information...
commit b97f724c1668353b52e6ab94093ee8ebf3b79226 1 parent ef6b8bf
@josevalim josevalim authored
View
33 lib/dynamo/app.ex → lib/dynamo/dispatcher.ex
@@ -1,29 +1,30 @@
-defmodule Dynamo::App do
+# Dynamo::Dispatcher allows any Elixir module to match
+# requests based on the path and take action accordingly.
+#
+# ## Examples
+#
+# defmodule MyApp do
+# use Dynamo::Dispatcher
+#
+# get "users/:id" do
+# response.write_head 200, [{ "Content-Type", "application/json" }]
+# response.end JSON.encode(User.find(id))
+# end
+# end
+#
+defmodule Dynamo::Dispatcher do
# Hook invoked when Dynamo::App is used.
# It initializes the app data, registers a
# compile callback and import Dynamo::DSL macros.
defmacro __using__(module) do
- Module.merge_data module, routes: []
Module.add_compile_callback module, __MODULE__
quote do
- import Dynamo::DSL
+ import Dynamo::Dispatcher::DSL
end
end
- defmacro __compiling__(module) do
- # Compile routes
- routes = Module.read_data module, :routes
- Dynamo::Router.compile(module, routes)
-
- # Generate both an service entry points
+ defmacro __compiling__(_) do
quote do
- # Clean up routes
- @routes nil
-
- def run(options // []) do
- Dynamo.run(__MODULE__, options)
- end
-
def service(request, response) do
{ :abs_path, path } = request.get(:uri)
verb = request.get(:method)
View
34 lib/dynamo/dispatcher/dsl.ex
@@ -0,0 +1,34 @@
+defmodule Dynamo::Dispatcher::DSL do
+ defmacro route(verb, expression, contents) do
+ { path, guards } = extract_path_and_guards(expression, default_guard)
+
+ tail = quote hygiene: false do
+ [request, response]
+ end
+
+ quote do
+ match = Dynamo::Router.generate_match to_char_list(unquote(path))
+ def :dispatch, [unquote(verb), match.segments | unquote(tail)], unquote(guards), unquote(contents)
+ end
+ end
+
+ defmacro get(path, contents) do
+ route(:GET, path, contents)
+ end
+
+ ## Helpers
+
+ defp extract_path_and_guards({ :when, _, [path, guards] }, extra_guard) do
+ { path, { :andalso, 0, [extra_guard, guards] } }
+ end
+
+ defp extract_path_and_guards(path, extra_guard) do
+ { path, extra_guard }
+ end
+
+ defp default_guard do
+ quote hygiene: false do
+ is_tuple(request) andalso is_tuple(response)
+ end
+ end
+end
View
19 lib/dynamo/dsl.ex
@@ -1,19 +0,0 @@
-defmodule Dynamo::DSL do
- defmacro route(verb, path, contents) do
- quote do
- _path = unquote(path)
- _list_path = to_char_list(_path)
- _bin_path = to_binary(_path)
-
- _routes = @routes
- _name = :"_action_#{_bin_path}_#{length(_routes)}"
-
- @routes [{ _list_path, {unquote(verb), _name} }|_routes]
- def _name, [var!(request), var!(response)], is_tuple(var!(request)) andalso is_tuple(var!(response)), unquote(contents)
- end
- end
-
- defmacro get(path, contents) do
- route(:GET, path, contents)
- end
-end
View
66 lib/dynamo/router.ex
@@ -2,7 +2,6 @@ defexception Dynamo::Router::InvalidSpec, message: "invalid route specification"
defrecord Dynamo::Router::Match, identifiers: [], segments: []
defmodule Dynamo::Router do
-
# Generates a representation that will only match routes according to the
# given `spec`.
#
@@ -31,46 +30,55 @@ defmodule Dynamo::Router do
## Helpers
- # Loops each segment checking finding dynamic matches.
+ # Loops each segment checking for matches.
defp generate_match([h|t], match) do
- final =
- case dynamic_match(h, []) do
- match: { :literal, literal }
- match.prepend_segments([literal])
- match: { :identifier, identifier, expr }
- match.
- prepend_segments([expr]).
- prepend_identifiers([identifier])
- match: { :glob, identifier, expr }
- if t != [], do:
- raise(InvalidSpec, message: "cannot have a *glob followed by other segments")
+ handle_segment_match segment_match(h, []), t, match
+ end
- [hs|ts] = match.segments
- acc = [{ :|, 0, [hs, expr] } | ts]
+ defp generate_match([], match) do
+ match.update_segments(List.reverse(&1))
+ end
- match.segments(acc).
- prepend_identifiers([identifier])
- end
+ # Handle each segment match. They can either be a
+ # :literal ('foo'), an identifier (':bar') or a glob ('*path')
+ def handle_segment_match({ :literal, literal }, t, match) do
+ generate_match t, match.prepend_segments([literal])
+ end
- generate_match(t, final)
+ def handle_segment_match({ :identifier, identifier, expr }, t, match) do
+ generate_match t, match.
+ prepend_segments([expr]).
+ prepend_identifiers([identifier])
end
- defp generate_match([], match) do
- match.update_segments(List.reverse(&1))
+ def handle_segment_match({ :glob, identifier, expr }, t, match) do
+ if t != [] do
+ raise(InvalidSpec, message: "cannot have a *glob followed by other segments")
+ end
+
+ match = match.prepend_identifiers([identifier])
+
+ case match.segments do
+ match: [hs|ts]
+ acc = [{ :|, 0, [hs, expr] } | ts]
+ match.segments(List.reverse(acc))
+ else:
+ match.segments(expr)
+ end
end
- # In a given segment, checks if there is a dynamic match.
- defp dynamic_match([?:|argument], []) do
+ # In a given segment, checks if there is a match.
+ defp segment_match([?:|argument], []) do
identifier = list_to_atom(argument)
{ :identifier, identifier, { identifier, 0, :quoted } }
end
- defp dynamic_match([?*|argument], []) do
+ defp segment_match([?*|argument], []) do
identifier = list_to_atom(argument)
{ :glob, identifier, { identifier, 0, :quoted } }
end
- defp dynamic_match([?:|argument], buffer) do
+ defp segment_match([?:|argument], buffer) do
identifier = list_to_atom(argument)
var = { identifier, 0, :quoted }
expr = quote do
@@ -79,7 +87,7 @@ defmodule Dynamo::Router do
{ :identifier, identifier, expr }
end
- defp dynamic_match([?*|argument], buffer) do
+ defp segment_match([?*|argument], buffer) do
identifier = list_to_atom(argument)
var = { identifier, 0, :quoted }
expr = quote do
@@ -88,11 +96,11 @@ defmodule Dynamo::Router do
{ :glob, identifier, expr }
end
- defp dynamic_match([h|t], buffer) do
- dynamic_match t, [h|buffer]
+ defp segment_match([h|t], buffer) do
+ segment_match t, [h|buffer]
end
- defp dynamic_match([], buffer) do
+ defp segment_match([], buffer) do
{ :literal, List.reverse buffer }
end
View
10 test/dynamo/router_test.exs
@@ -28,7 +28,7 @@ defmodule Dynamo::RouterTest do
assert_equal ['foo', 'bar'], R.split('foo/bar/')
end
- def test_generate_match_with_ligeral do
+ def test_generate_match_with_literal do
assert_quoted ['foo'], R.generate_match('/foo').segments
assert_quoted ['foo'], R.generate_match('foo').segments
end
@@ -43,6 +43,14 @@ defmodule Dynamo::RouterTest do
assert_quoted ['foo', 'bar' ++ username], R.generate_match('foo/bar:username').segments
end
+ def test_generate_match_only_with_glob do
+ assert_quoted bar, R.generate_match('*bar').segments
+ assert_quoted glob, R.generate_match('/*glob').segments
+
+ assert_quoted ['id-' ++ _ | _] = bar, R.generate_match('id-*bar').segments
+ assert_quoted ['id-' ++ _ | _] = glob, R.generate_match('/id-*glob').segments
+ end
+
def test_generate_match_with_glob do
assert_quoted ['foo' | bar], R.generate_match('/foo/*bar').segments
assert_quoted ['foo' | glob], R.generate_match('foo/*glob').segments
Please sign in to comment.
Something went wrong with that request. Please try again.