diff --git a/Cargo.lock b/Cargo.lock index 14586216..168e63c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,6 +173,24 @@ dependencies = [ "syn 2.0.33", ] +[[package]] +name = "axum-test-helper" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298f62fa902c2515c169ab0bfb56c593229f33faa01131215d58e3d4898e3aa9" +dependencies = [ + "axum", + "bytes", + "http", + "http-body", + "hyper", + "reqwest", + "serde", + "tokio", + "tower", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -1008,11 +1026,14 @@ dependencies = [ "async-trait", "axum", "axum-macros", + "axum-test-helper", "base64 0.21.4", + "bytes", "clap", "gdc_rust_types", "http", "indexmap 1.9.3", + "mime", "ndc-client", "ndc-test", "opentelemetry", @@ -1556,10 +1577,12 @@ dependencies = [ "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "winreg", ] @@ -2433,6 +2456,19 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.64" diff --git a/rust-connector-sdk/Cargo.toml b/rust-connector-sdk/Cargo.toml index bcf54f5a..80cd661c 100644 --- a/rust-connector-sdk/Cargo.toml +++ b/rust-connector-sdk/Cargo.toml @@ -53,3 +53,8 @@ url = "2.4.1" uuid = "^1.3.4" gdc_rust_types = { git = "https://github.com/hasura/gdc_rust_types", rev = "bc57c40" } indexmap = "^1" +bytes = "1.5.0" +mime = "0.3.17" + +[dev-dependencies] +axum-test-helper = "0.3.0" diff --git a/rust-connector-sdk/src/connector.rs b/rust-connector-sdk/src/connector.rs index 90f8a4aa..868d8960 100644 --- a/rust-connector-sdk/src/connector.rs +++ b/rust-connector-sdk/src/connector.rs @@ -3,6 +3,9 @@ use ndc_client::models; use serde::Serialize; use std::error::Error; use thiserror::Error; + +use crate::json_response::JsonResponse; + pub mod example; /// Errors which occur when trying to validate connector @@ -35,7 +38,7 @@ pub enum KeyOrIndex { #[derive(Debug, Error)] pub enum UpdateConfigurationError { #[error("error validating configuration: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when trying to initialize connector @@ -45,7 +48,7 @@ pub enum UpdateConfigurationError { #[derive(Debug, Error)] pub enum InitializationError { #[error("error initializing connector state: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when trying to update metrics. @@ -54,7 +57,7 @@ pub enum InitializationError { #[derive(Debug, Error)] pub enum FetchMetricsError { #[error("error fetching metrics: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when checking connector health. @@ -63,7 +66,7 @@ pub enum FetchMetricsError { #[derive(Debug, Error)] pub enum HealthError { #[error("error checking health status: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when retrieving the connector schema. @@ -72,7 +75,7 @@ pub enum HealthError { #[derive(Debug, Error)] pub enum SchemaError { #[error("error retrieving the schema: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when executing a query. @@ -91,7 +94,7 @@ pub enum QueryError { #[error("unsupported operation: {0}")] UnsupportedOperation(String), #[error("error executing query: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when explaining a query. @@ -110,7 +113,7 @@ pub enum ExplainError { #[error("unsupported operation: {0}")] UnsupportedOperation(String), #[error("error explaining query: {0}")] - Other(Box), + Other(#[from] Box), } /// Errors which occur when executing a mutation. @@ -137,7 +140,7 @@ pub enum MutationError { #[error("mutation violates constraint: {0}")] ConstraintNotMet(String), #[error("error executing mutation: {0}")] - Other(Box), + Other(#[from] Box), } /// Connectors using this library should implement this trait. @@ -238,7 +241,7 @@ pub trait Connector { /// /// This function implements the [capabilities endpoint](https://hasura.github.io/ndc-spec/specification/capabilities.html) /// from the NDC specification. - async fn get_capabilities() -> models::CapabilitiesResponse; + async fn get_capabilities() -> JsonResponse; /// Get the connector's schema. /// @@ -246,7 +249,7 @@ pub trait Connector { /// from the NDC specification. async fn get_schema( configuration: &Self::Configuration, - ) -> Result; + ) -> Result, SchemaError>; /// Explain a query by creating an execution plan /// @@ -256,7 +259,7 @@ pub trait Connector { configuration: &Self::Configuration, state: &Self::State, request: models::QueryRequest, - ) -> Result; + ) -> Result, ExplainError>; /// Execute a mutation /// @@ -266,7 +269,7 @@ pub trait Connector { configuration: &Self::Configuration, state: &Self::State, request: models::MutationRequest, - ) -> Result; + ) -> Result, MutationError>; /// Execute a query /// @@ -276,5 +279,5 @@ pub trait Connector { configuration: &Self::Configuration, state: &Self::State, request: models::QueryRequest, - ) -> Result; + ) -> Result, QueryError>; } diff --git a/rust-connector-sdk/src/connector/example.rs b/rust-connector-sdk/src/connector/example.rs index fdb4f20e..42ffb10b 100644 --- a/rust-connector-sdk/src/connector/example.rs +++ b/rust-connector-sdk/src/connector/example.rs @@ -50,7 +50,7 @@ impl Connector for Example { Ok(()) } - async fn get_capabilities() -> models::CapabilitiesResponse { + async fn get_capabilities() -> JsonResponse { models::CapabilitiesResponse { versions: "^0.1.0".into(), capabilities: models::Capabilities { @@ -64,11 +64,12 @@ impl Connector for Example { }), }, } + .into() } async fn get_schema( _configuration: &Self::Configuration, - ) -> Result { + ) -> Result, SchemaError> { async { info_span!("inside tracing example"); } @@ -81,14 +82,15 @@ impl Connector for Example { procedures: vec![], object_types: BTreeMap::new(), scalar_types: BTreeMap::new(), - }) + } + .into()) } async fn explain( _configuration: &Self::Configuration, _state: &Self::State, _request: models::QueryRequest, - ) -> Result { + ) -> Result, ExplainError> { todo!() } @@ -96,7 +98,7 @@ impl Connector for Example { _configuration: &Self::Configuration, _state: &Self::State, _request: models::MutationRequest, - ) -> Result { + ) -> Result, MutationError> { todo!() } @@ -104,7 +106,7 @@ impl Connector for Example { _configuration: &Self::Configuration, _state: &Self::State, _request: models::QueryRequest, - ) -> Result { + ) -> Result, QueryError> { todo!() } } diff --git a/rust-connector-sdk/src/default_main.rs b/rust-connector-sdk/src/default_main.rs index 7ff95e2c..1adaff62 100644 --- a/rust-connector-sdk/src/default_main.rs +++ b/rust-connector-sdk/src/default_main.rs @@ -3,6 +3,7 @@ mod v2_compat; use crate::{ check_health, connector::{Connector, InvalidRange, SchemaError, UpdateConfigurationError}, + json_response::JsonResponse, routes, tracing::{init_tracing, make_span, on_response}, }; @@ -373,7 +374,7 @@ async fn get_metrics( routes::get_metrics::(&state.configuration, &state.state, state.metrics) } -async fn get_capabilities() -> Json { +async fn get_capabilities() -> JsonResponse { routes::get_capabilities::().await } @@ -385,28 +386,28 @@ async fn get_health( async fn get_schema( State(state): State>, -) -> Result, (StatusCode, Json)> { +) -> Result, (StatusCode, Json)> { routes::get_schema::(&state.configuration).await } async fn post_explain( State(state): State>, request: Json, -) -> Result, (StatusCode, Json)> { +) -> Result, (StatusCode, Json)> { routes::post_explain::(&state.configuration, &state.state, request).await } async fn post_mutation( State(state): State>, request: Json, -) -> Result, (StatusCode, Json)> { +) -> Result, (StatusCode, Json)> { routes::post_mutation::(&state.configuration, &state.state, request).await } async fn post_query( State(state): State>, request: Json, -) -> Result, (StatusCode, Json)> { +) -> Result, (StatusCode, Json)> { routes::post_query::(&state.configuration, &state.state, request).await } @@ -528,12 +529,15 @@ where Json(ValidateErrors::InvalidConfiguration { ranges }), ), })?; - let schema = C::get_schema(&configuration).await.map_err(|e| match e { - SchemaError::Other(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ValidateErrors::UnableToBuildSchema), - ), - })?; + let schema = C::get_schema(&configuration) + .await + .and_then(JsonResponse::into_value) + .map_err(|e| match e { + SchemaError::Other(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ValidateErrors::UnableToBuildSchema), + ), + })?; let resolved_config_bytes = serde_json::to_vec(&configuration).map_err(|e| { ( StatusCode::INTERNAL_SERVER_ERROR, @@ -561,12 +565,17 @@ where async fn get_capabilities( &self, ) -> Result { - Ok(C::get_capabilities().await) + C::get_capabilities() + .await + .into_value::>() + .map_err(|err| ndc_test::Error::OtherError(err)) } async fn get_schema(&self) -> Result { match C::get_schema(&self.configuration).await { - Ok(response) => Ok(response), + Ok(response) => response + .into_value::>() + .map_err(|err| ndc_test::Error::OtherError(err)), Err(err) => Err(ndc_test::Error::OtherError(err.into())), } } @@ -575,7 +584,10 @@ where &self, request: ndc_client::models::QueryRequest, ) -> Result { - match C::query(&self.configuration, &self.state, request).await { + match C::query(&self.configuration, &self.state, request) + .await + .and_then(JsonResponse::into_value) + { Ok(response) => Ok(response), Err(err) => Err(ndc_test::Error::OtherError(err.into())), } diff --git a/rust-connector-sdk/src/default_main/v2_compat.rs b/rust-connector-sdk/src/default_main/v2_compat.rs index fdfe5267..9af6d34f 100644 --- a/rust-connector-sdk/src/default_main/v2_compat.rs +++ b/rust-connector-sdk/src/default_main/v2_compat.rs @@ -16,10 +16,9 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::BTreeMap; -use crate::{ - connector::{Connector, ExplainError, QueryError}, - default_main::ServerState, -}; +use crate::connector::{Connector, ExplainError, QueryError}; +use crate::default_main::ServerState; +use crate::json_response::JsonResponse; pub async fn get_health() -> impl IntoResponse { // todo: if source_name and config provided, check if that specific source is healthy @@ -29,17 +28,31 @@ pub async fn get_health() -> impl IntoResponse { pub async fn get_capabilities( State(state): State>, ) -> Result, (StatusCode, Json)> { - let v3_capabilities = C::get_capabilities().await; - let v3_schema = C::get_schema(&state.configuration).await.map_err(|err| { - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - details: None, - message: err.to_string(), - r#type: None, - }), - ) - })?; + let v3_capabilities = C::get_capabilities().await.into_value().map_err( + |err: Box| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse { + details: None, + message: err.to_string(), + r#type: None, + }), + ) + }, + )?; + let v3_schema = C::get_schema(&state.configuration) + .await + .and_then(JsonResponse::into_value) + .map_err(|err| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse { + details: None, + message: err.to_string(), + r#type: None, + }), + ) + })?; let scalar_types = IndexMap::from_iter(v3_schema.scalar_types.into_iter().map( |(name, scalar_type)| { @@ -171,16 +184,19 @@ pub async fn post_schema( State(state): State>, request: Option>, ) -> Result, (StatusCode, Json)> { - let v3_schema = C::get_schema(&state.configuration).await.map_err(|err| { - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - details: None, - message: err.to_string(), - r#type: None, - }), - ) - })?; + let v3_schema = C::get_schema(&state.configuration) + .await + .and_then(JsonResponse::into_value) + .map_err(|err| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(ErrorResponse { + details: None, + message: err.to_string(), + r#type: None, + }), + ) + })?; let schema = map_schema(v3_schema).map_err(|err| (StatusCode::BAD_REQUEST, Json(err)))?; let schema = if let Some(request) = request { @@ -390,6 +406,7 @@ pub async fn post_query( let request = map_query_request(request).map_err(|err| (StatusCode::BAD_REQUEST, Json(err)))?; let response = C::query(&state.configuration, &state.state, request) .await + .and_then(JsonResponse::into_value) .map_err(|err| match err { QueryError::InvalidRequest(message) | QueryError::UnsupportedOperation(message) => ( StatusCode::BAD_REQUEST, @@ -408,8 +425,7 @@ pub async fn post_query( }), ), })?; - let response = map_query_response(response); - Ok(Json(response)) + Ok(Json(map_query_response(response))) } pub async fn post_explain( @@ -440,6 +456,7 @@ pub async fn post_explain( })?; let response = C::explain(&state.configuration, &state.state, request.clone()) .await + .and_then(JsonResponse::into_value) .map_err(|err| match err { ExplainError::InvalidRequest(message) | ExplainError::UnsupportedOperation(message) => { ( diff --git a/rust-connector-sdk/src/json_response.rs b/rust-connector-sdk/src/json_response.rs new file mode 100644 index 00000000..85be6e89 --- /dev/null +++ b/rust-connector-sdk/src/json_response.rs @@ -0,0 +1,122 @@ +use axum::response::IntoResponse; +use bytes::Bytes; +use http::{header, HeaderValue}; + +/// Represents a response value that will be serialized to JSON. +/// +/// The value may be of a type that implements `serde::Serialize`, or it may be +/// a contiguous sequence of bytes, which are _assumed_ to be valid JSON. +#[derive(Debug, Clone)] +pub enum JsonResponse { + /// A value that can be serialized to JSON. + Value(A), + /// A serialized JSON bytestring that is assumed to represent a value of + /// type `A`. This is not guaranteed by the SDK; the connector is + /// responsible for ensuring this. + Serialized(Bytes), +} + +impl From for JsonResponse { + fn from(value: A) -> Self { + Self::Value(value) + } +} + +impl serde::Deserialize<'de>)> JsonResponse { + /// Unwraps the value, deserializing if necessary. + /// + /// This is only intended for testing and compatibility. If it lives on a + /// critical path, we recommend you avoid it. + pub(crate) fn into_value>>( + self, + ) -> Result { + match self { + Self::Value(value) => Ok(value), + Self::Serialized(bytes) => { + serde_json::de::from_slice(&bytes).map_err(|err| E::from(Box::new(err))) + } + } + } +} + +impl IntoResponse for JsonResponse { + fn into_response(self) -> axum::response::Response { + match self { + Self::Value(value) => axum::Json(value).into_response(), + Self::Serialized(bytes) => ( + [( + header::CONTENT_TYPE, + HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()), + )], + bytes, + ) + .into_response(), + } + } +} + +#[cfg(test)] +mod tests { + use axum::{routing, Router}; + use axum_test_helper::TestClient; + use reqwest::StatusCode; + + use super::*; + + #[tokio::test] + async fn serializes_value_to_json() { + let app = Router::new().route( + "/", + routing::get(|| async { + JsonResponse::Value(Person { + name: "Alice Appleton".to_owned(), + age: 42, + }) + }), + ); + + let client = TestClient::new(app); + let response = client.get("/").send().await; + + assert_eq!(response.status(), StatusCode::OK); + + let headers = response.headers(); + assert_eq!( + headers.get_all("Content-Type").iter().collect::>(), + vec!["application/json"] + ); + + let body = response.text().await; + assert_eq!(body, r#"{"name":"Alice Appleton","age":42}"#); + } + + #[tokio::test] + async fn writes_json_string_directly() { + let app = Router::new().route( + "/", + routing::get(|| async { + JsonResponse::Serialized::(r#"{"name":"Bob Davis","age":7}"#.into()) + }), + ); + + let client = TestClient::new(app); + let response = client.get("/").send().await; + + assert_eq!(response.status(), StatusCode::OK); + + let headers = response.headers(); + assert_eq!( + headers.get_all("Content-Type").iter().collect::>(), + vec!["application/json"] + ); + + let body = response.text().await; + assert_eq!(body, r#"{"name":"Bob Davis","age":7}"#); + } + + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + struct Person { + name: String, + age: u16, + } +} diff --git a/rust-connector-sdk/src/lib.rs b/rust-connector-sdk/src/lib.rs index f5d504e2..940485b4 100644 --- a/rust-connector-sdk/src/lib.rs +++ b/rust-connector-sdk/src/lib.rs @@ -1,6 +1,7 @@ pub mod check_health; pub mod connector; pub mod default_main; +pub mod json_response; pub mod routes; pub mod tracing; diff --git a/rust-connector-sdk/src/routes.rs b/rust-connector-sdk/src/routes.rs index 8f5f4b03..c29ed9f5 100644 --- a/rust-connector-sdk/src/routes.rs +++ b/rust-connector-sdk/src/routes.rs @@ -2,7 +2,10 @@ use axum::{http::StatusCode, Json}; use ndc_client::models; use prometheus::{Registry, TextEncoder}; -use crate::connector::{Connector, HealthError}; +use crate::{ + connector::{Connector, HealthError}, + json_response::JsonResponse, +}; pub fn get_metrics( configuration: &C::Configuration, @@ -35,8 +38,8 @@ pub fn get_metrics( }) } -pub async fn get_capabilities() -> Json { - Json(C::get_capabilities().await) +pub async fn get_capabilities() -> JsonResponse { + C::get_capabilities().await } pub async fn get_health( @@ -58,10 +61,30 @@ pub async fn get_health( pub async fn get_schema( configuration: &C::Configuration, -) -> Result, (StatusCode, Json)> { - Ok(Json(C::get_schema(configuration).await.map_err( - |e| match e { - crate::connector::SchemaError::Other(err) => ( +) -> Result, (StatusCode, Json)> { + C::get_schema(configuration).await.map_err(|e| match e { + crate::connector::SchemaError::Other(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(models::ErrorResponse { + message: "Internal error".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "cause".into(), + serde_json::Value::String(err.to_string()), + )])), + }), + ), + }) +} + +pub async fn post_explain( + configuration: &C::Configuration, + state: &C::State, + Json(request): Json, +) -> Result, (StatusCode, Json)> { + C::explain(configuration, state, request) + .await + .map_err(|e| match e { + crate::connector::ExplainError::Other(err) => ( StatusCode::INTERNAL_SERVER_ERROR, Json(models::ErrorResponse { message: "Internal error".into(), @@ -71,155 +94,127 @@ pub async fn get_schema( )])), }), ), - }, - )?)) -} - -pub async fn post_explain( - configuration: &C::Configuration, - state: &C::State, - Json(request): Json, -) -> Result, (StatusCode, Json)> { - Ok(Json( - C::explain(configuration, state, request) - .await - .map_err(|e| match e { - crate::connector::ExplainError::Other(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(models::ErrorResponse { - message: "Internal error".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "cause".into(), - serde_json::Value::String(err.to_string()), - )])), - }), - ), - crate::connector::ExplainError::InvalidRequest(detail) => ( - StatusCode::BAD_REQUEST, - Json(models::ErrorResponse { - message: "Invalid request".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - crate::connector::ExplainError::UnsupportedOperation(detail) => ( - StatusCode::NOT_IMPLEMENTED, - Json(models::ErrorResponse { - message: "Unsupported operation".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - })?, - )) + crate::connector::ExplainError::InvalidRequest(detail) => ( + StatusCode::BAD_REQUEST, + Json(models::ErrorResponse { + message: "Invalid request".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + crate::connector::ExplainError::UnsupportedOperation(detail) => ( + StatusCode::NOT_IMPLEMENTED, + Json(models::ErrorResponse { + message: "Unsupported operation".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + }) } pub async fn post_mutation( configuration: &C::Configuration, state: &C::State, Json(request): Json, -) -> Result, (StatusCode, Json)> { - Ok(Json( - C::mutation(configuration, state, request) - .await - .map_err(|e| match e { - crate::connector::MutationError::Other(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(models::ErrorResponse { - message: "Internal error".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "cause".into(), - serde_json::Value::String(err.to_string()), - )])), - }), - ), - crate::connector::MutationError::InvalidRequest(detail) => ( - StatusCode::BAD_REQUEST, - Json(models::ErrorResponse { - message: "Invalid request".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - crate::connector::MutationError::UnsupportedOperation(detail) => ( - StatusCode::NOT_IMPLEMENTED, - Json(models::ErrorResponse { - message: "Unsupported operation".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - crate::connector::MutationError::Conflict(detail) => ( - StatusCode::CONFLICT, - Json(models::ErrorResponse { - message: "Request would create a conflicting state".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - crate::connector::MutationError::ConstraintNotMet(detail) => ( - StatusCode::FORBIDDEN, - Json(models::ErrorResponse { - message: "Constraint not met".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - })?, - )) +) -> Result, (StatusCode, Json)> { + C::mutation(configuration, state, request) + .await + .map_err(|e| match e { + crate::connector::MutationError::Other(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(models::ErrorResponse { + message: "Internal error".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "cause".into(), + serde_json::Value::String(err.to_string()), + )])), + }), + ), + crate::connector::MutationError::InvalidRequest(detail) => ( + StatusCode::BAD_REQUEST, + Json(models::ErrorResponse { + message: "Invalid request".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + crate::connector::MutationError::UnsupportedOperation(detail) => ( + StatusCode::NOT_IMPLEMENTED, + Json(models::ErrorResponse { + message: "Unsupported operation".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + crate::connector::MutationError::Conflict(detail) => ( + StatusCode::CONFLICT, + Json(models::ErrorResponse { + message: "Request would create a conflicting state".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + crate::connector::MutationError::ConstraintNotMet(detail) => ( + StatusCode::FORBIDDEN, + Json(models::ErrorResponse { + message: "Constraint not met".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + }) } pub async fn post_query( configuration: &C::Configuration, state: &C::State, Json(request): Json, -) -> Result, (StatusCode, Json)> { - Ok(Json( - C::query(configuration, state, request) - .await - .map_err(|e| match e { - crate::connector::QueryError::Other(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(models::ErrorResponse { - message: "Internal error".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "cause".into(), - serde_json::Value::String(err.to_string()), - )])), - }), - ), - crate::connector::QueryError::InvalidRequest(detail) => ( - StatusCode::BAD_REQUEST, - Json(models::ErrorResponse { - message: "Invalid request".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - crate::connector::QueryError::UnsupportedOperation(detail) => ( - StatusCode::NOT_IMPLEMENTED, - Json(models::ErrorResponse { - message: "Unsupported operation".into(), - details: serde_json::Value::Object(serde_json::Map::from_iter([( - "detail".into(), - serde_json::Value::String(detail), - )])), - }), - ), - })?, - )) +) -> Result, (StatusCode, Json)> { + C::query(configuration, state, request) + .await + .map_err(|e| match e { + crate::connector::QueryError::Other(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(models::ErrorResponse { + message: "Internal error".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "cause".into(), + serde_json::Value::String(err.to_string()), + )])), + }), + ), + crate::connector::QueryError::InvalidRequest(detail) => ( + StatusCode::BAD_REQUEST, + Json(models::ErrorResponse { + message: "Invalid request".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + crate::connector::QueryError::UnsupportedOperation(detail) => ( + StatusCode::NOT_IMPLEMENTED, + Json(models::ErrorResponse { + message: "Unsupported operation".into(), + details: serde_json::Value::Object(serde_json::Map::from_iter([( + "detail".into(), + serde_json::Value::String(detail), + )])), + }), + ), + }) }