darpi

A web api framework with speed and safety in mind. One of the big goals is to catch all errors at compile time, if possible. The framework uses hyper and ideally, the performance should be as if you were using hyper yourself. The framework also uses shaku for compile time verifiable dependency injection.

The framework is in early development. All feedback is appreciated.

An example of a simple app

Cargo.toml [dependencies] darpi = {git = "https://github.com/petar-dambovaliev/darpi.git", branch = "master"} serde = { version = "1.0", features = ["derive"] } tokio = {version = "0.2.11", features = ["full"]} shaku = {version = "0.5.0", features = ["thread_safe"]} main.rs ```rust use asynctrait::asynctrait; use darpi::{ app, handler, middleware, middleware::Expect, pathtype, querytype, request::ExtractBody, response::ResponderError, Body, Json, Method, Path, Query, RequestParts, }; use derive_more::{Display, From}; use serde::{Deserialize, Serialize}; use shaku::{module, Component, Interface}; use std::sync::Arc; use UserRole::Admin;

///////////// setup dependencies with shaku ///////////

[derive(Component)]

[shaku(interface = UserExtractor)]

struct UserExtractorImpl;

[async_trait]

impl UserExtractor for UserExtractorImpl { async fn extract(&self, p: &RequestParts) -> Result { Ok(UserRole::Admin) } }

[async_trait]

trait UserExtractor: Interface { async fn extract(&self, p: &RequestParts) -> Result; }

module! { Container { components = [UserExtractorImpl, MyLogger], providers = [], } }

fn make_container() -> Container { Container::builder().build() }

////////////////////////

[derive(Debug, Display, From)]

enum Error { #[display(fmt = "no auth header")] NoAuthHeaderError, #[display(fmt = "Access denied")] AccessDenied, }

impl ResponderError for Error {}

[derive(Eq, PartialEq, Ord, PartialOrd)]

enum UserRole { Regular, Admin, }

// there are 2 types of middleware Request and Response // the constant argument that needs to be present is &RequestParts // everything else is up to the user // Arc types are injected from the shaku container // Expect is a special type that is provided by the user when // the middleware is linked to a handler. This allows the expected value // to be different per handler + middleware // middlewares are obgligated to return Result<(), impl ResponderErr> // if a middleware returns an Err(e) all work is aborted and the coresponding // response is sent to the user

[middleware(Request)]

async fn accesscontrol( userroleextractor: Arc, p: &RequestParts, expectedrole: Expect, ) -> Result<(), Error> { let actualrole = userrole_extractor.extract(p).await?;

if expected_role > actual_role {
    return Err(Error::AccessDenied);
}
Ok(())

}

[path_type]

[query_type]

[derive(Deserialize, Serialize, Debug)]

pub struct Name { name: String, }

// Path is extracted from the registered path "/hello_world/{name}" // and it is always mandatory. A request without "{name}" will result // in the request path not matching the handler. It will either match another // handler or result in an 404 // Option> is extracted from the url query "?name=jason" // it is optional, as the type suggests. To make it mandatory, simply // remove the Option type. If there is a Query in the handler and // an incoming request url does not contain the query parameters, it will // result in an error response

[handler]

async fn helloworld(p: Path, q: Option>) -> String { let other = q.mapor("nobody".to_owned(), |n| n.0.name); format!("{} sends hello to {}", p.name, other) }

// the handler macro has 2 optional arguments // the shaku container type and a collection of middlewares // the enum variant Admin is corresponding to the middlewre access_control's Expect // ExtractBody> is extracted from the request body // where T is the format and it has to implement the trait FromRequestBody // Json, Yaml and Xml are supported out of the box // failure to extract will result in an error response

[handler(Container, [access_control(Admin)])]

async fn do_something(p: Path, payload: ExtractBody>) -> String { format!("{} sends hello to {}", p.name, payload.name) }

#[tokio::main] async fn main() -> Result<(), darpi::Error> { // the app macro creates a server and allows the user to call // the method run and await on that future app!({ // if the address is a string literal, it is verified at compile time // if it is a string variable, obviously, it won't address: "127.0.0.1:3000", // via the container we inject our dependencies // in this case, MyLogger type // any handler that has the trait Logger as an argument // will be given MyLogger module: makecontainer => Container, bind: [ { // When a path argument is defined in the route, // the handler is required to have Path as an argument // if not present, it will result in a compilation error route: "/helloworld/{name}", method: Method::GET, // handlers bound with a GET method are not allowed // to request a body(payload) from the request. // Json argument would result in a compilation error handler: helloworld }, { route: "/helloworld/{name}", method: Method::POST, // the POST method allows this handler to have // Json as an argument handler: do_something }, ], }) .run() .await } ```