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 {
298 message: Box<str>,
299 },
300}
301
302#[derive(Debug)]
303pub struct TrackedError {
304 pub kind: TrackedErrorKind,
305 pub fatal: bool,
306 pub path: Box<str>,
307}
308
309impl TrackedError {
310 pub fn message(&self) -> &str {
311 match &self.kind {
312 TrackedErrorKind::DuplicateField => "duplicate field",
313 TrackedErrorKind::UnknownField => "unknown field",
314 TrackedErrorKind::MissingField => "missing field",
315 TrackedErrorKind::InvalidField { message } => message.as_ref(),
316 }
317 }
318}
319
320impl std::fmt::Display for TrackedError {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 match &self.kind {
323 TrackedErrorKind::DuplicateField => write!(f, "`{}` was already provided", self.path),
324 TrackedErrorKind::UnknownField => write!(f, "unknown field `{}`", self.path),
325 TrackedErrorKind::MissingField => write!(f, "missing field `{}`", self.path),
326 TrackedErrorKind::InvalidField { message } => write!(f, "`{}`: {}", self.path, message),
327 }
328 }
329}
330
331impl TrackedError {
332 fn new(kind: TrackedErrorKind, fatal: bool) -> Self {
333 Self {
334 kind,
335 fatal,
336 path: match tinc_cel::CelMode::current() {
337 tinc_cel::CelMode::Serde => SerdePathToken::current_path().into_boxed_str(),
338 tinc_cel::CelMode::Proto => ProtoPathToken::current_path().into_boxed_str(),
339 },
340 }
341 }
342
343 pub fn unknown_field(fatal: bool) -> Self {
344 Self::new(TrackedErrorKind::UnknownField, fatal)
345 }
346
347 pub fn invalid_field(message: impl Into<Box<str>>) -> Self {
348 Self::new(TrackedErrorKind::InvalidField { message: message.into() }, true)
349 }
350
351 pub fn duplicate_field() -> Self {
352 Self::new(TrackedErrorKind::DuplicateField, true)
353 }
354
355 pub fn missing_field() -> Self {
356 Self::new(TrackedErrorKind::MissingField, true)
357 }
358}
359
360#[derive(Default, Debug)]
361pub struct TrackerSharedState {
362 pub fail_fast: bool,
363 pub errors: Vec<TrackedError>,
364}
365
366impl TrackerSharedState {
367 pub fn in_scope<F, R>(&mut self, f: F) -> R
368 where
369 F: FnOnce() -> R,
370 {
371 let _guard = TrackerStateGuard::new(self);
372 f()
373 }
374}
375
376pub struct MapKey(&'static dyn std::fmt::Debug);
377
378impl std::fmt::Debug for MapKey {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 write!(f, "MapKey({:?})", self.0)
381 }
382}
383
384#[cfg(feature = "tonic")]
385pub fn handle_tonic_status(status: &tonic::Status) -> axum::response::Response {
386 use tonic_types::StatusExt;
387
388 let code = HttpErrorResponseCode::from(status.code());
389 let details = status.get_error_details();
390 let details = HttpErrorResponseDetails::from(&details);
391 HttpErrorResponse {
392 message: status.message(),
393 code,
394 details,
395 }
396 .into_response()
397}
398
399pub fn handle_response_build_error(err: impl std::error::Error) -> axum::response::Response {
400 HttpErrorResponse {
401 message: &err.to_string(),
402 code: HttpErrorResponseCode::Internal,
403 details: Default::default(),
404 }
405 .into_response()
406}
407
408#[derive(Debug, serde_derive::Serialize)]
409pub struct HttpErrorResponse<'a> {
410 pub message: &'a str,
411 pub code: HttpErrorResponseCode,
412 #[serde(skip_serializing_if = "is_default")]
413 pub details: HttpErrorResponseDetails<'a>,
414}
415
416impl axum::response::IntoResponse for HttpErrorResponse<'_> {
417 fn into_response(self) -> axum::response::Response {
418 let status = self.code.to_http_status();
419 (status, axum::Json(self)).into_response()
420 }
421}
422
423#[derive(Debug)]
424pub enum HttpErrorResponseCode {
425 Aborted,
426 Cancelled,
427 AlreadyExists,
428 DataLoss,
429 DeadlineExceeded,
430 FailedPrecondition,
431 Internal,
432 InvalidArgument,
433 NotFound,
434 OutOfRange,
435 PermissionDenied,
436 ResourceExhausted,
437 Unauthenticated,
438 Unavailable,
439 Unimplemented,
440 Unknown,
441 Ok,
442}
443
444impl serde::Serialize for HttpErrorResponseCode {
445 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
446 where
447 S: serde::Serializer,
448 {
449 self.to_http_status()
450 .as_str()
451 .serialize(serializer)
452 .map_err(serde::ser::Error::custom)
453 }
454}
455
456impl HttpErrorResponseCode {
457 pub fn to_http_status(&self) -> http::StatusCode {
458 match self {
459 Self::Aborted => http::StatusCode::from_u16(499).unwrap_or(http::StatusCode::BAD_REQUEST),
460 Self::Cancelled => http::StatusCode::from_u16(499).unwrap_or(http::StatusCode::BAD_REQUEST),
461 Self::AlreadyExists => http::StatusCode::ALREADY_REPORTED,
462 Self::DataLoss => http::StatusCode::INTERNAL_SERVER_ERROR,
463 Self::DeadlineExceeded => http::StatusCode::GATEWAY_TIMEOUT,
464 Self::FailedPrecondition => http::StatusCode::PRECONDITION_FAILED,
465 Self::Internal => http::StatusCode::INTERNAL_SERVER_ERROR,
466 Self::InvalidArgument => http::StatusCode::BAD_REQUEST,
467 Self::NotFound => http::StatusCode::NOT_FOUND,
468 Self::OutOfRange => http::StatusCode::BAD_REQUEST,
469 Self::PermissionDenied => http::StatusCode::FORBIDDEN,
470 Self::ResourceExhausted => http::StatusCode::TOO_MANY_REQUESTS,
471 Self::Unauthenticated => http::StatusCode::UNAUTHORIZED,
472 Self::Unavailable => http::StatusCode::SERVICE_UNAVAILABLE,
473 Self::Unimplemented => http::StatusCode::NOT_IMPLEMENTED,
474 Self::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR,
475 Self::Ok => http::StatusCode::OK,
476 }
477 }
478}
479
480#[cfg(feature = "tonic")]
481impl From<tonic::Code> for HttpErrorResponseCode {
482 fn from(code: tonic::Code) -> Self {
483 match code {
484 tonic::Code::Aborted => Self::Aborted,
485 tonic::Code::Cancelled => Self::Cancelled,
486 tonic::Code::AlreadyExists => Self::AlreadyExists,
487 tonic::Code::DataLoss => Self::DataLoss,
488 tonic::Code::DeadlineExceeded => Self::DeadlineExceeded,
489 tonic::Code::FailedPrecondition => Self::FailedPrecondition,
490 tonic::Code::Internal => Self::Internal,
491 tonic::Code::InvalidArgument => Self::InvalidArgument,
492 tonic::Code::NotFound => Self::NotFound,
493 tonic::Code::OutOfRange => Self::OutOfRange,
494 tonic::Code::PermissionDenied => Self::PermissionDenied,
495 tonic::Code::ResourceExhausted => Self::ResourceExhausted,
496 tonic::Code::Unauthenticated => Self::Unauthenticated,
497 tonic::Code::Unavailable => Self::Unavailable,
498 tonic::Code::Unimplemented => Self::Unimplemented,
499 tonic::Code::Unknown => Self::Unknown,
500 tonic::Code::Ok => Self::Ok,
501 }
502 }
503}
504
505fn is_default<T>(t: &T) -> bool
506where
507 T: Default + PartialEq,
508{
509 t == &T::default()
510}
511
512#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
513pub struct HttpErrorResponseDetails<'a> {
514 #[serde(skip_serializing_if = "is_default")]
515 pub retry: HttpErrorResponseRetry,
516 #[serde(skip_serializing_if = "is_default")]
517 pub debug: HttpErrorResponseDebug<'a>,
518 #[serde(skip_serializing_if = "is_default")]
519 pub quota: Vec<HttpErrorResponseQuotaViolation<'a>>,
520 #[serde(skip_serializing_if = "is_default")]
521 pub error: HttpErrorResponseError<'a>,
522 #[serde(skip_serializing_if = "is_default")]
523 pub precondition: Vec<HttpErrorResponsePreconditionViolation<'a>>,
524 #[serde(skip_serializing_if = "is_default")]
525 pub request: HttpErrorResponseRequest<'a>,
526 #[serde(skip_serializing_if = "is_default")]
527 pub resource: HttpErrorResponseResource<'a>,
528 #[serde(skip_serializing_if = "is_default")]
529 pub help: Vec<HttpErrorResponseHelpLink<'a>>,
530 #[serde(skip_serializing_if = "is_default")]
531 pub localized: HttpErrorResponseLocalized<'a>,
532}
533
534#[cfg(feature = "tonic")]
535impl<'a> From<&'a tonic_types::ErrorDetails> for HttpErrorResponseDetails<'a> {
536 fn from(value: &'a tonic_types::ErrorDetails) -> Self {
537 Self {
538 retry: HttpErrorResponseRetry::from(value.retry_info()),
539 debug: HttpErrorResponseDebug::from(value.debug_info()),
540 quota: HttpErrorResponseQuota::from(value.quota_failure()).violations,
541 error: HttpErrorResponseError::from(value.error_info()),
542 precondition: HttpErrorResponsePrecondition::from(value.precondition_failure()).violations,
543 request: HttpErrorResponseRequest::from((value.bad_request(), value.request_info())),
544 resource: HttpErrorResponseResource::from(value.resource_info()),
545 help: HttpErrorResponseHelp::from(value.help()).links,
546 localized: HttpErrorResponseLocalized::from(value.localized_message()),
547 }
548 }
549}
550
551#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
552pub struct HttpErrorResponseRetry {
553 #[serde(skip_serializing_if = "is_default")]
554 pub after: Option<std::time::Duration>,
555 #[serde(skip_serializing_if = "is_default")]
556 pub at: Option<chrono::DateTime<chrono::Utc>>,
557}
558
559#[cfg(feature = "tonic")]
560impl From<Option<&tonic_types::RetryInfo>> for HttpErrorResponseRetry {
561 fn from(retry_info: Option<&tonic_types::RetryInfo>) -> Self {
562 Self {
563 after: retry_info.and_then(|ri| ri.retry_delay),
564 at: retry_info.and_then(|ri| ri.retry_delay).map(|d| {
565 let now = chrono::Utc::now();
566 now + d
567 }),
568 }
569 }
570}
571
572#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
573pub struct HttpErrorResponseDebug<'a> {
574 #[serde(skip_serializing_if = "is_default")]
575 pub stack: &'a [String],
576 #[serde(skip_serializing_if = "is_default")]
577 pub details: &'a str,
578}
579
580#[cfg(feature = "tonic")]
581impl<'a> From<Option<&'a tonic_types::DebugInfo>> for HttpErrorResponseDebug<'a> {
582 fn from(debug_info: Option<&'a tonic_types::DebugInfo>) -> Self {
583 Self {
584 stack: debug_info.as_ref().map_or(&[], |d| &d.stack_entries),
585 details: debug_info.as_ref().map_or("", |d| &d.detail),
586 }
587 }
588}
589
590#[derive(Default)]
591pub struct HttpErrorResponseQuota<'a> {
592 pub violations: Vec<HttpErrorResponseQuotaViolation<'a>>,
593}
594
595#[cfg(feature = "tonic")]
596impl<'a> From<Option<&'a tonic_types::QuotaFailure>> for HttpErrorResponseQuota<'a> {
597 fn from(quota_failure: Option<&'a tonic_types::QuotaFailure>) -> Self {
598 Self {
599 violations: quota_failure.as_ref().map_or_else(Vec::new, |qf| {
600 qf.violations
601 .iter()
602 .map(|violation| HttpErrorResponseQuotaViolation {
603 subject: &violation.subject,
604 description: &violation.description,
605 })
606 .filter(|violation| !is_default(violation))
607 .collect()
608 }),
609 }
610 }
611}
612
613#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
614pub struct HttpErrorResponseQuotaViolation<'a> {
615 #[serde(skip_serializing_if = "is_default")]
616 pub subject: &'a str,
617 #[serde(skip_serializing_if = "is_default")]
618 pub description: &'a str,
619}
620
621#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
622pub struct HttpErrorResponseError<'a> {
623 #[serde(skip_serializing_if = "is_default")]
624 pub reason: &'a str,
625 #[serde(skip_serializing_if = "is_default")]
626 pub domain: &'a str,
627 #[serde(skip_serializing_if = "is_default")]
628 pub metadata: HashMap<&'a str, &'a str>,
629}
630
631#[cfg(feature = "tonic")]
632impl<'a> From<Option<&'a tonic_types::ErrorInfo>> for HttpErrorResponseError<'a> {
633 fn from(error_info: Option<&'a tonic_types::ErrorInfo>) -> Self {
634 Self {
635 reason: error_info.map_or("", |ei| ei.reason.as_str()),
636 domain: error_info.map_or("", |ei| ei.domain.as_str()),
637 metadata: error_info
638 .map(|ei| {
639 ei.metadata
640 .iter()
641 .map(|(k, v)| (k.as_str(), v.as_str()))
642 .filter(|kv| !is_default(kv))
643 .collect()
644 })
645 .unwrap_or_default(),
646 }
647 }
648}
649
650pub struct HttpErrorResponsePrecondition<'a> {
651 pub violations: Vec<HttpErrorResponsePreconditionViolation<'a>>,
652}
653
654#[cfg(feature = "tonic")]
655impl<'a> From<Option<&'a tonic_types::PreconditionFailure>> for HttpErrorResponsePrecondition<'a> {
656 fn from(precondition_failure: Option<&'a tonic_types::PreconditionFailure>) -> Self {
657 Self {
658 violations: precondition_failure.as_ref().map_or_else(Vec::new, |pf| {
659 pf.violations
660 .iter()
661 .map(|violation| HttpErrorResponsePreconditionViolation {
662 type_: &violation.r#type,
663 subject: &violation.subject,
664 description: &violation.description,
665 })
666 .filter(|violation| !is_default(violation))
667 .collect()
668 }),
669 }
670 }
671}
672
673#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
674pub struct HttpErrorResponsePreconditionViolation<'a> {
675 #[serde(skip_serializing_if = "is_default", rename = "type")]
676 pub type_: &'a str,
677 #[serde(skip_serializing_if = "is_default")]
678 pub subject: &'a str,
679 #[serde(skip_serializing_if = "is_default")]
680 pub description: &'a str,
681}
682
683#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
684pub struct HttpErrorResponseRequest<'a> {
685 #[serde(skip_serializing_if = "is_default")]
686 pub violations: Vec<HttpErrorResponseRequestViolation<'a>>,
687 #[serde(skip_serializing_if = "is_default")]
688 pub id: &'a str,
689 #[serde(skip_serializing_if = "is_default")]
690 pub serving_data: &'a str,
691}
692
693#[cfg(feature = "tonic")]
694impl<'a> From<(Option<&'a tonic_types::BadRequest>, Option<&'a tonic_types::RequestInfo>)> for HttpErrorResponseRequest<'a> {
695 fn from(
696 (bad_request, request_info): (Option<&'a tonic_types::BadRequest>, Option<&'a tonic_types::RequestInfo>),
697 ) -> Self {
698 Self {
699 violations: bad_request
700 .as_ref()
701 .map(|br| {
702 br.field_violations
703 .iter()
704 .map(|violation| HttpErrorResponseRequestViolation {
705 field: &violation.field,
706 description: &violation.description,
707 })
708 .filter(|violation| !violation.field.is_empty() && !violation.description.is_empty())
709 .collect()
710 })
711 .unwrap_or_default(),
712 id: request_info.map_or("", |ri| ri.request_id.as_str()),
713 serving_data: request_info.map_or("", |ri| ri.serving_data.as_str()),
714 }
715 }
716}
717
718#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
719pub struct HttpErrorResponseRequestViolation<'a> {
720 #[serde(skip_serializing_if = "is_default")]
721 pub field: &'a str,
722 #[serde(skip_serializing_if = "is_default")]
723 pub description: &'a str,
724}
725
726#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
727pub struct HttpErrorResponseResource<'a> {
728 #[serde(skip_serializing_if = "is_default")]
729 pub name: &'a str,
730 #[serde(skip_serializing_if = "is_default", rename = "type")]
731 pub type_: &'a str,
732 #[serde(skip_serializing_if = "is_default")]
733 pub owner: &'a str,
734 #[serde(skip_serializing_if = "is_default")]
735 pub description: &'a str,
736}
737
738#[cfg(feature = "tonic")]
739impl<'a> From<Option<&'a tonic_types::ResourceInfo>> for HttpErrorResponseResource<'a> {
740 fn from(resource_info: Option<&'a tonic_types::ResourceInfo>) -> Self {
741 Self {
742 name: resource_info.map_or("", |ri| ri.resource_name.as_str()),
743 type_: resource_info.map_or("", |ri| ri.resource_type.as_str()),
744 owner: resource_info.map_or("", |ri| ri.owner.as_str()),
745 description: resource_info.map_or("", |ri| ri.description.as_str()),
746 }
747 }
748}
749
750pub struct HttpErrorResponseHelp<'a> {
751 pub links: Vec<HttpErrorResponseHelpLink<'a>>,
752}
753
754#[cfg(feature = "tonic")]
755impl<'a> From<Option<&'a tonic_types::Help>> for HttpErrorResponseHelp<'a> {
756 fn from(help: Option<&'a tonic_types::Help>) -> Self {
757 Self {
758 links: help.as_ref().map_or_else(Vec::new, |h| {
759 h.links
760 .iter()
761 .map(|link| HttpErrorResponseHelpLink {
762 description: &link.description,
763 url: &link.url,
764 })
765 .filter(|link| !is_default(link))
766 .collect()
767 }),
768 }
769 }
770}
771
772#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
773pub struct HttpErrorResponseHelpLink<'a> {
774 #[serde(skip_serializing_if = "is_default")]
775 pub description: &'a str,
776 #[serde(skip_serializing_if = "is_default")]
777 pub url: &'a str,
778}
779
780#[derive(Debug, serde_derive::Serialize, Default, PartialEq)]
781pub struct HttpErrorResponseLocalized<'a> {
782 #[serde(skip_serializing_if = "is_default")]
783 pub locale: &'a str,
784 #[serde(skip_serializing_if = "is_default")]
785 pub message: &'a str,
786}
787
788#[cfg(feature = "tonic")]
789impl<'a> From<Option<&'a tonic_types::LocalizedMessage>> for HttpErrorResponseLocalized<'a> {
790 fn from(localized_message: Option<&'a tonic_types::LocalizedMessage>) -> Self {
791 Self {
792 locale: localized_message.map_or("", |lm| lm.locale.as_str()),
793 message: localized_message.map_or("", |lm| lm.message.as_str()),
794 }
795 }
796}