diff --git a/runtimes/core/src/api/endpoint.rs b/runtimes/core/src/api/endpoint.rs index 184606c43e..0fcb12accc 100644 --- a/runtimes/core/src/api/endpoint.rs +++ b/runtimes/core/src/api/endpoint.rs @@ -26,6 +26,8 @@ use crate::names::EndpointName; use crate::trace; use crate::{model, Hosted}; +use super::reqauth::caller::Caller; + #[derive(Debug)] pub struct SuccessResponse { pub status: axum::http::StatusCode, @@ -61,7 +63,7 @@ impl IntoResponse for SuccessResponse { Ok(()) => bld .body(axum::body::Body::from(buf.into_inner().freeze())) .unwrap(), - Err(err) => Error::internal(err).into_response(), + Err(err) => Error::internal(err).to_response(None), } } None => bld.body(axum::body::Body::empty()).unwrap(), @@ -69,22 +71,23 @@ impl IntoResponse for SuccessResponse { } } -impl AsRef for Error { - fn as_ref(&self) -> &Self { - self - } +pub trait ToResponse { + fn to_response(&self, caller: Option) -> axum::response::Response; } -impl IntoResponse for Error { - fn into_response(self) -> axum::http::Response { - self.as_ref().into_response() - } -} +impl ToResponse for Error { + fn to_response(&self, caller: Option) -> axum::http::Response { + // considure response to be external if caller is gateway, or if the caller is + // unknown + let internal_call = caller.map(|caller| !caller.is_gateway()).unwrap_or(false); -impl IntoResponse for &Error { - fn into_response(self) -> axum::http::Response { let mut buf = BytesMut::with_capacity(128).writer(); - serde_json::to_writer(&mut buf, &self).unwrap(); + + if internal_call { + serde_json::to_writer(&mut buf, &self).unwrap(); + } else { + serde_json::to_writer(&mut buf, &self.as_external()).unwrap(); + } axum::http::Response::builder() .status::(self.code.into()) @@ -498,9 +501,11 @@ impl EndpointHandler { Box::pin(async move { let request = match self.parse_request(axum_req).await { Ok(req) => req, - Err(err) => return err.into_response(), + Err(err) => return err.to_response(None), }; + let internal_caller = request.internal_caller.clone(); + // If the endpoint isn't exposed, return a 404. if !self.endpoint.exposed && !request.allows_private_endpoint_call() { return Error { @@ -510,7 +515,7 @@ impl EndpointHandler { stack: None, details: None, } - .into_response(); + .to_response(internal_caller); } else if self.endpoint.requires_auth && !request.has_authenticated_user() { return Error { code: ErrCode::Unauthenticated, @@ -519,7 +524,7 @@ impl EndpointHandler { stack: None, details: None, } - .into_response(); + .to_response(internal_caller); } let logger = crate::log::root(); @@ -576,13 +581,13 @@ impl EndpointHandler { self.endpoint .response .encode(&payload) - .unwrap_or_else(|err| err.into_response()), + .unwrap_or_else(|err| err.to_response(internal_caller)), Some(payload), None, ), ResponseData::Typed(Err(err)) => ( err.code.status_code().as_u16(), - err.as_ref().into_response(), + err.as_ref().to_response(internal_caller), None, Some(err), ), diff --git a/runtimes/core/src/api/error.rs b/runtimes/core/src/api/error.rs index 21bccfebd6..76ae84476a 100644 --- a/runtimes/core/src/api/error.rs +++ b/runtimes/core/src/api/error.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use crate::error::{AppError, StackTrace}; use axum::extract::ws::rejection::WebSocketUpgradeRejection; +use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; use serde_with::{DeserializeFromStr, SerializeDisplay}; @@ -18,7 +19,28 @@ pub struct Error { pub stack: Option, } +/// ErrorExternal hides internal information on `Error` when it serializes +#[derive(Debug)] +pub struct ExternalError<'a>(&'a Error); + +impl Serialize for ExternalError<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut error = serializer.serialize_struct("Error", 3)?; + error.serialize_field("code", &self.0.code)?; + error.serialize_field("message", &self.0.message)?; + error.serialize_field("details", &self.0.details)?; + error.end() + } +} + impl Error { + pub fn as_external(&self) -> ExternalError { + ExternalError(self) + } + pub fn internal(cause: E) -> Self where E: Into, @@ -129,6 +151,12 @@ impl Display for Error { } } +impl AsRef for Error { + fn as_ref(&self) -> &Self { + self + } +} + /// Represents an API Error. #[derive(SerializeDisplay, DeserializeFromStr, Debug, Copy, Clone, PartialEq, Eq)] pub enum ErrCode { diff --git a/runtimes/core/src/api/gateway/mod.rs b/runtimes/core/src/api/gateway/mod.rs index 2a27162523..16ed767656 100644 --- a/runtimes/core/src/api/gateway/mod.rs +++ b/runtimes/core/src/api/gateway/mod.rs @@ -460,7 +460,7 @@ fn as_api_error(err: &pingora::Error) -> Option<&api::Error> { fn api_error_response(err: &api::Error) -> (ResponseHeader, bytes::Bytes) { let mut buf = BytesMut::with_capacity(128).writer(); - serde_json::to_writer(&mut buf, &err).unwrap(); + serde_json::to_writer(&mut buf, &err.as_external()).unwrap(); let mut resp = ResponseHeader::build(err.code.status_code(), Some(5)).unwrap(); resp.insert_header(header::SERVER, &pingora::protocols::http::SERVER_NAME[..]) diff --git a/runtimes/core/src/api/manager.rs b/runtimes/core/src/api/manager.rs index 4abca662bf..9531d120b6 100644 --- a/runtimes/core/src/api/manager.rs +++ b/runtimes/core/src/api/manager.rs @@ -3,7 +3,6 @@ use std::future::{Future, IntoFuture}; use std::sync::{Arc, Mutex}; use anyhow::Context; -use axum::response::IntoResponse; use crate::api::auth::{LocalAuthHandler, RemoteAuthHandler}; use crate::api::call::ServiceRegistry; @@ -15,7 +14,7 @@ use crate::api::schema::encoding::EncodingConfig; use crate::api::schema::JSONPayload; use crate::api::{ auth, cors, encore_routes, endpoints_from_meta, jsonschema, paths, reqauth, server, APIResult, - Endpoint, + Endpoint, ToResponse, }; use crate::encore::parser::meta::v1 as meta; use crate::encore::runtime::v1 as runtime; @@ -342,7 +341,7 @@ impl Manager { stack: None, details: None, } - .into_response() + .to_response(None) } let encore_routes = encore_routes::Desc { diff --git a/runtimes/core/src/api/reqauth/caller.rs b/runtimes/core/src/api/reqauth/caller.rs index 3fdb5ec172..0da03142af 100644 --- a/runtimes/core/src/api/reqauth/caller.rs +++ b/runtimes/core/src/api/reqauth/caller.rs @@ -45,6 +45,10 @@ impl Caller { Gateway { .. } => false, } } + + pub fn is_gateway(&self) -> bool { + matches!(&self, Caller::Gateway { .. }) + } } impl FromStr for Caller { diff --git a/runtimes/core/src/api/server.rs b/runtimes/core/src/api/server.rs index ac36ed370b..eb4e4d8b43 100644 --- a/runtimes/core/src/api/server.rs +++ b/runtimes/core/src/api/server.rs @@ -4,13 +4,11 @@ use std::future::Future; use std::pin::Pin; use std::sync::{Arc, Mutex, RwLock}; -use axum::response::IntoResponse; - -use crate::api; use crate::api::endpoint::{EndpointHandler, SharedEndpointData}; use crate::api::paths::Pather; use crate::api::reqauth::svcauth; use crate::api::static_assets::StaticAssetsHandler; +use crate::api::{self, ToResponse}; use crate::api::{paths, reqauth, schema, BoxedHandler, EndpointMap}; use crate::encore::parser::meta::v1 as meta; use crate::names::EndpointName; @@ -60,7 +58,7 @@ impl Server { stack: None, details: None, } - .into_response() + .to_response(None) } let mut fallback_router = axum::Router::new(); @@ -256,7 +254,7 @@ where stack: None, details: None, } - .into_response(); + .to_response(None); std::task::Poll::Ready(resp) } } diff --git a/runtimes/core/src/pubsub/gcp/push_sub.rs b/runtimes/core/src/pubsub/gcp/push_sub.rs index ad4b76bbf6..544c13eab3 100644 --- a/runtimes/core/src/pubsub/gcp/push_sub.rs +++ b/runtimes/core/src/pubsub/gcp/push_sub.rs @@ -5,13 +5,12 @@ use std::sync::{Arc, RwLock}; use anyhow::{Context, Result}; use axum::extract::Request; -use axum::response::IntoResponse; use axum::RequestExt; use chrono::{DateTime, Utc}; use http_body_util::BodyExt; use serde::Deserialize; -use crate::api::{self, APIResult}; +use crate::api::{self, APIResult, ToResponse}; use crate::encore::runtime::v1 as pb; use crate::pubsub::manager::SubHandler; use crate::pubsub::{self, MessageId}; @@ -96,7 +95,7 @@ impl pubsub::PushRequestHandler for PushHandler { Ok(()) => axum::response::Response::new(axum::body::Body::empty()), Err(e) => { log::error!("push handler returned error: {:?}", e); - e.into_response() + e.to_response(None) } } }) diff --git a/runtimes/core/src/pubsub/push_registry.rs b/runtimes/core/src/pubsub/push_registry.rs index 2fe727728c..0d7d6e063c 100644 --- a/runtimes/core/src/pubsub/push_registry.rs +++ b/runtimes/core/src/pubsub/push_registry.rs @@ -1,10 +1,10 @@ use std::{collections::HashMap, pin::Pin, sync::Arc}; -use axum::{extract::Path, response::IntoResponse, RequestExt}; +use axum::{extract::Path, RequestExt}; use futures::Future; use std::sync::RwLock; -use crate::api::{self}; +use crate::api::{self, ToResponse}; use super::PushRequestHandler; @@ -54,7 +54,7 @@ impl PushHandlerRegistry { "encore pub/sub push handler: unable to extract push id from path: {:?}", e ); - return api::Error::internal(e).into_response(); + return api::Error::internal(e).to_response(None); } }; @@ -64,9 +64,8 @@ impl PushHandlerRegistry { match handler { Some(handler) => handler.handle_push(req).await, - None => { - api::Error::not_found("no handler registered for push subscription").into_response() - } + None => api::Error::not_found("no handler registered for push subscription") + .to_response(None), } } } diff --git a/runtimes/js/src/raw_api.rs b/runtimes/js/src/raw_api.rs index dd2bb75322..2e822625b2 100644 --- a/runtimes/js/src/raw_api.rs +++ b/runtimes/js/src/raw_api.rs @@ -5,14 +5,13 @@ use std::sync::Arc; use axum::body::Body; use axum::http::{Response, StatusCode}; -use axum::response::IntoResponse; use bytes::Bytes; use napi::bindgen_prelude::{Buffer, Either3}; use napi::{Either, Env, JsFunction, JsUnknown, NapiRaw}; use napi_derive::napi; use tokio::sync::{mpsc, oneshot}; -use encore_runtime_core::api; +use encore_runtime_core::api::{self, ToResponse}; use crate::api::Request; use crate::error::coerce_to_api_error; @@ -343,9 +342,11 @@ impl api::BoxedHandler for JSRawHandler { Box::pin(async move { let (body_tx, mut body_rx) = oneshot::channel(); + let internal_caller = req.internal_caller.clone(); + let Some(body) = req.take_raw_body() else { let err = api::Error::internal(anyhow::anyhow!("missing body")); - return api::ResponseData::Raw(err.into_response()); + return api::ResponseData::Raw(err.to_response(internal_caller)); }; // Call the handler. @@ -374,20 +375,20 @@ impl api::BoxedHandler for JSRawHandler { Ok(resp) => resp, Err(_) => { let err_resp = api::Error::internal(anyhow::anyhow!("handler did not respond")); - err_resp.into_response() + err_resp.to_response(internal_caller) } } } err = err_rx.recv() => { match err { - Some(Err(err)) => err.into_response(), + Some(Err(err)) => err.to_response(internal_caller), _ => { // We didn't get an error. Wait for the response body instead. match body_rx.await { Ok(resp) => resp, Err(_) => { let err_resp = api::Error::internal(anyhow::anyhow!("handler did not respond")); - err_resp.into_response() + err_resp.to_response(internal_caller) } } } diff --git a/runtimes/js/src/websocket_api.rs b/runtimes/js/src/websocket_api.rs index 51ae9210a7..124eb75a44 100644 --- a/runtimes/js/src/websocket_api.rs +++ b/runtimes/js/src/websocket_api.rs @@ -4,10 +4,9 @@ use crate::napi_util::PromiseHandler; use crate::threadsafe_function::{ ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, }; -use axum::response::IntoResponse; use encore_runtime_core::api::websocket::StreamMessagePayload; -use encore_runtime_core::api::websocket_client; use encore_runtime_core::api::{self, schema, APIResult, HandlerRequest}; +use encore_runtime_core::api::{websocket_client, ToResponse}; use napi::{Env, JsFunction, NapiRaw}; use napi_derive::napi; use std::future::Future; @@ -30,6 +29,7 @@ impl api::BoxedHandler for JSWebSocketHandler { req: HandlerRequest, ) -> Pin + Send + 'static>> { Box::pin(async move { + let internal_caller = req.internal_caller.clone(); let resp = api::websocket::upgrade_request(req, |req, payload, tx| async move { self.handler.call( WsRequestMessage { @@ -43,7 +43,7 @@ impl api::BoxedHandler for JSWebSocketHandler { match resp { Ok(resp) => api::ResponseData::Raw(resp), - Err(e) => api::ResponseData::Raw(e.into_response()), + Err(e) => api::ResponseData::Raw(e.to_response(internal_caller)), } }) }