@@ -45,27 +45,45 @@ type (
4545 WithPreconditionFailure (failure * errdetails.PreconditionFailure ) SpecErrorable
4646 WithBadRequest (request * errdetails.BadRequest ) SpecErrorable
4747 WithHelp (help * errdetails.Help ) SpecErrorable
48+ WithDeveloperHelp (help * errdetails.Help ) SpecErrorable
4849 WithSpecDetail (spec * specv2pb.Spec ) SpecErrorable
4950 WithLocalizedMessage (message * errdetails.LocalizedMessage ) SpecErrorable
5051 WithInternalErrorDetail (errs ... error ) SpecErrorable
5152 // WithDebugDetail(ctx context.Context, spec *specv2pb.Spec, errs ...error) SpecErrorable
5253 SpecReason () Reason
5354 ToStatus () * status.Status
5455 ToConnectError () * connect.Error
56+ Code () connect.Code
57+ Details () * typev2pb.SpecErrorDetails
5558 error
5659 }
5760
5861 // SpecError the main Error type
5962 SpecError struct {
6063 reason Reason
6164 ConnectErr connect.Error
65+ details * typev2pb.SpecErrorDetails
6266 }
6367)
6468
6569// NewSpecError creates a new connect.Error with a specified code, detail, and message, adding the detail to the error.
6670func NewSpecError (code connect.Code , message string ) * SpecError {
71+ err := connect .NewError (code , errors .New (message ))
72+ details := extractSpecErrorDetails (err )
6773 ee := SpecError {
68- ConnectErr : * connect .NewError (code , errors .New (message )),
74+ ConnectErr : * err ,
75+ details : details ,
76+ }
77+
78+ return & ee
79+ }
80+
81+ // NewSpecErrorFromConnectError converts a *connect.Error to *SpecError.
82+ func NewSpecErrorFromConnectError (err * connect.Error ) * SpecError {
83+ details := extractSpecErrorDetails (err )
84+ ee := SpecError {
85+ ConnectErr : * err ,
86+ details : details ,
6987 }
7088
7189 return & ee
@@ -75,8 +93,10 @@ func NewSpecErrorFromStatus(status *status.Status) SpecErrorable {
7593 if status == nil {
7694 return ErrServerInternal .WithInternalErrorDetail (errors .New ("status is nil when attempting to create a new spec error" ))
7795 }
96+
97+ cerr := connect .NewError (connect .Code (status .Code ), errors .New (status .Message )) // nolint:gosec
7898 ee := SpecError {
79- ConnectErr : * connect . NewError ( connect . Code ( status . Code ), errors . New ( status . Message )), // nolint:gosec
99+ ConnectErr : * cerr ,
80100 }
81101
82102 if status .Details != nil {
@@ -90,6 +110,9 @@ func NewSpecErrorFromStatus(status *status.Status) SpecErrorable {
90110 }
91111 }
92112
113+ details := extractSpecErrorDetails (cerr )
114+ ee .details = details
115+
93116 return & ee
94117}
95118
@@ -219,6 +242,20 @@ func (se *SpecError) WithHelp(help *errdetails.Help) SpecErrorable {
219242 return se
220243}
221244
245+ func (se * SpecError ) WithDeveloperHelp (help * errdetails.Help ) SpecErrorable {
246+ for _ , detail := range help .GetLinks () {
247+ detail .Description = "dev: " + detail .Description
248+ }
249+ d , err := connect .NewErrorDetail (help )
250+ if err != nil {
251+ apexlog .Error ("server: SpecError creating new Developer Help" )
252+ return se
253+ }
254+
255+ se .ConnectErr .AddDetail (d )
256+ return se
257+ }
258+
222259func (se * SpecError ) WithLocalizedMessage (message * errdetails.LocalizedMessage ) SpecErrorable {
223260 d , err := connect .NewErrorDetail (message )
224261 if err != nil {
@@ -374,6 +411,8 @@ func (se *SpecError) ToConnectError() *connect.Error {
374411 return & se .ConnectErr
375412}
376413
414+ func (e * SpecError ) Details () * typev2pb.SpecErrorDetails { return e .details }
415+
377416// IsSpecErrorable checks if the given error implements SpecErrorable.
378417// Returns the casted SpecErrorable and a bool indicating success.
379418func IsSpecErrorable (err error ) (SpecErrorable , bool ) {
@@ -385,8 +424,59 @@ func IsSpecErrorable(err error) (SpecErrorable, bool) {
385424 return se , ok
386425}
387426
427+ func ToSpecErrorable (err error ) SpecErrorable {
428+ var cerr * connect.Error
429+ if errors .As (err , & cerr ) {
430+ return NewSpecErrorFromConnectError (cerr )
431+ }
432+
433+ return NewSpecError (connect .CodeInternal , err .Error ())
434+ }
435+
388436// IsReason Quick check by reason, anywhere in the error chain
389437func IsReason (err error , reason Reason ) bool {
390438 var hr HasReason
391439 return errors .As (err , & hr ) && hr .SpecReason () == reason
392440}
441+
442+ // extractSpecErrorDetails parses well-known google.rpc.* details into SpecErrorDetails.
443+ func extractSpecErrorDetails (err * connect.Error ) * typev2pb.SpecErrorDetails {
444+ d := & typev2pb.SpecErrorDetails {
445+ Code : uint32 (err .Code ()),
446+ Reason : err .Message (),
447+ Error : err .Message (),
448+ }
449+
450+ for _ , det := range err .Details () {
451+ msg , _ := det .Value ()
452+ switch v := msg .(type ) {
453+ case * errdetails.LocalizedMessage :
454+ d .LocalizedMessage = v
455+ case * errdetails.RequestInfo :
456+ d .RequestInto = v
457+ case * errdetails.ResourceInfo :
458+ d .ResourceInto = v
459+ case * errdetails.ErrorInfo :
460+ d .ErrorInfo = v
461+ case * errdetails.RetryInfo :
462+ d .RetryInfo = v
463+ case * errdetails.DebugInfo :
464+ d .DebugInfo = v
465+ case * errdetails.QuotaFailure :
466+ d .QuotaFailure = v
467+ case * errdetails.PreconditionFailure :
468+ d .PreconditionFailure = v
469+ case * errdetails.BadRequest :
470+ d .BadRequest = v
471+ case * errdetails.Help :
472+ // Decide whether it’s developer_help or user_help based on link descriptions
473+ if len (v .Links ) > 0 && strings .HasPrefix (v .Links [0 ].Description , "dev:" ) {
474+ d .DeveloperHelp = v
475+ } else {
476+ d .Help = v
477+ }
478+ }
479+ }
480+
481+ return d
482+ }
0 commit comments