at_jet/
response.rs

1//! Response types for AT-Jet
2//!
3//! Provides Protobuf-aware response types for axum handlers.
4
5use {crate::content_types::APPLICATION_PROTOBUF,
6     axum::{http::{StatusCode,
7                   header::CONTENT_TYPE},
8            response::{IntoResponse,
9                       Response}},
10     prost::Message};
11
12/// Protobuf response wrapper
13///
14/// Encodes a Protobuf message and returns it with the correct content type.
15///
16/// # Example
17///
18/// ```rust,ignore
19/// use at_jet::prelude::*;
20///
21/// async fn get_user(Path(id): Path<i32>) -> ProtobufResponse<User> {
22///     let user = User { id, name: "John".to_string() };
23///     ProtobufResponse::ok(user)
24/// }
25///
26/// async fn create_user(
27///     ProtobufRequest(req): ProtobufRequest<CreateUserRequest>
28/// ) -> ProtobufResponse<User> {
29///     let user = User { id: 1, name: req.name };
30///     ProtobufResponse::created(user)
31/// }
32/// ```
33pub struct ProtobufResponse<T>
34where
35  T: Message, {
36  status:  StatusCode,
37  message: T,
38}
39
40impl<T> ProtobufResponse<T>
41where
42  T: Message,
43{
44  /// Create a new response with custom status code
45  pub fn new(status: StatusCode, message: T) -> Self {
46    Self { status, message }
47  }
48
49  /// Create a 200 OK response
50  pub fn ok(message: T) -> Self {
51    Self::new(StatusCode::OK, message)
52  }
53
54  /// Create a 201 Created response
55  pub fn created(message: T) -> Self {
56    Self::new(StatusCode::CREATED, message)
57  }
58
59  /// Create a 202 Accepted response
60  pub fn accepted(message: T) -> Self {
61    Self::new(StatusCode::ACCEPTED, message)
62  }
63}
64
65impl<T> IntoResponse for ProtobufResponse<T>
66where
67  T: Message,
68{
69  fn into_response(self) -> Response {
70    let bytes = self.message.encode_to_vec();
71
72    (self.status, [(CONTENT_TYPE, APPLICATION_PROTOBUF)], bytes).into_response()
73  }
74}
75
76/// Result type that can return either a Protobuf response or an error
77pub type ProtobufResult<T> = Result<ProtobufResponse<T>, crate::error::JetError>;
78
79/// Empty response (204 No Content)
80pub struct NoContent;
81
82impl IntoResponse for NoContent {
83  fn into_response(self) -> Response {
84    StatusCode::NO_CONTENT.into_response()
85  }
86}
87
88/// Protobuf response with optional body
89pub enum MaybeProtobuf<T>
90where
91  T: Message, {
92  /// Response with body
93  Some(ProtobufResponse<T>),
94  /// No content response
95  None,
96}
97
98impl<T> IntoResponse for MaybeProtobuf<T>
99where
100  T: Message,
101{
102  fn into_response(self) -> Response {
103    match self {
104      | MaybeProtobuf::Some(response) => response.into_response(),
105      | MaybeProtobuf::None => StatusCode::NO_CONTENT.into_response(),
106    }
107  }
108}
109
110/// Helper trait for creating Protobuf responses from messages
111pub trait IntoProtobufResponse: Message + Sized {
112  /// Convert to a 200 OK Protobuf response
113  fn into_pb_response(self) -> ProtobufResponse<Self> {
114    ProtobufResponse::ok(self)
115  }
116
117  /// Convert to a Protobuf response with custom status
118  fn into_pb_response_with_status(self, status: StatusCode) -> ProtobufResponse<Self> {
119    ProtobufResponse::new(status, self)
120  }
121}
122
123// Implement for all Message types
124impl<T: Message> IntoProtobufResponse for T {}