Skip to content

Commit

Permalink
feat: add api axum based handlers (ongoing)
Browse files Browse the repository at this point in the history
  • Loading branch information
subotic committed Sep 25, 2023
1 parent cb8fee3 commit a99cc26
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 69 deletions.
34 changes: 33 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ serde_json = "1" # JSON serialization
tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] }
tokio-test = "0.4.2"
tower = "0.4.13"
tower-http = { version = "0.4.0", features = ["trace"] }
hyper = "0.14.27"
tracing = "0.1"
tracing-subscriber = "0.3"
Expand Down Expand Up @@ -51,6 +52,7 @@ serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tower.workspace = true
tower-http.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tracing-test.workspace = true
Expand Down
58 changes: 2 additions & 56 deletions src/bin/dsp_meta_server/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use std::sync::Arc;

use axum::routing::{get, post};
use axum::Router;
use dsp_meta::api::project_metadata_handler;
use dsp_meta::app_state::app_state::AppState;
use dsp_meta::app_state::AppState;
use dsp_meta::repo::project_metadata_repository::ProjectMetadataRepository;
use dsp_meta::service::project_metadata_service::ProjectMetadataService;
use tracing::{trace, Level};
Expand All @@ -28,58 +25,7 @@ async fn main() {

// run it with hyper on localhost:3000
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app(shared_state).into_make_service())
.serve(dsp_meta::api::app::app(shared_state).into_make_service())
.await
.unwrap();
}

/// Having a function that produces our app makes it easy to call it from tests
/// without having to create an HTTP server.
fn app(shared_state: Arc<AppState>) -> Router {
Router::new()
.route(
"/",
get(project_metadata_handler::get_root).post(project_metadata_handler::post_root),
)
.route("/hello_world", get(project_metadata_handler::hello_world))
.route("/foo/bar", get(project_metadata_handler::foo_bar))
// `POST /users` goes to `create_user`
.route("/users", post(project_metadata_handler::create_user))
.with_state(shared_state)
}

#[cfg(test)]
mod tests {
use axum::body::Body;
use axum::http::{Request, StatusCode};
// use tower::Service; // for `call`
use tower::ServiceExt; // for `oneshot` and `ready`

use super::*;

#[tokio::test]
async fn hello_world() {
let shared_state = Arc::new(AppState {
project_metadata_service: ProjectMetadataService::new(ProjectMetadataRepository::new()),
});

let app = app(shared_state);

// `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server.
let response = app
.oneshot(
Request::builder()
.uri("/hello_world")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"Hello, World!");
}
}
113 changes: 113 additions & 0 deletions src/dsp_meta/api/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::sync::Arc;
use std::time::Duration;

use axum::body::Bytes;
use axum::extract::MatchedPath;
use axum::http::{HeaderMap, Request};
use axum::response::Response;
use axum::routing::{get, post};
use axum::Router;
use tower_http::classify::ServerErrorsFailureClass;
use tower_http::trace::TraceLayer;
use tracing::{info_span, Span};

use crate::api::project_metadata_handler;
use crate::app_state::AppState;

/// Having a function that produces our app makes it easy to call it from tests
/// without having to create an HTTP server.
pub fn app(shared_state: Arc<AppState>) -> Router {
Router::new()
.route(
"/",
get(project_metadata_handler::get_root).post(project_metadata_handler::post_root),
)
.route("/hello_world", get(project_metadata_handler::hello_world))
.route("/foo/bar", get(project_metadata_handler::foo_bar))
// `POST /users` goes to `create_user`
.route("/users", post(project_metadata_handler::create_user))
.with_state(shared_state)
// `TraceLayer` is provided by tower-http so you have to add that as a dependency.
// It provides good defaults but is also very customizable.
//
// See https://docs.rs/tower-http/0.1.1/tower_http/trace/index.html for more details.
//
// If you want to customize the behavior using closures here is how.
.layer(
TraceLayer::new_for_http()
.make_span_with(|request: &Request<_>| {
// Log the matched route's path (with placeholders not filled in).
// Use request.uri() or OriginalUri if you want the real path.
let matched_path = request
.extensions()
.get::<MatchedPath>()
.map(MatchedPath::as_str);

info_span!(
"http_request",
method = ?request.method(),
matched_path,
some_other_field = tracing::field::Empty,
)
})
.on_request(|_request: &Request<_>, _span: &Span| {
// You can use `_span.record("some_other_field", value)` in one of these
// closures to attach a value to the initially empty field in the info_span
// created above.
})
.on_response(|_response: &Response, _latency: Duration, _span: &Span| {
// ...
})
.on_body_chunk(|_chunk: &Bytes, _latency: Duration, _span: &Span| {
// ...
})
.on_eos(
|_trailers: Option<&HeaderMap>, _stream_duration: Duration, _span: &Span| {
// ...
},
)
.on_failure(
|_error: ServerErrorsFailureClass, _latency: Duration, _span: &Span| {
// ...
},
),
)
}

#[cfg(test)]
mod tests {
use axum::body::Body;
use axum::http::{Request, StatusCode};
// use tower::Service; // for `call`
use tower::ServiceExt; // for `oneshot` and `ready`

use super::*;
use crate::repo::project_metadata_repository::ProjectMetadataRepository;
use crate::service::project_metadata_service::ProjectMetadataService;

#[tokio::test]
async fn hello_world() {
let shared_state = Arc::new(AppState {
project_metadata_service: ProjectMetadataService::new(ProjectMetadataRepository::new()),
});

let app = app(shared_state);

// `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server.
let response = app
.oneshot(
Request::builder()
.uri("/hello_world")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
assert_eq!(&body[..], b"Hello, World!");
}
}
1 change: 1 addition & 0 deletions src/dsp_meta/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod app;
pub mod project_metadata_handler;
10 changes: 5 additions & 5 deletions src/dsp_meta/api/project_metadata_handler.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::sync::Arc;

use axum::extract::State;
use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::Json;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::trace;

use crate::app_state::app_state::AppState;
use crate::app_state::AppState;
use crate::domain::value::Shortcode;
use crate::service::project_metadata_api_contract::ProjectMetadataApiContract;

Expand All @@ -17,7 +17,7 @@ use crate::service::project_metadata_api_contract::ProjectMetadataApiContract;
/// TODO: Add error handling with correct status codes
/// TODO: Add parameter extraction
pub async fn get_project_metadata_by_shortcode(
shortcode: String,
Path(shortcode): Path<String>, // TODO: Change to Shortcode
State(state): State<Arc<AppState>>,
) -> Json<Value> {
trace!("entered dsp_meta::api::get_project_metadata_by_shortcode()");
Expand Down Expand Up @@ -57,13 +57,13 @@ pub async fn create_user(
}

// the input to our `create_user` handler
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
pub struct CreateUser {
username: String,
}

// the output to our `create_user` handler
#[derive(Serialize)]
#[derive(Debug, Serialize)]
pub struct User {
id: u64,
username: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::repo::project_metadata_repository::ProjectMetadataRepository;
use crate::service::project_metadata_service::ProjectMetadataService;

#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct AppState {
pub project_metadata_service: ProjectMetadataService<ProjectMetadataRepository>,
}
1 change: 0 additions & 1 deletion src/dsp_meta/app_state/mod.rs

This file was deleted.

4 changes: 3 additions & 1 deletion src/dsp_meta/domain/entity/dataset.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use serde::Serialize;

use crate::domain::value::Title;
use crate::errors::DspMetaError;

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Dataset {
pub title: Title,
}
Expand Down
4 changes: 3 additions & 1 deletion src/dsp_meta/domain/entity/person.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#[derive(Debug, Clone, Default, PartialEq)]
use serde::Serialize;

#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct Person {
id: String,
}
2 changes: 1 addition & 1 deletion src/dsp_meta/domain/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ pub struct EndDate(pub String);
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
pub struct ContactPoint(pub String);

#[derive(Debug, Default, Clone, PartialEq)]
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
pub struct Title(pub String);
2 changes: 1 addition & 1 deletion src/dsp_meta/repo/project_metadata_repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::domain::value::Shortcode;
use crate::errors::DspMetaError;
use crate::service::project_metadata_repository_contract::ProjectMetadataRepositoryContract;

#[derive(Default, Clone)]
#[derive(Debug, Default, Clone)]
pub struct ProjectMetadataRepository {
db: Arc<RwLock<HashMap<String, ProjectMetadata>>>,
}
Expand Down
2 changes: 1 addition & 1 deletion src/dsp_meta/service/project_metadata_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::errors::DspMetaError;
use crate::service::project_metadata_api_contract::ProjectMetadataApiContract;
use crate::service::project_metadata_repository_contract::ProjectMetadataRepositoryContract;

#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct ProjectMetadataService<R> {
repo: R,
}
Expand Down

0 comments on commit a99cc26

Please sign in to comment.