# warp
## General
### Better `wrap_fn` example
`routes.rs`
```rust
use warp::{Filter, Rejection, Reply};
use tokio::sync::mpsc;
/// Endpoints that can only be called by the node owner.
fn owner(
activity_tx: mpsc::Sender<()>,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let owner = warp::path("owner")
.map(move || {
// Not exactly a wrapper, but can produce side
// effects that requires state, e.g. activity_tx
println!("Sending activity event");
let _ = activity_tx.try_send(());
})
.untuple_one();
let node_info = warp::path("node_info")
.and(warp::get())
// .and(with_db(db.clone()))
// .and(warp::query())
.then(owner::node_info);
// Example wrapper implemented with a function
owner
.and(node_info)
.with(warp::wrap_fn(wrapper::activity))
}
```
`wrapper.rs`
```rust
use warp::{Filter, Rejection, Reply};
pub fn activity<F, T>(
filter: F,
) -> impl Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static
where
F: Filter<Extract = (T,), Error = Rejection>
+ Clone
+ Send
+ Sync
+ 'static,
F::Extract: Reply,
{
warp::any()
.map(|| {
println!("This is printed before the filter");
})
.untuple_one()
.and(filter)
}
```
#### Alternative: wrapping with closure
```rust
owner.and(node_info).with(warp::wrap_fn(|filter| {
warp::any()
.map(|| {
println!("Before wrapper using closure");
})
.untuple_one()
.and(filter)
}))
```
### Workaround: use a closure in `.map()`
```rust
let owner = warp::path("owner")
.map(move || {
// Not exactly a wrapper, but can produce side
// effects that requires state, e.g. activity_tx
println!("Sending activity event");
let _ = activity_tx.try_send(());
})
.untuple_one();
```
### (Compiling, non-working) Call a fn that returns a fn that can be passed into `wrap_fn`
```rust
// FIXME: This compiles but doesn't actually produce a side effect
// Need to include the side effect in the map closure
pub fn attempt_3<T, U>(activity_tx: mpsc::Sender<()>) -> impl Fn(U) -> U
where
U: Filter<Extract = (T,), Error = Rejection>
+ Clone
+ Send
+ Sync
+ 'static,
T: Reply,
{
move |filter| {
println!("Activity wrapper triggered");
let _ = activity_tx.send(());
// warp::any()
// .map(|| {
// let _ = activity_tx.send(());
// println!("wrapper generator");
// })
// .untuple_one()
// .and(filter)
filter
}
}
```
### (Non-compiling) Call a fn that returns a fn that can be passed into `wrap_fn`
### Query parameters must be defined with structs, not handler arguments
Assuming the following route:
```rust
let get_channelmonitors = warp::path("channel_monitor")
.and(warp::get())
.and(with_db(db.clone()))
.and(warp::query())
.then(v1::get_channel_monitors);
```
This is a correct example implementation of the handler:
```rust
#[derive(Deserialize)]
pub struct GetByPublicKey {
pub public_key: String,
}
pub async fn get_channel_monitors(
db: Db,
req: GetByPublicKey,
) -> Result<impl Reply, ApiError> {
let cm_vec = channel_monitor::Entity::find()
.filter(channel_monitor::Column::NodePublicKey.eq(req.public_key))
.all(&*db)
.await?;
Ok(reply::json(&cm_vec))
}
```
The following code will compile but will produce a `404 Invalid query string` when called on e.g. `http://127.0.0.1:3030/v1/channel_monitor?public_key=038bdcb770b4b8229d086c9ba400976c5559abdf52f19141a3f2a3e48af3723ad2`:
```rust
pub async fn get_channel_monitors(
db: Db,
public_key: String,
) -> Result<impl Reply, ApiError> {
let cm_vec = channel_monitor::Entity::find()
.filter(channel_monitor::Column::NodePublicKey.eq(public_key))
.all(&*db)
.await?;
Ok(reply::json(&cm_vec))
}
```
If all fields of the query parameter struct are nullable (i.e. `Option<T>`), then you may need to use the querystring deserializer from `serde_qs`. See [warp issue #905](https://github.com/seanmonstar/warp/issues/905) for details.
## Blog post from creator (it's good):
https://seanmonstar.com/post/176530511587/warp
"**The thing that makes warp special is its `Filter` system.**"
### FILTERS
A `Filter` in warp is essentially a function that can operate on some input, either something from a request, or something from a previous `Filter`, and returns some output, which could be some app-specific type you wish to pass around, or can be some reply to send back as an HTTP response. That might sound simple, but the exciting part is the combinators that exist on the `Filter` trait. These allow composing smaller `Filter`s into larger ones, allowing you modularize, and reuse any part of your web server.
Let me show you what I mean. Suppose you need to piece together data from several different places of a request before your have your domain object. Maybe an ID is a path segment, some verification is in a header, and other data is in the body.
```rust
let id = warp::path::param();
let verify = warp::header("my-app-header");
let body = warp::body::json();
```
Each of these is a single `Filter`. We can combine them together with `and`, and then `map` the combined result to get a really natural feeling handler:
```rust
let route = id
.and(verify)
.and(body)
.map(|id: u64, ver: MyVerification, body: MyAppThingy| {
// ...
});
```
The above `route` is a _new_ `Filter`. It has combined the results of the others, and provided their results naturally to the supplied function for `map`. Additionally, the types are enforced, cause well yea, this is Rust! If you were to change around one of the filters such that it returned a different type, the compiler would let you know that you need to adjust for that change.
This combining of results is **smart**: it is able to automatically toss results that are nothing (well, unit, so `()`), instead of passing worthless unit arguments to your handlers. So if you needed to combine a new `Filter` into this route that **only** checks some request values to determine if the request is valid, and otherwise returns nothing, your handler doesn’t need to change.
Besides dropping units, did you notice how even though multiple results were combined together, the `map` closure received each as individual arguments? This greatly improves development, since that means that `id.and(verify).and(body)` is actually exactly the same as `id.and(verify.and(body))`, but using just tuples would have changed around the signature of the results. The [routing documentation](https://href.li/?https://docs.rs/warp/0.1.*/warp/filters/path/) shows more ways this is useful.
This concept powers everything in warp. Once you know you can match a single path segment via `warp::path("foo")`, then the idea of mounting doesn’t need to be something special. You just have your filter chain for a set of endpoints, and simply “and” it with a new path filter. If your “mount” location needs to also gate on headers, or something else, you can just `and` those `Filter`s as well.
### BUILT-IN FUNCTIONALITY
As awesome as the `Filter` system is, if warp didn’t provide common web server features, it’d still be annoying to work with. Thus, warp provides a bunch of built-in `Filter`s, allowing you compose the functionality you need to descibe each route or resource or sub-whatever.
- Path routing and parameter extraction
- Header requirements and extraction
- Query string deserialization
- JSON and Form bodies
- Static Files and Directories
- Websockets
- Access logging
- And others, and more being added.
The [docs](https://href.li/?https://docs.rs/warp/0.1.*/warp/filters/) explains how to use each, and the [examples](https://href.li/?https://github.com/seanmonstar/warp/blob/master/examples) go more in-depth on how to combine them to make actual web servers.
## Notes
### [[CSRF]] / [[CORS]]
https://docs.rs/warp/0.3.1/warp/filters/cors/fn.cors.html
```rust
use warp::Filter;
let cors = warp::cors()
.allow_origin("https://hyper.rs")
.allow_methods(vec!["GET", "POST", "DELETE"]);
let route = warp::any()
.map(warp::reply)
.with(cors);
```
### Dependency injection
https://stackoverflow.com/questions/60554872/dependency-injection-in-rust-warp
In `endpoints.rs`:
```rust
pub fn with_lnd(
lnd: LndClient,
) -> impl Filter<Extract = (LndClient,), Error = Infallible> + Clone {
warp::any().map(move || lnd.clone())
}
pub fn add_invoice(
lnd: &LndClient,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone
{
warp::post()
.and(warp::path("invoice"))
.and(warp::body::json())
.and(with_lnd(lnd.clone()))
.and_then(add_invoice_handler)
}
async fn add_invoice_handler(
invoice_request: AddInvoiceRequest,
lnd: LndClient,
) -> Result<impl warp::Reply, warp::Rejection> {
// ...
// Do stuff with lnd
let add_invoice = lnd.add_invoice(memo, value, lnd::DEFAULT_EXPIRY).await;
// ... Return Result
match add_invoice {
Ok(add_invoice) => {
info!("Add invoice response: {:?}", add_invoice);
Ok(warp::reply::json(&add_invoice))
}
Err(ref e) => {
warn!("Failed to add invoice: {:?}", add_invoice);
let hotspot_error = HotspotError {
message: String::from(
"Could not add invoice for {:?} satoshis",
),
error: e.to_string(),
};
Err(warp::reject::custom(hotspot_error))
}
}
}
```
- This implementation requires two clones but I couldn't find any way to work around it without introducing a bunch of ugly lifetimes stuff
Usage in `main.rs` looks only adds a pass-by-reference:
```rust
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
...
let add_invoice = endpoints::add_invoice(&lnd);
let routes = warp::any()
.and(homepage.or(hello).or(add).or(mul).or(auth).or(add_invoice))
.with(log_all_requests);
// Serve
info!("Serving daemon");
warp::serve(routes).run(([0, 0, 0, 0], 2080)).await;
}
```