FileServer
action, which is a standard and minimal file server
HttpRequest
class and services to create it
Serializable::inspect
to show more useful information
more_collections :: more_collections
Highly specific, but useful, collections-related classes.serialization :: serialization_core
Abstract services to serialize Nit objects to different formatscore :: union_find
union–find algorithm using an efficient disjoint-set data structure
# Internal routes representation.
module pop_routes
import nitcorn
# AppRoute provide services for path and uri manipulation and matching..
#
# Default strict routes like `/` or `/user` match the same URI string.
# An exception is done for the trailing `/`, which is always omitted during the
# parsing.
#
# ~~~
# var route = new AppRoute("/")
# assert route.match("")
# assert route.match("/")
# assert not route.match("/user")
# assert not route.match("user")
#
# route = new AppRoute("/user")
# assert not route.match("/")
# assert route.match("/user")
# assert route.match("/user/")
# assert not route.match("/user/10")
# assert not route.match("/foo")
# assert not route.match("user")
# assert not route.match("/username")
# ~~~
class AppRoute
# Route relative path from server root.
var path: String
# Does self match the `req`?
fun match(uri: String): Bool do
uri = uri.simplify_path
var path = resolve_path(uri)
if uri.is_empty and path == "/" then return true
return uri == path
end
# Replace path parameters with concrete values from the `uri`.
#
# For strict routes, it returns the path unchanged:
# ~~~
# var route = new AppRoute("/")
# assert route.resolve_path("/user/10/profile") == "/"
#
# route = new AppRoute("/user")
# assert route.resolve_path("/user/10/profile") == "/user"
# ~~~
fun resolve_path(uri: String): String do return path.simplify_path
# Remove `resolved_path` prefix from `uri`.
#
# Mainly used to resolve and match mountable routes.
#
# ~~~
# var route = new AppRoute("/")
# assert route.uri_root("/user/10/profile") == "/user/10/profile"
#
# route = new AppRoute("/user")
# assert route.uri_root("/user/10/profile") == "/10/profile"
# ~~~
fun uri_root(uri: String): String do
var path = resolve_path(uri)
if path == "/" then return uri
return uri.substring(path.length, uri.length).simplify_path
end
end
# Parameterizable routes.
#
# Routes that can contains variables parts that will be resolved during the
# matching process.
#
# Route parameters are marked with a colon `:`
# ~~~
# var route = new AppParamRoute("/:id")
# assert not route.match("/")
# assert route.match("/user")
# assert route.match("/user/")
# assert not route.match("/user/10")
# ~~~
#
# It is possible to use more than one parameter in the same route:
# ~~~
# route = new AppParamRoute("/user/:userId/items/:itemId")
# assert not route.match("/user/10/items/")
# assert route.match("/user/10/items/e895346")
# assert route.match("/user/USER/items/0/")
# assert not route.match("/user/10/items/10/profile")
# ~~~
class AppParamRoute
super AppRoute
init do parse_path_parameters(path)
# Cut `path` into `UriParts`.
fun parse_path_parameters(path: String) do
for part in path.split("/") do
if not part.is_empty and part.first == ':' then
# is an uri param
path_parts.add new UriParam(part.substring(1, part.length))
else
# is a standard string
path_parts.add new UriString(part)
end
end
end
# For parameterized routes, parameter names are replaced by their value in the URI.
# ~~~
# var route = new AppParamRoute("/user/:id")
# assert route.resolve_path("/user/10/profile") == "/user/10"
#
# route = new AppParamRoute("/user/:userId/items/:itemId")
# assert route.resolve_path("/user/Morriar/items/i156/desc") == "/user/Morriar/items/i156"
# ~~~
redef fun resolve_path(uri) do
var uri_params = parse_uri_parameters(uri)
var path = "/"
for part in path_parts do
if part isa UriString then
path /= part.string
else if part isa UriParam then
path /= uri_params.get_or_default(part.name, part.name)
end
end
return path.simplify_path
end
# Extract parameter values from `uri`.
# ~~~
# var route = new AppParamRoute("/user/:userId/items/:itemId")
# var params = route.parse_uri_parameters("/user/10/items/i125/desc")
# assert params["userId"] == "10"
# assert params["itemId"] == "i125"
# assert params.length == 2
#
# params = route.parse_uri_parameters("/")
# assert params.is_empty
# ~~~
fun parse_uri_parameters(uri: String): Map[String, String] do
var res = new HashMap[String, String]
if path_parts.is_empty then return res
var parts = uri.split("/")
for i in [0 .. path_parts.length[ do
if i >= parts.length then return res
var ppart = path_parts[i]
var part = parts[i]
if not ppart.match(part) then return res
if ppart isa UriParam then
res[ppart.name] = part
end
end
return res
end
private var path_parts = new Array[UriPart]
end
# Route with glob.
#
# Route variable part is suffixed with a star `*`:
# ~~~
# var route = new AppGlobRoute("/*")
# assert route.match("/")
# assert route.match("/user")
# assert route.match("/user/10")
# ~~~
#
# Glob routes can be combined with parameters:
# ~~~
# route = new AppGlobRoute("/user/:id/*")
# assert not route.match("/user")
# assert route.match("/user/10")
# assert route.match("/user/10/profile")
# ~~~
#
# Note that the star can be used directly on the end of an URI part:
# ~~~
# route = new AppGlobRoute("/user*")
# assert route.match("/user")
# assert route.match("/username")
# assert route.match("/user/10/profile")
# assert not route.match("/foo")
# ~~~
#
# For now, stars cannot be used inside a route, use URI parameters instead.
class AppGlobRoute
super AppParamRoute
# Path without the trailing `*`.
# ~~~
# var route = new AppGlobRoute("/user/:id/*")
# assert route.resolve_path("/user/10/profile") == "/user/10"
#
# route = new AppGlobRoute("/user/:userId/items/:itemId*")
# assert route.resolve_path("/user/Morriar/items/i156/desc") == "/user/Morriar/items/i156"
# ~~~
redef fun resolve_path(uri) do
var path = super
if path.has_suffix("*") then
return path.substring(0, path.length - 1).simplify_path
end
return path.simplify_path
end
redef fun match(uri) do
var path = resolve_path(uri)
return uri.has_prefix(path.substring(0, path.length - 1))
end
end
# A String that compose an URI.
#
# In practice, UriPart can be parameters or static strings.
private interface UriPart
# Does `self` matches a part of the uri?
fun match(uri_part: String): Bool is abstract
end
# An uri parameter string like `:id`.
private class UriParam
super UriPart
# Param `name` in the route uri.
var name: String
# Parameters match everything.
redef fun match(part) do return not part.is_empty
redef fun to_s do return name
end
# A static uri string like `users`.
private class UriString
super UriPart
# Uri part string.
var string: String
# Empty strings match everything otherwise matching is based on string equality.
redef fun match(part) do return string.is_empty or string == part
redef fun to_s do return string
end
lib/popcorn/pop_routes.nit:17,1--263,3