diff --git a/Cargo.lock b/Cargo.lock index 2afac6329..0743ce327 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1979,7 +1979,6 @@ dependencies = [ "backoff", "derivative", "http", - "http-api-problem", "itertools", "janus_core", "janus_messages 0.6.0-prerelease-2", @@ -2005,7 +2004,6 @@ dependencies = [ "derivative", "fixed", "fixed-macro", - "http-api-problem", "janus_collector", "janus_core", "janus_messages 0.6.0-prerelease-2", diff --git a/aggregator/src/aggregator.rs b/aggregator/src/aggregator.rs index 0ea6775ac..db8c8f814 100644 --- a/aggregator/src/aggregator.rs +++ b/aggregator/src/aggregator.rs @@ -41,12 +41,11 @@ use janus_core::test_util::dummy_vdaf; use janus_core::{ auth_tokens::AuthenticationToken, hpke::{self, HpkeApplicationInfo, HpkeKeypair, Label}, - http::response_to_problem_details, + http::HttpErrorResponse, time::{Clock, DurationExt, IntervalExt, TimeExt}, vdaf::{VdafInstance, VERIFY_KEY_LENGTH}, }; use janus_messages::{ - problem_type::DapProblemType, query_type::{FixedSize, TimeInterval}, taskprov::TaskConfig, AggregateShare, AggregateShareAad, AggregateShareReq, AggregationJobContinueReq, @@ -3077,15 +3076,9 @@ async fn send_request_to_helper( KeyValue::new("endpoint", route_label), ], ); - let problem_details = response_to_problem_details(response).await; - let dap_problem_type = problem_details - .type_url - .as_ref() - .and_then(|str| str.parse::().ok()); - return Err(Error::Http { - problem_details: Box::new(problem_details), - dap_problem_type, - }); + return Err(Error::Http(Box::new( + HttpErrorResponse::from_response(response).await, + ))); } match response.bytes().await { diff --git a/aggregator/src/aggregator/aggregation_job_driver.rs b/aggregator/src/aggregator/aggregation_job_driver.rs index 14efa0a3e..9591726a6 100644 --- a/aggregator/src/aggregator/aggregation_job_driver.rs +++ b/aggregator/src/aggregator/aggregation_job_driver.rs @@ -907,7 +907,7 @@ struct SteppedAggregation { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, Some(DapProblemType::UnauthorizedRequest)); + Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::UnauthorizedRequest); } ); aggregation_job_driver @@ -2049,9 +2050,9 @@ mod tests { .unwrap_err(); assert_matches!( error.downcast().unwrap(), - Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, Some(DapProblemType::UnauthorizedRequest)); + Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::UnauthorizedRequest); } ); aggregation_job_driver @@ -2609,9 +2610,9 @@ mod tests { .unwrap_err(); assert_matches!( error.downcast().unwrap(), - Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, Some(DapProblemType::UnrecognizedTask)); + Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::UnrecognizedTask); } ); aggregation_job_driver @@ -2996,9 +2997,9 @@ mod tests { .unwrap_err(); assert_matches!( error.downcast().unwrap(), - Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, Some(DapProblemType::UnrecognizedTask)); + Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::UnrecognizedTask); } ); aggregation_job_driver diff --git a/aggregator/src/aggregator/collection_job_driver.rs b/aggregator/src/aggregator/collection_job_driver.rs index 72f109943..ff225f445 100644 --- a/aggregator/src/aggregator/collection_job_driver.rs +++ b/aggregator/src/aggregator/collection_job_driver.rs @@ -525,7 +525,7 @@ impl CollectionJobDriverMetrics { #[cfg(test)] mod tests { use crate::{ - aggregator::{collection_job_driver::CollectionJobDriver, DapProblemType, Error}, + aggregator::{collection_job_driver::CollectionJobDriver, Error}, binary_utils::job_driver::JobDriver, }; use assert_matches::assert_matches; @@ -555,9 +555,9 @@ mod tests { Runtime, }; use janus_messages::{ - query_type::TimeInterval, AggregateShare, AggregateShareReq, AggregationJobStep, - BatchSelector, Duration, HpkeCiphertext, HpkeConfigId, Interval, Query, ReportIdChecksum, - Role, + problem_type::DapProblemType, query_type::TimeInterval, AggregateShare, AggregateShareReq, + AggregationJobStep, BatchSelector, Duration, HpkeCiphertext, HpkeConfigId, Interval, Query, + ReportIdChecksum, Role, }; use prio::codec::{Decode, Encode}; use rand::random; @@ -898,11 +898,9 @@ mod tests { .unwrap_err(); assert_matches!( error, - Error::Http { - problem_details, - dap_problem_type: Some(DapProblemType::BatchQueriedTooManyTimes), - } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + Error::Http(error_response) => { + assert_matches!(error_response.dap_problem_type(), Some(DapProblemType::BatchQueriedTooManyTimes)); + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); } ); diff --git a/aggregator/src/aggregator/error.rs b/aggregator/src/aggregator/error.rs index c9c517aac..1a8decc56 100644 --- a/aggregator/src/aggregator/error.rs +++ b/aggregator/src/aggregator/error.rs @@ -1,8 +1,8 @@ -use http_api_problem::HttpApiProblem; use janus_aggregator_core::{datastore, task}; +use janus_core::http::HttpErrorResponse; use janus_messages::{ - problem_type::DapProblemType, AggregationJobId, AggregationJobStep, CollectionJobId, - HpkeConfigId, Interval, PrepareError, ReportId, ReportIdChecksum, Role, TaskId, Time, + AggregationJobId, AggregationJobStep, CollectionJobId, HpkeConfigId, Interval, PrepareError, + ReportId, ReportIdChecksum, Role, TaskId, Time, }; use opentelemetry::{metrics::Counter, KeyValue}; use prio::{topology::ping_pong::PingPongError, vdaf::VdafError}; @@ -103,11 +103,8 @@ pub enum Error { #[error("HTTP client error: {0}")] HttpClient(#[from] reqwest::Error), /// HTTP server returned an error status code. - #[error("HTTP response status {problem_details}")] - Http { - problem_details: Box, - dap_problem_type: Option, - }, + #[error("HTTP response status {0}")] + Http(Box), /// An aggregate share request was rejected. #[error("task {0}: {1}")] AggregateShareRequestRejected(TaskId, String), diff --git a/aggregator/src/aggregator/problem_details.rs b/aggregator/src/aggregator/problem_details.rs index 64476aec5..bab032015 100644 --- a/aggregator/src/aggregator/problem_details.rs +++ b/aggregator/src/aggregator/problem_details.rs @@ -251,8 +251,8 @@ mod tests { // Confirm that post_to_helper() correctly parsed the error type from error_handler(). assert_matches!( actual_error, - Error::Http { dap_problem_type: problem_type, .. } => { - assert_eq!(problem_type, test_case.expected_problem_type); + Error::Http(error_response) => { + assert_eq!(error_response.dap_problem_type(), test_case.expected_problem_type.as_ref()); } ); } diff --git a/client/Cargo.toml b/client/Cargo.toml index 308129862..82093af73 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -13,7 +13,6 @@ version.workspace = true backoff = { version = "0.4.0", features = ["tokio"] } derivative = "2.2.0" http = "0.2.9" -http-api-problem = "0.57.0" itertools.workspace = true janus_core.workspace = true janus_messages.workspace = true diff --git a/client/src/lib.rs b/client/src/lib.rs index 16543a8ff..cad449f16 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -3,11 +3,10 @@ use backoff::ExponentialBackoff; use derivative::Derivative; use http::header::CONTENT_TYPE; -use http_api_problem::HttpApiProblem; use itertools::Itertools; use janus_core::{ hpke::{self, HpkeApplicationInfo, Label}, - http::response_to_problem_details, + http::HttpErrorResponse, retries::{http_request_exponential_backoff, retry_http_request}, time::{Clock, TimeExt}, url_ensure_trailing_slash, @@ -33,7 +32,7 @@ pub enum Error { #[error("codec error: {0}")] Codec(#[from] prio::codec::CodecError), #[error("HTTP response status {0}")] - Http(Box), + Http(Box), #[error("URL parse: {0}")] Url(#[from] url::ParseError), #[error("VDAF error: {0}")] @@ -149,7 +148,7 @@ pub async fn aggregator_hpke_config( let status = hpke_config_response.status(); if !status.is_success() { return Err(Error::Http(Box::new( - response_to_problem_details(hpke_config_response).await, + HttpErrorResponse::from_response(hpke_config_response).await, ))); } @@ -282,7 +281,7 @@ impl, C: Clock> Client { let status = upload_response.status(); if !status.is_success() { return Err(Error::Http(Box::new( - response_to_problem_details(upload_response).await, + HttpErrorResponse::from_response(upload_response).await, ))); } @@ -399,8 +398,8 @@ mod tests { assert_matches!( client.upload(&1).await, - Err(Error::Http(problem)) => { - assert_eq!(problem.status.unwrap(), StatusCode::NOT_IMPLEMENTED); + Err(Error::Http(error_response)) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::NOT_IMPLEMENTED); } ); @@ -432,14 +431,14 @@ mod tests { assert_matches!( client.upload(&1).await, - Err(Error::Http(problem)) => { - assert_eq!(problem.status.unwrap(), StatusCode::BAD_REQUEST); + Err(Error::Http(error_response)) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::BAD_REQUEST); assert_eq!( - problem.type_url.unwrap(), + error_response.type_uri().unwrap(), "urn:ietf:params:ppm:dap:error:invalidMessage" ); assert_eq!( - problem.detail.unwrap(), + error_response.detail().unwrap(), "The message type for a response was incorrect or the payload was malformed." ); } diff --git a/collector/Cargo.toml b/collector/Cargo.toml index 8268d5ec7..30b0fb5c6 100644 --- a/collector/Cargo.toml +++ b/collector/Cargo.toml @@ -21,7 +21,6 @@ test-util = [] backoff = { version = "0.4.0", features = ["tokio"] } chrono.workspace = true derivative = "2.2.0" -http-api-problem = "0.57.0" janus_core.workspace = true janus_messages.workspace = true fixed = { version = "1.23", optional = true } diff --git a/collector/src/lib.rs b/collector/src/lib.rs index 6c46bc71c..69fad331c 100644 --- a/collector/src/lib.rs +++ b/collector/src/lib.rs @@ -56,17 +56,15 @@ use backoff::{backoff::Backoff, ExponentialBackoff}; use chrono::{DateTime, Duration, TimeZone, Utc}; use derivative::Derivative; -use http_api_problem::HttpApiProblem; pub use janus_core::auth_tokens::AuthenticationToken; use janus_core::{ hpke::{self, HpkeApplicationInfo, HpkePrivateKey}, - http::response_to_problem_details, + http::HttpErrorResponse, retries::{http_request_exponential_backoff, retry_http_request}, time::{DurationExt, TimeExt}, url_ensure_trailing_slash, }; use janus_messages::{ - problem_type::DapProblemType, query_type::{QueryType, TimeInterval}, AggregateShareAad, BatchSelector, Collection as CollectionMessage, CollectionJobId, CollectionReq, HpkeConfig, PartialBatchSelector, Query, Role, TaskId, @@ -94,11 +92,8 @@ use url::Url; pub enum Error { #[error("HTTP client error: {0}")] HttpClient(#[from] reqwest::Error), - #[error("HTTP response status {problem_details}")] - Http { - problem_details: Box, - dap_problem_type: Option, - }, + #[error("HTTP response status {0}")] + Http(Box), #[error("URL parse: {0}")] Url(#[from] url::ParseError), #[error("missing Location header in See Other response")] @@ -129,15 +124,7 @@ impl Error { /// Construct an error from an HTTP response's status and problem details document, if present /// in the body. async fn from_http_response(response: Response) -> Error { - let problem_details = response_to_problem_details(response).await; - let dap_problem_type = problem_details - .type_url - .as_ref() - .and_then(|str| str.parse::().ok()); - Error::Http { - problem_details: Box::new(problem_details), - dap_problem_type, - } + Error::Http(Box::new(HttpErrorResponse::from_response(response).await)) } } @@ -415,10 +402,7 @@ impl Collector { return Err(Error::from_http_response(response).await); } else if status != StatusCode::CREATED { // Incorrect success/redirect status code: - return Err(Error::Http { - problem_details: Box::new(HttpApiProblem::new(status)), - dap_problem_type: None, - }); + return Err(Error::Http(Box::new(status.into()))); } } // Retryable error status code, but ran out of retries: @@ -474,10 +458,7 @@ impl Collector { return Err(Error::from_http_response(response).await); } _ => { - return Err(Error::Http { - problem_details: Box::new(HttpApiProblem::new(status)), - dap_problem_type: None, - }) + return Err(Error::Http(Box::new(HttpErrorResponse::from(status)))); } } } @@ -1292,9 +1273,9 @@ mod tests { .start_collection(Query::new_time_interval(batch_interval), &()) .await .unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, None); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert!(error_response.dap_problem_type().is_none()); }); mock_server_error.assert_async().await; @@ -1316,10 +1297,10 @@ mod tests { .start_collection(Query::new_time_interval(batch_interval), &()) .await .unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(problem_details.type_url.unwrap(), "http://example.com/test_server_error"); - assert_eq!(dap_problem_type, None); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(error_response.type_uri().unwrap(), "http://example.com/test_server_error"); + assert!(error_response.dap_problem_type().is_none()); }); mock_server_error_details.assert_async().await; @@ -1345,11 +1326,11 @@ mod tests { .start_collection(Query::new_time_interval(batch_interval), &()) .await .unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::BAD_REQUEST); - assert_eq!(problem_details.type_url.unwrap(), "urn:ietf:params:ppm:dap:error:invalidMessage"); - assert_eq!(problem_details.detail.unwrap(), "The message type for a response was incorrect or the payload was malformed."); - assert_eq!(dap_problem_type, Some(DapProblemType::InvalidMessage)); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::BAD_REQUEST); + assert_eq!(error_response.type_uri().unwrap(), "urn:ietf:params:ppm:dap:error:invalidMessage"); + assert_eq!(error_response.detail().unwrap(), "The message type for a response was incorrect or the payload was malformed."); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::InvalidMessage); }); mock_bad_request.assert_async().await; @@ -1390,9 +1371,9 @@ mod tests { .await .unwrap(); let error = collector.poll_once(&job).await.unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, None); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert!(error_response.dap_problem_type().is_none()); }); mock_collect_start.assert_async().await; @@ -1412,10 +1393,10 @@ mod tests { .await; let error = collector.poll_once(&job).await.unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(problem_details.type_url.unwrap(), "http://example.com/test_server_error"); - assert_eq!(dap_problem_type, None); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert_eq!(error_response.type_uri().unwrap(), "http://example.com/test_server_error"); + assert!(error_response.dap_problem_type().is_none()); }); mock_collection_job_server_error_details @@ -1436,11 +1417,11 @@ mod tests { .await; let error = collector.poll_once(&job).await.unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::BAD_REQUEST); - assert_eq!(problem_details.type_url.unwrap(), "urn:ietf:params:ppm:dap:error:invalidMessage"); - assert_eq!(problem_details.detail.unwrap(), "The message type for a response was incorrect or the payload was malformed."); - assert_eq!(dap_problem_type, Some(DapProblemType::InvalidMessage)); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::BAD_REQUEST); + assert_eq!(error_response.type_uri().unwrap(), "urn:ietf:params:ppm:dap:error:invalidMessage"); + assert_eq!(error_response.detail().unwrap(), "The message type for a response was incorrect or the payload was malformed."); + assert_eq!(*error_response.dap_problem_type().unwrap(), DapProblemType::InvalidMessage); }); mock_collection_job_bad_request.assert_async().await; @@ -1585,9 +1566,9 @@ mod tests { .create_async() .await; let error = collector.poll_until_complete(&job).await.unwrap_err(); - assert_matches!(error, Error::Http { problem_details, dap_problem_type } => { - assert_eq!(problem_details.status.unwrap(), StatusCode::INTERNAL_SERVER_ERROR); - assert_eq!(dap_problem_type, None); + assert_matches!(error, Error::Http(error_response) => { + assert_eq!(*error_response.status().unwrap(), StatusCode::INTERNAL_SERVER_ERROR); + assert!(error_response.dap_problem_type().is_none()); }); mock_collection_job_always_fail.assert_async().await; } diff --git a/core/src/http.rs b/core/src/http.rs index 190c350ae..483b53b02 100644 --- a/core/src/http.rs +++ b/core/src/http.rs @@ -1,27 +1,89 @@ use crate::auth_tokens::AuthenticationToken; use anyhow::{anyhow, Context}; +use http::StatusCode; use http_api_problem::{HttpApiProblem, PROBLEM_JSON_MEDIA_TYPE}; +use janus_messages::problem_type::DapProblemType; use reqwest::{header::CONTENT_TYPE, Response}; +use std::fmt::{self, Display, Formatter}; use tracing::warn; use trillium::Conn; -/// Turn a [`reqwest::Response`] into a [`HttpApiProblem`]. If applicable, a JSON problem details -/// document is parsed from the request's body, otherwise it is solely constructed from the -/// response's status code. (see [RFC 7807](https://www.rfc-editor.org/rfc/rfc7807.html)) -pub async fn response_to_problem_details(response: Response) -> HttpApiProblem { - let status = response.status(); - if let Some(content_type) = response.headers().get(CONTENT_TYPE) { - if content_type == PROBLEM_JSON_MEDIA_TYPE { - match response.json::().await { - Ok(mut problem) => { - problem.status = Some(status); - return problem; +/// This captures an HTTP status code and parsed problem details document from an HTTP response. +#[derive(Debug)] +pub struct HttpErrorResponse { + problem_details: HttpApiProblem, + dap_problem_type: Option, +} + +impl HttpErrorResponse { + /// Turn a [`reqwest::Response`] into a [`HttpErrorResponse`]. If applicable, a JSON problem + /// details document is parsed from the request's body, otherwise it is solely constructed from + /// the response's status code. (see [RFC 7807](https://www.rfc-editor.org/rfc/rfc7807.html)) + pub async fn from_response(response: Response) -> Self { + let status = response.status(); + if let Some(content_type) = response.headers().get(CONTENT_TYPE) { + if content_type == PROBLEM_JSON_MEDIA_TYPE { + match response.json::().await { + Ok(mut problem) => { + problem.status = Some(status); + return problem.into(); + } + Err(error) => warn!(%error, "Failed to parse problem details"), } - Err(error) => warn!(%error, "Failed to parse problem details"), } } + status.into() + } + + /// The HTTP status code returned by the server. + pub fn status(&self) -> Option<&StatusCode> { + self.problem_details.status.as_ref() + } + + /// A URI that identifies the problem type. + pub fn type_uri(&self) -> Option<&str> { + self.problem_details.type_url.as_deref() + } + + /// A short summary of the problem type. + pub fn title(&self) -> Option<&str> { + self.problem_details.title.as_deref() + } + + /// Specific details about this instance of a problem. + pub fn detail(&self) -> Option<&str> { + self.problem_details.detail.as_deref() + } + + /// The DAP-specific problem type, if applicable. + pub fn dap_problem_type(&self) -> Option<&DapProblemType> { + self.dap_problem_type.as_ref() + } +} + +impl From for HttpErrorResponse { + fn from(problem_details: HttpApiProblem) -> Self { + let dap_problem_type = problem_details + .type_url + .as_ref() + .and_then(|str| str.parse::().ok()); + Self { + problem_details, + dap_problem_type, + } + } +} + +impl From for HttpErrorResponse { + fn from(value: StatusCode) -> Self { + HttpApiProblem::new(value).into() + } +} + +impl Display for HttpErrorResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.problem_details.fmt(f) } - HttpApiProblem::new(status) } /// If the request in `conn` has an `authorization` header, returns the bearer token in the header diff --git a/integration_tests/tests/common/mod.rs b/integration_tests/tests/common/mod.rs index 2182f4b5c..087655b3e 100644 --- a/integration_tests/tests/common/mod.rs +++ b/integration_tests/tests/common/mod.rs @@ -101,12 +101,19 @@ where async move { match collector.collect(query, aggregation_parameter).await { Ok(collection) => Ok(collection), - Err( - error @ janus_collector::Error::Http { - dap_problem_type: Some(DapProblemType::InvalidBatchSize), - .. - }, - ) => Err(backoff::Error::transient(error)), + Err(janus_collector::Error::Http(error_response)) => { + if let Some(DapProblemType::InvalidBatchSize) = + error_response.dap_problem_type() + { + Err(backoff::Error::transient(janus_collector::Error::Http( + error_response, + ))) + } else { + Err(backoff::Error::permanent(janus_collector::Error::Http( + error_response, + ))) + } + } Err(error) => Err(backoff::Error::permanent(error)), } } diff --git a/messages/src/problem_type.rs b/messages/src/problem_type.rs index 16267ee9c..3d6a21882 100644 --- a/messages/src/problem_type.rs +++ b/messages/src/problem_type.rs @@ -1,7 +1,7 @@ use std::str::FromStr; /// Representation of the different problem types defined in Table 1 in ยง3.2. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DapProblemType { InvalidMessage, UnrecognizedTask,