Skip to content

Commit eeba83e

Browse files
committed
feat(grpc): introduce Status and ServerStatus types
Introduces the foundational `Status` and `ServerStatus` types for gRPC status handling. * `Status`: Represents a standard gRPC status with a code and message. * `ServerStatus`: A wrapper around `Status` designed to prevent accidental leakage of sensitive server errors to the client. This type distinction ensures that handlers must explicitly convert to `Status`, encouraging a conscious decision about what information is exposed. * `StatusCode`: Enum representing standard gRPC status codes. This implementation provides the necessary types to unblock server-side API development. This is not meant to be an intermediate or final Status implementation but serves as a way to implement functional requirements while we design the final `Status`.
1 parent 72f734f commit eeba83e

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed

grpc/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub mod client;
3535
pub mod credentials;
3636
pub mod inmemory;
3737
mod macros;
38+
mod status;
39+
pub use status::{ServerStatus, Status, StatusCode};
3840
pub mod rt;
3941
pub mod server;
4042
pub mod service;

grpc/src/status.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
mod server_status;
2+
mod status_code;
3+
4+
pub use server_status::ServerStatus;
5+
pub use status_code::StatusCode;
6+
7+
/// Represents a gRPC status.
8+
#[derive(Debug, Clone)]
9+
pub struct Status {
10+
code: StatusCode,
11+
message: String,
12+
}
13+
14+
impl Status {
15+
/// Create a new `Status` with the given code and message.
16+
pub fn new(code: StatusCode, message: impl Into<String>) -> Self {
17+
Status {
18+
code,
19+
message: message.into(),
20+
}
21+
}
22+
23+
/// Get the `StatusCode` of this `Status`.
24+
pub fn code(&self) -> StatusCode {
25+
self.code
26+
}
27+
28+
/// Get the message of this `Status`.
29+
pub fn message(&self) -> &str {
30+
&self.message
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use super::*;
37+
38+
#[test]
39+
fn test_status_new() {
40+
let status = Status::new(StatusCode::OK, "ok");
41+
assert_eq!(status.code(), StatusCode::OK);
42+
assert_eq!(status.message(), "ok");
43+
}
44+
45+
#[test]
46+
fn test_status_debug() {
47+
let status = Status::new(StatusCode::OK, "ok");
48+
let debug = format!("{:?}", status);
49+
assert!(debug.contains("Status"));
50+
assert!(debug.contains("OK"));
51+
assert!(debug.contains("ok"));
52+
}
53+
}

grpc/src/status/server_status.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use super::status_code::StatusCode;
2+
use super::Status;
3+
4+
/// Represents a gRPC status on the server.
5+
///
6+
/// This is a separate type from `Status` to prevent accidental conversion and
7+
/// leaking of sensitive information from the server to the client.
8+
#[derive(Debug, Clone)]
9+
pub struct ServerStatus(Status);
10+
11+
impl std::ops::Deref for ServerStatus {
12+
type Target = Status;
13+
14+
fn deref(&self) -> &Self::Target {
15+
&self.0
16+
}
17+
}
18+
19+
impl ServerStatus {
20+
/// Create a new `ServerStatus` with the given code and message.
21+
pub fn new(code: StatusCode, message: impl Into<String>) -> Self {
22+
ServerStatus(Status::new(code, message))
23+
}
24+
25+
/// Converts the `ServerStatus` to a `Status` for client responses.
26+
pub(crate) fn to_status(self) -> Status {
27+
self.0
28+
}
29+
}
30+
31+
impl From<Status> for ServerStatus {
32+
fn from(status: Status) -> Self {
33+
ServerStatus(status)
34+
}
35+
}
36+
37+
#[cfg(test)]
38+
mod tests {
39+
use super::*;
40+
41+
#[test]
42+
fn test_server_status_new() {
43+
let status = ServerStatus::new(StatusCode::OK, "ok");
44+
assert_eq!(status.code(), StatusCode::OK);
45+
assert_eq!(status.message(), "ok");
46+
}
47+
48+
#[test]
49+
fn test_server_status_deref() {
50+
let status = ServerStatus::new(StatusCode::OK, "ok");
51+
assert_eq!(status.code(), StatusCode::OK);
52+
}
53+
54+
#[test]
55+
fn test_server_status_from_status() {
56+
let status = Status::new(StatusCode::OK, "ok");
57+
let server_status: ServerStatus = status.into();
58+
assert_eq!(server_status.code(), StatusCode::OK);
59+
}
60+
61+
#[test]
62+
fn test_server_status_to_status() {
63+
let server_status = ServerStatus::new(StatusCode::OK, "ok");
64+
let status = server_status.to_status();
65+
assert_eq!(status.code(), StatusCode::OK);
66+
}
67+
}

grpc/src/status/status_code.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// Represents a gRPC status code.
2+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3+
#[repr(i32)]
4+
pub enum StatusCode {
5+
Ok = 0,
6+
Cancelled = 1,
7+
Unknown = 2,
8+
InvalidArgument = 3,
9+
DeadlineExceeded = 4,
10+
NotFound = 5,
11+
AlreadyExists = 6,
12+
PermissionDenied = 7,
13+
ResourceExhausted = 8,
14+
FailedPrecondition = 9,
15+
Aborted = 10,
16+
OutOfRange = 11,
17+
Unimplemented = 12,
18+
Internal = 13,
19+
Unavailable = 14,
20+
DataLoss = 15,
21+
Unauthenticated = 16,
22+
}

0 commit comments

Comments
 (0)