Skip to content

Commit

Permalink
Move request method matching to the bottom level instead of the top (#30
Browse files Browse the repository at this point in the history
)

* initial transition

* Add in some documentation
  • Loading branch information
matthewmcgarvey committed Aug 5, 2020
1 parent 95fcaf7 commit 7314801
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 50 deletions.
6 changes: 3 additions & 3 deletions spec/fragment_spec.cr
Expand Up @@ -4,7 +4,7 @@ describe LuckyRouter::Fragment do
it "adds parts successfully" do
fragment = build_fragment

fragment.process_parts(["users", ":id"], :show)
fragment.process_parts(["users", ":id"], "get", :show)

users_fragment = fragment.static_parts["users"]
users_fragment.dynamic_part.should_not be_nil
Expand All @@ -13,8 +13,8 @@ describe LuckyRouter::Fragment do
it "static parts after dynamic parts do not overwrite each other" do
fragment = build_fragment

fragment.process_parts(["users", ":id", "edit"], :edit)
fragment.process_parts(["users", ":id", "new"], :new)
fragment.process_parts(["users", ":id", "edit"], "get", :edit)
fragment.process_parts(["users", ":id", "new"], "get", :new)

users_fragment = fragment.static_parts["users"]
id_fragment = users_fragment.dynamic_part.not_nil!.fragment
Expand Down
14 changes: 8 additions & 6 deletions src/lucky_router/fragment.cr
Expand Up @@ -52,17 +52,19 @@ class LuckyRouter::Fragment(T)
end

alias StaticPartName = String
# The payload is only set on the last fragment
property payload : T?
property dynamic_part : DynamicFragment(T)?
getter static_parts = Hash(StaticPartName, Fragment(T)).new
# Every path can have multiple request methods
# and since each fragment represents a request path
# the final step to finding the payload is to search for a matching request method
getter method_to_payload = Hash(String, T).new

def process_parts(parts : Array(String), payload : T)
PartProcessor(T).new(self, parts: parts, payload: payload).run
def process_parts(parts : Array(String), method : String, payload : T)
PartProcessor(T).new(self, parts: parts, method: method, payload: payload).run
self
end

def find(parts : Array(String)) : Match(T) | NoMatch
MatchFinder(T).new(self, parts: parts).run
def find(parts : Array(String), method : String) : Match(T) | NoMatch
MatchFinder(T).new(self, parts: parts, method: method).run
end
end
8 changes: 5 additions & 3 deletions src/lucky_router/match_finder.cr
@@ -1,5 +1,5 @@
class LuckyRouter::MatchFinder(T)
private getter parts, params
private getter parts, params, method
private property fragment

@fragment : Fragment(T)
Expand All @@ -13,9 +13,10 @@ class LuckyRouter::MatchFinder(T)
# it'll then create a new MatchFinder and pass ["1", "edit"], until the last
# fragment which will have just ["edit"] as the `parts`
@parts : Array(String)
@method : String
@params : Hash(String, String)

def initialize(@fragment, @parts, @params = {} of String => String)
def initialize(@fragment, @parts, @method, @params = {} of String => String)
end

# This looks for a matching fragment for the given parts
Expand All @@ -27,7 +28,8 @@ class LuckyRouter::MatchFinder(T)

add_to_params if has_param?
if last_part? && has_match?
return Match(T).new(matched_fragment.not_nil!.payload.not_nil!, params)
payload = matched_fragment.not_nil!.method_to_payload[method]?
return payload.nil? ? NoMatch.new : Match(T).new(payload, params)
end
self.fragment = match
parts.shift
Expand Down
37 changes: 5 additions & 32 deletions src/lucky_router/matcher.cr
Expand Up @@ -16,32 +16,11 @@
# router.match("get", "/users").payload # :index
# ```
class LuckyRouter::Matcher(T)
getter routes
# aliases to help clarify what the @routes has is made of
alias RoutePartsSize = Int32
alias HttpMethod = String

# The matcher stores routes based on the HTTP method
#
# Each route key is a `Fragment(T)`. Where `T` is the type of the payload. See
# `Fragment` for details on how it works
#
# ## Example
#
# ```
# router = LuckyRouter::Matcher(Symbol).new
# router.add("get", "/users/:user_id", :index)
#
# # Will make @routes look like:

# {
# "get" => Fragment(T) # The fragment for this route
# }
# ```
#
# So if trying to match a POST request it will not even try because the request
# method does not match any of the known routes.
@routes = Hash(HttpMethod, Fragment(T)).new
# starting point from which all fragments are located
getter root = Fragment(T).new

def add(method : String, path : String, payload : T)
all_path_parts = path.split("/")
Expand All @@ -64,16 +43,15 @@ class LuckyRouter::Matcher(T)
private def process_and_add_path(method : String, path : String, payload : T)
parts = extract_parts(path)
if method.downcase == "get"
add_route("head", parts, payload)
root.process_parts(parts, "head", payload)
end

add_route(method, parts, payload)
root.process_parts(parts, method, payload)
end

def match(method : String, path_to_match : String) : Match(T)?
parts_to_match = extract_parts(path_to_match)
return unless routes[method]?
match = routes[method].find(parts_to_match)
match = root.find(parts_to_match, method)

if match.is_a?(Match)
match
Expand All @@ -89,9 +67,4 @@ class LuckyRouter::Matcher(T)
parts.pop if parts.last.blank?
parts
end

private def add_route(method : String, parts : Array(String), payload : T)
routes[method] ||= Fragment(T).new
routes[method].process_parts(parts, payload)
end
end
13 changes: 7 additions & 6 deletions src/lucky_router/part_processor.cr
@@ -1,11 +1,12 @@
class LuckyRouter::PartProcessor(T)
private getter fragment, payload, parts
private getter fragment, payload, method, parts

@fragment : Fragment(T)
@payload : T
@parts : Array(String)
@method : String

def initialize(@fragment, @parts, @payload)
def initialize(@fragment, @parts, @method, @payload)
end

def run
Expand Down Expand Up @@ -39,7 +40,7 @@ class LuckyRouter::PartProcessor(T)
name: current_part.gsub(":", ""),
fragment: Fragment(T).new
)
fragment.dynamic_part.not_nil!.fragment.process_parts(next_parts, payload)
fragment.dynamic_part.not_nil!.fragment.process_parts(next_parts, method, payload)

if on_last_part?
add_payload_to_dynamic_part
Expand All @@ -52,14 +53,14 @@ class LuckyRouter::PartProcessor(T)

private def add_static_part
fragment.static_parts[current_part] ||= Fragment(T).new
fragment.static_parts[current_part].process_parts(next_parts, payload)
fragment.static_parts[current_part].process_parts(next_parts, method, payload)

if next_parts.empty?
fragment.static_parts[current_part].payload = payload
fragment.static_parts[current_part].method_to_payload[method] = payload
end
end

private def add_payload_to_dynamic_part
fragment.dynamic_part.not_nil!.fragment.payload = payload
fragment.dynamic_part.not_nil!.fragment.method_to_payload[method] = payload
end
end

0 comments on commit 7314801

Please sign in to comment.