tinc/private/
error.rs

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                // SAFETY: `field` has a lifetime of `'a`, field-name hides the field so it cannot be accessed outside of this module.
27                // We return a `PathToken` that has a lifetime of `'a` which makes it impossible to access this field after its lifetime ends.
28                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                // SAFETY: `key` has a lifetime of `'a`, map-key hides the key so it cannot be accessed outside of this module.
49                // We return a `PathToken` that has a lifetime of `'a` which makes it impossible to access this key after its lifetime ends.
50                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                // SAFETY: `field` has a lifetime of `'a`, field-name hides the field so it cannot be accessed outside of this module.
154                // We return a `PathToken` that has a lifetime of `'a` which makes it impossible to access this field after its lifetime ends.
155                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                // SAFETY: `key` has a lifetime of `'a`, map-key hides the key so it cannot be accessed outside of this module.
186                // We return a `PathToken` that has a lifetime of `'a` which makes it impossible to access this key after its lifetime ends.
187                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}