1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::fmt::Write;
4use std::marker::PhantomData;
5
6use axum::response::IntoResponse;
7
8use super::FuncFmt;
9
10#[derive(Debug)]
11pub enum PathItem {
12 Field(&'static str),
13 Index(usize),
14 Key(MapKey),
15}
16
17pub struct ProtoPathToken<'a> {
18 _no_send: PhantomData<*const ()>,
19 _marker: PhantomData<&'a ()>,
20}
21
22impl<'a> ProtoPathToken<'a> {
23 pub fn push_field(field: &'a str) -> Self {
24 PROTO_PATH_BUFFER.with(|buffer| {
25 buffer.borrow_mut().push(PathItem::Field(
26 unsafe { std::mem::transmute::<&'a str, &'static str>(field) },
29 ))
30 });
31 Self {
32 _marker: PhantomData,
33 _no_send: PhantomData,
34 }
35 }
36
37 pub fn push_index(index: usize) -> Self {
38 PROTO_PATH_BUFFER.with(|buffer| buffer.borrow_mut().push(PathItem::Index(index)));
39 Self {
40 _marker: PhantomData,
41 _no_send: PhantomData,
42 }
43 }
44
45 pub fn push_key(key: &'a dyn std::fmt::Debug) -> Self {
46 PROTO_PATH_BUFFER.with(|buffer| {
47 buffer.borrow_mut().push(PathItem::Key(
48 MapKey(unsafe { std::mem::transmute::<&'a dyn std::fmt::Debug, &'static dyn std::fmt::Debug>(key) }),
51 ))
52 });
53 Self {
54 _marker: PhantomData,
55 _no_send: PhantomData,
56 }
57 }
58
59 pub fn current_path() -> String {
60 PROTO_PATH_BUFFER.with(|buffer| format_path_items(buffer.borrow().as_slice()))
61 }
62}
63
64impl Drop for ProtoPathToken<'_> {
65 fn drop(&mut self) {
66 PROTO_PATH_BUFFER.with(|buffer| {
67 buffer.borrow_mut().pop();
68 });
69 }
70}
71
72pub struct SerdePathToken<'a> {
73 previous: Option<PathItem>,
74 _marker: PhantomData<&'a ()>,
75 _no_send: PhantomData<*const ()>,
76}
77
78pub fn report_de_error<E>(error: E) -> Result<(), E>
79where
80 E: serde::de::Error,
81{
82 STATE.with_borrow_mut(|state| {
83 if let Some(state) = state {
84 if state.irrecoverable || state.unwinding {
85 state.unwinding = true;
86 return Err(error);
87 }
88
89 state
90 .inner
91 .errors
92 .push(TrackedError::invalid_field(error.to_string().into_boxed_str()));
93
94 if state.inner.fail_fast {
95 state.unwinding = true;
96 Err(error)
97 } else {
98 Ok(())
99 }
100 } else {
101 Err(error)
102 }
103 })
104}
105
106pub fn report_tracked_error<E>(error: TrackedError) -> Result<(), E>
107where
108 E: serde::de::Error,
109{
110 STATE.with_borrow_mut(|state| {
111 if let Some(state) = state {
112 if state.irrecoverable || state.unwinding {
113 state.unwinding = true;
114 return Err(E::custom(&error));
115 }
116
117 let result = if state.inner.fail_fast && error.fatal {
118 state.unwinding = true;
119 Err(E::custom(&error))
120 } else {
121 Ok(())
122 };
123
124 state.inner.errors.push(error);
125
126 result
127 } else if error.fatal {
128 Err(E::custom(&error))
129 } else {
130 Ok(())
131 }
132 })
133}
134
135#[inline(always)]
136pub fn is_path_allowed() -> bool {
137 true
138}
139
140#[track_caller]
141pub fn set_irrecoverable() {
142 STATE.with_borrow_mut(|state| {
143 if let Some(state) = state {
144 state.irrecoverable = true;
145 }
146 });
147}
148
149impl<'a> SerdePathToken<'a> {
150 pub fn push_field(field: &'a str) -> Self {
151 SERDE_PATH_BUFFER.with(|buffer| {
152 buffer.borrow_mut().push(PathItem::Field(
153 unsafe { std::mem::transmute::<&'a str, &'static str>(field) },
156 ))
157 });
158 Self {
159 _marker: PhantomData,
160 _no_send: PhantomData,
161 previous: None,
162 }
163 }
164
165 pub fn replace_field(field: &'a str) -> Self {
166 let previous = SERDE_PATH_BUFFER.with(|buffer| buffer.borrow_mut().pop());
167 Self {
168 previous,
169 ..Self::push_field(field)
170 }
171 }
172
173 pub fn push_index(index: usize) -> Self {
174 SERDE_PATH_BUFFER.with(|buffer| buffer.borrow_mut().push(PathItem::Index(index)));
175 Self {
176 _marker: PhantomData,
177 _no_send: PhantomData,
178 previous: None,
179 }
180 }
181
182 pub fn push_key(key: &'a dyn std::fmt::Debug) -> Self {
183 SERDE_PATH_BUFFER.with(|buffer| {
184 buffer.borrow_mut().push(PathItem::Key(
185 MapKey(unsafe { std::mem::transmute::<&'a dyn std::fmt::Debug, &'static dyn std::fmt::Debug>(key) }),
188 ))
189 });
190 Self {
191 _marker: PhantomData,
192 _no_send: PhantomData,
193 previous: None,
194 }
195 }
196
197 pub fn current_path() -> String {
198 SERDE_PATH_BUFFER.with(|buffer| format_path_items(buffer.borrow().as_slice()))
199 }
200}
201
202fn format_path_items(items: &[PathItem]) -> String {
203 FuncFmt(|fmt| {
204 let mut first = true;
205 for token in items {
206 match token {
207 PathItem::Field(field) => {
208 if !first {
209 fmt.write_char('.')?;
210 }
211 first = false;
212 fmt.write_str(field)?;
213 }
214 PathItem::Index(index) => {
215 fmt.write_char('[')?;
216 std::fmt::Display::fmt(index, fmt)?;
217 fmt.write_char(']')?;
218 }
219 PathItem::Key(key) => {
220 fmt.write_char('[')?;
221 key.0.fmt(fmt)?;
222 fmt.write_char(']')?;
223 }
224 }
225 }
226
227 Ok(())
228 })
229 .to_string()
230}
231
232impl Drop for SerdePathToken<'_> {
233 fn drop(&mut self) {
234 SERDE_PATH_BUFFER.with(|buffer| {
235 buffer.borrow_mut().pop();
236 if let Some(previous) = self.previous.take() {
237 buffer.borrow_mut().push(previous);
238 }
239 });
240 }
241}
242
243thread_local! {
244 static SERDE_PATH_BUFFER: RefCell<Vec<PathItem>> = const { RefCell::new(Vec::new()) };
245 static PROTO_PATH_BUFFER: RefCell<Vec<PathItem>> = const { RefCell::new(Vec::new()) };
246 static STATE: RefCell<Option<InternalTrackerState>> = const { RefCell::new(None) };
247}
248
249struct InternalTrackerState {
250 irrecoverable: bool,
251 unwinding: bool,
252 inner: TrackerSharedState,
253}
254
255struct TrackerStateGuard<'a> {
256 state: &'a mut TrackerSharedState,
257 _no_send: PhantomData<*const ()>,
258}
259
260impl<'a> TrackerStateGuard<'a> {
261 fn new(state: &'a mut TrackerSharedState) -> Self {
262 STATE.with_borrow_mut(|current| {
263 if current.is_none() {
264 *current = Some(InternalTrackerState {
265 irrecoverable: false,
266 unwinding: false,
267 inner: std::mem::take(state),
268 });
269 } else {
270 panic!("TrackerStateGuard: already in use");
271 }
272 TrackerStateGuard {
273 state,
274 _no_send: PhantomData,
275 }
276 })
277 }
278}
279
280impl Drop for TrackerStateGuard<'_> {
281 fn drop(&mut self) {
282 STATE.with_borrow_mut(|state| {
283 if let Some(InternalTrackerState { inner, .. }) = state.take() {
284 *self.state = inner;
285 } else {
286 panic!("TrackerStateGuard: already dropped");
287 }
288 });
289 }
290}
291
292#[derive(Debug)]
293pub enum TrackedErrorKind {
294 DuplicateField,
295 UnknownField,
296 MissingField,
297 InvalidField { message: Box<str> },
298}
299
300#[derive(Debug)]
301pub struct TrackedError {
302 pub kind: TrackedErrorKind,
303 pub fatal: bool,
304 pub path: Box<str>,
305}
306
307impl TrackedError {
308 pub fn message(&self) -> &str {
309 match &self.kind {
310 TrackedErrorKind::DuplicateField => "duplicate field",
311 TrackedErrorKind::UnknownField => "unknown field",
312 TrackedErrorKind::MissingField => "missing field",
313 TrackedErrorKind::InvalidField { message } => message.as_ref(),
314 }
315 }
316}
317
318impl std::fmt::Display for TrackedError {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 match &self.kind {
321 TrackedErrorKind::DuplicateField => write!(f, "`{}` was already provided", self.path),
322 TrackedErrorKind::UnknownField => write!(f, "unknown field `{}`", self.path),
323 TrackedErrorKind::MissingField => write!(f, "missing field `{}`", self.path),
324 TrackedErrorKind::InvalidField { message } => write!(f, "`{}`: {}", self.path, message),
325 }
326 }
327}
328
329impl TrackedError {
330 fn new(kind: TrackedErrorKind, fatal: bool) -> Self {
331 Self {
332 kind,
333 fatal,
334 path: match tinc_cel::CelMode::current() {
335 tinc_cel::CelMode::Serde => SerdePathToken::current_path().into_boxed_str(),
336 tinc_cel::CelMode::Proto => ProtoPathToken::current_path().into_boxed_str(),
337 },
338 }
339 }
340
341 pub fn unknown_field(fatal: bool) -> Self {
342 Self::new(TrackedErrorKind::UnknownField, fatal)
343 }
344
345 pub fn invalid_field(message: impl Into<Box<str>>) -> Self {
346 Self::new(TrackedErrorKind::InvalidField { message: message.into() }, true)
347 }
348
349 pub fn duplicate_field() -> Self {
350 Self::new(TrackedErrorKind::DuplicateField, true)
351 }
352
353 pub fn missing_field() -> Self {
354 Self::new(TrackedErrorKind::MissingField, true)
355 }
356}
357
358#[derive(Default, Debug)]
359pub struct TrackerSharedState {
360 pub fail_fast: bool,
361 pub errors: Vec<TrackedError>,
362}
363
364impl TrackerSharedState {
365 pub fn in_scope<F, R>(&mut self, f: F) -> R
366 where
367 F: FnOnce() -> R,
368 {
369 let _guard = TrackerStateGuard::new(self);
370 f()
371 }
372}
373
374pub struct MapKey(&'static dyn std::fmt::Debug);
375
376impl std::fmt::Debug for MapKey {
377 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378 write!(f, "MapKey({:?})", self.0)
379 }
380}
381
382#[cfg(feature = "tonic")]
383pub fn handle_tonic_status(status: &tonic::Status) -> axum::response::Response {
384 use tonic_types::StatusExt;
385
386 let code = HttpErrorResponseCode::from(status.code());
387 let details = status.get_error_details();
388 let details = HttpErrorResponseDetails::from(&details);
389 HttpErrorResponse {
390 message: status.message(),
391 code,
392 details,
393 }
394 .into_response()
395}
396
397pub fn handle_response_build_error(err: impl std::error::Error) -> axum::response::Response {
398 HttpErrorResponse {
399 message: &err.to_string(),
400 code: HttpErrorResponseCode::Internal,
401 details: Default::default(),
402 }
403 .into_response()
404}
405
406#[derive(Debug, serde_derive::Serialize)]
407pub struct HttpErrorResponse<'a> {
408 pub message: &'a str,
409 pub code: HttpErrorResponseCode,
410 #[serde(skip_serializing_if = "is_default")]
411 pub details: HttpErrorResponseDetails<'a>,
412}
413
414impl axum::response::IntoResponse for HttpErrorResponse<'_> {
415 fn into_response(self) -> axum::response::Response {
416 let status = self.code.to_http_status();
417 (status, axum::Json(self)).into_response()
418 }
419}
420
421#[derive(Debug)]
422pub enum HttpErrorResponseCode {
423 Aborted,
424 Cancelled,
425 AlreadyExists,
426 DataLoss,
427 DeadlineExceeded,
428 FailedPrecondition,
429 Internal,
430 InvalidArgument,
431 NotFound,
432 OutOfRange,
433 PermissionDenied,
434 ResourceExhausted,
435 Unauthenticated,
436 Unavailable,
437 Unimplemented,
438 Unknown,
439 Ok,
440}
441
442impl serde::Serialize for HttpErrorResponseCode {
443 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
444 where
445 S: serde::Serializer,
446 {
447 self.to_http_status()
448 .as_str()
449 .serialize(serializer)
450 .map_err(serde::ser::Error::custom)
451 }
452}
453
454impl HttpErrorResponseCode {
455 pub fn to_http_status(&self) -> http::StatusCode {
456 match self {
457 Self::Aborted => http::StatusCode::from_u16(499).unwrap_or(http::StatusCode::BAD_REQUEST),
458 Self::Cancelled => http::StatusCode::from_u16(499).unwrap_or(http::StatusCode::BAD_REQUEST),
459 Self::AlreadyExists => http::StatusCode::ALREADY_REPORTED,
460 Self::DataLoss => http::StatusCode::INTERNAL_SERVER_ERROR,
461 Self::DeadlineExceeded => http::StatusCode::GATEWAY_TIMEOUT,
462 Self::FailedPrecondition => http::StatusCode::PRECONDITION_FAILED,
463 Self::Internal => http::StatusCode::INTERNAL_SERVER_ERROR,
464 Self::InvalidArgument => http::StatusCode::BAD_REQUEST,
465 Self::NotFound => http::StatusCode::NOT_FOUND,
466 Self::OutOfRange => http::StatusCode::BAD_REQUEST,
467 Self::PermissionDenied => http::StatusCode::FORBIDDEN,
468 Self::ResourceExhausted => http::StatusCode::TOO_MANY_REQUESTS,
469 Self::Unauthenticated => http::StatusCode::UNAUTHORIZED,
470 Self::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
471 Self::Unimplemented => http::StatusCode::NOT_IMPLEMENTED,
472 Self::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR,
473 Self::Ok => http::StatusCode::OK,
474 }
475 }
476}
477
478#[cfg(feature = "tonic")]
479impl From<tonic::Code> for HttpErrorResponseCode {
480 fn from(code: tonic::Code) -> Self {
481 match code {
482 tonic::Code::Aborted => Self::Aborted,
483 tonic::Code::Cancelled => Self::Cancelled,
484 tonic::Code::AlreadyExists => Self::AlreadyExists,
485 tonic::Code::DataLoss => Self::DataLoss,
486 tonic::Code::DeadlineExceeded => Self::DeadlineExceeded,
487 tonic::Code::FailedPrecondition => Self::FailedPrecondition,
488 tonic::Code::Internal => Self::Internal,
489 tonic::Code::InvalidArgument => Self::InvalidArgument,
490 tonic::Code::NotFound => Self::NotFound,
491 tonic::Code::OutOfRange => Self::OutOfRange,
492 tonic::Code::PermissionDenied => Self::PermissionDenied,
493 tonic::Code::ResourceExhausted => Self::ResourceExhausted,
494 tonic::Code::Unauthenticated => Self::Unauthenticated,
495 tonic::Code::Unavailable => Self::Unavailable,
496 tonic::Code::Unimplemented => Self::Unimplemented,
497 tonic::Code::Unknown => Self::Unknown,
498 tonic::Code::Ok => Self::Ok,
499 }
500 }
501}
502
503fn is_default<T>(t: &T) -> bool
504where
505 T: Default + PartialEq,
506{
507 t == &T::default()
508}
509
510#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
511pub struct HttpErrorResponseDetails<'a> {
512 #[serde(skip_serializing_if = "is_default")]
513 pub retry: HttpErrorResponseRetry,
514 #[serde(skip_serializing_if = "is_default")]
515 pub debug: HttpErrorResponseDebug<'a>,
516 #[serde(skip_serializing_if = "is_default")]
517 pub quota: Vec<HttpErrorResponseQuotaViolation<'a>>,
518 #[serde(skip_serializing_if = "is_default")]
519 pub error: HttpErrorResponseError<'a>,
520 #[serde(skip_serializing_if = "is_default")]
521 pub precondition: Vec<HttpErrorResponsePreconditionViolation<'a>>,
522 #[serde(skip_serializing_if = "is_default")]
523 pub request: HttpErrorResponseRequest<'a>,
524 #[serde(skip_serializing_if = "is_default")]
525 pub resource: HttpErrorResponseResource<'a>,
526 #[serde(skip_serializing_if = "is_default")]
527 pub help: Vec<HttpErrorResponseHelpLink<'a>>,
528 #[serde(skip_serializing_if = "is_default")]
529 pub localized: HttpErrorResponseLocalized<'a>,
530}
531
532#[cfg(feature = "tonic")]
533impl<'a> From<&'a tonic_types::ErrorDetails> for HttpErrorResponseDetails<'a> {
534 fn from(value: &'a tonic_types::ErrorDetails) -> Self {
535 Self {
536 retry: HttpErrorResponseRetry::from(value.retry_info()),
537 debug: HttpErrorResponseDebug::from(value.debug_info()),
538 quota: HttpErrorResponseQuota::from(value.quota_failure()).violations,
539 error: HttpErrorResponseError::from(value.error_info()),
540 precondition: HttpErrorResponsePrecondition::from(value.precondition_failure()).violations,
541 request: HttpErrorResponseRequest::from((value.bad_request(), value.request_info())),
542 resource: HttpErrorResponseResource::from(value.resource_info()),
543 help: HttpErrorResponseHelp::from(value.help()).links,
544 localized: HttpErrorResponseLocalized::from(value.localized_message()),
545 }
546 }
547}
548
549#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
550pub struct HttpErrorResponseRetry {
551 #[serde(skip_serializing_if = "is_default")]
552 pub after: Option<std::time::Duration>,
553 #[serde(skip_serializing_if = "is_default")]
554 pub at: Option<chrono::DateTime<chrono::Utc>>,
555}
556
557#[cfg(feature = "tonic")]
558impl From<Option<&tonic_types::RetryInfo>> for HttpErrorResponseRetry {
559 fn from(retry_info: Option<&tonic_types::RetryInfo>) -> Self {
560 Self {
561 after: retry_info.and_then(|ri| ri.retry_delay),
562 at: retry_info.and_then(|ri| ri.retry_delay).map(|d| {
563 let now = chrono::Utc::now();
564 now + d
565 }),
566 }
567 }
568}
569
570#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
571pub struct HttpErrorResponseDebug<'a> {
572 #[serde(skip_serializing_if = "is_default")]
573 pub stack: &'a [String],
574 #[serde(skip_serializing_if = "is_default")]
575 pub details: &'a str,
576}
577
578#[cfg(feature = "tonic")]
579impl<'a> From<Option<&'a tonic_types::DebugInfo>> for HttpErrorResponseDebug<'a> {
580 fn from(debug_info: Option<&'a tonic_types::DebugInfo>) -> Self {
581 Self {
582 stack: debug_info.as_ref().map_or(&[], |d| &d.stack_entries),
583 details: debug_info.as_ref().map_or("", |d| &d.detail),
584 }
585 }
586}
587
588#[derive(Default)]
589pub struct HttpErrorResponseQuota<'a> {
590 pub violations: Vec<HttpErrorResponseQuotaViolation<'a>>,
591}
592
593#[cfg(feature = "tonic")]
594impl<'a> From<Option<&'a tonic_types::QuotaFailure>> for HttpErrorResponseQuota<'a> {
595 fn from(quota_failure: Option<&'a tonic_types::QuotaFailure>) -> Self {
596 Self {
597 violations: quota_failure.as_ref().map_or_else(Vec::new, |qf| {
598 qf.violations
599 .iter()
600 .map(|violation| HttpErrorResponseQuotaViolation {
601 subject: &violation.subject,
602 description: &violation.description,
603 })
604 .filter(|violation| !is_default(violation))
605 .collect()
606 }),
607 }
608 }
609}
610
611#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
612pub struct HttpErrorResponseQuotaViolation<'a> {
613 #[serde(skip_serializing_if = "is_default")]
614 pub subject: &'a str,
615 #[serde(skip_serializing_if = "is_default")]
616 pub description: &'a str,
617}
618
619#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
620pub struct HttpErrorResponseError<'a> {
621 #[serde(skip_serializing_if = "is_default")]
622 pub reason: &'a str,
623 #[serde(skip_serializing_if = "is_default")]
624 pub domain: &'a str,
625 #[serde(skip_serializing_if = "is_default")]
626 pub metadata: HashMap<&'a str, &'a str>,
627}
628
629#[cfg(feature = "tonic")]
630impl<'a> From<Option<&'a tonic_types::ErrorInfo>> for HttpErrorResponseError<'a> {
631 fn from(error_info: Option<&'a tonic_types::ErrorInfo>) -> Self {
632 Self {
633 reason: error_info.map_or("", |ei| ei.reason.as_str()),
634 domain: error_info.map_or("", |ei| ei.domain.as_str()),
635 metadata: error_info
636 .map(|ei| {
637 ei.metadata
638 .iter()
639 .map(|(k, v)| (k.as_str(), v.as_str()))
640 .filter(|kv| !is_default(kv))
641 .collect()
642 })
643 .unwrap_or_default(),
644 }
645 }
646}
647
648pub struct HttpErrorResponsePrecondition<'a> {
649 pub violations: Vec<HttpErrorResponsePreconditionViolation<'a>>,
650}
651
652#[cfg(feature = "tonic")]
653impl<'a> From<Option<&'a tonic_types::PreconditionFailure>> for HttpErrorResponsePrecondition<'a> {
654 fn from(precondition_failure: Option<&'a tonic_types::PreconditionFailure>) -> Self {
655 Self {
656 violations: precondition_failure.as_ref().map_or_else(Vec::new, |pf| {
657 pf.violations
658 .iter()
659 .map(|violation| HttpErrorResponsePreconditionViolation {
660 type_: &violation.r#type,
661 subject: &violation.subject,
662 description: &violation.description,
663 })
664 .filter(|violation| !is_default(violation))
665 .collect()
666 }),
667 }
668 }
669}
670
671#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
672pub struct HttpErrorResponsePreconditionViolation<'a> {
673 #[serde(skip_serializing_if = "is_default", rename = "type")]
674 pub type_: &'a str,
675 #[serde(skip_serializing_if = "is_default")]
676 pub subject: &'a str,
677 #[serde(skip_serializing_if = "is_default")]
678 pub description: &'a str,
679}
680
681#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
682pub struct HttpErrorResponseRequest<'a> {
683 #[serde(skip_serializing_if = "is_default")]
684 pub violations: Vec<HttpErrorResponseRequestViolation<'a>>,
685 #[serde(skip_serializing_if = "is_default")]
686 pub id: &'a str,
687 #[serde(skip_serializing_if = "is_default")]
688 pub serving_data: &'a str,
689}
690
691#[cfg(feature = "tonic")]
692impl<'a> From<(Option<&'a tonic_types::BadRequest>, Option<&'a tonic_types::RequestInfo>)> for HttpErrorResponseRequest<'a> {
693 fn from(
694 (bad_request, request_info): (Option<&'a tonic_types::BadRequest>, Option<&'a tonic_types::RequestInfo>),
695 ) -> Self {
696 Self {
697 violations: bad_request
698 .as_ref()
699 .map(|br| {
700 br.field_violations
701 .iter()
702 .map(|violation| HttpErrorResponseRequestViolation {
703 field: &violation.field,
704 description: &violation.description,
705 })
706 .filter(|violation| !violation.field.is_empty() && !violation.description.is_empty())
707 .collect()
708 })
709 .unwrap_or_default(),
710 id: request_info.map_or("", |ri| ri.request_id.as_str()),
711 serving_data: request_info.map_or("", |ri| ri.serving_data.as_str()),
712 }
713 }
714}
715
716#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
717pub struct HttpErrorResponseRequestViolation<'a> {
718 #[serde(skip_serializing_if = "is_default")]
719 pub field: &'a str,
720 #[serde(skip_serializing_if = "is_default")]
721 pub description: &'a str,
722}
723
724#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
725pub struct HttpErrorResponseResource<'a> {
726 #[serde(skip_serializing_if = "is_default")]
727 pub name: &'a str,
728 #[serde(skip_serializing_if = "is_default", rename = "type")]
729 pub type_: &'a str,
730 #[serde(skip_serializing_if = "is_default")]
731 pub owner: &'a str,
732 #[serde(skip_serializing_if = "is_default")]
733 pub description: &'a str,
734}
735
736#[cfg(feature = "tonic")]
737impl<'a> From<Option<&'a tonic_types::ResourceInfo>> for HttpErrorResponseResource<'a> {
738 fn from(resource_info: Option<&'a tonic_types::ResourceInfo>) -> Self {
739 Self {
740 name: resource_info.map_or("", |ri| ri.resource_name.as_str()),
741 type_: resource_info.map_or("", |ri| ri.resource_type.as_str()),
742 owner: resource_info.map_or("", |ri| ri.owner.as_str()),
743 description: resource_info.map_or("", |ri| ri.description.as_str()),
744 }
745 }
746}
747
748pub struct HttpErrorResponseHelp<'a> {
749 pub links: Vec<HttpErrorResponseHelpLink<'a>>,
750}
751
752#[cfg(feature = "tonic")]
753impl<'a> From<Option<&'a tonic_types::Help>> for HttpErrorResponseHelp<'a> {
754 fn from(help: Option<&'a tonic_types::Help>) -> Self {
755 Self {
756 links: help.as_ref().map_or_else(Vec::new, |h| {
757 h.links
758 .iter()
759 .map(|link| HttpErrorResponseHelpLink {
760 description: &link.description,
761 url: &link.url,
762 })
763 .filter(|link| !is_default(link))
764 .collect()
765 }),
766 }
767 }
768}
769
770#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
771pub struct HttpErrorResponseHelpLink<'a> {
772 #[serde(skip_serializing_if = "is_default")]
773 pub description: &'a str,
774 #[serde(skip_serializing_if = "is_default")]
775 pub url: &'a str,
776}
777
778#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
779pub struct HttpErrorResponseLocalized<'a> {
780 #[serde(skip_serializing_if = "is_default")]
781 pub locale: &'a str,
782 #[serde(skip_serializing_if = "is_default")]
783 pub message: &'a str,
784}
785
786#[cfg(feature = "tonic")]
787impl<'a> From<Option<&'a tonic_types::LocalizedMessage>> for HttpErrorResponseLocalized<'a> {
788 fn from(localized_message: Option<&'a tonic_types::LocalizedMessage>) -> Self {
789 Self {
790 locale: localized_message.map_or("", |lm| lm.locale.as_str()),
791 message: localized_message.map_or("", |lm| lm.message.as_str()),
792 }
793 }
794}