Coder Social home page Coder Social logo

obsidian's Introduction

Obsidian Logo

Obsidian

Obsidian is an ergonomic Rust async http framework for reliable and efficient web.

Obsidian serve

Get Started

[dependencies]
# add these 2 dependencies in Cargo.toml file
obsidian = "0.2.2"
tokio = "0.2.21"

Hello World

use obsidian::{context::Context, App};

#[tokio::main]
async fn main() {
    let mut app: App = App::new();

    app.get("/", |ctx: Context| async { ctx.build("Hello World").ok() });

    app.listen(3000).await;
}

Hello World (with handler function)

use obsidian::{context::Context, App, ContextResult};

async fn hello_world(ctx: Context) -> ContextResult {
    ctx.build("Hello World").ok()
}


#[tokio::main]
async fn main() {
    let mut app: App = App::new();

    app.get("/", hello_world);

    app.listen(3000).await;
}

JSON Response

use obsidian::{context::Context, App, ContextResult};
use serde::*;

async fn get_user(ctx: Context) -> ContextResult {
    #[derive(Serialize, Deserialize)]
    struct User {
        name: String,
    };

    let user = User {
        name: String::from("Obsidian"),
    };
    ctx.build_json(user).ok()
}

#[tokio::main]
async fn main() {
    let mut app: App = App::new();

    app.get("/user", get_user);

    app.listen(3000).await;
}

Example Files

Example are located in example/main.rs.

Run Example

cargo run --example example

Current State

NOT READY FOR PRODUCTION YET!

obsidian's People

Contributors

jk-gan avatar plwai avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

obsidian's Issues

[WIP] Define the generated project structure

Since we want to experiment on generating a project template in the coming version, so we have to define the generated project structure.

My suggestion is to separate web interface and application logic:

Application logic

  • repositories

Web interface

  • routes
  • views

*Issue Work in Progress

Optimize Request Serializer

Problem

The current serializer for context.param, context.form, context.json and context.query_params performance is bad.

Possible root cause

There are too many clone and loops in the context.parse_queries function. Optimize the algorithm there should solve this problem.

Routing path conflict

Reproduce step

Currently, the path example below are not allowed in the framework. Wrong panic will be thrown.

  1. param/:id
  2. params/:id

Enhance params parsing

Enable Option as the type.

Proposal

Params

app.get("/paramtest/:id", |ctx: Context| async move {
    let param_test: Option<i32> = ctx.param("id")?;

    Ok(response::json(param_test, StatusCode::OK))
});

If :id cannot be parsed, it will be None.

Forms/Query

app.post("/formtest", |mut ctx: Context| async move{
    let param_test: Option<i32>= ctx.form().await?;

    Ok(response::json(param_test, StatusCode::OK))
});

If item cannot be found, it will be None. Apply to fields in struct as well.

Can't use the header value from context in Response due to owndership

I want to use the header value from request and return in Response:

#[derive(Serialize)]
struct IndexResponse {
  message: String,
}

async fn index(ctx: Context) -> ContextResult {
  let hello = ctx.headers()
                .get("hello")
                .and_then(|v| v.to_str().ok())
                .unwrap_or_else(|| "world");

  ctx.build_json(IndexResponse { message: hello.to_owned() }).ok()
}

and I get the error:

❯ cargo run
   Compiling messages-obsidian v0.1.0 (/Users/jkgan/Developer/Rust/messages-obsidian)
error[E0505]: cannot move out of `ctx` because it is borrowed
  --> src/lib.rs:30:3
   |
25 |   let hello = ctx.headers()
   |               --- borrow of `ctx` occurs here
...
30 |   ctx.build_json(IndexResponse { message: hello.to_owned() }).ok()
   |   ^^^ move out of `ctx` occurs here       ----- borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0505`.
error: could not compile `messages-obsidian`.

To learn more, run the command again with --verbose.

Proposal for better error handling

1. Early return Error

Question: How do we know what response content-type to be returned by the Handler if user using ? to early return Error?

Currently, the Error will always return String which might not be what users expecting if they are developing json API.

Note: for Obsidian 0.2.x, the user only can handle this by manually write Error response instead of early return using ?.

From

pub async fn create_user(mut ctx: Context) -> ContextResult {
    #[derive(Serialize, Deserialize)]
    struct User {
        name: String,
        age: i8,
    }

    let user: User = match ctx.json().await {
        Ok(body) => body,
        Err(error) => {
            return ctx
                .build_json(error)
                .with_status(StatusCode::INTERNAL_SERVER_ERROR)
                .ok()
        }
    };

    // create user logic here

    ctx.build_json(user)
        .with_status(StatusCode::CREATED)
        .ok()
}

To

pub async fn create_user(mut ctx: Context) -> ContextResult {
    #[derive(Serialize, Deserialize)]
    struct User {
        name: String,
        age: i8,
    }

    let user: User = ctx.json().await?; // this will return a response with json content-type if Error

    // create user logic here

    ctx.build_json(user)
        .with_status(StatusCode::CREATED)
        .ok()
}

Expected response

// Http Status Code: 400
{"error":"'name' is required"}

The suggestion section is a work-in-progress

Suggestion

1. Trait

To allow the user to control error handling globally, we can provide an ErrorResponse trait.

pub trait ErrorResponse {
    fn respond_to(&self) -> Response;
}

impl ErrorResponse for SomeError {
    fn respond_to(&self) -> Response {
        // generate Response here
    }
}

2. Middleware

We can have a EarlyReturnErrorMiddleware to handle it:

#[async_trait]
impl Middleware for EarlyReturnErrorMiddleware {
    async fn handle<'a>(
        &'a self,
        context: Context,
        ep_executor: EndpointExecutor<'a>,
    ) -> ContextResult {
        if matches!(ep_executor.next(context).await, Err(error)) {
            // handle error   
        }
    }
}

3. Annotation

#[response(type="json")]
async fn get_user(ctx: Context) -> ContextResult {}

Obsidian v0.3 Discussion

Version 0.3 is about Evolution, we hope to improve DX and productivity by converting it from a micro-framework into rails/phoenix like framework. And this is the first step:

  • A cargo tool to generate project
  • Define the generated project structure #59
  • Improve router syntax
  • Websocket
  • Better error handling #61
  • Better console output
  • Prelude
  • Refine README #40
  • Documentation #35
  • Session cookies support #42
  • Handler mocking

Set Application State which is shared globally with all routes

Currently, there is no simple way to share app states in Obsidian. App state can be a database client, connection pool and etc.

Simple suggestion(not final!):

#[tokio::main]
async fn obsidian_run(db: Database) {
    let mut app = App::new();

    // here to inject App State
    app.inject("db", db);

    let addr = ([127, 0, 0, 1], 3000).into();

    app.get("/", |_ctx| async { "Hello World" });
    app.get("/user", get_user);

    app.listen(&addr, || {
        println!("server is listening to {}", &addr);
    })
    .await;
}

// Handler
async fn get_user(ctx: Context) -> impl Responder {
    let db = ctx.get_state("db");
    // database query here
    Response::ok().json(user)
}

Documentation

As we want to improve developer experience, we need to have a good documentation. Rust has one of the best official documentation as well.

Can refer to this thread

Refine README

  • Design a logo
  • Add more explanations
  • Add Contribution instructions

Obsidian v0.2 Discussion

With the vision to make Rust web development fun and actually works, there are few parts we need to look into before v0.2:

  • Async/Await #31
  • Custom header support for Responder #30
  • Improve response syntax #43
  • Improve the middleware syntax for sub router and particular path
  • Dynamic data in Context #38
  • Enhance params parsing #36
  • Refine README #40
  • Documentation #35
  • Prelude
  • Set Application State which is shared globally with all routes

Moved to next release

  • Session cookies support #42

Dynamic data in context

The ability to store dynamic data in context.

Problem

Middleware developers might want to provide some data to the handler. Currently, there is not way to pass the data.

Async/Await Migration

Implement Async/Await to bring better developer experience.

Currently, Async/Await drastically lower down the performance within hyper. In Obsidian there is one part consumes a lot of resource too.

let service = make_service_fn(|_| {
    let server_clone = app_server.clone();
    async {
        Ok::<_, hyper::Error>(service_fn(move |req| {
            // This clone takes time to complete as it will be call every endpoint request invoked
            let server_clone = server_clone.clone(); 
            async move { Ok::<_, hyper::Error>(server_clone.resolve_endpoint(req).await) }
        }))
    }
});

Unable to use context in response flow

In current App flow, context lifetime ends at the user endpoint handler which causing the flow of send dynamic data to response flow becomes difficult. In order to extend context lifetime, context should be return as the response.

Routing Function

Routing container for mapping routing path and the event handler.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.