Skip to content

Commit

Permalink
WIP: initial API
Browse files Browse the repository at this point in the history
  • Loading branch information
m4tx committed Jul 23, 2024
1 parent 15694a1 commit e1ddb69
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 10 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ async-trait = "0.1.80"
axum = "0.7.5"
chrono = { version = "0.4.38", features = ["serde"] }
clap = { version = "4.5.8", features = ["derive", "env"] }
derive_builder = "0.20.0"
env_logger = "0.11.3"
indexmap = "2.2.6"
itertools = "0.13.0"
log = "0.4.22"
regex = "1.10.5"
serde = "1.0.203"
slug = "0.1.5"
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
tower = "0.4.13"
thiserror = "1.0.61"
3 changes: 3 additions & 0 deletions examples/hello-world/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ name = "example-hello-world"
version = "0.1.0"
publish = false
description = "Hello World - Flareon example."
edition = "2021"

[dependencies]
flareon = { path = "../../flareon" }
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
31 changes: 30 additions & 1 deletion examples/hello-world/src/main.rs
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
fn main() {}
use std::sync::Arc;

use flareon::prelude::{
Body, Error, FlareonApp, FlareonProject, Request, RequestHandler, Response, Route, StatusCode,
};

fn return_hello(_request: Request) -> Result<Response, Error> {
Ok(Response::new_html(
StatusCode::OK,
Body::fixed("<h1>Hello Flareon!</h1>".as_bytes().to_vec()),
))
}

#[tokio::main]
async fn main() {
let app = FlareonApp::builder()
.urls([Route::new("/", Arc::new(Box::new(return_hello)))])
.build()
.unwrap();

let flareon_project = FlareonProject::builder()
.apps([app])
.urls([Route::new("/", Arc::new(Box::new(return_hello)))])
.build()
.unwrap();

flareon::run(flareon_project, "127.0.0.1:8000")
.await
.unwrap();
}
8 changes: 8 additions & 0 deletions flareon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ license.workspace = true
description = "Modern web framework focused on speed and ease of use."

[dependencies]
async-trait.workspace = true
axum.workspace = true
derive_builder.workspace = true
indexmap.workspace = true
regex.workspace = true
thiserror.workspace = true
tokio.workspace = true
tower.workspace = true
203 changes: 194 additions & 9 deletions flareon/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,199 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
pub mod prelude;

use std::fmt::{Debug, Formatter};
use std::io::Read;
use std::rc::Rc;
use std::sync::Arc;

use async_trait::async_trait;
use axum::handler::HandlerWithoutStateExt;
use derive_builder::Builder;
use indexmap::IndexMap;
use thiserror::Error;

pub type StatusCode = axum::http::StatusCode;

#[async_trait]
pub trait RequestHandler {
async fn handle(&self, request: Request) -> Result<Response, Error>;
}

#[derive(Clone, Debug)]
pub struct Router {
urls: Vec<Route>,
}

impl Router {
#[must_use]
pub fn with_urls<T: Into<Vec<Route>>>(urls: T) -> Self {
Self { urls: urls.into() }
}
}

#[async_trait]
impl RequestHandler for Router {
async fn handle(&self, request: Request) -> Result<Response, Error> {
let path = request.uri().path();

for route in &self.urls {
if path.starts_with(&route.url) {
return route.view.handle(request).await;
}
}

unimplemented!("404 handler is not implemented yet")
}
}

#[async_trait]
impl<T> RequestHandler for T
where
T: Fn(Request) -> Result<Response, Error> + Send + Sync,
{
async fn handle(&self, request: Request) -> Result<Response, Error> {
self(request)
}
}

#[derive(Clone, Debug, Builder)]
#[builder(setter(into))]
pub struct FlareonApp {
router: Router,
}

impl FlareonApp {
#[must_use]
pub fn builder() -> FlareonAppBuilder {
FlareonAppBuilder::default()
}
}

impl FlareonAppBuilder {
#[allow(unused_mut)]
pub fn urls<T: Into<Vec<Route>>>(&mut self, urls: T) -> &mut Self {
let mut new = self;
new.router = Some(Router::with_urls(urls.into()));
new
}
}

#[derive(Clone)]
pub struct Route {
url: String,
view: Arc<Box<dyn RequestHandler + Send + Sync>>,
}

impl Debug for Route {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

impl Route {
#[must_use]
pub fn new<T: Into<String>>(url: T, view: Arc<Box<dyn RequestHandler + Send + Sync>>) -> Self {
Self {
url: url.into(),
view,
}
}
}

pub type Request = axum::extract::Request;

type HeadersMap = IndexMap<String, String>;

#[derive(Debug)]
pub struct Response {
status: StatusCode,
headers: HeadersMap,
body: Body,
}

const CONTENT_TYPE_HEADER: &str = "Content-Type";
const HTML_CONTENT_TYPE: &str = "text/html";

impl Response {
#[must_use]
pub fn new_html(status: StatusCode, body: Body) -> Self {
Self {
status,
headers: Self::html_headers(),
body,
}
}

#[must_use]
fn html_headers() -> HeadersMap {
let mut headers = HeadersMap::new();
headers.insert(CONTENT_TYPE_HEADER.to_owned(), HTML_CONTENT_TYPE.to_owned());
headers
}
}

pub enum Body {
NoContent,
Fixed(Vec<u8>),
Streaming(Box<dyn Read>),
}

impl Debug for Body {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

#[cfg(test)]
mod tests {
use super::*;
impl Body {
#[must_use]
pub fn empty() -> Self {
Self::NoContent
}

#[must_use]
pub fn fixed(data: Vec<u8>) -> Self {
Self::Fixed(data)
}

// TODO streaming
}

#[derive(Debug, thiserror::Error)]
pub enum Error {}

#[derive(Clone, Debug, Builder)]
#[builder(setter(into))]
pub struct FlareonProject {
apps: Vec<FlareonApp>,
router: Router,
}

#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
impl FlareonProjectBuilder {
#[allow(unused_mut)]
pub fn urls<T: Into<Vec<Route>>>(&mut self, urls: T) -> &mut Self {
let mut new = self;
new.router = Some(Router::with_urls(urls.into()));
new
}
}

impl FlareonProject {
#[must_use]
pub fn builder() -> FlareonProjectBuilder {
FlareonProjectBuilder::default()
}
}

pub async fn run(project: FlareonProject, address_str: &str) -> Result<(), Error> {
let listener = tokio::net::TcpListener::bind(address_str).await.unwrap();

let handler = |request: axum::extract::Request| async move {
let _response = project.router.handle(request).await;

"123"
};
axum::serve(listener, handler.into_make_service())
.await
.unwrap();

todo!()
}
3 changes: 3 additions & 0 deletions flareon/src/prelude.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub use crate::{
Body, Error, FlareonApp, FlareonProject, Request, RequestHandler, Response, Route, StatusCode,
};

0 comments on commit e1ddb69

Please sign in to comment.