From 780e758f1e3541322c1e75e2ee8e229767d4ac82 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 5 Oct 2017 11:40:31 -0700 Subject: [PATCH] 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. --- tfdiags/diagnostics.go | 18 +++++++++ tfdiags/rpc_friendly.go | 53 ++++++++++++++++++++++++++ tfdiags/rpc_friendly_test.go | 72 ++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 tfdiags/rpc_friendly.go create mode 100644 tfdiags/rpc_friendly_test.go 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)) + } +}