mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
104 lines
3.2 KiB
Go
104 lines
3.2 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// 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,
|
|
OutputType: parseOutput,
|
|
VariableType: parseVariable,
|
|
}
|
|
|
|
// 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
|
|
}
|