diff --git a/tfdiags/diagnostics.go b/tfdiags/diagnostics.go index 23c92fa8b..354285784 100644 --- a/tfdiags/diagnostics.go +++ b/tfdiags/diagnostics.go @@ -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. // diff --git a/tfdiags/rpc_friendly.go b/tfdiags/rpc_friendly.go new file mode 100644 index 000000000..6cc95cc2e --- /dev/null +++ b/tfdiags/rpc_friendly.go @@ -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)) +} diff --git a/tfdiags/rpc_friendly_test.go b/tfdiags/rpc_friendly_test.go new file mode 100644 index 000000000..ff60590db --- /dev/null +++ b/tfdiags/rpc_friendly_test.go @@ -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)) + } +}