Skip to content

Commit 85d0be8

Browse files
diegommmantonmedv
andauthored
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]>
1 parent 98b0990 commit 85d0be8

File tree

4 files changed

+130
-13
lines changed

4 files changed

+130
-13
lines changed

builtin/lib.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,14 @@ func get(params ...any) (out any, err error) {
421421
fieldName := i.(string)
422422
value := v.FieldByNameFunc(func(name string) bool {
423423
field, _ := v.Type().FieldByName(name)
424-
if field.Tag.Get("expr") == fieldName {
424+
switch field.Tag.Get("expr") {
425+
case "-":
426+
return false
427+
case fieldName:
425428
return true
429+
default:
430+
return name == fieldName
426431
}
427-
return name == fieldName
428432
})
429433
if value.IsValid() {
430434
return value.Interface(), nil

checker/nature/utils.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import (
66
"github.com/expr-lang/expr/internal/deref"
77
)
88

9-
func fieldName(field reflect.StructField) string {
10-
if taggedName := field.Tag.Get("expr"); taggedName != "" {
11-
return taggedName
9+
func fieldName(field reflect.StructField) (string, bool) {
10+
switch taggedName := field.Tag.Get("expr"); taggedName {
11+
case "-":
12+
return "", false
13+
case "":
14+
return field.Name, true
15+
default:
16+
return taggedName, true
1217
}
13-
return field.Name
1418
}
1519

1620
func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
@@ -23,7 +27,7 @@ func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
2327
for i := 0; i < t.NumField(); i++ {
2428
field := t.Field(i)
2529
// Search all fields, even embedded structs.
26-
if fieldName(field) == name {
30+
if n, ok := fieldName(field); ok && n == name {
2731
return field, true
2832
}
2933
}
@@ -69,7 +73,11 @@ func StructFields(t reflect.Type) map[string]Nature {
6973
}
7074
}
7175

72-
table[fieldName(f)] = Nature{
76+
name, ok := fieldName(f)
77+
if !ok {
78+
continue
79+
}
80+
table[name] = Nature{
7381
Type: f.Type,
7482
FieldIndex: f.Index,
7583
}

expr_test.go

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"os"
88
"reflect"
9+
"strings"
910
"sync"
1011
"testing"
1112
"time"
@@ -139,7 +140,86 @@ func ExampleEnv_tagged_field_names() {
139140

140141
fmt.Printf("%v", output)
141142

142-
// Output : Hello World
143+
// Output: Hello World
144+
}
145+
146+
func ExampleEnv_hidden_tagged_field_names() {
147+
type Internal struct {
148+
Visible string
149+
Hidden string `expr:"-"`
150+
}
151+
type environment struct {
152+
Visible string
153+
Hidden string `expr:"-"`
154+
HiddenInternal Internal `expr:"-"`
155+
VisibleInternal Internal
156+
}
157+
158+
env := environment{
159+
Hidden: "First level secret",
160+
HiddenInternal: Internal{
161+
Visible: "Second level secret",
162+
Hidden: "Also hidden",
163+
},
164+
VisibleInternal: Internal{
165+
Visible: "Not a secret",
166+
Hidden: "Hidden too",
167+
},
168+
}
169+
170+
hiddenValues := []string{
171+
`Hidden`,
172+
`HiddenInternal`,
173+
`HiddenInternal.Visible`,
174+
`HiddenInternal.Hidden`,
175+
`VisibleInternal["Hidden"]`,
176+
}
177+
for _, expression := range hiddenValues {
178+
output, err := expr.Eval(expression, env)
179+
if err == nil || !strings.Contains(err.Error(), "cannot fetch") {
180+
fmt.Printf("unexpected output: %v; err: %v\n", output, err)
181+
return
182+
}
183+
fmt.Printf("%q is hidden as expected\n", expression)
184+
}
185+
186+
visibleValues := []string{
187+
`Visible`,
188+
`VisibleInternal`,
189+
`VisibleInternal["Visible"]`,
190+
}
191+
for _, expression := range visibleValues {
192+
_, err := expr.Eval(expression, env)
193+
if err != nil {
194+
fmt.Printf("unexpected error: %v\n", err)
195+
return
196+
}
197+
fmt.Printf("%q is visible as expected\n", expression)
198+
}
199+
200+
testWithIn := []string{
201+
`not ("Hidden" in $env)`,
202+
`"Visible" in $env`,
203+
`not ("Hidden" in VisibleInternal)`,
204+
`"Visible" in VisibleInternal`,
205+
}
206+
for _, expression := range testWithIn {
207+
val, err := expr.Eval(expression, env)
208+
shouldBeTrue, ok := val.(bool)
209+
if err != nil || !ok || !shouldBeTrue {
210+
fmt.Printf("unexpected result; value: %v; error: %v\n", val, err)
211+
return
212+
}
213+
}
214+
215+
// Output: "Hidden" is hidden as expected
216+
// "HiddenInternal" is hidden as expected
217+
// "HiddenInternal.Visible" is hidden as expected
218+
// "HiddenInternal.Hidden" is hidden as expected
219+
// "VisibleInternal[\"Hidden\"]" is hidden as expected
220+
// "Visible" is visible as expected
221+
// "VisibleInternal" is visible as expected
222+
// "VisibleInternal[\"Visible\"]" is visible as expected
143223
}
144224

145225
func ExampleAsKind() {
@@ -529,7 +609,7 @@ func ExamplePatch() {
529609
}
530610
fmt.Printf("%v", output)
531611

532-
// Output : Hello, you, world!
612+
// Output: Hello, you, world!
533613
}
534614

535615
func ExampleWithContext() {
@@ -2765,3 +2845,20 @@ func TestMemoryBudget(t *testing.T) {
27652845
})
27662846
}
27672847
}
2848+
2849+
func TestIssue807(t *testing.T) {
2850+
type MyStruct struct {
2851+
nonExported string
2852+
}
2853+
out, err := expr.Eval(` "nonExported" in $env `, MyStruct{})
2854+
if err != nil {
2855+
t.Fatalf("unexpected error: %v", err)
2856+
}
2857+
b, ok := out.(bool)
2858+
if !ok {
2859+
t.Fatalf("expected boolean type, got %T: %v", b, b)
2860+
}
2861+
if b {
2862+
t.Fatalf("expected 'in' operator to return false for unexported field")
2863+
}
2864+
}

vm/runtime/runtime.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,14 @@ func Fetch(from, i any) any {
6565
fieldName := i.(string)
6666
value := v.FieldByNameFunc(func(name string) bool {
6767
field, _ := v.Type().FieldByName(name)
68-
if field.Tag.Get("expr") == fieldName {
68+
switch field.Tag.Get("expr") {
69+
case "-":
70+
return false
71+
case fieldName:
6972
return true
73+
default:
74+
return name == fieldName
7075
}
71-
return name == fieldName
7276
})
7377
if value.IsValid() {
7478
return value.Interface()
@@ -213,7 +217,11 @@ func In(needle any, array any) bool {
213217
if !n.IsValid() || n.Kind() != reflect.String {
214218
panic(fmt.Sprintf("cannot use %T as field name of %T", needle, array))
215219
}
216-
value := v.FieldByName(n.String())
220+
field, ok := v.Type().FieldByName(n.String())
221+
if !ok || !field.IsExported() || field.Tag.Get("expr") == "-" {
222+
return false
223+
}
224+
value := v.FieldByIndex(field.Index)
217225
if value.IsValid() {
218226
return true
219227
}

0 commit comments

Comments
 (0)