mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Ignoring fields with struct tag expr:"-" and make "in" return false for unexported fields (#806)
* allow ignoring struct member using a - in expr struct tag * fix in operator * simplify logic * add regression test for #807 * fixes #807: make in operator return false for unexported struct fields --------- Co-authored-by: Anton Medvedev <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Anton Medvedev
parent
fefc347053
commit
13992b0526
+6
-2
@@ -421,10 +421,14 @@ func get(params ...any) (out any, err error) {
|
||||
fieldName := i.(string)
|
||||
value := v.FieldByNameFunc(func(name string) bool {
|
||||
field, _ := v.Type().FieldByName(name)
|
||||
if field.Tag.Get("expr") == fieldName {
|
||||
switch field.Tag.Get("expr") {
|
||||
case "-":
|
||||
return false
|
||||
case fieldName:
|
||||
return true
|
||||
default:
|
||||
return name == fieldName
|
||||
}
|
||||
return name == fieldName
|
||||
})
|
||||
if value.IsValid() {
|
||||
return value.Interface(), nil
|
||||
|
||||
+14
-6
@@ -6,11 +6,15 @@ import (
|
||||
"github.com/expr-lang/expr/internal/deref"
|
||||
)
|
||||
|
||||
func fieldName(field reflect.StructField) string {
|
||||
if taggedName := field.Tag.Get("expr"); taggedName != "" {
|
||||
return taggedName
|
||||
func fieldName(field reflect.StructField) (string, bool) {
|
||||
switch taggedName := field.Tag.Get("expr"); taggedName {
|
||||
case "-":
|
||||
return "", false
|
||||
case "":
|
||||
return field.Name, true
|
||||
default:
|
||||
return taggedName, true
|
||||
}
|
||||
return field.Name
|
||||
}
|
||||
|
||||
func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
|
||||
@@ -23,7 +27,7 @@ func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
// Search all fields, even embedded structs.
|
||||
if fieldName(field) == name {
|
||||
if n, ok := fieldName(field); ok && n == name {
|
||||
return field, true
|
||||
}
|
||||
}
|
||||
@@ -69,7 +73,11 @@ func StructFields(t reflect.Type) map[string]Nature {
|
||||
}
|
||||
}
|
||||
|
||||
table[fieldName(f)] = Nature{
|
||||
name, ok := fieldName(f)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
table[name] = Nature{
|
||||
Type: f.Type,
|
||||
FieldIndex: f.Index,
|
||||
}
|
||||
|
||||
+99
-2
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -139,7 +140,86 @@ func ExampleEnv_tagged_field_names() {
|
||||
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output : Hello World
|
||||
// Output: Hello World
|
||||
}
|
||||
|
||||
func ExampleEnv_hidden_tagged_field_names() {
|
||||
type Internal struct {
|
||||
Visible string
|
||||
Hidden string `expr:"-"`
|
||||
}
|
||||
type environment struct {
|
||||
Visible string
|
||||
Hidden string `expr:"-"`
|
||||
HiddenInternal Internal `expr:"-"`
|
||||
VisibleInternal Internal
|
||||
}
|
||||
|
||||
env := environment{
|
||||
Hidden: "First level secret",
|
||||
HiddenInternal: Internal{
|
||||
Visible: "Second level secret",
|
||||
Hidden: "Also hidden",
|
||||
},
|
||||
VisibleInternal: Internal{
|
||||
Visible: "Not a secret",
|
||||
Hidden: "Hidden too",
|
||||
},
|
||||
}
|
||||
|
||||
hiddenValues := []string{
|
||||
`Hidden`,
|
||||
`HiddenInternal`,
|
||||
`HiddenInternal.Visible`,
|
||||
`HiddenInternal.Hidden`,
|
||||
`VisibleInternal["Hidden"]`,
|
||||
}
|
||||
for _, expression := range hiddenValues {
|
||||
output, err := expr.Eval(expression, env)
|
||||
if err == nil || !strings.Contains(err.Error(), "cannot fetch") {
|
||||
fmt.Printf("unexpected output: %v; err: %v\n", output, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%q is hidden as expected\n", expression)
|
||||
}
|
||||
|
||||
visibleValues := []string{
|
||||
`Visible`,
|
||||
`VisibleInternal`,
|
||||
`VisibleInternal["Visible"]`,
|
||||
}
|
||||
for _, expression := range visibleValues {
|
||||
_, err := expr.Eval(expression, env)
|
||||
if err != nil {
|
||||
fmt.Printf("unexpected error: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%q is visible as expected\n", expression)
|
||||
}
|
||||
|
||||
testWithIn := []string{
|
||||
`not ("Hidden" in $env)`,
|
||||
`"Visible" in $env`,
|
||||
`not ("Hidden" in VisibleInternal)`,
|
||||
`"Visible" in VisibleInternal`,
|
||||
}
|
||||
for _, expression := range testWithIn {
|
||||
val, err := expr.Eval(expression, env)
|
||||
shouldBeTrue, ok := val.(bool)
|
||||
if err != nil || !ok || !shouldBeTrue {
|
||||
fmt.Printf("unexpected result; value: %v; error: %v\n", val, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Output: "Hidden" is hidden as expected
|
||||
// "HiddenInternal" is hidden as expected
|
||||
// "HiddenInternal.Visible" is hidden as expected
|
||||
// "HiddenInternal.Hidden" is hidden as expected
|
||||
// "VisibleInternal[\"Hidden\"]" is hidden as expected
|
||||
// "Visible" is visible as expected
|
||||
// "VisibleInternal" is visible as expected
|
||||
// "VisibleInternal[\"Visible\"]" is visible as expected
|
||||
}
|
||||
|
||||
func ExampleAsKind() {
|
||||
@@ -529,7 +609,7 @@ func ExamplePatch() {
|
||||
}
|
||||
fmt.Printf("%v", output)
|
||||
|
||||
// Output : Hello, you, world!
|
||||
// Output: Hello, you, world!
|
||||
}
|
||||
|
||||
func ExampleWithContext() {
|
||||
@@ -2765,3 +2845,20 @@ func TestMemoryBudget(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue807(t *testing.T) {
|
||||
type MyStruct struct {
|
||||
nonExported string
|
||||
}
|
||||
out, err := expr.Eval(` "nonExported" in $env `, MyStruct{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
b, ok := out.(bool)
|
||||
if !ok {
|
||||
t.Fatalf("expected boolean type, got %T: %v", b, b)
|
||||
}
|
||||
if b {
|
||||
t.Fatalf("expected 'in' operator to return false for unexported field")
|
||||
}
|
||||
}
|
||||
|
||||
+11
-3
@@ -65,10 +65,14 @@ func Fetch(from, i any) any {
|
||||
fieldName := i.(string)
|
||||
value := v.FieldByNameFunc(func(name string) bool {
|
||||
field, _ := v.Type().FieldByName(name)
|
||||
if field.Tag.Get("expr") == fieldName {
|
||||
switch field.Tag.Get("expr") {
|
||||
case "-":
|
||||
return false
|
||||
case fieldName:
|
||||
return true
|
||||
default:
|
||||
return name == fieldName
|
||||
}
|
||||
return name == fieldName
|
||||
})
|
||||
if value.IsValid() {
|
||||
return value.Interface()
|
||||
@@ -213,7 +217,11 @@ func In(needle any, array any) bool {
|
||||
if !n.IsValid() || n.Kind() != reflect.String {
|
||||
panic(fmt.Sprintf("cannot use %T as field name of %T", needle, array))
|
||||
}
|
||||
value := v.FieldByName(n.String())
|
||||
field, ok := v.Type().FieldByName(n.String())
|
||||
if !ok || !field.IsExported() || field.Tag.Get("expr") == "-" {
|
||||
return false
|
||||
}
|
||||
value := v.FieldByIndex(field.Index)
|
||||
if value.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user