package toproto import ( "unicode/utf8" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5" ) func Diagnostic(in *tfprotov5.Diagnostic) (*tfplugin5.Diagnostic, error) { diag := &tfplugin5.Diagnostic{ Severity: Diagnostic_Severity(in.Severity), Summary: forceValidUTF8(in.Summary), Detail: forceValidUTF8(in.Detail), } if in.Attribute != nil { attr, err := AttributePath(in.Attribute) if err != nil { return diag, err } diag.Attribute = attr } return diag, nil } func Diagnostic_Severity(in tfprotov5.DiagnosticSeverity) tfplugin5.Diagnostic_Severity { return tfplugin5.Diagnostic_Severity(in) } func Diagnostics(in []*tfprotov5.Diagnostic) ([]*tfplugin5.Diagnostic, error) { diagnostics := make([]*tfplugin5.Diagnostic, 0, len(in)) for _, diag := range in { if diag == nil { diagnostics = append(diagnostics, nil) continue } d, err := Diagnostic(diag) if err != nil { return diagnostics, err } diagnostics = append(diagnostics, d) } return diagnostics, nil } // forceValidUTF8 returns a string guaranteed to be valid UTF-8 even if the // input isn't, by replacing any invalid bytes with a valid UTF-8 encoding of // the Unicode Replacement Character (\uFFFD). // // The protobuf serialization library will reject invalid UTF-8 with an // unhelpful error message: // // string field contains invalid UTF-8 // // Passing a string result through this function makes invalid UTF-8 instead // emerge as placeholder characters on the other side of the wire protocol, // giving a better chance of still returning a partially-legible message // instead of a generic character encoding error. // // This is intended for user-facing messages such as diagnostic summary and // detail messages, where Terraform will just treat the value as opaque and // it's ultimately up to the user and their terminal or web browser to // interpret the result. Don't use this for strings that have machine-readable // meaning. func forceValidUTF8(s string) string { // Most strings that pass through here will already be valid UTF-8 and // utf8.ValidString has a fast path which will beat our rune-by-rune // analysis below, so it's worth the cost of walking the string twice // in the rarer invalid case. if utf8.ValidString(s) { return s } // If we get down here then we know there's at least one invalid UTF-8 // sequence in the string, so in this slow path we'll reconstruct the // string one rune at a time, guaranteeing that we'll only write valid // UTF-8 sequences into the resulting buffer. // // Any invalid string will grow at least a little larger as a result of // this operation because we'll be replacing each invalid byte with // the three-byte sequence \xEF\xBF\xBD, which is the UTF-8 encoding of // the replacement character \uFFFD. 9 is a magic number giving room for // three such expansions without any further allocation. ret := make([]byte, 0, len(s)+9) for { // If the first byte in s is not the start of a valid UTF-8 sequence // then the following will return utf8.RuneError, 1, where // utf8.RuneError is the unicode replacement character. r, advance := utf8.DecodeRuneInString(s) if advance == 0 { break } s = s[advance:] ret = utf8.AppendRune(ret, r) } return string(ret) } // we have to say this next thing to get golint to stop yelling at us about the // underscores in the function names. We want the function names to match // actually-generated code, so it feels like fair play. It's just a shame we // lose golint for the entire file. // // This file is not actually generated. You can edit it. Ignore this next line. // Code generated by hand ignore this next bit DO NOT EDIT.