1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
use core::fmt::Write;
pub use ap::Ap;
use toad_msg::MessageOptions;
use self::ap::state::{Complete, Hydrated};
use self::ap::{ApInner, Hydrate, Respond};
use crate::net::{Addrd, Socket};
use crate::platform::{Message, Platform, PlatformTypes};
use crate::req::Req;
use crate::resp::Resp;
use crate::step::Step;
use crate::todo::String;
/// Server flow applicative
pub mod ap;
/// Path manipulation
///
/// * [`segment`](path::segment)
/// * [`next()`](path::segment::next) - consume the next segment of the route & combine it with data in the `Ap`
/// * [`check`](path::segment::check)
/// * [`next_is()`](path::segment::check::next_is) - assert that the next route segment matches a predicate
/// * [`next_equals()`](path::segment::check::next_is) - assert that the next route segment equals a string
/// * [`param`](path::segment::param)
/// * [`u32()`](path::segment::param::u32) - consume the next route segment and parse as u32, rejecting the request if parsing fails.
/// * [`rest()`](path::rest) - extract the full route, skipping consumed segments & combine it with data in the `Ap`
/// * [`check`](path::check)
/// * [`rest_is()`](path::check::rest_is) - assert that the rest of the route matches a predicate
/// * [`rest_equals()`](path::check::rest_equals) - assert that the rest of the route matches a string
/// * [`ends_with()`](path::check::ends_with) - assert that the rest of the route ends with a string
pub mod path;
/// Request method filters
pub mod method;
/// Respond to requests
pub mod respond;
/// [`Run`] errors
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
pub enum Error<E> {
/// Path was not valid UTF8
PathDecodeError(core::str::Utf8Error),
/// Request was ACK / EMPTY (these should be handled & swallowed by the toad runtime)
RequestInvalidType(toad_msg::Type),
/// Error of input type `E`
Other(E),
}
/// A request was received and needs to be handled by `Run`ning your code.
///
/// This data structure allows you to declare a re-runnable block
/// of code that will be invoked with every incoming request.
///
/// ```
/// use toad::server::ap::Hydrate;
/// use toad::server::{respond, Error, Run};
/// use toad::std::{dtls, PlatformTypes as Std};
///
/// let run: Run<Std<dtls::Y>, ()> = Run::Error(Error::Other(()));
/// run.maybe(|ap| {
/// let (_, Hydrate { req, .. }) = ap.try_unwrap_ok_hydrated().unwrap();
/// if req.data().path() == Ok(Some("hello")) {
/// let name = req.data().payload_str().unwrap_or("you nameless scoundrel");
/// respond::ok(format!("hi there, {}!", name).into()).hydrate(req)
/// } else {
/// respond::respond(toad::resp::code::NOT_FOUND, [].into()).hydrate(req)
/// }
/// });
/// ```
#[derive(Debug)]
pub enum Run<P, E>
where P: PlatformTypes
{
/// Request has not been matched yet
Unmatched(Addrd<Req<P>>),
/// Request has a response
Matched(Addrd<Message<P>>),
/// An Error occurred
Error(Error<E>),
}
impl<P, E> PartialEq for Run<P, E>
where P: PlatformTypes,
E: PartialEq
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
| (Self::Unmatched(a), Self::Unmatched(b)) => a == b,
| (Self::Matched(a), Self::Matched(b)) => a == b,
| (Self::Error(a), Self::Error(b)) => a == b,
| _ => false,
}
}
}
impl<P, E> Run<P, E>
where P: PlatformTypes,
E: core::fmt::Debug
{
/// Lift an [`Ap`] to [`Run`]
pub fn handle(ap: Ap<Complete, P, (), E>) -> Self {
match ap.0 {
| ApInner::Err(e) => Self::Error(Error::Other(e)),
| ApInner::RespondHydrated(Respond { code,
payload,
etag, },
Addrd(req, addr)) => {
let mut resp = Resp::non(&req);
resp.set_code(code);
resp.set_payload(payload);
if let Some(etag) = etag {
resp.msg_mut().add_etag(etag.as_ref()).ok();
}
Self::Matched(Addrd(resp.into(), addr))
},
| ApInner::RejectHydrated(req) => Self::Unmatched(req),
| a @ ApInner::Respond { .. }
| a @ ApInner::Reject
| a @ ApInner::Phantom(_)
| a @ ApInner::Ok(_)
| a @ ApInner::OkHydrated { .. } => unreachable!("{a:?}"),
}
}
/// Use a function to potentially respond to a request
///
/// Each "maybe" branch corresponds roughly to a route / RESTful CoAP resource.
pub fn maybe<F>(self, mut f: F) -> Self
where F: FnMut(Ap<Hydrated, P, (), E>) -> Ap<Complete, P, (), E>
{
match self {
| Run::Matched(m) => Run::Matched(m),
| Run::Error(e) => Run::Error(e),
| Run::Unmatched(req) => Self::handle(f(Ap::ok_hydrated((), Hydrate::from_request(req)))),
}
}
}
/// Newtype wrapper of an initialization function
#[derive(Debug, Clone, Copy)]
pub struct Init<T>(pub Option<T>);
impl Init<fn()> {
/// No initialization
pub fn none() -> Self {
Self(None)
}
}
/// Use a CoAP [`Platform`] as a server
///
/// This trait provides a function [`.run()`](BlockingServer::run) that
/// allows you to provide some work to do when the server initializes ([`Init`])
/// and a closure that handles incoming requests.
///
/// Servers are thread-safe, meaning that [`run`](BlockingServer::run) may
/// be invoked concurrently by multiple worker threads.
pub trait BlockingServer<S>: Sized + Platform<S>
where S: Step<Self::Types, PollReq = Addrd<Req<Self::Types>>, PollResp = Addrd<Resp<Self::Types>>>
{
#[allow(missing_docs)]
fn run<I, R>(&self, init: Init<I>, mut handle_request: R) -> Result<(), Error<Self::Error>>
where I: FnMut(),
R: FnMut(Run<Self::Types, Self::Error>) -> Run<Self::Types, Self::Error>
{
let mut startup_msg = String::<1000>::default();
write!(
&mut startup_msg,
r#"
=====================================
_
__ ___.--'_`.
( _`.'. - 'o` )
_\.'_' _.-'
( \`. ) //\`
\_`-'`---'\\__,
\` `-\
`
toad server up and running! 🐸
listening on `{}`.
====================================="#,
self.socket().local_addr()
).ok();
self.log(log::Level::Info, startup_msg)
.map_err(Error::Other)?;
init.0.map(|mut f| f());
loop {
let req = nb::block!(self.poll_req()).map_err(Error::Other)?;
match handle_request(Run::Unmatched(req)) {
| Run::Unmatched(req) => {
let mut msg = String::<1000>::default();
write!(&mut msg,
"IGNORING Request, not handled by any routes! {:?}",
req).ok();
self.log(log::Level::Error, msg).map_err(Error::Other)?;
let mut msg = String::<1000>::default();
write!(
&mut msg,
r#"
Do you need a fallback?
server.run(|run| run.maybe(..)
.maybe(..)
.maybe(..)
.maybe(|ap| ap.bind(|_| respond::not_found(\"Not found!\"))))
)"#
).ok();
},
| Run::Matched(rep) => nb::block!(self.send_msg(rep.clone())).map_err(Error::Other)
.map(|_| ())?,
| Run::Error(e) => break Err(e),
}
}
}
}
impl<S, T> BlockingServer<S> for T
where S: Step<Self::Types, PollReq = Addrd<Req<Self::Types>>, PollResp = Addrd<Resp<Self::Types>>>,
T: Sized + Platform<S>
{
}
#[cfg(test)]
mod tests {
mod compiles {
use crate::server::{path, respond, Error, Run};
use crate::std::{dtls, PlatformTypes as Std};
#[allow(dead_code)]
fn foo() {
let _ = Run::<Std<dtls::Y>, _>::Error(Error::Other(())).maybe(|a| {
a.pipe(path::segment::check::next_equals("user"))
.pipe(path::segment::param::u32)
.bind(|(_, user_id)| respond::ok(format!("hello, user ID {}!", user_id).into()))
});
}
}
}