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 { 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}