diff --git a/Cargo.lock b/Cargo.lock index 3a208df..a51cd8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -887,7 +887,6 @@ dependencies = [ "axum", "chrono", "http-body-util", - "openadr-vtn", "openadr-wire", "rangemap", "reqwest", @@ -931,6 +930,7 @@ name = "openadr-wire" version = "0.1.0" dependencies = [ "chrono", + "http", "iso8601-duration", "quickcheck", "serde", diff --git a/Cargo.toml b/Cargo.toml index ddf2eac..9bcf344 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ thiserror = "1.0.61" validator = {version = "0.18.1", features = ["derive"] } uuid = { version = "1.8.0", features = ["v4"] } url = "2.5.0" +http = "^1.0.0" mime = "0.3" tower-http = { version = "0.5.2" , features = ["trace"]} http-body-util = "0.1.0" diff --git a/openadr-client/Cargo.toml b/openadr-client/Cargo.toml index f492770..138e65b 100644 --- a/openadr-client/Cargo.toml +++ b/openadr-client/Cargo.toml @@ -12,7 +12,6 @@ rust-version.workspace = true [dependencies] openadr-wire.workspace = true -openadr-vtn.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/openadr-client/src/error.rs b/openadr-client/src/error.rs index 91e2203..233d355 100644 --- a/openadr-client/src/error.rs +++ b/openadr-client/src/error.rs @@ -4,7 +4,7 @@ pub enum Error { Reqwest(reqwest::Error), Serde(serde_json::Error), UrlParseError(url::ParseError), - Problem(openadr_vtn::Problem), + Problem(openadr_wire::problem::Problem), AuthProblem(openadr_wire::oauth::OAuthError), OAuthTokenNotBearer, ObjectNotFound, @@ -31,8 +31,8 @@ impl From for Error { } } -impl From for Error { - fn from(err: openadr_vtn::Problem) -> Self { +impl From for Error { + fn from(err: openadr_wire::problem::Problem) -> Self { Error::Problem(err) } } diff --git a/openadr-client/src/lib.rs b/openadr-client/src/lib.rs index 308b5ad..b7ea25d 100644 --- a/openadr-client/src/lib.rs +++ b/openadr-client/src/lib.rs @@ -216,7 +216,7 @@ impl ReqwestClientRef { // handle any errors returned by the server if !res.status().is_success() { - let problem = res.json::().await?; + let problem = res.json::().await?; return Err(crate::error::Error::from(problem)); } diff --git a/openadr-vtn/src/error.rs b/openadr-vtn/src/error.rs index 644d4f6..264ce43 100644 --- a/openadr-vtn/src/error.rs +++ b/openadr-vtn/src/error.rs @@ -3,8 +3,8 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use axum::Json; use axum_extra::extract::QueryRejection; +use openadr_wire::problem::Problem; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; use tracing::{error, trace, warn}; use uuid::Uuid; @@ -28,8 +28,8 @@ pub enum AppError { Auth(String), } -impl IntoResponse for AppError { - fn into_response(self) -> Response { +impl AppError { + fn into_problem(self) -> Problem { let reference = Uuid::new_v4(); match self { @@ -134,7 +134,13 @@ impl IntoResponse for AppError { } } } - .into_response() + } +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let problem = self.into_problem(); + (problem.status, Json(problem)).into_response() } } @@ -146,61 +152,3 @@ impl Default for ProblemUri { Self("about:blank".to_string()) } } - -/// Reusable error response. From . -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] -#[skip_serializing_none] -#[serde(rename_all = "camelCase")] -pub struct Problem { - /// An absolute URI that identifies the problem type. - /// When dereferenced, it SHOULD provide human-readable documentation for the problem type - /// (e.g., using HTML). - #[serde(default)] - pub r#type: ProblemUri, - /// A short, summary of the problem type. - /// Written in english and readable for engineers - /// (usually not suited for non-technical stakeholders and not localized); - /// example: Service Unavailable. - pub title: Option, - /// The HTTP status code generated by the origin server for this occurrence of the problem. - #[serde(with = "status_code_serialization")] - pub status: StatusCode, - /// A human-readable explanation specific to this occurrence of the problem. - pub detail: Option, - /// An absolute URI that identifies the specific occurrence of the problem. - /// It may or may not yield further information if dereferenced. - pub instance: Option, -} - -mod status_code_serialization { - use reqwest::StatusCode; - use serde::de::Unexpected; - use serde::{Deserialize, Deserializer, Serializer}; - - pub fn serialize(code: &StatusCode, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_u16(code.as_u16()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - u16::deserialize(deserializer).and_then(|code| { - StatusCode::from_u16(code).map_err(|_| { - serde::de::Error::invalid_value( - Unexpected::Unsigned(code as u64), - &"Valid http status code", - ) - }) - }) - } -} - -impl IntoResponse for Problem { - fn into_response(self) -> Response { - (self.status, Json(self)).into_response() - } -} diff --git a/openadr-vtn/src/lib.rs b/openadr-vtn/src/lib.rs index 4429c40..1105340 100644 --- a/openadr-vtn/src/lib.rs +++ b/openadr-vtn/src/lib.rs @@ -3,5 +3,3 @@ pub mod data_source; mod error; pub mod jwt; pub mod state; - -pub use error::Problem; diff --git a/openadr-wire/Cargo.toml b/openadr-wire/Cargo.toml index 1a1b796..f8dd2cc 100644 --- a/openadr-wire/Cargo.toml +++ b/openadr-wire/Cargo.toml @@ -17,6 +17,7 @@ serde_with.workspace = true uuid.workspace = true iso8601-duration.workspace = true thiserror.workspace = true +http.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/openadr-wire/src/lib.rs b/openadr-wire/src/lib.rs index aa836cb..8bf2726 100644 --- a/openadr-wire/src/lib.rs +++ b/openadr-wire/src/lib.rs @@ -16,6 +16,7 @@ pub use report::Report; pub mod event; pub mod interval; pub mod oauth; +pub mod problem; pub mod program; pub mod report; pub mod target; diff --git a/openadr-wire/src/problem.rs b/openadr-wire/src/problem.rs new file mode 100644 index 0000000..7fc0f7b --- /dev/null +++ b/openadr-wire/src/problem.rs @@ -0,0 +1,65 @@ +use http::StatusCode; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct ProblemUri(String); + +impl Default for ProblemUri { + fn default() -> Self { + Self("about:blank".to_string()) + } +} + +/// Reusable error response. From . +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] +#[skip_serializing_none] +#[serde(rename_all = "camelCase")] +pub struct Problem { + /// An absolute URI that identifies the problem type. + /// When dereferenced, it SHOULD provide human-readable documentation for the problem type + /// (e.g., using HTML). + #[serde(default)] + pub r#type: ProblemUri, + /// A short, summary of the problem type. + /// Written in english and readable for engineers + /// (usually not suited for non-technical stakeholders and not localized); + /// example: Service Unavailable. + pub title: Option, + /// The HTTP status code generated by the origin server for this occurrence of the problem. + #[serde(with = "status_code_serialization")] + pub status: StatusCode, + /// A human-readable explanation specific to this occurrence of the problem. + pub detail: Option, + /// An absolute URI that identifies the specific occurrence of the problem. + /// It may or may not yield further information if dereferenced. + pub instance: Option, +} + +mod status_code_serialization { + use super::*; + + use serde::de::Unexpected; + use serde::{Deserializer, Serializer}; + + pub fn serialize(code: &StatusCode, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_u16(code.as_u16()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + u16::deserialize(deserializer).and_then(|code| { + StatusCode::from_u16(code).map_err(|_| { + serde::de::Error::invalid_value( + Unexpected::Unsigned(code as u64), + &"Valid http status code", + ) + }) + }) + } +}