refactor: move routes to lib, add tests

This commit is contained in:
Joonas Koivunen 2020-03-16 14:49:48 +02:00 committed by Joonas Koivunen
parent 8b1abb3333
commit d13fe11dee
2 changed files with 126 additions and 70 deletions

View File

@ -2,8 +2,6 @@ use std::num::NonZeroU16;
use std::path::PathBuf;
use structopt::StructOpt;
use warp::query;
use ipfs::{Ipfs, IpfsOptions, IpfsTypes, UninitializedIpfs};
use rust_ipfs_http::{config, v0};
@ -177,80 +175,15 @@ fn main() {
fn serve<Types: IpfsTypes>(
ipfs: &Ipfs<Types>,
) -> (std::net::SocketAddr, impl std::future::Future<Output = ()>) {
use tokio::stream::StreamExt;
use warp::Filter;
let (shutdown_tx, mut shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
// Set up routes
// /api
let api = warp::path("api");
// /api/v0
let v0 = api.and(warp::path("v0"));
// /api/v0/shutdown
let shutdown = warp::post()
.and(warp::path!("shutdown"))
.and(warp::any().map(move || shutdown_tx.clone()))
.and_then(shutdown);
// the http libraries seem to prefer POST method
let api = shutdown
.or(v0::id::identity(ipfs))
// Placeholder paths
// https://docs.rs/warp/0.2.2/warp/macro.path.html#path-prefixes
.or(warp::path!("add").and_then(not_implemented))
.or(warp::path!("bitswap" / ..).and_then(not_implemented))
.or(warp::path!("block" / ..).and_then(not_implemented))
.or(warp::path!("bootstrap" / ..).and_then(not_implemented))
.or(warp::path!("config" / ..).and_then(not_implemented))
.or(warp::path!("dag" / ..).and_then(not_implemented))
.or(warp::path!("dht" / ..).and_then(not_implemented))
.or(warp::path!("get").and_then(not_implemented))
.or(warp::path!("key" / ..).and_then(not_implemented))
.or(warp::path!("name" / ..).and_then(not_implemented))
.or(warp::path!("object" / ..).and_then(not_implemented))
.or(warp::path!("pin" / ..).and_then(not_implemented))
.or(warp::path!("ping" / ..).and_then(not_implemented))
.or(warp::path!("pubsub" / ..).and_then(not_implemented))
.or(warp::path!("refs" / ..).and_then(not_implemented))
.or(warp::path!("repo" / ..).and_then(not_implemented))
.or(warp::path!("stats" / ..).and_then(not_implemented))
.or(warp::path!("swarm" / "connect")
.and(query::<v0::swarm::ConnectQuery>())
.and_then(v0::swarm::connect))
.or(warp::path!("swarm" / "peers")
.and(query::<v0::swarm::PeersQuery>())
.and_then(v0::swarm::peers))
.or(warp::path!("swarm" / "addrs").and_then(v0::swarm::addrs))
.or(warp::path!("swarm" / "addrs" / "local")
.and(query::<v0::swarm::AddrsLocalQuery>())
.and_then(v0::swarm::addrs_local))
.or(warp::path!("swarm" / "disconnect")
.and(query::<v0::swarm::DisconnectQuery>())
.and_then(v0::swarm::disconnect))
.or(warp::path!("version")
.and(query::<v0::version::Query>())
.and_then(v0::version::version));
let routes = v0.and(api.recover(v0::recover_as_message_response));
let routes = v0::routes(ipfs, shutdown_tx);
let routes = routes.with(warp::log("rust-ipfs-http-v0"));
warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 0), async move {
shutdown_rx.next().await;
})
}
async fn shutdown(
mut tx: tokio::sync::mpsc::Sender<()>,
) -> Result<impl warp::Reply, std::convert::Infallible> {
Ok(match tx.send(()).await {
Ok(_) => warp::http::StatusCode::OK,
Err(_) => warp::http::StatusCode::NOT_IMPLEMENTED,
})
}
async fn not_implemented() -> Result<impl warp::Reply, std::convert::Infallible> {
Ok(warp::http::StatusCode::NOT_IMPLEMENTED)
}

View File

@ -1,7 +1,130 @@
use ipfs::{Ipfs, IpfsTypes};
use std::convert::Infallible;
pub mod id;
pub mod swarm;
pub mod version;
pub mod support;
pub(crate) use support::{with_ipfs, NotImplemented, InvalidPeerId, StringError};
pub use support::recover_as_message_response;
pub(crate) use support::{with_ipfs, InvalidPeerId, NotImplemented, StringError};
pub fn routes<T>(
ipfs: &Ipfs<T>,
shutdown_tx: tokio::sync::mpsc::Sender<()>,
) -> impl warp::Filter<Extract = impl warp::Reply, Error = Infallible> + Clone
where
T: IpfsTypes,
{
use warp::{query, Filter};
// /api/v0/shutdown
let shutdown = warp::post()
.and(warp::path!("shutdown"))
.and(warp::any().map(move || shutdown_tx.clone()))
.and_then(handle_shutdown);
let mount = warp::path("api").and(warp::path("v0"));
let api = mount.and(
shutdown
.or(id::identity(ipfs))
// Placeholder paths
// https://docs.rs/warp/0.2.2/warp/macro.path.html#path-prefixes
.or(warp::path!("add").and_then(not_implemented))
.or(warp::path!("bitswap" / ..).and_then(not_implemented))
.or(warp::path!("block" / ..).and_then(not_implemented))
.or(warp::path!("bootstrap" / ..).and_then(not_implemented))
.or(warp::path!("config" / ..).and_then(not_implemented))
.or(warp::path!("dag" / ..).and_then(not_implemented))
.or(warp::path!("dht" / ..).and_then(not_implemented))
.or(warp::path!("get").and_then(not_implemented))
.or(warp::path!("key" / ..).and_then(not_implemented))
.or(warp::path!("name" / ..).and_then(not_implemented))
.or(warp::path!("object" / ..).and_then(not_implemented))
.or(warp::path!("pin" / ..).and_then(not_implemented))
.or(warp::path!("ping" / ..).and_then(not_implemented))
.or(warp::path!("pubsub" / ..).and_then(not_implemented))
.or(warp::path!("refs" / ..).and_then(not_implemented))
.or(warp::path!("repo" / ..).and_then(not_implemented))
.or(warp::path!("stats" / ..).and_then(not_implemented))
.or(warp::path!("swarm" / "connect")
.and(query::<swarm::ConnectQuery>())
.and_then(swarm::connect))
.or(warp::path!("swarm" / "peers")
.and(query::<swarm::PeersQuery>())
.and_then(swarm::peers))
.or(warp::path!("swarm" / "addrs").and_then(swarm::addrs))
.or(warp::path!("swarm" / "addrs" / "local")
.and(query::<swarm::AddrsLocalQuery>())
.and_then(swarm::addrs_local))
.or(warp::path!("swarm" / "disconnect")
.and(query::<swarm::DisconnectQuery>())
.and_then(swarm::disconnect))
.or(warp::path!("version")
.and(query::<version::Query>())
.and_then(version::version))
);
let routes = api.recover(recover_as_message_response);
routes
}
pub async fn handle_shutdown(
mut tx: tokio::sync::mpsc::Sender<()>,
) -> Result<impl warp::Reply, std::convert::Infallible> {
Ok(match tx.send(()).await {
Ok(_) => warp::http::StatusCode::OK,
Err(_) => warp::http::StatusCode::NOT_IMPLEMENTED,
})
}
async fn not_implemented() -> Result<impl warp::Reply, std::convert::Infallible> {
Ok(warp::http::StatusCode::NOT_IMPLEMENTED)
}
/// Creates routes for tests, the ipfs will not work as no background task is being spawned.
#[cfg(test)]
async fn non_functioning_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = Infallible> + Clone {
use ipfs::{IpfsOptions, TestTypes, UninitializedIpfs};
let options = IpfsOptions::<TestTypes>::default();
let (ipfs, fut) = UninitializedIpfs::new(options).await.start().await.unwrap();
drop(fut);
let (shutdown_tx, shutdown_rx) = tokio::sync::mpsc::channel::<()>(1);
drop(shutdown_rx);
routes(&ipfs, shutdown_tx)
}
#[tokio::test]
async fn not_found() {
let routes = non_functioning_routes().await;
let resp = warp::test::request()
.method("GET")
.path("/api/v0/id_foobar")
.reply(&routes)
.await;
assert_eq!(resp.status(), 404);
// from go-ipfs
assert_eq!(resp.body(), "404 page not found");
println!("{:?}", resp);
}
#[tokio::test]
async fn invalid_peer_id() {
let routes = non_functioning_routes().await;
let resp = warp::test::request()
.method("GET")
.path("/api/v0/id?arg=foobar")
.reply(&routes)
.await;
assert_eq!(resp.status(), 400);
// from go-ipfs
assert_eq!(resp.body(), r#"{"Message":"invalid peer id","Code":0,"Type":"error"}"#);
println!("{:?}", resp);
}