diff --git a/test-files/foo/index.html b/test-files/foo/index.html
new file mode 100644
index 00000000..ef2c5cc2
--- /dev/null
+++ b/test-files/foo/index.html
@@ -0,0 +1 @@
+HTML!
diff --git a/tower-http/Cargo.toml b/tower-http/Cargo.toml
index 0cb95bf4..9a23c707 100644
--- a/tower-http/Cargo.toml
+++ b/tower-http/Cargo.toml
@@ -30,7 +30,7 @@ http-body-util = { version = "0.1.0", optional = true }
http-range-header = { version = "0.4.0", optional = true }
mime = { version = "0.3.17", optional = true, default-features = false }
mime_guess = { version = "2", optional = true, default-features = false }
-percent-encoding = { version = "2.1.0", optional = true }
+percent-encoding = { version = "2.1.0" }
url = { version = "2.5", optional = true }
tokio = { version = "1.6", optional = true, default-features = false }
tokio-util = { version = "0.7", optional = true, default-features = false, features = ["io"] }
@@ -90,7 +90,7 @@ auth = ["base64", "validate-request"]
catch-panic = ["tracing", "futures-util/std", "dep:http-body", "dep:http-body-util"]
cors = []
follow-redirect = ["futures-util", "dep:http-body", "dep:url", "tower/util"]
-fs = ["dep:tokio", "tokio?/fs", "tokio?/io-util", "futures-core", "futures-util", "dep:http-body", "dep:http-body-util", "tokio-util/io", "dep:http-range-header", "mime_guess", "mime", "percent-encoding", "httpdate", "set-status", "futures-util/alloc"]
+fs = ["dep:tokio", "tokio?/fs", "tokio?/io-util", "futures-core", "futures-util", "dep:http-body", "dep:http-body-util", "tokio-util/io", "dep:http-range-header", "mime_guess", "mime", "httpdate", "set-status", "futures-util/alloc"]
limit = ["dep:http-body", "dep:http-body-util"]
map-request-body = []
map-response-body = []
diff --git a/tower-http/src/classify/grpc_errors_as_failures.rs b/tower-http/src/classify/grpc_errors_as_failures.rs
index 417a884a..5e06d390 100644
--- a/tower-http/src/classify/grpc_errors_as_failures.rs
+++ b/tower-http/src/classify/grpc_errors_as_failures.rs
@@ -1,6 +1,7 @@
use super::{ClassifiedResponse, ClassifyEos, ClassifyResponse, SharedClassifier};
use bitflags::bitflags;
use http::{HeaderMap, Response};
+use percent_encoding::percent_decode;
use std::{fmt, num::NonZeroI32};
/// gRPC status codes.
@@ -8,42 +9,44 @@ use std::{fmt, num::NonZeroI32};
/// These variants match the [gRPC status codes].
///
/// [gRPC status codes]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md#status-codes-and-their-use-in-grpc
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(i32)]
+#[non_exhaustive]
pub enum GrpcCode {
/// The operation completed successfully.
- Ok,
+ Ok = 0,
/// The operation was cancelled.
- Cancelled,
+ Cancelled = 1,
/// Unknown error.
- Unknown,
+ Unknown = 2,
/// Client specified an invalid argument.
- InvalidArgument,
+ InvalidArgument = 3,
/// Deadline expired before operation could complete.
- DeadlineExceeded,
+ DeadlineExceeded = 4,
/// Some requested entity was not found.
- NotFound,
+ NotFound = 5,
/// Some entity that we attempted to create already exists.
- AlreadyExists,
+ AlreadyExists = 6,
/// The caller does not have permission to execute the specified operation.
- PermissionDenied,
+ PermissionDenied = 7,
/// Some resource has been exhausted.
- ResourceExhausted,
+ ResourceExhausted = 8,
/// The system is not in a state required for the operation's execution.
- FailedPrecondition,
+ FailedPrecondition = 9,
/// The operation was aborted.
- Aborted,
+ Aborted = 10,
/// Operation was attempted past the valid range.
- OutOfRange,
+ OutOfRange = 11,
/// Operation is not implemented or not supported.
- Unimplemented,
+ Unimplemented = 12,
/// Internal error.
- Internal,
+ Internal = 13,
/// The service is currently unavailable.
- Unavailable,
+ Unavailable = 14,
/// Unrecoverable data loss or corruption.
- DataLoss,
+ DataLoss = 15,
/// The request does not have valid authentication credentials
- Unauthenticated,
+ Unauthenticated = 16,
}
impl GrpcCode {
@@ -68,6 +71,29 @@ impl GrpcCode {
Self::Unauthenticated => GrpcCodeBitmask::UNAUTHENTICATED,
}
}
+
+ fn from_i32(code: i32) -> Option {
+ match code {
+ 0 => Some(GrpcCode::Ok),
+ 1 => Some(GrpcCode::Cancelled),
+ 2 => Some(GrpcCode::Unknown),
+ 3 => Some(GrpcCode::InvalidArgument),
+ 4 => Some(GrpcCode::DeadlineExceeded),
+ 5 => Some(GrpcCode::NotFound),
+ 6 => Some(GrpcCode::AlreadyExists),
+ 7 => Some(GrpcCode::PermissionDenied),
+ 8 => Some(GrpcCode::ResourceExhausted),
+ 9 => Some(GrpcCode::FailedPrecondition),
+ 10 => Some(GrpcCode::Aborted),
+ 11 => Some(GrpcCode::OutOfRange),
+ 12 => Some(GrpcCode::Unimplemented),
+ 13 => Some(GrpcCode::Internal),
+ 14 => Some(GrpcCode::Unavailable),
+ 15 => Some(GrpcCode::DataLoss),
+ 16 => Some(GrpcCode::Unauthenticated),
+ _ => None,
+ }
+ }
}
/// Converts an `i32` gRPC status code into a [`GrpcCode`].
@@ -128,27 +154,26 @@ bitflags! {
}
}
-impl GrpcCodeBitmask {
- fn try_from_u32(code: u32) -> Option {
+impl From for GrpcCodeBitmask {
+ fn from(code: GrpcCode) -> Self {
match code {
- 0 => Some(Self::OK),
- 1 => Some(Self::CANCELLED),
- 2 => Some(Self::UNKNOWN),
- 3 => Some(Self::INVALID_ARGUMENT),
- 4 => Some(Self::DEADLINE_EXCEEDED),
- 5 => Some(Self::NOT_FOUND),
- 6 => Some(Self::ALREADY_EXISTS),
- 7 => Some(Self::PERMISSION_DENIED),
- 8 => Some(Self::RESOURCE_EXHAUSTED),
- 9 => Some(Self::FAILED_PRECONDITION),
- 10 => Some(Self::ABORTED),
- 11 => Some(Self::OUT_OF_RANGE),
- 12 => Some(Self::UNIMPLEMENTED),
- 13 => Some(Self::INTERNAL),
- 14 => Some(Self::UNAVAILABLE),
- 15 => Some(Self::DATA_LOSS),
- 16 => Some(Self::UNAUTHENTICATED),
- _ => None,
+ GrpcCode::Ok => GrpcCodeBitmask::OK,
+ GrpcCode::Cancelled => GrpcCodeBitmask::CANCELLED,
+ GrpcCode::Unknown => GrpcCodeBitmask::UNKNOWN,
+ GrpcCode::InvalidArgument => GrpcCodeBitmask::INVALID_ARGUMENT,
+ GrpcCode::DeadlineExceeded => GrpcCodeBitmask::DEADLINE_EXCEEDED,
+ GrpcCode::NotFound => GrpcCodeBitmask::NOT_FOUND,
+ GrpcCode::AlreadyExists => GrpcCodeBitmask::ALREADY_EXISTS,
+ GrpcCode::PermissionDenied => GrpcCodeBitmask::PERMISSION_DENIED,
+ GrpcCode::ResourceExhausted => GrpcCodeBitmask::RESOURCE_EXHAUSTED,
+ GrpcCode::FailedPrecondition => GrpcCodeBitmask::FAILED_PRECONDITION,
+ GrpcCode::Aborted => GrpcCodeBitmask::ABORTED,
+ GrpcCode::OutOfRange => GrpcCodeBitmask::OUT_OF_RANGE,
+ GrpcCode::Unimplemented => GrpcCodeBitmask::UNIMPLEMENTED,
+ GrpcCode::Internal => GrpcCodeBitmask::INTERNAL,
+ GrpcCode::Unavailable => GrpcCodeBitmask::UNAVAILABLE,
+ GrpcCode::DataLoss => GrpcCodeBitmask::DATA_LOSS,
+ GrpcCode::Unauthenticated => GrpcCodeBitmask::UNAUTHENTICATED,
}
}
}
@@ -225,11 +250,11 @@ impl ClassifyResponse for GrpcErrorsAsFailures {
res: &Response,
) -> ClassifiedResponse {
match classify_grpc_metadata(res.headers(), self.success_codes) {
- ParsedGrpcStatus::Success
- | ParsedGrpcStatus::HeaderNotString
- | ParsedGrpcStatus::HeaderNotInt => ClassifiedResponse::Ready(Ok(())),
+ ParsedGrpcStatus::Success | ParsedGrpcStatus::HeaderNotGrpcCode => {
+ ClassifiedResponse::Ready(Ok(()))
+ }
ParsedGrpcStatus::NonSuccess(status) => {
- ClassifiedResponse::Ready(Err(GrpcFailureClass::Code(status)))
+ ClassifiedResponse::Ready(Err(GrpcFailureClass::Status(status)))
}
ParsedGrpcStatus::GrpcStatusHeaderMissing => {
ClassifiedResponse::RequiresEos(GrpcEosErrorsAsFailures {
@@ -261,9 +286,8 @@ impl ClassifyEos for GrpcEosErrorsAsFailures {
match classify_grpc_metadata(trailers, self.success_codes) {
ParsedGrpcStatus::Success
| ParsedGrpcStatus::GrpcStatusHeaderMissing
- | ParsedGrpcStatus::HeaderNotString
- | ParsedGrpcStatus::HeaderNotInt => Ok(()),
- ParsedGrpcStatus::NonSuccess(status) => Err(GrpcFailureClass::Code(status)),
+ | ParsedGrpcStatus::HeaderNotGrpcCode => Ok(()),
+ ParsedGrpcStatus::NonSuccess(status) => Err(GrpcFailureClass::Status(status)),
}
} else {
Ok(())
@@ -280,9 +304,10 @@ impl ClassifyEos for GrpcEosErrorsAsFailures {
/// The failure class for [`GrpcErrorsAsFailures`].
#[derive(Debug)]
+#[non_exhaustive]
pub enum GrpcFailureClass {
/// A gRPC response was classified as a failure with the corresponding status.
- Code(std::num::NonZeroI32),
+ Status(GrpcStatus),
/// A gRPC response was classified as an error with the corresponding error description.
Error(String),
}
@@ -290,12 +315,16 @@ pub enum GrpcFailureClass {
impl fmt::Display for GrpcFailureClass {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
- Self::Code(code) => write!(f, "Code: {}", code),
+ Self::Status(status) => {
+ write!(f, "Status: {}", status)
+ }
Self::Error(error) => write!(f, "Error: {}", error),
}
}
}
+impl std::error::Error for GrpcFailureClass {}
+
pub(crate) fn classify_grpc_metadata(
headers: &HeaderMap,
success_codes: GrpcCodeBitmask,
@@ -310,28 +339,77 @@ pub(crate) fn classify_grpc_metadata(
};
}
- let status = or_else!(headers.get("grpc-status"), GrpcStatusHeaderMissing);
- let status = or_else!(status.to_str().ok(), HeaderNotString);
- let status = or_else!(status.parse::().ok(), HeaderNotInt);
+ let code_header = or_else!(headers.get("grpc-status"), GrpcStatusHeaderMissing);
+ let code_value: i32 = or_else!(
+ code_header.to_str().ok().and_then(|s| s.parse().ok()),
+ HeaderNotGrpcCode
+ );
+ let grpc_code = GrpcCode::from_i32(code_value);
- if GrpcCodeBitmask::try_from_u32(status as _)
- .filter(|code| success_codes.contains(*code))
- .is_some()
- {
- ParsedGrpcStatus::Success
- } else {
- ParsedGrpcStatus::NonSuccess(NonZeroI32::new(status).unwrap())
+ if let Some(code) = grpc_code {
+ if success_codes.contains(GrpcCodeBitmask::from(code)) {
+ return ParsedGrpcStatus::Success;
+ }
+ }
+
+ let message = headers.get("grpc-message").map(|header| {
+ percent_decode(header.as_bytes())
+ .decode_utf8_lossy()
+ .into_owned()
+ });
+
+ ParsedGrpcStatus::NonSuccess(GrpcStatus {
+ code: grpc_code,
+ code_raw: code_value,
+ message,
+ })
+}
+
+/// A gRPC status extracted from response headers/trailers.
+#[derive(Debug, PartialEq, Eq)]
+pub struct GrpcStatus {
+ code: Option,
+ code_raw: i32,
+ message: Option,
+}
+
+impl GrpcStatus {
+ /// Returns the status code as a [`GrpcCode`], or `None` if the code is not recognized.
+ pub fn code(&self) -> Option {
+ self.code
+ }
+
+ /// Returns the raw integer status code.
+ pub fn code_raw(&self) -> i32 {
+ self.code_raw
+ }
+
+ /// Returns the percent-decoded gRPC error message, if present.
+ pub fn message(&self) -> Option<&str> {
+ self.message.as_deref()
+ }
+}
+
+impl fmt::Display for GrpcStatus {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.code {
+ Some(code) => write!(f, "{:?}", code)?,
+ None => write!(f, "Code({})", self.code_raw)?,
+ }
+ if let Some(message) = self.message.as_ref() {
+ write!(f, ": {}", message)?;
+ }
+ Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum ParsedGrpcStatus {
Success,
- NonSuccess(NonZeroI32),
+ NonSuccess(GrpcStatus),
GrpcStatusHeaderMissing,
- // these two are treated as `Success` but kept separate for clarity
- HeaderNotString,
- HeaderNotInt,
+ // this is treated as `Success` but kept separate for clarity
+ HeaderNotGrpcCode,
}
#[cfg(test)]
@@ -344,11 +422,29 @@ mod tests {
status: $status:expr,
success_flags: $success_flags:expr,
expected: $expected:expr,
+ ) => {
+ classify_grpc_metadata_test!(
+ name: $name,
+ status: $status,
+ message: "",
+ success_flags: $success_flags,
+ expected: $expected,
+ );
+ };
+ (
+ name: $name:ident,
+ status: $status:expr,
+ message: $message:expr,
+ success_flags: $success_flags:expr,
+ expected: $expected:expr,
) => {
#[test]
fn $name() {
let mut headers = HeaderMap::new();
headers.insert("grpc-status", $status.parse().unwrap());
+ if !$message.is_empty() {
+ headers.insert("grpc-message", $message.parse().unwrap());
+ }
let status = classify_grpc_metadata(&headers, $success_flags);
assert_eq!(status, $expected);
}
@@ -366,7 +462,11 @@ mod tests {
name: basic_error,
status: "1",
success_flags: GrpcCodeBitmask::OK,
- expected: ParsedGrpcStatus::NonSuccess(NonZeroI32::new(1).unwrap()),
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: Some(GrpcCode::Cancelled),
+ code_raw: 1,
+ message: None,
+ }),
}
classify_grpc_metadata_test! {
@@ -386,8 +486,109 @@ mod tests {
classify_grpc_metadata_test! {
name: two_success_codes_none_matches,
status: "16",
+ message: "mock message",
success_flags: GrpcCodeBitmask::OK | GrpcCodeBitmask::INVALID_ARGUMENT,
- expected: ParsedGrpcStatus::NonSuccess(NonZeroI32::new(16).unwrap()),
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: Some(GrpcCode::Unauthenticated),
+ code_raw: 16,
+ message: Some("mock message".to_string()),
+ }),
+ }
+
+ classify_grpc_metadata_test! {
+ name: percent_encoded_message,
+ status: "2",
+ message: "hello%20world",
+ success_flags: GrpcCodeBitmask::OK,
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: Some(GrpcCode::Unknown),
+ code_raw: 2,
+ message: Some("hello world".to_string()),
+ }),
+ }
+
+ classify_grpc_metadata_test! {
+ name: invalid_percent_encoding,
+ status: "13",
+ message: "bad%2Gencode",
+ success_flags: GrpcCodeBitmask::OK,
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: Some(GrpcCode::Internal),
+ code_raw: 13,
+ message: Some("bad%2Gencode".to_string()),
+ }),
+ }
+
+ classify_grpc_metadata_test! {
+ name: empty_grpc_message,
+ status: "5",
+ message: "",
+ success_flags: GrpcCodeBitmask::OK,
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: Some(GrpcCode::NotFound),
+ code_raw: 5,
+ message: None,
+ }),
+ }
+
+ classify_grpc_metadata_test! {
+ name: unknown_status_code_above_16,
+ status: "99",
+ message: "custom error",
+ success_flags: GrpcCodeBitmask::OK,
+ expected: ParsedGrpcStatus::NonSuccess(GrpcStatus{
+ code: None,
+ code_raw: 99,
+ message: Some("custom error".to_string()),
+ }),
+ }
+
+ #[test]
+ fn invalid_utf8_after_percent_decode() {
+ let mut headers = HeaderMap::new();
+ headers.insert("grpc-status", "2".parse().unwrap());
+ // %80 is an invalid UTF-8 start byte; lossy decode replaces it with U+FFFD
+ headers.insert("grpc-message", "bad%80byte".parse().unwrap());
+ let status = classify_grpc_metadata(&headers, GrpcCodeBitmask::OK);
+ assert_eq!(
+ status,
+ ParsedGrpcStatus::NonSuccess(GrpcStatus {
+ code: Some(GrpcCode::Unknown),
+ code_raw: 2,
+ message: Some("bad\u{FFFD}byte".to_string()),
+ })
+ );
+ }
+
+ #[test]
+ fn valid_utf8_percent_encoded() {
+ let mut headers = HeaderMap::new();
+ headers.insert("grpc-status", "3".parse().unwrap());
+ // %C3%A9 is the percent-encoded form of 'é' (U+00E9) in UTF-8
+ headers.insert("grpc-message", "caf%C3%A9".parse().unwrap());
+ let status = classify_grpc_metadata(&headers, GrpcCodeBitmask::OK);
+ assert_eq!(
+ status,
+ ParsedGrpcStatus::NonSuccess(GrpcStatus {
+ code: Some(GrpcCode::InvalidArgument),
+ code_raw: 3,
+ message: Some("café".to_string()),
+ })
+ );
+ }
+
+ #[test]
+ fn grpc_ok_classified_as_success() {
+ use http::Response;
+
+ let res = Response::builder()
+ .header("grpc-status", "0")
+ .body(())
+ .unwrap();
+
+ let classifier = GrpcErrorsAsFailures::new();
+ let result = classifier.classify_response(&res);
+ assert!(matches!(result, ClassifiedResponse::Ready(Ok(()))));
}
#[test]
diff --git a/tower-http/src/classify/mod.rs b/tower-http/src/classify/mod.rs
index a3147843..49a203f2 100644
--- a/tower-http/src/classify/mod.rs
+++ b/tower-http/src/classify/mod.rs
@@ -9,7 +9,7 @@ mod status_in_range_is_error;
pub use self::{
grpc_errors_as_failures::{
- GrpcCode, GrpcEosErrorsAsFailures, GrpcErrorsAsFailures, GrpcFailureClass,
+ GrpcCode, GrpcEosErrorsAsFailures, GrpcErrorsAsFailures, GrpcFailureClass, GrpcStatus,
},
map_failure_class::MapFailureClass,
status_in_range_is_error::{StatusInRangeAsFailures, StatusInRangeFailureClass},
diff --git a/tower-http/src/services/fs/serve_dir/open_file.rs b/tower-http/src/services/fs/serve_dir/open_file.rs
index 9dd1e012..ff1d7e46 100644
--- a/tower-http/src/services/fs/serve_dir/open_file.rs
+++ b/tower-http/src/services/fs/serve_dir/open_file.rs
@@ -121,6 +121,7 @@ pub(super) async fn open_file(
};
let meta = file.metadata().await?;
+
let last_modified = meta.modified().ok().map(LastModified::from);
if let Some(output) = check_modified_headers(
last_modified.as_ref(),
@@ -283,7 +284,15 @@ async fn maybe_redirect_or_append_path(
uri: &Uri,
append_index_html_on_directories: bool,
) -> Option {
- if !is_dir(path_to_file).await {
+ let uri_path = uri.path();
+
+ let is_directory = is_dir(path_to_file).await;
+
+ if uri_path.ends_with('/') && uri_path != "/" && !is_directory {
+ return Some(OpenFileOutput::FileNotFound);
+ }
+
+ if !is_directory {
return None;
}
@@ -291,7 +300,7 @@ async fn maybe_redirect_or_append_path(
return Some(OpenFileOutput::FileNotFound);
}
- if uri.path().ends_with('/') {
+ if uri_path.ends_with('/') {
path_to_file.push("index.html");
None
} else {
diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs
index e0680be9..3023bdf8 100644
--- a/tower-http/src/services/fs/serve_dir/tests.rs
+++ b/tower-http/src/services/fs/serve_dir/tests.rs
@@ -893,6 +893,72 @@ async fn calls_fallback_on_null() {
assert_eq!(res.headers()["from-fallback"], "1");
}
+#[tokio::test]
+async fn not_found_when_file_requested_with_trailing_slash() {
+ let svc = ServeDir::new("../test-files");
+
+ let req = Request::builder()
+ .uri("/index.html/")
+ .body(Body::empty())
+ .unwrap();
+ let res = svc.oneshot(req).await.unwrap();
+
+ assert_eq!(res.status(), StatusCode::NOT_FOUND);
+ assert!(res.headers().get(header::CONTENT_TYPE).is_none());
+
+ let body = body_into_text(res.into_body()).await;
+ assert!(body.is_empty());
+}
+
+#[tokio::test]
+async fn file_requested_with_trailing_slash_with_fallback() {
+ async fn fallback(req: Request) -> Result, Infallible> {
+ Ok(Response::new(Body::from(format!(
+ "from fallback {}",
+ req.uri().path()
+ ))))
+ }
+
+ let svc = ServeDir::new("../test-files").fallback(tower::service_fn(fallback));
+
+ let req = Request::builder()
+ .uri("/index.html/")
+ .body(Body::empty())
+ .unwrap();
+ let res = svc.oneshot(req).await.unwrap();
+
+ assert_eq!(res.status(), StatusCode::OK);
+
+ let body = body_into_text(res.into_body()).await;
+ assert_eq!(body, "from fallback /index.html/");
+}
+
+#[tokio::test]
+async fn directory_with_trailing_slash_appends_index_html() {
+ let svc = ServeDir::new("../test-files").append_index_html_on_directories(true);
+ let req = Request::builder().uri("/foo/").body(Body::empty()).unwrap();
+
+ let res = svc.oneshot(req).await.unwrap();
+
+ assert_eq!(res.status(), StatusCode::OK);
+ assert_eq!(res.headers()["content-type"], "text/html");
+ let body = body_into_text(res.into_body()).await;
+ assert_eq!(body, "HTML!\n");
+}
+
+#[tokio::test]
+async fn root_with_trailing_slash_serves_appends_index_html() {
+ let svc = ServeDir::new("../test-files").append_index_html_on_directories(true);
+ let req = Request::builder().uri("/").body(Body::empty()).unwrap();
+
+ let res = svc.oneshot(req).await.unwrap();
+
+ assert_eq!(res.status(), StatusCode::OK);
+ assert_eq!(res.headers()["content-type"], "text/html");
+ let body = body_into_text(res.into_body()).await;
+ assert_eq!(body, "HTML!\n");
+}
+
#[cfg(windows)]
fn verify_windows_device(name: &str, is_positive: bool) {
use std::fs::OpenOptions;
diff --git a/tower-http/src/trace/on_eos.rs b/tower-http/src/trace/on_eos.rs
index ab90fc9c..95788d7e 100644
--- a/tower-http/src/trace/on_eos.rs
+++ b/tower-http/src/trace/on_eos.rs
@@ -94,10 +94,8 @@ impl OnEos for DefaultOnEos {
trailers,
crate::classify::GrpcCode::Ok.into_bitmask(),
) {
- ParsedGrpcStatus::Success
- | ParsedGrpcStatus::HeaderNotString
- | ParsedGrpcStatus::HeaderNotInt => Some(0),
- ParsedGrpcStatus::NonSuccess(status) => Some(status.get()),
+ ParsedGrpcStatus::Success | ParsedGrpcStatus::HeaderNotGrpcCode => Some(0),
+ ParsedGrpcStatus::NonSuccess(status) => Some(status.code_raw()),
ParsedGrpcStatus::GrpcStatusHeaderMissing => None,
}
});
diff --git a/tower-http/src/trace/on_response.rs b/tower-http/src/trace/on_response.rs
index c6ece840..edcf498b 100644
--- a/tower-http/src/trace/on_response.rs
+++ b/tower-http/src/trace/on_response.rs
@@ -147,10 +147,8 @@ fn status(res: &Response) -> Option {
res.headers(),
crate::classify::GrpcCode::Ok.into_bitmask(),
) {
- ParsedGrpcStatus::Success
- | ParsedGrpcStatus::HeaderNotString
- | ParsedGrpcStatus::HeaderNotInt => Some(0),
- ParsedGrpcStatus::NonSuccess(status) => Some(status.get()),
+ ParsedGrpcStatus::Success | ParsedGrpcStatus::HeaderNotGrpcCode => Some(0),
+ ParsedGrpcStatus::NonSuccess(status) => Some(status.code_raw()),
// if `grpc-status` is missing then its a streaming response and there is no status
// _yet_, so its neither success nor error
ParsedGrpcStatus::GrpcStatusHeaderMissing => None,