1use crate::context::{
2 Aborted, AlreadyExists, Cancelled, DataLoss, DeadlineExceeded, FailedPrecondition,
3 FieldViolation, Internal, InvalidArgument, NotFound, OutOfRange, PermissionDenied,
4 PreconditionViolation, QuotaViolation, ResourceExhausted, ServiceUnavailable, Unauthenticated,
5 Unimplemented, Unknown,
6};
7use crate::error::CanonicalError;
8
9#[doc(hidden)]
14pub struct ResourceAbsent;
15#[doc(hidden)]
16pub struct ResourceOptional;
17#[doc(hidden)]
18pub struct ResourceMissing;
19#[doc(hidden)]
20pub struct ResourceSet(String);
21
22#[doc(hidden)]
27pub struct NoContext;
28#[doc(hidden)]
29pub struct NeedsFieldViolation;
30#[doc(hidden)]
31pub struct HasFieldViolations(Vec<FieldViolation>);
32#[doc(hidden)]
33pub struct NeedsPreconditionViolation;
34#[doc(hidden)]
35pub struct HasPreconditionViolations(Vec<PreconditionViolation>);
36#[doc(hidden)]
37pub struct NeedsQuotaViolation;
38#[doc(hidden)]
39pub struct HasQuotaViolations(Vec<QuotaViolation>);
40#[doc(hidden)]
41pub struct HasFormatMessage(String);
42#[doc(hidden)]
43pub struct HasConstraintMessage(String);
44#[doc(hidden)]
45pub struct NeedsReason;
46#[doc(hidden)]
47pub struct HasReason(String);
48
49#[doc(hidden)]
54pub trait ResourceResolved {
55 fn resolve(self) -> Option<String>;
56}
57
58impl ResourceResolved for ResourceAbsent {
59 fn resolve(self) -> Option<String> {
60 None
61 }
62}
63
64impl ResourceResolved for ResourceOptional {
65 fn resolve(self) -> Option<String> {
66 None
67 }
68}
69
70impl ResourceResolved for ResourceSet {
71 fn resolve(self) -> Option<String> {
72 Some(self.0)
73 }
74}
75
76#[doc(hidden)]
77pub struct ContextData {
78 pub field_violations: Vec<FieldViolation>,
79 pub precondition_violations: Vec<PreconditionViolation>,
80 pub quota_violations: Vec<QuotaViolation>,
81 pub format_message: Option<String>,
82 pub constraint_message: Option<String>,
83 pub reason: String,
84}
85
86#[doc(hidden)]
87pub trait ContextResolved {
88 fn into_context_data(self) -> ContextData;
89}
90
91impl ContextResolved for NoContext {
92 fn into_context_data(self) -> ContextData {
93 ContextData {
94 field_violations: Vec::new(),
95 precondition_violations: Vec::new(),
96 quota_violations: Vec::new(),
97 format_message: None,
98 constraint_message: None,
99 reason: String::new(),
100 }
101 }
102}
103
104impl ContextResolved for HasFieldViolations {
105 fn into_context_data(self) -> ContextData {
106 ContextData {
107 field_violations: self.0,
108 precondition_violations: Vec::new(),
109 quota_violations: Vec::new(),
110 format_message: None,
111 constraint_message: None,
112 reason: String::new(),
113 }
114 }
115}
116
117impl ContextResolved for HasFormatMessage {
118 fn into_context_data(self) -> ContextData {
119 ContextData {
120 field_violations: Vec::new(),
121 precondition_violations: Vec::new(),
122 quota_violations: Vec::new(),
123 format_message: Some(self.0),
124 constraint_message: None,
125 reason: String::new(),
126 }
127 }
128}
129
130impl ContextResolved for HasConstraintMessage {
131 fn into_context_data(self) -> ContextData {
132 ContextData {
133 field_violations: Vec::new(),
134 precondition_violations: Vec::new(),
135 quota_violations: Vec::new(),
136 format_message: None,
137 constraint_message: Some(self.0),
138 reason: String::new(),
139 }
140 }
141}
142
143impl ContextResolved for HasPreconditionViolations {
144 fn into_context_data(self) -> ContextData {
145 ContextData {
146 field_violations: Vec::new(),
147 precondition_violations: self.0,
148 quota_violations: Vec::new(),
149 format_message: None,
150 constraint_message: None,
151 reason: String::new(),
152 }
153 }
154}
155
156impl ContextResolved for HasQuotaViolations {
157 fn into_context_data(self) -> ContextData {
158 ContextData {
159 field_violations: Vec::new(),
160 precondition_violations: Vec::new(),
161 quota_violations: self.0,
162 format_message: None,
163 constraint_message: None,
164 reason: String::new(),
165 }
166 }
167}
168
169impl ContextResolved for HasReason {
170 fn into_context_data(self) -> ContextData {
171 ContextData {
172 field_violations: Vec::new(),
173 precondition_violations: Vec::new(),
174 quota_violations: Vec::new(),
175 format_message: None,
176 constraint_message: None,
177 reason: self.0,
178 }
179 }
180}
181
182#[doc(hidden)]
187#[derive(Debug, Clone, Copy)]
188pub enum ErrorVariant {
189 Cancelled,
190 Unknown,
191 InvalidArgument,
192 DeadlineExceeded,
193 NotFound,
194 AlreadyExists,
195 PermissionDenied,
196 ResourceExhausted,
197 FailedPrecondition,
198 Aborted,
199 OutOfRange,
200 Unimplemented,
201 Internal,
202 DataLoss,
203 Unauthenticated,
204}
205
206pub struct ResourceErrorBuilder<Resource, Context> {
211 resource_type: Option<&'static str>,
212 detail: String,
213 variant: ErrorVariant,
214 resource: Resource,
215 context: Context,
216}
217
218impl ResourceErrorBuilder<ResourceMissing, NoContext> {
223 #[doc(hidden)]
224 pub fn __not_found(resource_type: &'static str, detail: impl Into<String>) -> Self {
225 ResourceErrorBuilder {
226 resource_type: Some(resource_type),
227 detail: detail.into(),
228 variant: ErrorVariant::NotFound,
229 resource: ResourceMissing,
230 context: NoContext,
231 }
232 }
233
234 #[doc(hidden)]
235 pub fn __already_exists(resource_type: &'static str, detail: impl Into<String>) -> Self {
236 ResourceErrorBuilder {
237 resource_type: Some(resource_type),
238 detail: detail.into(),
239 variant: ErrorVariant::AlreadyExists,
240 resource: ResourceMissing,
241 context: NoContext,
242 }
243 }
244
245 #[doc(hidden)]
246 pub fn __data_loss(resource_type: &'static str, detail: impl Into<String>) -> Self {
247 ResourceErrorBuilder {
248 resource_type: Some(resource_type),
249 detail: detail.into(),
250 variant: ErrorVariant::DataLoss,
251 resource: ResourceMissing,
252 context: NoContext,
253 }
254 }
255}
256
257impl ResourceErrorBuilder<ResourceOptional, NeedsReason> {
258 #[doc(hidden)]
259 pub fn __aborted(resource_type: &'static str, detail: impl Into<String>) -> Self {
260 ResourceErrorBuilder {
261 resource_type: Some(resource_type),
262 detail: detail.into(),
263 variant: ErrorVariant::Aborted,
264 resource: ResourceOptional,
265 context: NeedsReason,
266 }
267 }
268}
269
270impl ResourceErrorBuilder<ResourceOptional, NoContext> {
271 #[doc(hidden)]
272 pub fn __unknown(resource_type: &'static str, detail: impl Into<String>) -> Self {
273 ResourceErrorBuilder {
274 resource_type: Some(resource_type),
275 detail: detail.into(),
276 variant: ErrorVariant::Unknown,
277 resource: ResourceOptional,
278 context: NoContext,
279 }
280 }
281
282 #[doc(hidden)]
283 pub fn __deadline_exceeded(resource_type: &'static str, detail: impl Into<String>) -> Self {
284 ResourceErrorBuilder {
285 resource_type: Some(resource_type),
286 detail: detail.into(),
287 variant: ErrorVariant::DeadlineExceeded,
288 resource: ResourceOptional,
289 context: NoContext,
290 }
291 }
292
293 #[doc(hidden)]
294 pub fn __unimplemented(resource_type: &'static str, detail: impl Into<String>) -> Self {
295 ResourceErrorBuilder {
296 resource_type: Some(resource_type),
297 detail: detail.into(),
298 variant: ErrorVariant::Unimplemented,
299 resource: ResourceOptional,
300 context: NoContext,
301 }
302 }
303}
304
305impl ResourceErrorBuilder<ResourceAbsent, NeedsReason> {
306 #[doc(hidden)]
307 pub fn __permission_denied(resource_type: &'static str, detail: impl Into<String>) -> Self {
308 ResourceErrorBuilder {
309 resource_type: Some(resource_type),
310 detail: detail.into(),
311 variant: ErrorVariant::PermissionDenied,
312 resource: ResourceAbsent,
313 context: NeedsReason,
314 }
315 }
316}
317
318impl ResourceErrorBuilder<ResourceAbsent, NoContext> {
319 #[doc(hidden)]
320 pub fn __cancelled(resource_type: &'static str, detail: impl Into<String>) -> Self {
321 ResourceErrorBuilder {
322 resource_type: Some(resource_type),
323 detail: detail.into(),
324 variant: ErrorVariant::Cancelled,
325 resource: ResourceAbsent,
326 context: NoContext,
327 }
328 }
329}
330
331impl ResourceErrorBuilder<ResourceOptional, NeedsFieldViolation> {
332 #[doc(hidden)]
333 pub fn __invalid_argument(resource_type: &'static str, detail: impl Into<String>) -> Self {
334 ResourceErrorBuilder {
335 resource_type: Some(resource_type),
336 detail: detail.into(),
337 variant: ErrorVariant::InvalidArgument,
338 resource: ResourceOptional,
339 context: NeedsFieldViolation,
340 }
341 }
342
343 #[doc(hidden)]
344 pub fn __out_of_range(resource_type: &'static str, detail: impl Into<String>) -> Self {
345 ResourceErrorBuilder {
346 resource_type: Some(resource_type),
347 detail: detail.into(),
348 variant: ErrorVariant::OutOfRange,
349 resource: ResourceOptional,
350 context: NeedsFieldViolation,
351 }
352 }
353}
354
355impl ResourceErrorBuilder<ResourceOptional, NeedsQuotaViolation> {
356 #[doc(hidden)]
357 pub fn __resource_exhausted(resource_type: &'static str, detail: impl Into<String>) -> Self {
358 ResourceErrorBuilder {
359 resource_type: Some(resource_type),
360 detail: detail.into(),
361 variant: ErrorVariant::ResourceExhausted,
362 resource: ResourceOptional,
363 context: NeedsQuotaViolation,
364 }
365 }
366}
367
368impl ResourceErrorBuilder<ResourceOptional, NeedsPreconditionViolation> {
369 #[doc(hidden)]
370 pub fn __failed_precondition(resource_type: &'static str, detail: impl Into<String>) -> Self {
371 ResourceErrorBuilder {
372 resource_type: Some(resource_type),
373 detail: detail.into(),
374 variant: ErrorVariant::FailedPrecondition,
375 resource: ResourceOptional,
376 context: NeedsPreconditionViolation,
377 }
378 }
379}
380
381impl<Context> ResourceErrorBuilder<ResourceMissing, Context> {
386 #[must_use]
387 pub fn with_resource(
388 self,
389 resource: impl Into<String>,
390 ) -> ResourceErrorBuilder<ResourceSet, Context> {
391 ResourceErrorBuilder {
392 resource_type: self.resource_type,
393 detail: self.detail,
394 variant: self.variant,
395 resource: ResourceSet(resource.into()),
396 context: self.context,
397 }
398 }
399}
400
401impl<Context> ResourceErrorBuilder<ResourceOptional, Context> {
402 #[must_use]
403 pub fn with_resource(
404 self,
405 resource: impl Into<String>,
406 ) -> ResourceErrorBuilder<ResourceSet, Context> {
407 ResourceErrorBuilder {
408 resource_type: self.resource_type,
409 detail: self.detail,
410 variant: self.variant,
411 resource: ResourceSet(resource.into()),
412 context: self.context,
413 }
414 }
415}
416
417impl<Resource> ResourceErrorBuilder<Resource, NeedsFieldViolation> {
422 #[must_use]
423 pub fn with_field_violation(
424 self,
425 field: impl Into<String>,
426 description: impl Into<String>,
427 reason: impl Into<String>,
428 ) -> ResourceErrorBuilder<Resource, HasFieldViolations> {
429 ResourceErrorBuilder {
430 resource_type: self.resource_type,
431 detail: self.detail,
432 variant: self.variant,
433 resource: self.resource,
434 context: HasFieldViolations(vec![FieldViolation::new(field, description, reason)]),
435 }
436 }
437
438 #[must_use]
439 pub fn with_format(
440 self,
441 message: impl Into<String>,
442 ) -> ResourceErrorBuilder<Resource, HasFormatMessage> {
443 let msg = message.into();
444 ResourceErrorBuilder {
445 resource_type: self.resource_type,
446 detail: msg.clone(),
447 variant: self.variant,
448 resource: self.resource,
449 context: HasFormatMessage(msg),
450 }
451 }
452
453 #[must_use]
454 pub fn with_constraint(
455 self,
456 message: impl Into<String>,
457 ) -> ResourceErrorBuilder<Resource, HasConstraintMessage> {
458 let msg = message.into();
459 ResourceErrorBuilder {
460 resource_type: self.resource_type,
461 detail: msg.clone(),
462 variant: self.variant,
463 resource: self.resource,
464 context: HasConstraintMessage(msg),
465 }
466 }
467}
468
469impl<Resource> ResourceErrorBuilder<Resource, HasFieldViolations> {
470 #[must_use]
471 pub fn with_field_violation(
472 mut self,
473 field: impl Into<String>,
474 description: impl Into<String>,
475 reason: impl Into<String>,
476 ) -> Self {
477 self.context
478 .0
479 .push(FieldViolation::new(field, description, reason));
480 self
481 }
482}
483
484impl<Resource> ResourceErrorBuilder<Resource, NeedsPreconditionViolation> {
489 #[must_use]
490 pub fn with_precondition_violation(
491 self,
492 subject: impl Into<String>,
493 description: impl Into<String>,
494 type_: impl Into<String>,
495 ) -> ResourceErrorBuilder<Resource, HasPreconditionViolations> {
496 ResourceErrorBuilder {
497 resource_type: self.resource_type,
498 detail: self.detail,
499 variant: self.variant,
500 resource: self.resource,
501 context: HasPreconditionViolations(vec![PreconditionViolation::new(
502 type_,
503 subject,
504 description,
505 )]),
506 }
507 }
508}
509
510impl<Resource> ResourceErrorBuilder<Resource, HasPreconditionViolations> {
511 #[must_use]
512 pub fn with_precondition_violation(
513 mut self,
514 subject: impl Into<String>,
515 description: impl Into<String>,
516 type_: impl Into<String>,
517 ) -> Self {
518 self.context
519 .0
520 .push(PreconditionViolation::new(type_, subject, description));
521 self
522 }
523}
524
525impl<Resource> ResourceErrorBuilder<Resource, NeedsQuotaViolation> {
530 #[must_use]
531 pub fn with_quota_violation(
532 self,
533 subject: impl Into<String>,
534 description: impl Into<String>,
535 ) -> ResourceErrorBuilder<Resource, HasQuotaViolations> {
536 ResourceErrorBuilder {
537 resource_type: self.resource_type,
538 detail: self.detail,
539 variant: self.variant,
540 resource: self.resource,
541 context: HasQuotaViolations(vec![QuotaViolation::new(subject, description)]),
542 }
543 }
544}
545
546impl<Resource> ResourceErrorBuilder<Resource, HasQuotaViolations> {
547 #[must_use]
548 pub fn with_quota_violation(
549 mut self,
550 subject: impl Into<String>,
551 description: impl Into<String>,
552 ) -> Self {
553 self.context
554 .0
555 .push(QuotaViolation::new(subject, description));
556 self
557 }
558
559 #[must_use]
568 pub fn with_quota_violation_retry_after_seconds(mut self, seconds: u64) -> Self {
569 if let Some(last) = self.context.0.last_mut() {
570 last.retry_after_seconds = Some(seconds);
571 }
572 self
573 }
574}
575
576impl<Resource> ResourceErrorBuilder<Resource, NeedsReason> {
581 #[must_use]
582 pub fn with_reason(
583 self,
584 reason: impl Into<String>,
585 ) -> ResourceErrorBuilder<Resource, HasReason> {
586 ResourceErrorBuilder {
587 resource_type: self.resource_type,
588 detail: self.detail,
589 variant: self.variant,
590 resource: self.resource,
591 context: HasReason(reason.into()),
592 }
593 }
594}
595
596impl CanonicalError {
601 #[must_use]
602 pub fn internal(detail: impl Into<String>) -> ResourceErrorBuilder<ResourceAbsent, NoContext> {
603 ResourceErrorBuilder {
604 resource_type: None,
605 detail: detail.into(),
606 variant: ErrorVariant::Internal,
607 resource: ResourceAbsent,
608 context: NoContext,
609 }
610 }
611
612 #[must_use]
613 pub fn service_unavailable() -> ServiceUnavailableBuilder {
614 ServiceUnavailableBuilder {
615 retry_after_seconds: None,
616 detail: None,
617 }
618 }
619
620 #[must_use]
621 pub fn unauthenticated() -> ResourceErrorBuilder<ResourceAbsent, NeedsReason> {
622 ResourceErrorBuilder {
623 resource_type: None,
624 detail: String::from("Authentication required"),
625 variant: ErrorVariant::Unauthenticated,
626 resource: ResourceAbsent,
627 context: NeedsReason,
628 }
629 }
630}
631
632impl<Resource, Context> ResourceErrorBuilder<Resource, Context>
637where
638 Resource: ResourceResolved,
639 Context: ContextResolved,
640{
641 #[must_use]
642 pub fn create(self) -> CanonicalError {
643 let resource_name = self.resource.resolve();
644 let ctx_data = self.context.into_context_data();
645
646 let err = match self.variant {
647 ErrorVariant::NotFound => CanonicalError::__not_found(NotFound::new()),
648 ErrorVariant::AlreadyExists => CanonicalError::__already_exists(AlreadyExists::new()),
649 ErrorVariant::Aborted => CanonicalError::__aborted(Aborted::new(&ctx_data.reason)),
650 ErrorVariant::Unknown => CanonicalError::__unknown(Unknown::new(&self.detail)),
651 ErrorVariant::DeadlineExceeded => {
652 CanonicalError::__deadline_exceeded(DeadlineExceeded::new())
653 }
654 ErrorVariant::PermissionDenied => {
655 CanonicalError::__permission_denied(PermissionDenied::new(&ctx_data.reason))
656 }
657 ErrorVariant::InvalidArgument => {
658 let ctx = if let Some(fmt) = ctx_data.format_message {
659 InvalidArgument::format(fmt)
660 } else if let Some(cst) = ctx_data.constraint_message {
661 InvalidArgument::constraint(cst)
662 } else {
663 InvalidArgument::fields(ctx_data.field_violations)
664 };
665 CanonicalError::__invalid_argument(ctx)
666 }
667 ErrorVariant::OutOfRange => {
668 CanonicalError::__out_of_range(OutOfRange::new(ctx_data.field_violations))
669 }
670 ErrorVariant::ResourceExhausted => CanonicalError::__resource_exhausted(
671 ResourceExhausted::new(ctx_data.quota_violations),
672 ),
673 ErrorVariant::FailedPrecondition => CanonicalError::__failed_precondition(
674 FailedPrecondition::new(ctx_data.precondition_violations),
675 ),
676 ErrorVariant::Cancelled => CanonicalError::__cancelled(Cancelled::new()),
677 ErrorVariant::Unimplemented => CanonicalError::__unimplemented(Unimplemented::new()),
678 ErrorVariant::Internal => CanonicalError::__internal(Internal::new(&self.detail)),
679 ErrorVariant::DataLoss => CanonicalError::__data_loss(DataLoss::new()),
680 ErrorVariant::Unauthenticated => {
681 let mut ctx = Unauthenticated::new();
682 if !ctx_data.reason.is_empty() {
683 ctx = ctx.with_reason(ctx_data.reason);
684 }
685 CanonicalError::__unauthenticated(ctx)
686 }
687 };
688
689 let mut err = if matches!(
690 err,
691 CanonicalError::Internal { .. } | CanonicalError::Unknown { .. }
692 ) {
693 err
694 } else {
695 err.with_detail(&self.detail)
696 };
697
698 if let Some(rt) = self.resource_type {
699 err = err.with_resource_type(rt);
700 }
701
702 if let Some(rn) = resource_name {
703 err.with_resource(rn)
704 } else {
705 err
706 }
707 }
708}
709
710pub struct ServiceUnavailableBuilder {
715 retry_after_seconds: Option<u64>,
716 detail: Option<String>,
717}
718
719impl ServiceUnavailableBuilder {
720 #[must_use]
721 pub fn with_retry_after_seconds(mut self, seconds: u64) -> Self {
722 self.retry_after_seconds = Some(seconds);
723 self
724 }
725
726 #[must_use]
741 pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
742 self.detail = Some(detail.into());
743 self
744 }
745
746 #[must_use]
747 pub fn create(self) -> CanonicalError {
748 let detail = self
749 .detail
750 .unwrap_or_else(|| "Service temporarily unavailable".to_owned());
751 CanonicalError::__service_unavailable(ServiceUnavailable::new(self.retry_after_seconds))
752 .with_detail(detail)
753 }
754}