Last active
July 20, 2023 18:08
-
-
Save jpbetz/039a7eabe8d7e33852ca8552102b955a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ValidFieldPath validates that jsonPath is a valid JSON Path containing only field and map accessors | |
// that are valid for the given schema, and returns a field.Path representation of the validated jsonPath or an error. | |
func ValidFieldPath(jsonPath string, schema *schema.Structural) (validFieldPath *field.Path, err error) { | |
appendToPath := func(name string) error { | |
if schema.AdditionalProperties != nil { | |
validFieldPath = validFieldPath.Key(name) | |
schema = schema.AdditionalProperties.Structural | |
} else if schema.Properties != nil { | |
validFieldPath = validFieldPath.Child(name) | |
val, ok := schema.Properties[name] | |
if !ok { | |
return fmt.Errorf("does not refer to a valid field") | |
} | |
schema = &val | |
} else { | |
return fmt.Errorf("does not refer to a valid field") | |
} | |
return nil | |
} | |
validFieldPath = nil | |
scanner := bufio.NewScanner(strings.NewReader(jsonPath)) | |
// configure the scanner to split the string into tokens. | |
// The three delimiters ('.', '[', ']') will be returned as single char tokens. | |
// All other text between delimiters is returned as string tokens. | |
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { | |
if len(data) > 0 { | |
for i := 0; i < len(data); i++ { | |
// If in a single quoted string, look for the end of string | |
// ignoring delimiters. | |
if data[0] == '\'' { | |
if i > 0 && data[i] == '\'' && data[i-1] != '\\' { | |
// Return quoted string | |
return i + 1, data[:i+1], nil | |
} | |
continue | |
} | |
switch data[i] { | |
case '.', '[', ']': // delimiters | |
if i == 0 { | |
// Return the delimiter. | |
return 1, data[:1], nil | |
} else { | |
// Return identifier leading up to the delimiter. | |
// The next call to split will return the delimiter. | |
return i, data[:i], nil | |
} | |
} | |
} | |
if atEOF { | |
// Return the string. | |
return len(data), data, nil | |
} | |
} | |
return 0, nil, nil | |
}) | |
var tok string | |
for scanner.Scan() { | |
tok = scanner.Text() | |
switch tok { | |
case "[": | |
if !scanner.Scan() { | |
return nil, fmt.Errorf("unexpected end of JSON path") | |
} | |
tok = scanner.Text() | |
if !strings.HasPrefix(tok, "'") || !strings.HasSuffix(tok, "'") { | |
return nil, fmt.Errorf("expected single quoted string but got %s", tok) | |
} | |
unescaped, err := unescapeSingleQuote(tok[1 : len(tok)-1]) | |
if err != nil { | |
return nil, fmt.Errorf("invalid string literal: %v", err) | |
} | |
if err := appendToPath(unescaped); err != nil { | |
return nil, err | |
} | |
if !scanner.Scan() { | |
return nil, fmt.Errorf("unexpected end of JSON path") | |
} | |
tok = scanner.Text() | |
if tok != "]" { | |
return nil, fmt.Errorf("expected ] but got %s", tok) | |
} | |
case ".": | |
if !scanner.Scan() { | |
return nil, fmt.Errorf("unexpected end of JSON path") | |
} | |
tok = scanner.Text() | |
if err := appendToPath(tok); err != nil { | |
return nil, err | |
} | |
default: | |
return nil, fmt.Errorf("expected [ or . but got: %s", tok) | |
} | |
} | |
return validFieldPath, nil | |
} | |
var unescapeMatcher = regexp.MustCompile(`\\.`) | |
func unescapeSingleQuote(s string) (string, error) { | |
var err error | |
unescaped := unescapeMatcher.ReplaceAllStringFunc(s, func(matchStr string) string { | |
directive := matchStr[1] | |
switch directive { | |
case 'a': | |
return "\a" | |
case 'b': | |
return "\b" | |
case 'f': | |
return "\f" | |
case 'n': | |
return "\n" | |
case 'r': | |
return "\r" | |
case 't': | |
return "\t" | |
case 'v': | |
return "\v" | |
case '\'': | |
return "'" | |
case '\\': | |
return "\\" | |
default: | |
err = fmt.Errorf("invalid escape char %s", matchStr) | |
return "" | |
} | |
}) | |
return unescaped, err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment