diff --git a/Cargo.toml b/Cargo.toml index 4d84773..bfe3eb9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ authors = ["Sam Rijs "] futures = "0.1.18" hyper = "0.11.15" hyper-tls = "0.1.2" +route-recognizer = "0.1.12" semver = { version = "0.9.0", features = ["serde"] } serde = "1.0.27" serde_derive = "1.0.27" diff --git a/src/api.rs b/src/api.rs index 0bfdfd8..f41fbc1 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,8 +1,10 @@ use std::collections::BTreeMap; +use std::sync::Arc; -use futures::{Future, future}; -use hyper::{Error as HyperError, Request, Response, StatusCode}; +use futures::{Future, IntoFuture, future}; +use hyper::{Error as HyperError, Method, Request, Response, StatusCode}; use hyper::header::ContentType; +use route_recognizer::{Params, Router}; use semver::{Version, VersionReq}; use serde_json; use slog::Logger; @@ -11,8 +13,23 @@ use tokio_service::Service; use ::models::repo::RepoPath; use ::engine::Engine; +enum Route { + AnalyzeDependencies +} + +#[derive(Clone)] pub struct Api { - pub engine: Engine + engine: Engine, + router: Arc> +} + +impl Api { + pub fn new(engine: Engine) -> Api { + let mut router = Router::new(); + router.add("/api/v1/analyze/:site/:qual/:name", Route::AnalyzeDependencies); + + Api { engine, router: Arc::new(router) } + } } #[derive(Debug, Serialize)] @@ -38,45 +55,77 @@ impl Service for Api { type Future = Box>; fn call(&self, req: Request) -> Self::Future { - let repo_path = RepoPath::from_parts("github.com", "hyperium", "hyper").unwrap(); + if let Ok(route_match) = self.router.recognize(req.uri().path()) { + match route_match.handler { + &Route::AnalyzeDependencies => { + if *req.method() == Method::Get { + return Box::new(self.analyze_dependencies(req, route_match.params)); + } + } + } + } - let future = self.engine.analyze_dependencies(repo_path).then(|result| { - match result { + let mut response = Response::new(); + response.set_status(StatusCode::NotFound); + Box::new(future::ok(response)) + } +} + +impl Api { + fn analyze_dependencies<'r>(&self, _req: Request, params: Params) -> impl Future { + let engine = self.engine.clone(); + + let site = params.find("site").expect("route param 'site' not found"); + let qual = params.find("qual").expect("route param 'qual' not found"); + let name = params.find("name").expect("route param 'name' not found"); + + RepoPath::from_parts(site, qual, name).into_future().then(move |repo_path_result| { + match repo_path_result { Err(err) => { let mut response = Response::new(); - response.set_status(StatusCode::InternalServerError); + response.set_status(StatusCode::BadRequest); response.set_body(format!("{:?}", err)); future::Either::A(future::ok(response)) }, - Ok(dependencies) => { - let response_struct = AnalyzeDependenciesResponse { - dependencies: dependencies.main.into_iter() - .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { - outdated: analyzed.is_outdated(), - required: analyzed.required, - latest: analyzed.latest - })).collect(), - dev_dependencies: dependencies.dev.into_iter() - .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { - outdated: analyzed.is_outdated(), - required: analyzed.required, - latest: analyzed.latest - })).collect(), - build_dependencies: dependencies.build.into_iter() - .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { - outdated: analyzed.is_outdated(), - required: analyzed.required, - latest: analyzed.latest - })).collect() - }; - let mut response = Response::new() - .with_header(ContentType::json()) - .with_body(serde_json::to_string(&response_struct).unwrap()); - future::Either::B(future::ok(response)) + Ok(repo_path) => { + future::Either::B(engine.analyze_dependencies(repo_path).then(|analyze_result| { + match analyze_result { + Err(err) => { + let mut response = Response::new(); + response.set_status(StatusCode::InternalServerError); + response.set_body(format!("{:?}", err)); + future::Either::A(future::ok(response)) + }, + Ok(dependencies) => { + let response_struct = AnalyzeDependenciesResponse { + dependencies: dependencies.main.into_iter() + .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { + outdated: analyzed.is_outdated(), + required: analyzed.required, + latest: analyzed.latest + })).collect(), + dev_dependencies: dependencies.dev.into_iter() + .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { + outdated: analyzed.is_outdated(), + required: analyzed.required, + latest: analyzed.latest + })).collect(), + build_dependencies: dependencies.build.into_iter() + .map(|(name, analyzed)| (name.into(), AnalyzeDependenciesResponseDetail { + outdated: analyzed.is_outdated(), + required: analyzed.required, + latest: analyzed.latest + })).collect() + }; + let mut response = Response::new() + .with_header(ContentType::json()) + .with_body(serde_json::to_string(&response_struct).unwrap()); + future::Either::B(future::ok(response)) + } + } + })) } } - }); - - Box::new(future) + }) } -} +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 41fd86b..91a473d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ extern crate futures; extern crate hyper; extern crate hyper_tls; +extern crate route_recognizer; extern crate semver; #[macro_use] extern crate serde_derive; extern crate serde; @@ -61,9 +62,10 @@ fn main() { logger: logger.clone() }; - let serve = http.serve_addr_handle(&addr, &handle, move || { - Ok(Api { engine: engine.clone() }) - }).expect("failed to bind server"); + let api = Api::new(engine); + + let serve = http.serve_addr_handle(&addr, &handle, move || Ok(api.clone())) + .expect("failed to bind server"); let serving = serve.for_each(move |conn| { let conn_logger = logger.clone();