@@ -115,10 +115,18 @@ func encodeAsAny(ctx context.Context, err error, payload proto.Message) *types.A
115115func encodeWrapper (ctx context.Context , err , cause error ) EncodedError {
116116 var msg string
117117 var details errorspb.EncodedErrorDetails
118+ messageType := Prefix
118119
119120 if e , ok := err .(* opaqueWrapper ); ok {
121+ // We delegate all knowledge of the error string
122+ // to the original encoder and do not try to re-engineer
123+ // the prefix out of the error. This helps maintain
124+ // backward compatibility with earlier versions of the
125+ // encoder which don't have any understanding of
126+ // error string ownership by the wrapper.
120127 msg = e .prefix
121128 details = e .details
129+ messageType = e .messageType
122130 } else {
123131 details .OriginalTypeName , details .ErrorTypeMark .FamilyName , details .ErrorTypeMark .Extension = getTypeDetails (err , false /*onlyFamily*/ )
124132
@@ -127,12 +135,12 @@ func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
127135 // If we have a manually registered encoder, use that.
128136 typeKey := TypeKey (details .ErrorTypeMark .FamilyName )
129137 if enc , ok := encoders [typeKey ]; ok {
130- msg , details .ReportablePayload , payload = enc (ctx , err )
138+ msg , details .ReportablePayload , payload , messageType = enc (ctx , err )
131139 } else {
132140 // No encoder.
133141 // In that case, we'll try to compute a message prefix
134142 // manually.
135- msg = extractPrefix (err , cause )
143+ msg , messageType = extractPrefix (err , cause )
136144
137145 // If there are known safe details, use them.
138146 if s , ok := err .(SafeDetailer ); ok {
@@ -148,31 +156,47 @@ func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
148156 return EncodedError {
149157 Error : & errorspb.EncodedError_Wrapper {
150158 Wrapper : & errorspb.EncodedWrapper {
151- Cause : EncodeError (ctx , cause ),
152- MessagePrefix : msg ,
153- Details : details ,
159+ Cause : EncodeError (ctx , cause ),
160+ Message : msg ,
161+ Details : details ,
162+ MessageType : errorspb .MessageType (messageType ),
154163 },
155164 },
156165 }
157166}
158167
159168// extractPrefix extracts the prefix from a wrapper's error message.
160169// For example,
161- // err := errors.New("bar")
162- // err = errors.Wrap(err, "foo")
163- // extractPrefix(err)
170+ //
171+ // err := errors.New("bar")
172+ // err = errors.Wrap(err, "foo")
173+ // extractPrefix(err)
174+ //
164175// returns "foo".
165- func extractPrefix (err , cause error ) string {
176+ //
177+ // If a presumed wrapper does not have a message prefix, it is assumed
178+ // to override the entire error message and `extractPrefix` returns
179+ // the entire message and the boolean `true` to signify that the causes
180+ // should not be appended to it.
181+ func extractPrefix (err , cause error ) (string , MessageType ) {
166182 causeSuffix := cause .Error ()
167183 errMsg := err .Error ()
168184
169185 if strings .HasSuffix (errMsg , causeSuffix ) {
170186 prefix := errMsg [:len (errMsg )- len (causeSuffix )]
187+ // If error msg matches exactly then this is a wrapper
188+ // with no message of its own.
189+ if len (prefix ) == 0 {
190+ return "" , Prefix
191+ }
171192 if strings .HasSuffix (prefix , ": " ) {
172- return prefix [:len (prefix )- 2 ]
193+ return prefix [:len (prefix )- 2 ], Prefix
173194 }
174195 }
175- return ""
196+ // If we don't have the cause as a suffix, then we have
197+ // some other string as our error msg, preserve that and
198+ // mark as override
199+ return errMsg , FullMessage
176200}
177201
178202func getTypeDetails (
@@ -295,6 +319,35 @@ var leafEncoders = map[TypeKey]LeafEncoder{}
295319// or a different type, ensure that RegisterTypeMigration() was called
296320// prior to RegisterWrapperEncoder().
297321func RegisterWrapperEncoder (theType TypeKey , encoder WrapperEncoder ) {
322+ RegisterWrapperEncoderWithMessageType (
323+ theType ,
324+ func (ctx context.Context , err error ) (
325+ msgPrefix string ,
326+ safeDetails []string ,
327+ payload proto.Message ,
328+ messageType MessageType ,
329+ ) {
330+ prefix , details , payload := encoder (ctx , err )
331+ return prefix , details , payload , messageType
332+ })
333+ }
334+
335+ // RegisterWrapperEncoderWithMessageType can be used to register
336+ // new wrapper types to the library. Registered wrappers will be
337+ // encoded using their own Go type when an error is encoded. Wrappers
338+ // that have not been registered will be encoded using the
339+ // opaqueWrapper type.
340+ //
341+ // This function differs from RegisterWrapperEncoder by allowing the
342+ // caller to explicitly decide whether the wrapper owns the entire
343+ // error message or not. Otherwise, the relationship is inferred.
344+ //
345+ // Note: if the error type has been migrated from a previous location
346+ // or a different type, ensure that RegisterTypeMigration() was called
347+ // prior to RegisterWrapperEncoder().
348+ func RegisterWrapperEncoderWithMessageType (
349+ theType TypeKey , encoder WrapperEncoderWithMessageType ,
350+ ) {
298351 if encoder == nil {
299352 delete (encoders , theType )
300353 } else {
@@ -304,7 +357,42 @@ func RegisterWrapperEncoder(theType TypeKey, encoder WrapperEncoder) {
304357
305358// WrapperEncoder is to be provided (via RegisterWrapperEncoder above)
306359// by additional wrapper types not yet known to this library.
307- type WrapperEncoder = func (ctx context.Context , err error ) (msgPrefix string , safeDetails []string , payload proto.Message )
360+ type WrapperEncoder func (ctx context.Context , err error ) (
361+ msgPrefix string ,
362+ safeDetails []string ,
363+ payload proto.Message ,
364+ )
365+
366+ // MessageType is used to encode information about an error message
367+ // within a wrapper error type. This information is used to affect
368+ // display logic.
369+ type MessageType errorspb.MessageType
370+
371+ // Values below should match the ones in errorspb.MessageType for
372+ // direct conversion.
373+ const (
374+ // Prefix denotes an error message that should be prepended to the
375+ // message of its cause.
376+ Prefix MessageType = MessageType (errorspb .MessageType_PREFIX )
377+ // FullMessage denotes an error message that contains the text of its
378+ // causes and can be displayed standalone.
379+ FullMessage = MessageType (errorspb .MessageType_FULL_MESSAGE )
380+ )
381+
382+ // WrapperEncoderWithMessageType is to be provided (via
383+ // RegisterWrapperEncoderWithMessageType above) by additional wrapper
384+ // types not yet known to this library. This encoder returns an
385+ // additional enum which indicates whether the wrapper owns the error
386+ // message completely instead of simply being a prefix with the error
387+ // message of its causes appended to it. This information is encoded
388+ // along with the prefix in order to provide context during error
389+ // display.
390+ type WrapperEncoderWithMessageType func (ctx context.Context , err error ) (
391+ msgPrefix string ,
392+ safeDetails []string ,
393+ payload proto.Message ,
394+ messageType MessageType ,
395+ )
308396
309397// registry for RegisterWrapperType.
310- var encoders = map [TypeKey ]WrapperEncoder {}
398+ var encoders = map [TypeKey ]WrapperEncoderWithMessageType {}
0 commit comments