1use tonic::metadata::MetadataMap;
2
3use crate::consts::{PROJECT_ID_HEADER, REQUEST_ID_HEADER};
4use crate::grpc_helpers::RequestContext;
5use crate::model::{ModelIdError, ValidShardedId};
6use crate::types::{ProjectId, RequestId};
7
8pub trait OptionExt {
9 type Item;
10 fn unwrap_ref(&self) -> &Self::Item;
11 fn unwrap_mut(&mut self) -> &mut Self::Item;
12}
13impl<T> OptionExt for Option<T> {
14 type Item = T;
15
16 fn unwrap_ref(&self) -> &T {
17 self.as_ref().unwrap()
18 }
19
20 fn unwrap_mut(&mut self) -> &mut T {
21 self.as_mut().unwrap()
22 }
23}
24
25pub trait GrpcMetadataMapExt {
26 fn set_project_id(&mut self, project_id: ValidShardedId<ProjectId>);
27 fn project_id(&self) -> Option<ProjectId>;
28
29 fn set_request_id(&mut self, request_id: RequestId);
30 fn request_id(&self) -> Option<RequestId>;
31}
32
33impl GrpcMetadataMapExt for MetadataMap {
34 fn set_project_id(&mut self, project_id: ValidShardedId<ProjectId>) {
35 self.insert(
36 PROJECT_ID_HEADER,
37 project_id
38 .to_string()
39 .parse()
40 .expect("ProjectId is not HTTP header-friendly!"),
41 );
42 }
43
44 fn set_request_id(&mut self, request_id: RequestId) {
45 self.insert(
46 REQUEST_ID_HEADER,
47 request_id
48 .to_string()
49 .parse()
50 .expect("RequestId is not HTTP header-friendly!"),
51 );
52 }
53
54 fn project_id(&self) -> Option<ProjectId> {
55 self.get(PROJECT_ID_HEADER)
56 .and_then(|project_id| project_id.to_str().ok())
57 .map(|project_id| ProjectId::from(project_id.to_string()))
58 }
59
60 fn request_id(&self) -> Option<RequestId> {
61 self.get(REQUEST_ID_HEADER)
62 .and_then(|request_id| request_id.to_str().ok())
63 .map(|request_id| RequestId::from(request_id.to_string()))
64 }
65}
66
67pub trait TonicRequestExt {
68 fn project_id(&self) -> Result<&ValidShardedId<ProjectId>, tonic::Status>;
69 fn request_id(&self) -> Result<&RequestId, tonic::Status>;
70 fn context(&self) -> Result<RequestContext, tonic::Status>;
71}
72
73impl<T> TonicRequestExt for tonic::Request<T> {
74 fn project_id(&self) -> Result<&ValidShardedId<ProjectId>, tonic::Status> {
76 let raw_id = self
77 .extensions()
78 .get::<Result<ValidShardedId<ProjectId>, ModelIdError>>()
79 .ok_or_else(|| {
80 tonic::Status::internal(format!(
81 "{} was not set in GRPC headers!",
82 PROJECT_ID_HEADER
83 ))
84 })?;
85 match raw_id {
86 | Ok(id) => Ok(id),
87 | Err(err) => {
88 Err(tonic::Status::internal(format!(
89 "Invalid ProjectId was passed in headers: {}",
90 err,
91 )))
92 }
93 }
94 }
95
96 fn request_id(&self) -> Result<&RequestId, tonic::Status> {
97 self.extensions().get::<RequestId>().ok_or_else(|| {
98 tonic::Status::internal(format!(
99 "{} was not set in GRPC headers!",
100 REQUEST_ID_HEADER,
101 ))
102 })
103 }
104
105 fn context(&self) -> Result<RequestContext, tonic::Status> {
106 Ok(RequestContext::new(
107 self.request_id()?.clone(),
108 self.project_id()?.clone(),
109 ))
110 }
111}
112
113pub use dto::traits::ProstOptionExt;