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
560impl<Resource> ResourceErrorBuilder<Resource, NeedsReason> {
565 #[must_use]
566 pub fn with_reason(
567 self,
568 reason: impl Into<String>,
569 ) -> ResourceErrorBuilder<Resource, HasReason> {
570 ResourceErrorBuilder {
571 resource_type: self.resource_type,
572 detail: self.detail,
573 variant: self.variant,
574 resource: self.resource,
575 context: HasReason(reason.into()),
576 }
577 }
578}
579
580impl CanonicalError {
585 #[must_use]
586 pub fn internal(detail: impl Into<String>) -> ResourceErrorBuilder<ResourceAbsent, NoContext> {
587 ResourceErrorBuilder {
588 resource_type: None,
589 detail: detail.into(),
590 variant: ErrorVariant::Internal,
591 resource: ResourceAbsent,
592 context: NoContext,
593 }
594 }
595
596 #[must_use]
597 pub fn service_unavailable() -> ServiceUnavailableBuilder {
598 ServiceUnavailableBuilder {
599 retry_after_seconds: None,
600 }
601 }
602
603 #[must_use]
604 pub fn unauthenticated() -> ResourceErrorBuilder<ResourceAbsent, NeedsReason> {
605 ResourceErrorBuilder {
606 resource_type: None,
607 detail: String::from("Authentication required"),
608 variant: ErrorVariant::Unauthenticated,
609 resource: ResourceAbsent,
610 context: NeedsReason,
611 }
612 }
613}
614
615impl<Resource, Context> ResourceErrorBuilder<Resource, Context>
620where
621 Resource: ResourceResolved,
622 Context: ContextResolved,
623{
624 #[must_use]
625 pub fn create(self) -> CanonicalError {
626 let resource_name = self.resource.resolve();
627 let ctx_data = self.context.into_context_data();
628
629 let err = match self.variant {
630 ErrorVariant::NotFound => CanonicalError::__not_found(NotFound::new()),
631 ErrorVariant::AlreadyExists => CanonicalError::__already_exists(AlreadyExists::new()),
632 ErrorVariant::Aborted => CanonicalError::__aborted(Aborted::new(&ctx_data.reason)),
633 ErrorVariant::Unknown => CanonicalError::__unknown(Unknown::new(&self.detail)),
634 ErrorVariant::DeadlineExceeded => {
635 CanonicalError::__deadline_exceeded(DeadlineExceeded::new())
636 }
637 ErrorVariant::PermissionDenied => {
638 CanonicalError::__permission_denied(PermissionDenied::new(&ctx_data.reason))
639 }
640 ErrorVariant::InvalidArgument => {
641 let ctx = if let Some(fmt) = ctx_data.format_message {
642 InvalidArgument::format(fmt)
643 } else if let Some(cst) = ctx_data.constraint_message {
644 InvalidArgument::constraint(cst)
645 } else {
646 InvalidArgument::fields(ctx_data.field_violations)
647 };
648 CanonicalError::__invalid_argument(ctx)
649 }
650 ErrorVariant::OutOfRange => {
651 CanonicalError::__out_of_range(OutOfRange::new(ctx_data.field_violations))
652 }
653 ErrorVariant::ResourceExhausted => CanonicalError::__resource_exhausted(
654 ResourceExhausted::new(ctx_data.quota_violations),
655 ),
656 ErrorVariant::FailedPrecondition => CanonicalError::__failed_precondition(
657 FailedPrecondition::new(ctx_data.precondition_violations),
658 ),
659 ErrorVariant::Cancelled => CanonicalError::__cancelled(Cancelled::new()),
660 ErrorVariant::Unimplemented => CanonicalError::__unimplemented(Unimplemented::new()),
661 ErrorVariant::Internal => CanonicalError::__internal(Internal::new(&self.detail)),
662 ErrorVariant::DataLoss => CanonicalError::__data_loss(DataLoss::new()),
663 ErrorVariant::Unauthenticated => {
664 let mut ctx = Unauthenticated::new();
665 if !ctx_data.reason.is_empty() {
666 ctx = ctx.with_reason(ctx_data.reason);
667 }
668 CanonicalError::__unauthenticated(ctx)
669 }
670 };
671
672 let mut err = if matches!(
673 err,
674 CanonicalError::Internal { .. } | CanonicalError::Unknown { .. }
675 ) {
676 err
677 } else {
678 err.with_detail(&self.detail)
679 };
680
681 if let Some(rt) = self.resource_type {
682 err = err.with_resource_type(rt);
683 }
684
685 if let Some(rn) = resource_name {
686 err.with_resource(rn)
687 } else {
688 err
689 }
690 }
691}
692
693pub struct ServiceUnavailableBuilder {
698 retry_after_seconds: Option<u64>,
699}
700
701impl ServiceUnavailableBuilder {
702 #[must_use]
703 pub fn with_retry_after_seconds(mut self, seconds: u64) -> Self {
704 self.retry_after_seconds = Some(seconds);
705 self
706 }
707
708 #[must_use]
709 pub fn create(self) -> CanonicalError {
710 CanonicalError::__service_unavailable(ServiceUnavailable::new(self.retry_after_seconds))
711 .with_detail("Service temporarily unavailable")
712 }
713}