From bbf7751143816c2a7e1dcc9f6d8d3a8315538a67 Mon Sep 17 00:00:00 2001 From: Samir Talwar Date: Tue, 10 Oct 2023 12:43:49 +0200 Subject: [PATCH] Introduce a "fast path" for returning already-serialized JSON. This introduces a new enum, `JsonResponse`, which is designed to supplant `axum::Json`. It implements `axum::IntoResponse`. It has two variants: 1. `Value`, which behaves the same as `axum::Json`. 2. `Serialized`, which allows the connector to provide already-serialized JSON in the form of a `Bytes` value. The latter can be used to construct the JSON directly in the database and avoid deserialization and re-serialization. This is a breaking change, as at the very least, the connector implementations will now need to wrap responses in `JsonResponse::Value`. Hopefully it's not too much of a big deal. There is a helper function, `JsonResponse::into_value`, used to deserialize the serialized bytes in certain situations. This is because the v2 compatibility layer and the connector used for ndc-test both require transforming the data, and so cannot work directly with bytes. We should not call this when running the connector normally, and it is not available outside this crate. --- Cargo.lock | 36 +++ rust-connector-sdk/Cargo.toml | 5 + rust-connector-sdk/src/connector.rs | 29 +- rust-connector-sdk/src/connector/example.rs | 18 +- rust-connector-sdk/src/default_main.rs | 40 ++- .../src/default_main/v2_compat.rs | 71 +++-- rust-connector-sdk/src/json_response.rs | 116 +++++++ rust-connector-sdk/src/lib.rs | 1 + rust-connector-sdk/src/routes.rs | 287 +++++++++--------- 9 files changed, 394 insertions(+), 209 deletions(-) create mode 100644 rust-connector-sdk/src/json_response.rs diff --git a/Cargo.lock b/Cargo.lock index d2c8341d..b3aaf2b5 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 6025433d..f7fa394d 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..a5438939 100644 --- a/rust-connector-sdk/src/connector/example.rs +++ b/rust-connector-sdk/src/connector/example.rs @@ -50,8 +50,8 @@ impl Connector for Example { Ok(()) } - async fn get_capabilities() -> models::CapabilitiesResponse { - models::CapabilitiesResponse { + async fn get_capabilities() -> JsonResponse { + JsonResponse::Value(models::CapabilitiesResponse { versions: "^0.1.0".into(), capabilities: models::Capabilities { explain: None, @@ -63,32 +63,32 @@ impl Connector for Example { relation_comparisons: None, }), }, - } + }) } async fn get_schema( _configuration: &Self::Configuration, - ) -> Result { + ) -> Result, SchemaError> { async { info_span!("inside tracing example"); } .instrument(info_span!("tracing example")) .await; - Ok(models::SchemaResponse { + Ok(JsonResponse::Value(models::SchemaResponse { collections: vec![], functions: vec![], procedures: vec![], object_types: BTreeMap::new(), scalar_types: BTreeMap::new(), - }) + })) } async fn explain( _configuration: &Self::Configuration, _state: &Self::State, _request: models::QueryRequest, - ) -> Result { + ) -> Result, ExplainError> { todo!() } @@ -96,7 +96,7 @@ impl Connector for Example { _configuration: &Self::Configuration, _state: &Self::State, _request: models::MutationRequest, - ) -> Result { + ) -> Result, MutationError> { todo!() } @@ -104,7 +104,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..cc96b4ac --- /dev/null +++ b/rust-connector-sdk/src/json_response.rs @@ -0,0 +1,116 @@ +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 serde::Deserialize<'de>)> { + /// 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 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 { + JsonResponse::Value(value) => Ok(value), + JsonResponse::Serialized(bytes) => { + serde_json::de::from_slice(&bytes).map_err(|err| E::from(Box::new(err))) + } + } + } +} + +impl serde::Deserialize<'de>)> IntoResponse for JsonResponse { + fn into_response(self) -> axum::response::Response { + match self { + JsonResponse::Value(value) => axum::Json(value).into_response(), + JsonResponse::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), + )])), + }), + ), + }) }