tfdiags: Allow construction of RPC-friendly Diagnostics

Due to the use of interfaces, Diagnostics is not super-friendly to the gob
encoding we currently use for plugin RPC. To mitigate this, we provide
a helper that converts all of the wrapped objects into a predictable flat
structure that we can pre-emptively register with gob.

This means that the decoded Diagnostics still has the same meaning as
the original, though the original wrapped errors (if any) are lost and
thus our errwrap integration won't be effective any longer.
This commit is contained in:
Martin Atkins 2017-10-05 11:40:31 -07:00
parent ab5efb805c
commit 780e758f1e
3 changed files with 143 additions and 0 deletions

View File

@ -102,6 +102,24 @@ func (diags Diagnostics) HasErrors() bool {
return false
}
// ForRPC returns a version of the receiver that has been simplified so that
// it is friendly to RPC protocols.
//
// Currently this means that it can be serialized with encoding/gob and
// subsequently re-inflated. It may later grow to include other serialization
// formats.
//
// Note that this loses information about the original objects used to
// construct the diagnostics, so e.g. the errwrap API will not work as
// expected on an error-wrapped Diagnostics that came from ForRPC.
func (diags Diagnostics) ForRPC() Diagnostics {
ret := make(Diagnostics, len(diags))
for i := range diags {
ret[i] = makeRPCFriendlyDiag(diags[i])
}
return ret
}
// Err flattens a diagnostics list into a single Go error, or to nil
// if the diagnostics list does not include any error-level diagnostics.
//

53
tfdiags/rpc_friendly.go Normal file
View File

@ -0,0 +1,53 @@
package tfdiags
import (
"encoding/gob"
)
type rpcFriendlyDiag struct {
Severity_ Severity
Summary_ string
Detail_ string
Subject_ *SourceRange
Context_ *SourceRange
}
// rpcFriendlyDiag transforms a given diagnostic so that is more friendly to
// RPC.
//
// In particular, it currently returns an object that can be serialized and
// later re-inflated using gob. This definition may grow to include other
// serializations later.
func makeRPCFriendlyDiag(diag Diagnostic) Diagnostic {
desc := diag.Description()
source := diag.Source()
return &rpcFriendlyDiag{
Severity_: diag.Severity(),
Summary_: desc.Summary,
Detail_: desc.Detail,
Subject_: source.Subject,
Context_: source.Context,
}
}
func (d *rpcFriendlyDiag) Severity() Severity {
return d.Severity_
}
func (d *rpcFriendlyDiag) Description() Description {
return Description{
Summary: d.Summary_,
Detail: d.Detail_,
}
}
func (d *rpcFriendlyDiag) Source() Source {
return Source{
Subject: d.Subject_,
Context: d.Context_,
}
}
func init() {
gob.Register((*rpcFriendlyDiag)(nil))
}

View File

@ -0,0 +1,72 @@
package tfdiags
import (
"bytes"
"encoding/gob"
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl2/hcl"
)
func TestDiagnosticsForRPC(t *testing.T) {
var diags Diagnostics
diags = diags.Append(fmt.Errorf("bad"))
diags = diags.Append(SimpleWarning("less bad"))
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "bad bad bad",
Detail: "badily bad bad",
Subject: &hcl.Range{
Filename: "foo",
},
Context: &hcl.Range{
Filename: "bar",
},
})
buf := bytes.Buffer{}
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
rpcDiags := diags.ForRPC()
err := enc.Encode(rpcDiags)
if err != nil {
t.Fatalf("error on Encode: %s", err)
}
var got Diagnostics
err = dec.Decode(&got)
if err != nil {
t.Fatalf("error on Decode: %s", err)
}
want := Diagnostics{
&rpcFriendlyDiag{
Severity_: Error,
Summary_: "bad",
},
&rpcFriendlyDiag{
Severity_: Warning,
Summary_: "less bad",
},
&rpcFriendlyDiag{
Severity_: Error,
Summary_: "bad bad bad",
Detail_: "badily bad bad",
Subject_: &SourceRange{
Filename: "foo",
},
Context_: &SourceRange{
Filename: "bar",
},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}