diff --git a/dwn-server/src/lib.rs b/dwn-server/src/lib.rs index 685eded..259cd90 100644 --- a/dwn-server/src/lib.rs +++ b/dwn-server/src/lib.rs @@ -6,7 +6,10 @@ use axum::{ routing::post, Json, Router, }; -use dwn::data::{Message, RecordIdGenerator}; +use dwn::{ + request::{Message, RecordIdGenerator, RequestBody}, + response::{MessageResult, ResponseBody, Status}, +}; use tracing::{error, info, span, warn}; pub struct StartOptions { @@ -40,22 +43,29 @@ pub async fn start(StartOptions { port }: StartOptions) { } } -// https://identity.foundation/decentralized-web-node/spec/#request-level-status-coding -const DID_NOT_FOUND: (StatusCode, &str) = (StatusCode::NOT_FOUND, "DID not found"); -const SERVER_ERROR: (StatusCode, &str) = ( - StatusCode::INTERNAL_SERVER_ERROR, - "The request could not be processed correctly", -); - -async fn post_handler(body: Json) -> Response { - for message in body.0.messages.iter() { - if let Err(e) = process_message(message) { - warn!("{}", e); - return SERVER_ERROR.into_response(); - } - } - - StatusCode::OK.into_response() +async fn post_handler(body: Json) -> Response { + let replies = body + .0 + .messages + .iter() + .map(|message| match process_message(message) { + Ok(_) => StatusCode::OK, + Err(e) => { + warn!("{}", e); + StatusCode::BAD_REQUEST + } + }) + .map(|code| MessageResult { + status: Status::new(code.as_u16(), None), + entries: None, + }) + .collect::>(); + + Json(ResponseBody { + status: Some(Status::new(StatusCode::OK.as_u16(), None)), + replies: Some(replies), + }) + .into_response() } fn process_message(message: &Message) -> Result<(), Box> { diff --git a/dwn-server/tests/messages.rs b/dwn-server/tests/messages.rs index c6c0908..fb3b0e2 100644 --- a/dwn-server/tests/messages.rs +++ b/dwn-server/tests/messages.rs @@ -1,6 +1,9 @@ -use dwn::data::{DescriptorBuilder, Message, MessageBuilder, RequestBody}; +use dwn::{ + request::{DescriptorBuilder, Message, MessageBuilder, RequestBody}, + response::ResponseBody, +}; use dwn_server::StartOptions; -use reqwest::StatusCode; +use reqwest::{Response, StatusCode}; fn spawn_server() -> u16 { let port = port_check::free_local_port().expect("Failed to find free port"); @@ -15,21 +18,16 @@ fn spawn_server() -> u16 { port } -async fn send_post(data: dwn::data::RequestBody, port: u16) -> StatusCode { +async fn send_post(data: RequestBody, port: u16) -> Response { let client = reqwest::Client::new(); - let res = match client + client .post(format!("http://localhost:{}", port)) .header("Content-Type", "application/json") .body(serde_json::to_string(&data).expect("Failed to serialize data")) .send() .await - { - Ok(res) => res, - Err(e) => panic!("{}", e), - }; - - res.status() + .expect("Failed to send request") } fn empty_message() -> Message { @@ -53,24 +51,47 @@ async fn recieve_post() { messages: vec![empty_message()], }; - assert_eq!(send_post(body, port).await, StatusCode::OK); + let res = send_post(body, port).await; + + assert_eq!(res.status(), StatusCode::OK); +} + +async fn expect_status(body: RequestBody, port: u16, status: StatusCode) { + let res = send_post(body, port) + .await + .json::() + .await + .expect("Failed to parse response body"); + + for reply in res.replies.unwrap().iter() { + assert_eq!(reply.status.code, status); + } } #[tokio::test] async fn requires_valid_record_id() { let port = spawn_server(); - let mut msg = empty_message(); - msg.record_id = "invalid record id".to_string(); + // Valid record ID + { + let body = RequestBody { + messages: vec![empty_message()], + }; - let body = RequestBody { - messages: vec![msg], - }; + expect_status(body, port, StatusCode::OK).await; + } + + // Invalid record ID + { + let mut msg = empty_message(); + msg.record_id = "invalid record id".to_string(); - assert_eq!( - send_post(body, port).await, - StatusCode::INTERNAL_SERVER_ERROR - ); + let body = RequestBody { + messages: vec![msg], + }; + + expect_status(body, port, StatusCode::BAD_REQUEST).await; + } } #[tokio::test] @@ -82,30 +103,18 @@ async fn requires_data_descriptors() { msg.descriptor.data_cid = Some("test data cid".to_string()); msg.descriptor.data_format = Some("test data format".to_string()); + let mut without_cid = msg.clone(); + without_cid.descriptor.data_cid = None; + + let mut without_format = msg.clone(); + without_format.descriptor.data_format = None; + + let mut without_both = msg.clone(); + without_both.descriptor.data_cid = None; + let body = RequestBody { - messages: vec![msg], + messages: vec![without_cid, without_format, without_both], }; - let mut without_cid = body.clone(); - without_cid.messages[0].descriptor.data_cid = None; - - let mut without_format = body.clone(); - without_format.messages[0].descriptor.data_format = None; - - let mut without_both = body.clone(); - without_both.messages[0].descriptor.data_cid = None; - without_both.messages[0].descriptor.data_format = None; - - assert_eq!( - send_post(without_cid, port).await, - StatusCode::INTERNAL_SERVER_ERROR - ); - assert_eq!( - send_post(without_format, port).await, - StatusCode::INTERNAL_SERVER_ERROR - ); - assert_eq!( - send_post(without_both, port).await, - StatusCode::INTERNAL_SERVER_ERROR - ); + expect_status(body, port, StatusCode::BAD_REQUEST).await; } diff --git a/dwn/src/lib.rs b/dwn/src/lib.rs index 5b85385..7e051c1 100644 --- a/dwn/src/lib.rs +++ b/dwn/src/lib.rs @@ -1,2 +1,3 @@ -pub mod data; -pub mod util; +pub mod request; +pub mod response; +mod util; diff --git a/dwn/src/data.rs b/dwn/src/request.rs similarity index 100% rename from dwn/src/data.rs rename to dwn/src/request.rs diff --git a/dwn/src/response.rs b/dwn/src/response.rs new file mode 100644 index 0000000..c26ffd6 --- /dev/null +++ b/dwn/src/response.rs @@ -0,0 +1,32 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ResponseBody { + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub replies: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Status { + pub code: u16, + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, +} + +impl Status { + pub fn new(code: u16, detail: Option) -> Self { + Self { code, detail } + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct MessageResult { + pub status: Status, + #[serde(skip_serializing_if = "Option::is_none")] + pub entries: Option>, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Entry {}