stackstate/statekeys: Representation of stacks state keys

Our model for state for a stack involves a set of objects that is each
identified by an opaque string key. Although those keys are opaque to
callers of Terraform Core, we will actually be using them for some meaning
in Terraform Core itself, since that will avoid redundantly storing the
same information both in the key and in the object associated with the
key.

This therefore aims to encapsulate the generation and parsing of these
keys to help ensure we'll always use them consistently.
pull/34738/head
Martin Atkins 3 years ago
parent ce1f8dd22b
commit 8b431f5038

@ -0,0 +1,36 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"fmt"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
)
type ComponentInstance struct {
ComponentInstanceAddr stackaddrs.AbsComponentInstance
}
func parseComponentInstance(s string) (Key, error) {
addrRaw, ok := finalKeyField(s)
if !ok {
return nil, fmt.Errorf("unsupported extra field in component instance key")
}
addr, diags := stackaddrs.ParseAbsComponentInstanceStr(addrRaw)
if diags.HasErrors() {
return nil, fmt.Errorf("component instance key has invalid component instance address %q", addrRaw)
}
return ComponentInstance{
ComponentInstanceAddr: addr,
}, nil
}
func (k ComponentInstance) KeyType() KeyType {
return ComponentInstanceType
}
func (k ComponentInstance) rawSuffix() string {
return k.ComponentInstanceAddr.String()
}

@ -0,0 +1,28 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package statekeys contains the definitions for the various different kinds
// of tracking key we use (or have historically used) for objects in a stack
// state.
//
// Stack state is a mutable data structure whose storage strategy is delegated
// to whatever is calling into Terraform Core. To allow Terraform Core to
// emit updates to that data structure piecemeal, rather than having to return
// the whole dataset over and over, we use tracking keys for each
// separately-updatable element of the state that are opaque to the caller but
// meaningful to Terraform Core.
//
// Callers are expected to use simple character-for-character string matching
// to compare these to recognize whether an update is describing an entirely
// new object or a replacement for ane existing object, and so the main
// requirement is that the content of these keys remains consistent across
// Terraform Core releases. However, from Terraform Core's perspective we
// also use these keys to carry some metadata about what is being tracked
// so we can avoid redundantly storing the same information in both the key
// and in the associated stored object.
//
// The keys defined in this package are in principle valid for use both as
// raw state keys and as external description keys, but some of them are used
// only for one or the other since the raw and external description forms
// don't necessarily have the same level of detail.
package statekeys

@ -0,0 +1,74 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
// Key is implemented by types that can be used as state keys.
type Key interface {
// KeyType returns the [KeyType] used for keys belonging to a particular
// implementation of [Key].
KeyType() KeyType
// rawSuffix returns additional characters that should appear after the
// key type portion of the final raw key.
//
// This is unexported both to help prevent accidental misuse (external
// callers MUST use [String] to obtain the correct string representation],
// and to prevent implementations of this interface from other packages.
// This package is the sole authority on state keys.
rawSuffix() string
}
// String returns the string representation of the given key, ready to be used
// in the RPC API representation of a [stackstate.AppliedChange] object.
func String(k Key) string {
if k == nil {
panic("called statekeys.String with nil Key")
}
return string(k.KeyType()) + k.rawSuffix()
}
// RecognizedType returns true if the given key has a [KeyType] that's known
// to the current version of this package, or false otherwise.
//
// If RecognizedType returns false, use the key's KeyType method to obtain
// the unrecognized type and then use its UnrecognizedKeyHandling method
// to determine the appropriate handling for the unrecognized key type.
func RecognizedType(k Key) bool {
if k == nil {
panic("called statekeys.RecognizedType with nil Key")
}
_, unrecognized := k.(Unrecognized)
return !unrecognized
}
// Unrecognized is a fallback [Key] implementation used when a given
// key has an unrecognized type.
//
// Unrecognized keys are round-trippable in that the RawKey method will return
// the same string that was originally parsed. Use
// KeyType.UnrecognizedKeyHandling to determine how Terraform Core should
// respond to the key having an unrecognized type.
type Unrecognized struct {
// ApparentKeyType is a [KeyType] representation of the type portion of the
// unrecognized key. Unlike most other [KeyType] values, this one
// will presumably not match any of the [KeyType] constants defined
// elsewhere in this package.
ApparentKeyType KeyType
// Remainder is a verbatim copy of whatever appeared after the type
// in the given key string. This is preserved only for round-tripping
// purposes and so should be treated as opaque.
remainder string
}
// KeyType returns the value from the ApparentKeyType field, which will
// presumably not match any of the [KeyType] constants in this package
// (because otherwise we would've used a different implementation of [Key]).
func (k Unrecognized) KeyType() KeyType {
return k.ApparentKeyType
}
func (k Unrecognized) rawSuffix() string {
return k.remainder
}

@ -0,0 +1,38 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"strings"
)
// rawKeyBuilder is a helper for building multi-field keys in the format
// that's expected by [cutKeyField].
//
// The zero value of rawKeyBuilder is ready to use.
type rawKeyBuilder struct {
b strings.Builder
w bool
}
// AppendField appends the given string to the key-in-progress as an additional
// field.
//
// The given string must not contain any unquoted commas, because comma is the
// field delimiter. If given an invalid field value this function will panic.
func (b *rawKeyBuilder) AppendField(s string) {
if keyDelimiterIdx(s) != -1 {
panic("key field contains the field delimiter")
}
if b.w {
b.b.WriteByte(',')
}
b.w = true
b.b.WriteString(s)
}
// Raw returns the assembled raw key string.
func (b *rawKeyBuilder) Raw() string {
return b.b.String()
}

@ -0,0 +1,102 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"fmt"
)
// Parse attempts to parse the given string as a state key, and returns the
// result if successful.
//
// A returned error means that the given string is syntactically invalid,
// which could mean either that it doesn't meet the basic requirements for
// any state key, or that it has a recognized key type but the remainder is
// not valid for that type.
//
// Parse DOES NOT return an error for a syntactically-valid key of an
// unrecognized type. Instead, it returns an [UnrecognizedKey] value which
// callers can detect using [RecognizedType], which will return false for
// a key of an unrecognized type.
func Parse(raw string) (Key, error) {
if len(raw) < 4 {
// All state keys must have at least four characters, since that's
// how long a key prefix is.
return nil, fmt.Errorf("too short to be a valid state key")
}
keyType := KeyType(raw[:4])
remain := raw[4:]
parser := keyParsers[keyType]
if parser == nil {
if !isPlausibleRawKeyType(string(keyType)) {
return nil, fmt.Errorf("invalid key type prefix %q", keyType)
}
return Unrecognized{
ApparentKeyType: keyType,
remainder: remain,
}, nil
}
return parser(remain)
}
var keyParsers = map[KeyType]func(string) (Key, error){
ResourceInstanceObjectType: parseResourceInstanceObject,
ComponentInstanceType: parseComponentInstance,
}
// cutKeyField is a key parsing helper for key types that consist of
// multiple fields concatenated together.
//
// cutKeyField returns the raw string content of the next field, and
// also returns any remaining text after the field delimeter which
// could therefore be used in a subsequent call to cutKeyField.
//
// The field delimiter is a comma, but the parser ignores any comma
// that appears to be inside a pair of double-quote characters (")
// so that it's safe to include an address with a string-based instance key
// (which could potentially contain a literal comma) and get back that same
// address as a single field.
//
// If the given string does not contain any delimiters, the result is the
// same string verbatim and an empty "remain" result.
func cutKeyField(raw string) (field, remain string) {
i := keyDelimiterIdx(raw)
if i == -1 {
return raw, ""
}
return raw[:i], raw[i+1:]
}
// finalKeyField returns the given string and true if it doesn't contain a key
// field delimiter, or "", false if the string does have a delimiter.
func finalKeyField(raw string) (string, bool) {
i := keyDelimiterIdx(raw)
if i != -1 {
return "", false
}
return raw, true
}
// keyDelimiterIdx finds the index of the first delimiter in the given
// string, or returns -1 if there is no delimiter in the string.
func keyDelimiterIdx(raw string) int {
inQuotes := false
escape := false
for i, c := range raw {
if c == ',' && !inQuotes {
return i
}
if c == '\\' {
escape = true
continue
}
if c == '"' && !escape {
inQuotes = !inQuotes
}
escape = false
}
// If we fall out here then the entire string seems to be
// a single field, with no delimiters.
return -1
}

@ -0,0 +1,353 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/states"
)
func TestParse(t *testing.T) {
tests := []struct {
Input string
Want Key
WantErr string
WantUnrecognizedHandling UnrecognizedKeyHandling
}{
{
Input: "",
WantErr: `too short to be a valid state key`,
},
{
Input: "a",
WantErr: `too short to be a valid state key`,
},
{
Input: "aa",
WantErr: `too short to be a valid state key`,
},
{
Input: "aaa",
WantErr: `too short to be a valid state key`,
},
{
Input: "aaa!", // this is a suitable length but contains an invalid character
WantErr: `invalid key type prefix "aaa!"`,
},
{
Input: "aaaa",
Want: Unrecognized{
ApparentKeyType: KeyType("aaaa"),
remainder: "",
},
WantUnrecognizedHandling: DiscardIfUnrecognized,
},
{
Input: "AAAA",
Want: Unrecognized{
ApparentKeyType: KeyType("AAAA"),
remainder: "",
},
WantUnrecognizedHandling: FailIfUnrecognized,
},
{
Input: "aaaA",
Want: Unrecognized{
ApparentKeyType: KeyType("aaaA"),
remainder: "",
},
WantUnrecognizedHandling: PreserveIfUnrecognized,
},
// Resource instance object keys
{
Input: "RSRC",
WantErr: `resource instance object key has invalid component instance address ""`,
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,cur",
Want: ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
},
},
Item: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
},
},
},
},
DeposedKey: states.NotDeposed,
},
WantUnrecognizedHandling: FailIfUnrecognized,
},
{
// Commas inside quoted instance keys are not treated as
// delimiters.
Input: `RSRCcomponent.foo["a,a"],aws_instance.bar["c,c"],cur`,
Want: ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey("a,a"),
},
},
Item: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
},
Key: addrs.StringKey("c,c"),
},
},
},
DeposedKey: states.NotDeposed,
},
WantUnrecognizedHandling: FailIfUnrecognized,
},
{
// Commas inside quoted instance keys are not treated as
// delimiters even when there's quote-escaping hazards.
Input: `RSRCcomponent.foo["a\",a"],aws_instance.bar["c\",c"],cur`,
Want: ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey(`a",a`),
},
},
Item: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
},
Key: addrs.StringKey(`c",c`),
},
},
},
DeposedKey: states.NotDeposed,
},
WantUnrecognizedHandling: FailIfUnrecognized,
},
{
Input: `RSRCstack.beep["a"].component.foo["b"],module.boop[1].aws_instance.bar[2],cur`,
Want: ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance.Child("beep", addrs.StringKey("a")),
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey("b"),
},
},
Item: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance.Child("boop", addrs.IntKey(1)),
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
},
Key: addrs.IntKey(2),
},
},
},
DeposedKey: states.NotDeposed,
},
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,facecafe",
Want: ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
},
},
Item: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "aws_instance",
Name: "bar",
},
},
},
},
DeposedKey: states.DeposedKey("facecafe"),
},
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,beef", // deposed key is invalid because it's not long enough
WantErr: `resource instance object key has invalid deposed key "beef"`,
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,tootcafe", // deposed key is invalid because it isn't all hex digits
WantErr: `resource instance object key has invalid deposed key "tootcafe"`,
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,FACECAFE", // deposed key is invalid because it uses uppercase hex digits
WantErr: `resource instance object key has invalid deposed key "FACECAFE"`,
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,", // last field must either be "cur" or a deposed key
WantErr: `resource instance object key has invalid deposed key ""`,
},
{
Input: "RSRCcomponent.foo,aws_instance.bar,cur,",
WantErr: `unsupported extra field in resource instance object key`,
},
// Component instance keys
{
Input: "CMPT",
WantErr: `component instance key has invalid component instance address ""`,
},
{
Input: "CMPTcomponent.foo",
Want: ComponentInstance{
ComponentInstanceAddr: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
},
},
},
WantUnrecognizedHandling: FailIfUnrecognized,
},
{
Input: `CMPTcomponent.foo["baz"]`,
Want: ComponentInstance{
ComponentInstanceAddr: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey("baz"),
},
},
},
},
{
Input: `CMPTstack.boop.component.foo["baz"]`,
Want: ComponentInstance{
ComponentInstanceAddr: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance.Child("boop", addrs.NoKey),
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey("baz"),
},
},
},
},
{
Input: `CMPTcomponent.foo["b,b"]`,
Want: ComponentInstance{
ComponentInstanceAddr: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey(`b,b`),
},
},
},
},
{
Input: `CMPTcomponent.foo["b\",b"]`,
Want: ComponentInstance{
ComponentInstanceAddr: stackaddrs.AbsComponentInstance{
Stack: stackaddrs.RootStackInstance,
Item: stackaddrs.ComponentInstance{
Component: stackaddrs.Component{
Name: "foo",
},
Key: addrs.StringKey(`b",b`),
},
},
},
},
{
Input: "CMPTcomponent.foo,",
WantErr: `unsupported extra field in component instance key`,
},
}
cmpOpts := cmp.AllowUnexported(Unrecognized{})
for _, test := range tests {
t.Run(test.Input, func(t *testing.T) {
got, err := Parse(test.Input)
if diff := cmp.Diff(test.Want, got, cmpOpts); diff != "" {
t.Errorf("wrong result for: %s\n%s", test.Input, diff)
}
if test.WantErr == "" {
if err != nil {
t.Errorf("unexpected error: %s", err)
}
// Any valid key should round-trip back to what we were given.
if got != nil {
gotAsStr := String(got)
if gotAsStr != test.Input {
t.Errorf("valid key of type %T did not round-trip\ngot: %s\nwant: %s", got, gotAsStr, test.Input)
}
} else if err == nil {
t.Error("Parse returned nil Key and nil error")
}
} else {
if err == nil {
t.Errorf("unexpected success\nwant error: %s", test.WantErr)
} else {
if got, want := err.Error(), test.WantErr; got != want {
t.Errorf("wrong error\ngot: %s\nwant: %s", got, want)
}
}
}
})
}
}

@ -0,0 +1,98 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"fmt"
)
// A KeyType represents a particular type of state key, which is typically
// associated with a particular kind of object that can be represented in
// stack state.
//
// Each KeyType consists of four ASCII letters which are intended to be
// somewhat mnemonic (at least for the more commonly-appearing ones)
// but are not intended for end-user consumption, because state storage
// keys are to be considered opaque by anything other than Terraform Core.
//
// There are some additional semantics encoded in the case of some of the
// letters, to help keep the encoding relatively compact:
// - If the first letter is uppercase then that means the key type is
// "mandatory", while if it's lowercase then the key type is "ignorable".
// Terraform Core will raise an error during state decoding if it encounters
// a mandatory key type that it isn't familiar with, but it will silently
// allow unrecognized key types that are ignorable.
// - For key types that are ignorable, if the _last_ letter is lowercase
// then the key type is "discarded", while if it's uppercase then the
// key type is "preserved". When Terraform Core encounters an unrecognized
// key type that is both ignorable and "discarded" then it will proactively
// emit an event to delete that unrecognized object from the state.
// If the key type is "preserved" then Terraform Core will just ignore it
// and let the existing object with that key continue to exist in the
// state.
//
// These behaviors are intended as a lightweight way to achieve some
// forward-compatibility by allowing an older version of Terraform Core to,
// when it's safe to do so, silently discard or preserve objects that were
// presumably added by a later version of Terraform. When we add new key types
// in future we should consider which of the three unrecognized key handling
// methods is most appropriate, preferring one of the two "ignorable" modes
// if possible but using a "mandatory" key type if ignoring a particular
// object could cause an older version of Terraform Core to misinterpret
// the overall meaning of the prior state.
type KeyType string
const (
ResourceInstanceObjectType KeyType = "RSRC"
ComponentInstanceType KeyType = "CMPT"
)
// UnrecognizedKeyHandling returns an indication of which of the three possible
// actions should be taken if the receiver is an unrecognized key type.
//
// It only really makes sense to use this method for a [KeyType] included in
// an [UnrecognizedKey] value.
func (kt KeyType) UnrecognizedKeyHandling() UnrecognizedKeyHandling {
first := kt[0]
last := kt[3]
switch {
case first >= 'A' && first <= 'Z':
return FailIfUnrecognized
case last >= 'A' && last <= 'Z':
return PreserveIfUnrecognized
default:
return DiscardIfUnrecognized
}
}
func (kt KeyType) GoString() string {
return fmt.Sprintf("statekeys.KeyType(%q)", kt)
}
func isPlausibleRawKeyType(s string) bool {
if len(s) != 4 {
return false
}
// All of the characters must be ASCII letters
for _, c := range s {
if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
return false
}
}
return true
}
// UnrecognizedKeyHandling models the three different ways an unrecognized
// key type can be handled when decoding prior state.
//
// See the documentation for [KeyType] for more information.
type UnrecognizedKeyHandling rune
//go:generate go run golang.org/x/tools/cmd/stringer -type UnrecognizedKeyHandling
const (
FailIfUnrecognized UnrecognizedKeyHandling = 'F'
PreserveIfUnrecognized UnrecognizedKeyHandling = 'P'
DiscardIfUnrecognized UnrecognizedKeyHandling = 'D'
)

@ -0,0 +1,70 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package statekeys
import (
"fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/states"
)
// ResourceInstanceObject represents state keys for resource instance objects.
type ResourceInstanceObject struct {
ResourceInstance stackaddrs.AbsResourceInstance
DeposedKey states.DeposedKey
}
func parseResourceInstanceObject(s string) (Key, error) {
componentInstAddrRaw, s := cutKeyField(s)
resourceInstAddrRaw, s := cutKeyField(s)
deposedRaw, ok := finalKeyField(s)
if !ok {
return nil, fmt.Errorf("unsupported extra field in resource instance object key")
}
componentInstAddr, diags := stackaddrs.ParseAbsComponentInstanceStr(componentInstAddrRaw)
if diags.HasErrors() {
return nil, fmt.Errorf("resource instance object key has invalid component instance address %q", componentInstAddrRaw)
}
resourceInstAddr, diags := addrs.ParseAbsResourceInstanceStr(resourceInstAddrRaw)
if diags.HasErrors() {
return nil, fmt.Errorf("resource instance object key has invalid resource instance address %q", resourceInstAddrRaw)
}
var deposedKey states.DeposedKey
if deposedRaw != "cur" {
var err error
deposedKey, err = states.ParseDeposedKey(deposedRaw)
if err != nil {
return nil, fmt.Errorf("resource instance object key has invalid deposed key %q", deposedRaw)
}
} else {
deposedKey = states.NotDeposed
}
return ResourceInstanceObject{
ResourceInstance: stackaddrs.AbsResourceInstance{
Component: componentInstAddr,
Item: resourceInstAddr,
},
DeposedKey: deposedKey,
}, nil
}
func (k ResourceInstanceObject) KeyType() KeyType {
return ResourceInstanceObjectType
}
func (k ResourceInstanceObject) rawSuffix() string {
var b rawKeyBuilder
b.AppendField(k.ResourceInstance.Component.String())
b.AppendField(k.ResourceInstance.Item.String())
if k.DeposedKey != states.NotDeposed {
// A valid deposed key is always eight hex digits, and never
// contains a comma so we can write it unquoted.
b.AppendField(string(k.DeposedKey))
} else {
b.AppendField("cur") // short for "current"
}
return b.Raw()
}

@ -0,0 +1,33 @@
// Code generated by "stringer -type UnrecognizedKeyHandling"; DO NOT EDIT.
package statekeys
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[FailIfUnrecognized-70]
_ = x[PreserveIfUnrecognized-80]
_ = x[DiscardIfUnrecognized-68]
}
const (
_UnrecognizedKeyHandling_name_0 = "DiscardIfUnrecognized"
_UnrecognizedKeyHandling_name_1 = "FailIfUnrecognized"
_UnrecognizedKeyHandling_name_2 = "PreserveIfUnrecognized"
)
func (i UnrecognizedKeyHandling) String() string {
switch {
case i == 68:
return _UnrecognizedKeyHandling_name_0
case i == 70:
return _UnrecognizedKeyHandling_name_1
case i == 80:
return _UnrecognizedKeyHandling_name_2
default:
return "UnrecognizedKeyHandling(" + strconv.FormatInt(int64(i), 10) + ")"
}
}
Loading…
Cancel
Save