///|
pub(all) struct RouteMatch {
path : Array[Bytes]
verb : Method
} derive(Eq, Debug)
///|
pub extend RouteMatch with Eq::{equal, not_equal}
///|
pub extend RouteMatch with @debug.Debug::{to_repr}
///|
priv struct Route {
path : Array[Bytes]
verbs : Array[Method]
handler : (IncomingRequest) -> Response
}
///|
pub struct Router {
priv limits : Limits
priv capacity : Int
priv mut routes : Array[Route]
}
///|
pub fn Router::new(capacity : Int, limits : Limits) -> Result[Router, Failure] {
match limits.validate() {
Err(e) => return Err(e)
Ok(_) => ()
}
if capacity < 1 || capacity > 4096 {
return Err(Invalid("route capacity must be 1..4096"))
}
Ok({ limits, capacity, routes: [], })
}
///|
pub fn resource_path(
path : String,
limits : Limits,
) -> Result[Array[Bytes], Failure] {
if !path.has_prefix("/") || path.contains("?") || path.contains("#") {
return Err(
Invalid("resource path must be absolute without query or fragment"),
)
}
match CoapUri::parse("coap://localhost" + path, limits) {
Err(e) => Err(e)
Ok(uri) => Ok(uri.path)
}
}
///|
pub fn canonical_path(parts : Array[Bytes]) -> String {
if parts.is_empty() {
return "/"
}
let mut result = ""
for part in parts {
result = result + "/" + percent_encode(part)
}
result
}
///|
pub fn Router::add(
self : Router,
path : String,
verbs : Array[Method],
handler : (IncomingRequest) -> Response,
) -> Result[Unit, Failure] {
let parts = match resource_path(path, self.limits) {
Err(e) => return Err(e)
Ok(v) => v
}
if verbs.is_empty() {
return Err(Invalid("route requires at least one method"))
}
let unique : Array[Method] = []
for verb in verbs {
if unique.contains(verb) {
return Err(Invalid("duplicate route method"))
}
unique.push(verb)
}
for route in self.routes {
if route.path == parts && route.verbs.any(fn(v) { unique.contains(v) }) {
return Err(Conflict("route method is already registered"))
}
}
if self.routes.length() >= self.capacity {
return Err(Capacity("router is full"))
}
self.routes.push({ path: parts, verbs: unique, handler, })
Ok(())
}
///|
pub fn Router::remove(self : Router, path : String) -> Result[Int, Failure] {
let parts = match resource_path(path, self.limits) {
Err(e) => return Err(e)
Ok(v) => v
}
let before = self.routes.length()
self.routes = self.routes.filter(fn(route) { route.path != parts })
Ok(before - self.routes.length())
}
///|
pub fn Router::routes(self : Router) -> Array[RouteMatch] {
let result : Array[RouteMatch] = []
for route in self.routes {
for verb in route.verbs {
result.push({ path: route.path.copy(), verb, })
}
}
result
}
///|
pub fn Router::dispatch(self : Router, incoming : IncomingRequest) -> Response {
let message = incoming.message
if !message.code.is_request() {
return Response::error(128)
}
match validate_options(message, []) {
Err(_) => return Response::error(130)
Ok(_) => ()
}
let verb = match message.code.verb() {
None => return Response::error(133)
Some(v) => v
}
let parts = path_segments(message)
let mut found_path = false
for route in self.routes {
if route.path == parts {
found_path = true
if route.verbs.contains(verb) {
return (route.handler)({ ..incoming, message: message.copy(), })
}
}
}
Response::error(if found_path { 133 } else { 132 })
}
///|
pub fn Endpoint::serve(
self : Endpoint,
router : Router,
incoming : IncomingRequest,
now : Int64,
) -> Result[Unit, Failure] {
match self.observe_time(now) {
Err(e) => return Err(e)
Ok(_) => ()
}
match
self.ensure_actions(self.config.max_exchanges + self.config.max_dedup + 4) {
Err(e) => return Err(e)
Ok(_) => ()
}
let exchange = match self.server_by_id(incoming.id) {
None => return Err(NotFound("unknown server request"))
Some(v) => v
}
if exchange.expires_at <= now {
return Err(TimedOut)
}
if exchange.answered || exchange.handling {
return Err(Conflict("request handler has already run"))
}
if exchange.incoming != incoming {
return Err(Conflict("incoming request does not match registered record"))
}
exchange.handling = true
self.respond(incoming.id, router.dispatch(incoming), now, 0)
}