Repository: kubernetes/kubernetes
Pull request #131460: Add lazy reflective CEL object wrapper
Author: login: jpbetz
Labels: name: area/apiserver
name: lgtm
description: "Looks good to me", indicates that a PR is ready to be merged.
name: sig/api-machinery
description: Categorizes an issue or PR as relevant to SIG API Machinery.
name: size/XXL
description: Denotes a PR that changes 1000+ lines, ignoring generated files.
name: kind/feature
description: Categorizes issue or PR as related to a new feature.
name: release-note-none
description: Denotes a PR that doesn't merit a release note.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: cncf-cla: yes
description: Indicates the PR's author has signed the CNCF CLA.
name: priority/important-longterm
description: Important over the long term, but may not be staffed and/or may need multiple releases to complete.
name: triage/accepted
description: Indicates an issue or PR is ready to be actively worked on.
Created: 2025-04-25T04:18:43Z | Merged: 2025-04-29T20:43:55Z

#### What type of PR is this?

/kind feature

#### What this PR does / why we need it:

Provides a CEL `ref.Val` wrapper of k8s typed objects, avoiding the need to convert typed objects to unstructured. 

Added tests to ensure that the behavior of the reflective wrapper using `TypedToVal` matches the behavior of conversion to unstructured using `UnstructuredToVal`.  This is essential to guarantee OpenAPI compatibility-- that is, types that have the same OpenAPI schema should behave identically in CEL, regardless of if they are CRDs or native types.

#### Does this PR introduce a user-facing change?

```release-note
NONE
```



Diff:
diff --git a/staging/src/k8s.io/apiserver/pkg/cel/common/values_test.go b/staging/src/k8s.io/apiserver/pkg/cel/common/values_test.go
new file mode 100644
index 0000000000000..9610deced5499
--- /dev/null
+++ b/staging/src/k8s.io/apiserver/pkg/cel/common/values_test.go
@@ -0,0 +1,1268 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common_test
+
+import (
+	"github.com/google/cel-go/cel"
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"k8s.io/apiserver/pkg/cel/common"
+	"k8s.io/apiserver/pkg/cel/library"
+	"k8s.io/apiserver/pkg/cel/openapi"
+	"k8s.io/kube-openapi/pkg/validation/spec"
+	"testing"
+	"time"
+)
+
+// TestToValue tests that both UnstructuredToValue and TypedToValue correctly
+// convert Kubernetes typed objects and unstructured objects to CEL values.
+//
+// TestToValue also tests that UnstructuredToValue and TypedToValue behave identically
+// for a Kubernetes object.
+func TestToValue(t *testing.T) {
+	struct1value := Struct{S: "hello", I: 10, B: true, F: 1.5}
+	struct1 := typedValue{value: struct1value, schema: structSchema}
+	struct1Ptr := typedValue{value: &struct1value, schema: structSchema}
+	struct2value := Struct{S: "world", I: 20, B: false, F: 2.5}
+	struct2 := typedValue{value: struct2value, schema: structSchema}
+	struct1Again := typedValue{value: struct1value, schema: structSchema}
+	zeroStruct := typedValue{value: Struct{}, schema: structSchema}
+	zeroStructPtr := typedValue{value: Struct{}, schema: structSchema}
+
+	now := metav1.NewTime(time.Now().Truncate(0))
+	duration1 := metav1.Duration{Duration: 5 * time.Second}
+	time1Parsed, err := time.Parse(RFC3339, "2000-01-01T12:00:00Z")
+	if err != nil {
+		t.Fatal(err)
+	}
+	time1 := metav1.Time{Time: time1Parsed}
+	microTime1Parsed, err := time.Parse(RFC3339Micro, "2000-01-01T12:00:00.000001Z")
+	if err != nil {
+		t.Fatal(err)
+	}
+	microTime1 := metav1.MicroTime{Time: microTime1Parsed}
+
+	nested1value := Nested{Name: "nested1", Info: Struct{S: "hello", I: 10, B: true, F: 1.5}}
+
+	complex1value := Complex{
+		TypeMeta:    metav1.TypeMeta{Kind: "Complex", APIVersion: "v1"},
+		ObjectMeta:  metav1.ObjectMeta{Name: "complex1"},
+		ID:          "c1",
+		Tags:        []string{"a", "b", "c"},
+		Labels:      map[string]string{"key1": "val1", "key2": "val2"},
+		NestedObj:   nested1value,
+		Timeout:     duration1,
+		Time:        time1,
+		MicroTime:   microTime1,
+		RawBytes:    []byte("bytes1"),
+		NilBytes:    nil,
+		ChildPtr:    &struct2value,
+		NilPtr:      nil,
+		EmptySlice:  []int{},
+		NilSlice:    nil,
+		EmptyMap:    map[string]int{},
+		NilMap:      nil,
+		IntOrString: intstr.FromInt32(5),
+		Quantity:    resource.MustParse("100m"),
+		I32:         int32(32),
+		I64:         int64(64),
+		F32:         float32(32.5),
+		Enum:        EnumTypeA,
+		MapList: []MapListEntry{
+			{
+				Key1:  "k1v1",
+				Key2:  "k2v1",
+				Value: 1,
+			},
+			{
+				Key1:  "k1v2",
+				Key2:  "k2v2",
+				Value: 2,
+			},
+		},
+		SetList: []SetEntry{1, 2, 3},
+	}
+	complex1 := typedValue{value: complex1value, schema: complexSchema}
+
+	complex2value := Complex{
+		TypeMeta:    metav1.TypeMeta{Kind: "Complex2", APIVersion: "v1"},
+		ObjectMeta:  metav1.ObjectMeta{Name: "complex2"},
+		ID:          "c2",
+		Tags:        []string{"x", "y"},
+		Labels:      map[string]string{"key3": "val3"},
+		NestedObj:   Nested{Name: "nested2", Info: struct2value},
+		Timeout:     metav1.Duration{Duration: 10 * time.Second},
+		RawBytes:    []byte("bytes2"),
+		NilBytes:    []byte{}, // Non-nil but empty
+		ChildPtr:    &struct1value,
+		NilPtr:      nil,
+		EmptySlice:  []int{1},               // Non-empty
+		NilSlice:    []int{1},               // Non-nil
+		EmptyMap:    map[string]int{"a": 1}, // Non-empty
+		NilMap:      map[string]int{"a": 1}, // Non-nil
+		IntOrString: intstr.FromString("port"),
+		Quantity:    resource.MustParse("200m"),
+		I32:         int32(42),
+		I64:         int64(200),
+		F32:         float32(42.5),
+		Enum:        EnumTypeB,
+		MapList: []MapListEntry{
+			{
+				Key1:  "k1v2",
+				Key2:  "k2v2",
+				Value: 2,
+			},
+			{
+				Key1:  "k1v1",
+				Key2:  "k2v1",
+				Value: 1,
+			},
+		},
+		SetList: []SetEntry{3, 2, 1},
+	}
+	complex2 := typedValue{value: complex2value, schema: complexSchema}
+
+	complex3value := Complex{
+		MapList: []MapListEntry{
+			{
+				Key1:  "k1v3",
+				Key2:  "k2v3",
+				Value: 3,
+			},
+			{
+				Key1:  "k1v1",
+				Key2:  "k2v1",
+				Value: 1,
+			},
+		},
+		SetList: []SetEntry{4, 1},
+	}
+	complex3 := typedValue{value: complex3value, schema: complexSchema}
+
+	complex4value := Complex{
+		MapList: []MapListEntry{
+			{
+				Key1:  "k1v3",
+				Key2:  "k2v3",
+				Value: 3,
+			},
+			{
+				Key1:  "k1v2",
+				Key2:  "k2v2",
+				Value: 2,
+			},
+			{
+				Key1:  "k1v1",
+				Key2:  "k2v1",
+				Value: 1,
+			},
+		},
+		SetList: []SetEntry{4, 3, 2, 1},
+	}
+	complex4 := typedValue{value: complex4value, schema: complexSchema}
+
+	complex1Again := complex1 // Create a copy for equality checks
+
+	slice1 := []int{1, 2, 3}
+	slice1Again := []int{1, 2, 3}
+	slice2 := []int{1, 2, 4}
+	slice3 := []string{"a", "b"}
+
+	map1 := map[string]int{"a": 1, "b": 2}
+	map1Again := map[string]int{"b": 2, "a": 1}
+	map2 := map[string]int{"a": 1, "b": 3}        // Different value
+	map3 := map[string]int{"a": 1, "c": 2}        // Different key
+	map4 := map[string]string{"a": "1", "b": "2"} // Different value type
+
+	tests := []testCase{
+		// Basic Type Conversions
+		{
+			name:       "basic: int32",
+			expression: "c.i32 == 32",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "basic: int64",
+			expression: "c.i64 == 64",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "basic: float32",
+			expression: "c.f32 == 32.5",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "basic: enum",
+			expression: "c.enum == 'a'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "basic: nil bytes",
+			expression: "!has(c.nilBytes)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// Struct Tests
+		{
+			name:       "struct: zero value struct",
+			expression: "obj.s == '' && obj.i == 0 && obj.b == false && obj.f == 0.0",
+			activation: map[string]typedValue{"obj": zeroStruct},
+		},
+		{
+			name:       "struct: zero value struct pointer",
+			expression: "obj.s == '' && obj.i == 0 && obj.b == false && obj.f == 0.0",
+			activation: map[string]typedValue{"obj": zeroStructPtr},
+		},
+		{
+			name:       "struct: populated struct field access",
+			expression: "obj.s == 'hello' && obj.i == 10 && obj.b == true && obj.f == 1.5",
+			activation: map[string]typedValue{"obj": struct1},
+		},
+		{
+			name:       "struct: populated struct pointer field access",
+			expression: "obj.s == 'hello' && obj.i == 10 && obj.b == true && obj.f == 1.5",
+			activation: map[string]typedValue{"obj": struct1Ptr},
+		},
+		{
+			name:       "struct: access omitempty field (has)",
+			expression: "!has(obj.so)",
+			activation: map[string]typedValue{"obj": struct1},
+		},
+		{
+			name:       "struct: access non-existent field (has)",
+			expression: "!has(obj.nonExistent)",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "struct: access non-existent field direct (error)",
+			expression: "obj.nonExistent",
+			activation: map[string]typedValue{"obj": struct2},
+			wantErr:    "no such key: nonExistent",
+		},
+		{
+			name:       "struct: access with non-string key (get) (error)",
+			expression: "obj[1]",
+			activation: map[string]typedValue{"obj": struct2},
+			wantErr:    "no such overload",
+		},
+		{
+			name:       "struct: check contains non-string key (error)",
+			expression: "1 in obj",
+			activation: map[string]typedValue{"obj": struct2},
+			wantErr:    "no such overload",
+		},
+		{
+			name:       "struct: convert to its own type",
+			expression: "type(obj) == type(obj)",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "struct: embedded inline",
+			expression: "c.apiVersion == 'v1' && c.kind == 'Complex'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "struct: embedded inline: omitempty",
+			expression: "!has(c.apiVersion)",
+			activation: map[string]typedValue{"c": struct2},
+		},
+		{
+			name:       "struct: embedded struct",
+			expression: "c.metadata.name == 'complex1'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "struct: embedded struct: omitempty struct field",
+			expression: "!has(c.metadata.labels)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// Comparison Tests
+		{
+			name:       "compare: identity (struct)",
+			expression: "s1 == s1",
+			activation: map[string]typedValue{"s1": struct1},
+		},
+		{
+			name:       "compare: identical structs",
+			expression: "s1 == s1_again",
+			activation: map[string]typedValue{
+				"s1":       struct1,
+				"s1_again": struct1Again,
+			},
+		},
+		{
+			name:       "compare: different structs",
+			expression: "s1 != s2",
+			activation: map[string]typedValue{
+				"s1": struct1,
+				"s2": struct2,
+			},
+		},
+		{
+			name:       "compare: struct and pointer to identical struct",
+			expression: "s1 == s1_ptr",
+			activation: map[string]typedValue{
+				"s1":     struct1,
+				"s1_ptr": struct1Ptr,
+			},
+		},
+		{
+			name:       "compare: struct and nil",
+			expression: "s1 != null",
+			activation: map[string]typedValue{"s1": struct1},
+		},
+		{
+			name:       "compare: struct and different type",
+			expression: "s1 != 10",
+			activation: map[string]typedValue{"s1": struct1},
+		},
+		{
+			name:       "compare: nil struct pointer and null",
+			expression: "nil_obj == null",
+			activation: map[string]typedValue{"nil_obj": {value: nil, schema: structSchema}},
+		},
+		{
+			name:       "compare: identical slices (activation)",
+			expression: "sl1 == sl1a",
+			activation: map[string]typedValue{
+				"sl1":  {value: slice1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+				"sl1a": {value: slice1Again, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: different slices (activation)",
+			expression: "sl1 != sl2",
+			activation: map[string]typedValue{
+				"sl1": {value: slice1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+				"sl2": {value: slice2, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: slices of different types",
+			expression: "sl1 != sl3",
+			activation: map[string]typedValue{
+				"sl1": {value: slice1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+				"sl3": {value: slice3, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: stringSchema}}}},
+			},
+		},
+		{
+			name:       "compare: slice and non-list",
+			expression: "sl1 != 1",
+			activation: map[string]typedValue{
+				"sl1": {value: slice1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: identical maps (activation)",
+			expression: "m1 == m1a",
+			activation: map[string]typedValue{
+				"m1":  {value: map1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+				"m1a": {value: map1Again, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: different maps (value) (activation)",
+			expression: "m1 != m2",
+			activation: map[string]typedValue{
+				"m1": {value: map1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+				"m2": {value: map2, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: different maps (key) (activation)",
+			expression: "m1 != m3",
+			activation: map[string]typedValue{
+				"m1": {value: map1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+				"m3": {value: map3, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: different maps (value type)",
+			expression: "m1 != m4",
+			activation: map[string]typedValue{
+				"m1": {value: map1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+				"m4": {value: map4, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: stringSchema}}}},
+			},
+		},
+		{
+			name:       "compare: map and non-map",
+			expression: "m1 != 1",
+			activation: map[string]typedValue{
+				"m1": {value: map1, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+			},
+		},
+		{
+			name:       "compare: time instances (equal)",
+			expression: "t1 == t2",
+			activation: map[string]typedValue{
+				"t1": {value: now, schema: &timeFormat},
+				"t2": {value: now, schema: &timeFormat},
+			},
+		},
+		{
+			name:       "compare: time instances (different)",
+			expression: "t1 != t2",
+			activation: map[string]typedValue{
+				"t1": {value: now, schema: &timeFormat},
+				"t2": {value: metav1.MicroTime{Time: now.Add(time.Nanosecond)}, schema: stringSchema},
+			},
+		},
+		{
+			name:       "compare: microTime instances (equal)",
+			expression: "t1 == t2",
+			activation: map[string]typedValue{
+				"t1": {value: now, schema: stringSchema},
+				"t2": {value: now, schema: stringSchema},
+			},
+		},
+		{
+			name:       "compare: microTime instances (different)",
+			expression: "t1 != t2",
+			activation: map[string]typedValue{
+				"t1": {value: now, schema: stringSchema},
+				"t2": {value: metav1.MicroTime{Time: now.Add(time.Nanosecond)}, schema: stringSchema},
+			},
+		},
+		{
+			name:       "compare: duration instances (equal)",
+			expression: "d1 == d2",
+			activation: map[string]typedValue{
+				"d1": {value: duration1, schema: stringSchema},
+				"d2": {value: metav1.Duration{Duration: 5 * time.Second}, schema: stringSchema},
+			},
+		},
+		{
+			name:       "compare: duration instances (different)",
+			expression: "d1 != d2",
+			activation: map[string]typedValue{
+				"d1": {value: duration1, schema: stringSchema},
+				"d2": {value: metav1.Duration{Duration: 6 * time.Second}, schema: stringSchema},
+			},
+		},
+		{
+			name:       "compare: bytes instances (equal)",
+			expression: "b1 == b2",
+			activation: map[string]typedValue{
+				"b1": {value: []byte("abc"), schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "byte"}}},
+				"b2": {value: []byte("abc"), schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "byte"}}},
+			},
+		},
+		{
+			name:       "compare: bytes instances (different)",
+			expression: "b1 != b2",
+			activation: map[string]typedValue{
+				"b1": {value: []byte("abc"), schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "byte"}}},
+				"b2": {value: []byte("abd"), schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "byte"}}},
+			},
+		},
+		{
+			name:       "compare: empty slices (different underlying types)",
+			expression: "e1 == e2",
+			activation: map[string]typedValue{
+				"e1": {value: []int{}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: int64Schema}}}},
+				"e2": {value: []string{}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: stringSchema}}}},
+			},
+		},
+		{
+			name:       "compare: empty maps (different underlying types)",
+			expression: "m1 == m2",
+			activation: map[string]typedValue{
+				"m1": {value: map[string]int{}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: int64Schema}}}},
+				"m2": {value: map[string]bool{}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}, AdditionalProperties: &spec.SchemaOrBool{Schema: boolSchema}}}},
+			},
+		},
+
+		// Nested Struct Tests
+		{
+			name:       "nested: access field",
+			expression: "c.nestedObj.info.s == 'hello'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "nested: compare nested struct",
+			expression: "c1.nestedObj != c2.nestedObj",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "nested: compare identical nested struct",
+			expression: "c1.nestedObj == c1_again.nestedObj",
+			activation: map[string]typedValue{
+				"c1":       complex1,
+				"c1_again": complex1Again,
+			},
+		},
+
+		// Slice Tests
+		{
+			name:       "slice: access element",
+			expression: "c.tags[1] == 'b'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: size",
+			expression: "size(c.tags) == 3",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: contains ('in')",
+			expression: "'b' in c.tags",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: not contains ('in')",
+			expression: "!('d' in c.tags)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: contains with non-primitive (struct)",
+			expression: "s1 in structs",
+			activation: map[string]typedValue{
+				"structs": {value: []Struct{struct2value, struct1value}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: structSchema}}}},
+				"s1":      struct1,
+			},
+		},
+		{
+			name:       "slice: contains with non-primitive (struct ptr)",
+			expression: "s1 in structs",
+			activation: map[string]typedValue{
+				"structs": {value: []*Struct{&struct2value, &struct1value}, schema: &spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{Schema: structSchema}}}},
+				"s1":      struct1,
+			},
+		},
+		{
+			name:       "slice: add",
+			expression: "size(c1.tags + c2.tags) == 5 && (c1.tags + c2.tags)[3] == 'x'",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "slice: add non-list (error)",
+			expression: "c.tags + 1",
+			activation: map[string]typedValue{
+				"c": complex1,
+			},
+			wantErr: "no such overload",
+		},
+		{
+			name:       "slice: get with non-int index (error)",
+			expression: `c.tags['a']`,
+			activation: map[string]typedValue{
+				"c": complex1,
+			},
+			wantErr: `unsupported index type 'string' in list`,
+		},
+		{
+			name:       "slice: all() true",
+			expression: "c.tags.all(t, t.startsWith(''))",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: all() false",
+			expression: "!c.tags.all(t, t == 'a')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: exists() true",
+			expression: "c.tags.exists(t, t == 'c')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: exists() false",
+			expression: "!c.tags.exists(t, t == 'z')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: out of bounds access",
+			expression: "c.tags[5]",
+			activation: map[string]typedValue{"c": complex1},
+			wantErr:    "index out of bounds: 5",
+		},
+		{
+			name:       "slice: empty slice size",
+			expression: "size(c.emptySlice) == 0",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: exists() on empty",
+			expression: "!c.emptySlice.exists(x, true)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: all() on empty",
+			expression: "c.emptySlice.all(x, false)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: convert to list type",
+			expression: "type(c.tags) == list",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "slice: convert list to type type",
+			expression: "type(c.tags) == list",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// Map Tests
+		{
+			name:       "map: access element",
+			expression: "c.labels['key1'] == 'val1'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: size",
+			expression: "size(c.labels) == 2",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: contains key ('in')",
+			expression: "'key1' in c.labels",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: not contains key ('in')",
+			expression: "!('key3' in c.labels)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: has() key",
+			expression: "has(c.labels.key1)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: has() non-existent key",
+			expression: "!has(c.labels.key3)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: access non-existent key (error)",
+			expression: "c.labels['key3']",
+			activation: map[string]typedValue{"c": complex1},
+			wantErr:    "no such key: key3",
+		},
+		{
+			name:       "map: all() on keys true",
+			expression: "c.labels.all(name, name.startsWith('key'))",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: all() on keys false",
+			expression: "!c.labels.all(name, name == 'key1')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: exists() on keys true",
+			expression: "c.labels.exists(name, name == 'key2')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: exists() on keys false",
+			expression: "!c.labels.exists(name, name == 'key3')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: empty map size",
+			expression: "size(c.emptyMap) == 0",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: exists() on empty",
+			expression: "!c.emptyMap.exists(name, true)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: all() on empty",
+			expression: "c.emptyMap.all(name, false)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: convert to map type",
+			expression: "type(c.labels) == map",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "map: convert map to type type",
+			expression: "type(c.labels) == map",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// Pointer Tests
+		{
+			name:       "pointer: access through non-nil pointer field",
+			expression: "c.childPtr.s == 'world'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "pointer: compare non-nil pointer field",
+			expression: "c.childPtr == s2",
+			activation: map[string]typedValue{
+				"c":  complex1,
+				"s2": struct2,
+			},
+		},
+		{
+			name:       "pointer: access through nil pointer field (error)",
+			expression: "c.nilPtr.s",
+			activation: map[string]typedValue{"c": complex1},
+			wantErr:    "no such key: nilPtr", // Accessing field 's' on a null object
+		},
+		{
+			name:       "pointer: check has() nil pointer",
+			expression: "!has(c.nilPtr)",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// Type Tests
+		{
+			name:       "type: string",
+			expression: "type(obj.s) == string",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "type: int",
+			expression: "type(obj.i) == int",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "type: bool",
+			expression: "type(obj.b) == bool",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "type: float",
+			expression: "type(obj.f) == double",
+			activation: map[string]typedValue{"obj": struct2},
+		},
+		{
+			name:       "type: slice",
+			expression: "type(c.tags) == list",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: map",
+			expression: "type(c.labels) == map",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: time",
+			expression: "type(c.time) == google.protobuf.Timestamp",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: microTime",
+			expression: "type(c.microTime) == google.protobuf.Timestamp",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: duration",
+			expression: "type(c.timeout) == google.protobuf.Duration",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: bytes",
+			expression: "type(c.rawBytes) == bytes",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: int32",
+			expression: "type(c.i32) == int",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: int64",
+			expression: "type(c.i64) == int",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: float32",
+			expression: "type(c.f32) == double",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "type: enum",
+			expression: "type(c.enum) == string",
+			activation: map[string]typedValue{"c": complex1},
+		},
+
+		// listType=map
+		{
+			name:       "listType=map: equal",
+			expression: "c1.mapList == c2.mapList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "listType=map: not equal",
+			expression: "c1.mapList != c3.mapList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c3": complex3,
+			},
+		},
+		{
+			name:       "listType=map: add overlapping",
+			expression: "c1.mapList + c2.mapList == c1.mapList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "listType=map: add non-overlapping",
+			expression: "c1.mapList + c3.mapList == c4.mapList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c3": complex3,
+				"c4": complex4,
+			},
+		},
+
+		// listType=set
+		{
+			name:       "listType=set: equal",
+			expression: "c1.setList == c2.setList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "listType=set: not equal",
+			expression: "c1.setList != c3.setList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c3": complex3,
+			},
+		},
+		{
+			name:       "listType=set: add overlapping",
+			expression: "c1.setList + c2.setList == c1.setList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c2": complex2,
+			},
+		},
+		{
+			name:       "listType=set: add non-overlapping",
+			expression: "c1.setList + c3.setList == c4.setList",
+			activation: map[string]typedValue{
+				"c1": complex1,
+				"c3": complex3,
+				"c4": complex4,
+			},
+		},
+
+		// Special K8s Types
+		{
+			name:       "time: comparison equals",
+			expression: "c.time == timestamp('2000-01-01T12:00:00Z')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "microTime: comparison equals",
+			expression: "c.microTime == timestamp('2000-01-01T12:00:00.000001Z')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "duration: comparison equals",
+			expression: "c.timeout == duration('5s')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "duration: comparison greater",
+			expression: "c.timeout > duration('1s')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "intOrString: int comparison",
+			expression: "c.intOrString == 5",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "intOrString: string comparison",
+			expression: "c.intOrString == 'port'",
+			activation: map[string]typedValue{"c": complex2},
+		},
+		{
+			name:       "quantity: comparison",
+			expression: "quantity(c.quantity).isGreaterThan(quantity('99m')) && quantity(c.quantity).isLessThan(quantity('101m'))",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "quantity: equality",
+			expression: "quantity(c.quantity) == quantity('100m')",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "bytes: size",
+			expression: "size(c.rawBytes) == 6",
+			activation: map[string]typedValue{"c": complex1},
+		},
+		{
+			name:       "bytes: equality",
+			expression: "c.rawBytes == b'bytes1'",
+			activation: map[string]typedValue{"c": complex1},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var opts []cel.EnvOption
+			for k := range tt.activation {
+				opts = append(opts, cel.Variable(k, cel.DynType))
+			}
+			opts = append(opts, cel.StdLib(), library.Quantity())
+
+			env, err := cel.NewEnv(opts...)
+			if err != nil {
+				t.Fatalf("Env creation error: %v", err)
+			}
+
+			t.Run("TypedToVal", func(t *testing.T) {
+				testTypedToVal(t, env, tt)
+			})
+
+			t.Run("UnstructuredToVal", func(t *testing.T) {
+				testUnstructuredToVal(t, tt, env)
+			})
+		})
+	}
+}
+
+func testTypedToVal(t *testing.T, env *cel.Env, tt testCase) {
+	typedOut, typedErr := evalExpression(t, env, tt.expression, typedToValActivation(tt.activation))
+	if typedErr != nil && len(tt.wantErr) == 0 {
+		t.Fatalf("Unexpected err with typed values: %v", typedErr)
+	}
+	if len(tt.wantErr) > 0 {
+		if typedErr == nil {
+			t.Fatalf("Expected error '%s' during evaluation with typed values, but got none", tt.wantErr)
+		}
+		if typedErr.Error() != tt.wantErr {
+			t.Fatalf("Expected error '%s' during evaluation with typed values, but got: %v", tt.wantErr, typedErr)
+		}
+	}
+	if len(tt.wantErr) == 0 && typedOut != types.True {
+		t.Errorf("Expected true with typed values but got %v", typedOut)
+	}
+}
+
+func testUnstructuredToVal(t *testing.T, tt testCase, env *cel.Env) {
+	a, err := unstructuredToValActivation(tt.activation)
+	if err != nil {
+		t.Fatalf("Unexpected error converting activation to unstructured: %v", err)
+	}
+	unstructuredOut, unstructuredErr := evalExpression(t, env, tt.expression, a)
+	if unstructuredErr != nil && len(tt.wantErr) == 0 {
+		t.Fatalf("Unexpected err with unstructured values: %v", unstructuredErr)
+	}
+	if len(tt.wantErr) > 0 {
+		if unstructuredErr == nil {
+			t.Fatalf("Expected error '%s' during evaluation with unstructured values, but got none", tt.wantErr)
+		}
+		if unstructuredErr.Error() != tt.wantErr {
+			t.Fatalf("Expected error '%s' during evaluation with unstructured values, but got: %v", tt.wantErr, unstructuredErr)
+		}
+	}
+	if len(tt.wantErr) == 0 && unstructuredOut != types.True {
+		t.Errorf("Expected true with unstructured values but got %v", unstructuredOut)
+	}
+}
+
+func evalExpression(t *testing.T, env *cel.Env, expression string, activation map[string]interface{}) (ref.Val, error) {
+	ast, iss := env.Compile(expression)
+	if iss.Err() != nil {
+		t.Fatalf("Compile error: %v :: %s", iss.Err(), expression)
+	}
+
+	prg, err := env.Program(ast)
+	if err != nil {
+		t.Fatalf("Program error: %v :: %s", err, expression)
+	}
+
+	out, _, err := prg.Eval(activation)
+	return out, err
+}
+
+type Struct struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	S string  `json:"s"`
+	I int     `json:"i"`
+	B bool    `json:"b"`
+	F float64 `json:"f"`
+
+	SO string  `json:"so,omitempty"`
+	IO int     `json:"io,omitempty"`
+	BO bool    `json:"bo,omitempty"`
+	FO float64 `json:"fo,omitempty"`
+}
+
+var structSchema = &spec.Schema{
+	SchemaProps: spec.SchemaProps{
+		Type: []string{"object"},
+		Properties: map[string]spec.Schema{
+			"s":  *stringSchema,
+			"i":  *int64Schema,
+			"b":  *boolSchema,
+			"f":  *float64Schema,
+			"so": *stringSchema,
+			"io": *int64Schema,
+			"bo": *boolSchema,
+			"fo": *float64Schema,
+		},
+	},
+}
+
+func (s Struct) GetObjectKind() schema.ObjectKind {
+	panic("not implemented")
+}
+
+func (s Struct) DeepCopyObject() runtime.Object {
+	panic("not implemented")
+}
+
+type Nested struct {
+	Name string `json:"name"`
+	Info Struct `json:"info"`
+}
+
+var nestedSchema = &spec.Schema{
+	SchemaProps: spec.SchemaProps{
+		Type: []string{"object"},
+		Properties: map[string]spec.Schema{
+			"name": *stringSchema,
+			"info": *structSchema,
+		},
+	},
+}
+
+func (s Nested) GetObjectKind() schema.ObjectKind {
+	panic("not implemented")
+}
+
+func (s Nested) DeepCopyObject() runtime.Object {
+	panic("not implemented")
+}
+
+type Complex struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	ID          string             `json:"id"`
+	Tags        []string           `json:"tags"`
+	Labels      map[string]string  `json:"labels"`
+	NestedObj   Nested             `json:"nestedObj"`
+	Timeout     metav1.Duration    `json:"timeout"`
+	Time        metav1.Time        `json:"time"`
+	MicroTime   metav1.MicroTime   `json:"microTime"`
+	RawBytes    []byte             `json:"rawBytes"`
+	NilBytes    []byte             `json:"nilBytes"` // Always nil
+	ChildPtr    *Struct            `json:"childPtr"`
+	NilPtr      *Struct            `json:"nilPtr"` // Always nil
+	EmptySlice  []int              `json:"emptySlice"`
+	NilSlice    []int              `json:"nilSlice"` // Always nil
+	EmptyMap    map[string]int     `json:"emptyMap"`
+	NilMap      map[string]int     `json:"nilMap"` // Always nil
+	IntOrString intstr.IntOrString `json:"intOrString"`
+	Quantity    resource.Quantity  `json:"quantity"`
+	I32         int32              `json:"i32"`
+	I64         int64              `json:"i64"`
+	F32         float32            `json:"f32"`
+	Enum        EnumType           `json:"enum"`
+	MapList     []MapListEntry     `json:"mapList"`
+	SetList     []SetEntry         `json:"setList"`
+}
+
+type SetEntry int
+
+type MapListEntry struct {
+	Key1  string `json:"key1"`
+	Key2  string `json:"key2"`
+	Value int    `json:"value"`
+}
+
+var complexSchema = &spec.Schema{
+	SchemaProps: spec.SchemaProps{
+		Type: []string{"object"},
+		Properties: map[string]spec.Schema{
+			"kind":       *stringSchema,
+			"apiVersion": *stringSchema,
+			"metadata": {
+				SchemaProps: spec.SchemaProps{
+					Type: []string{"object"},
+					Properties: map[string]spec.Schema{
+						"name": *stringSchema,
+					},
+				},
+			},
+			"id":         *stringSchema,
+			"tags":       *stringArraySchema,
+			"labels":     *stringMapSchema,
+			"nestedObj":  *nestedSchema,
+			"timeout":    durationFormat,
+			"time":       timeFormat,
+			"microTime":  timeFormat,
+			"rawBytes":   bytesFormat,
+			"nilBytes":   bytesFormat,
+			"childPtr":   *structSchema,
+			"nilPtr":     *structSchema,
+			"emptySlice": *intArraySchema,
+			"nilSlice":   *intArraySchema,
+			"emptyMap":   *intMapSchema,
+			"nilMap":     *intMapSchema,
+			"intOrString": {
+				VendorExtensible: intOrStringSchema,
+			},
+			"quantity": *stringSchema, // TODO: If we add a quantity format to OpenAPI, test it here
+			"i32":      *int32Schema,
+			"i64":      *int64Schema,
+			"f32":      *float32Schema,
+			"enum":     *stringSchema,
+			"mapList": {
+				VendorExtensible: spec.VendorExtensible{Extensions: map[string]interface{}{
+					"x-kubernetes-list-type":     "map",
+					"x-kubernetes-list-map-keys": []any{"key1", "key2"},
+				}},
+				SchemaProps: spec.SchemaProps{Type: []string{"array"}, Items: &spec.SchemaOrArray{
+					Schema: &spec.Schema{SchemaProps: spec.SchemaProps{
+						Type: []string{"object"},
+						Properties: map[string]spec.Schema{
+							"key1":  *stringSchema,
+							"key2":  *stringSchema,
+							"value": *int64Schema,
+						},
+					}},
+				}},
+			},
+			"setList": {
+				VendorExtensible: spec.VendorExtensible{Extensions: map[string]interface{}{
+					"x-kubernetes-list-type": "set",
+				}},
+				SchemaProps: intArraySchema.SchemaProps,
+			},
+		},
+	},
+}
+
+func (c Complex) GetObjectKind() schema.ObjectKind {
+	panic("not implemented")
+}
+
+func (c Complex) DeepCopyObject() runtime.Object {
+	panic("not implemented")
+}
+
+type EnumType string
+
+const (
+	EnumTypeA EnumType = "a"
+	EnumTypeB EnumType = "b"
+)
+
+var (
+	stringSchema      = spec.StringProperty()
+	bytesFormat       = spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "byte"}}
+	durationFormat    = spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "duration"}}
+	timeFormat        = spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "date-time"}}
+	intOrStringSchema = spec.VendorExtensible{Extensions: map[string]interface{}{"x-kubernetes-int-or-string": true}}
+	int32Schema       = spec.Int32Property()
+	int64Schema       = spec.Int64Property()
+	boolSchema        = spec.BoolProperty()
+	float32Schema     = spec.Float32Property()
+	float64Schema     = spec.Float64Property()
+
+	stringArraySchema = spec.ArrayProperty(stringSchema)
+	intArraySchema    = spec.ArrayProperty(int64Schema)
+	stringMapSchema   = spec.MapProperty(stringSchema)
+	intMapSchema      = spec.MapProperty(int64Schema)
+)
+
+func typedToValActivation(vals map[string]typedValue) map[string]interface{} {
+	activation := make(map[string]interface{}, len(vals))
+	for k, tv := range vals {
+		s := &openapi.Schema{Schema: tv.schema}
+		activation[k] = common.TypedToVal(tv.value, s)
+	}
+	return activation
+}
+
+// unstructuredToValActivation converts the values in the activation map to map[string]interface{}.
+func unstructuredToValActivation(vals map[string]typedValue) (map[string]interface{}, error) {
+	activation := make(map[string]interface{}, len(vals))
+	for k, tv := range vals {
+		s := &openapi.Schema{Schema: tv.schema}
+		switch v := tv.value.(type) {
+		case runtime.Object:
+			u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&v)
+			if err != nil {
+				return nil, err
+			}
+			activation[k] = common.UnstructuredToVal(u, s)
+		case *runtime.Object:
+			u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(v)
+			if err != nil {
+				return nil, err
+			}
+			activation[k] = common.UnstructuredToVal(u, s)
+		default:
+			u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&wrap{Value: &v})
+			if err != nil {
+				return nil, err
+			}
+			if uv, ok := u["value"]; ok && uv != nil {
+				activation[k] = common.UnstructuredToVal(uv, s)
+			} else {
+				activation[k] = types.NullValue
+			}
+		}
+	}
+	return activation, nil
+}
+
+type wrap struct {
+	Value any `json:"value"`
+}
+
+type typedValue struct {
+	value  any
+	schema *spec.Schema
+}
+
+const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00"
+const RFC3339 = "2006-01-02T15:04:05Z07:00"
+
+type testCase struct {
+	name       string
+	expression string
+	activation map[string]typedValue
+	wantErr    string
+}
diff --git a/staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go b/staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
new file mode 100644
index 0000000000000..c1d9ddf6db4ed
--- /dev/null
+++ b/staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
@@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
+				return nil, false
+			}
+			return v, true
+		}
+	}
+	return nil, false
+}
+
+type typedList struct {
+	value reflect.Value // Kind is required to be: reflect.Slice
+
+	itemsSchema Schema
+}
+
+func (t *typedList) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	switch typeDesc.Kind() {
+	case reflect.Slice:
+		return t.value.Interface(), nil
+	default:
+		return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
+	}
+}
+
+func (t *typedList) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case types.ListType:
+		return t
+	case types.TypeType:
+		return types.ListType
+	}
+	return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
+}
+
+func (t *typedList) Equal(other ref.Val) ref.Val {
+	oList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	sz := types.Int(t.value.Len())
+	if sz != oList.Size() {
+		return types.False
+	}
+	for i := types.Int(0); i < sz; i++ {
+		eq := t.Get(i).Equal(oList.Get(i))
+		if eq != types.True {
+			return eq // either false or error
+		}
+	}
+	return types.True
+}
+
+func (t *typedList) Type() ref.Type {
+	return types.ListType
+}
+
+func (t *typedList) Value() interface{} {
+	return t.value
+}
+
+func (t *typedList) Add(other ref.Val) ref.Val {
+	oList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	resultValue := t.value
+	for it := oList.Iterator(); it.HasNext() == types.True; {
+		next := it.Next().Value()
+		resultValue = reflect.Append(resultValue, reflect.ValueOf(next))
+	}
+
+	return &typedList{value: resultValue, itemsSchema: t.itemsSchema}
+}
+
+func (t *typedList) Contains(val ref.Val) ref.Val {
+	if types.IsUnknownOrError(val) {
+		return val
+	}
+	var err ref.Val
+	sz := t.value.Len()
+	for i := 0; i < sz; i++ {
+		elem := TypedToVal(t.value.Index(i).Interface(), t.itemsSchema)
+		cmp := elem.Equal(val)
+		b, ok := cmp.(types.Bool)
+		if !ok && err == nil {
+			err = types.MaybeNoSuchOverloadErr(cmp)
+		}
+		if b == types.True {
+			return types.True
+		}
+	}
+	if err != nil {
+		return err
+	}
+	return types.False
+}
+
+func (t *typedList) Get(idx ref.Val) ref.Val {
+	iv, isInt := idx.(types.Int)
+	if !isInt {
+		return types.ValOrErr(idx, "unsupported index: %v", idx)
+	}
+	i := int(iv)
+	if i < 0 || i >= t.value.Len() {
+		return types.NewErr("index out of bounds: %v", idx)
+	}
+	return TypedToVal(t.value.Index(i).Interface(), t.itemsSchema)
+}
+
+func (t *typedList) Iterator() traits.Iterator {
+	elements := make([]ref.Val, t.value.Len())
+	sz := t.value.Len()
+	for i := 0; i < sz; i++ {
+		elements[i] = TypedToVal(t.value.Index(i).Interface(), t.itemsSchema)
+	}
+	return &sliceIter{typedList: t, elements: elements}
+}
+
+func (t *typedList) Size() ref.Val {
+	return types.Int(t.value.Len())
+}
+
+type sliceIter struct {
+	*typedList
+	elements []ref.Val
+	idx      int
+}
+
+func (it *sliceIter) HasNext() ref.Val {
+	return types.Bool(it.idx < len(it.elements))
+}
+
+func (it *sliceIter) Next() ref.Val {
+	if it.idx >= len(it.elements) {
+		return types.NewErr("iterator exhausted")
+	}
+	elem := it.elements[it.idx]
+	it.idx++
+	return elem
+}
+
+type typedMapList struct {
+	typedList
+	escapedKeyProps []string
+
+	sync.Once // for lazy load of mapOfList since it is only needed if Equals is called
+	mapOfList map[interface{}]interface{}
+}
+
+func (t *typedMapList) getMap() map[interface{}]interface{} {
+	t.Do(func() {
+		sz := t.value.Len()
+		t.mapOfList = make(map[interface{}]interface{}, sz)
+		for i := types.Int(0); i < types.Int(sz); i++ {
+			v := t.Get(i)
+			e := reflect.ValueOf(v.Value())
+			t.mapOfList[t.toMapKey(e)] = e.Interface()
+		}
+	})
+	return t.mapOfList
+}
+
+// toMapKey returns a valid golang map key for the given element of the map list.
+// element must be a valid map list entry where all map key props are scalar types (which are comparable in go
+// and valid for use in a golang map key).
+func (t *typedMapList) toMapKey(element reflect.Value) interface{} {
+	if element.Kind() != reflect.Struct {
+		return types.NewErr("unexpected data format for element of array with x-kubernetes-list-type=map: %T", element)
+	}
+	cacheEntry := value.TypeReflectEntryOf(element.Type())
+	var fieldEntries []*value.FieldCacheEntry
+	for i := 0; i < len(t.escapedKeyProps); i++ {
+		if ce, ok := cacheEntry.Fields()[t.escapedKeyProps[i]]; !ok {
+			return types.NewErr("unexpected data format for element of array with x-kubernetes-list-type=map: %T", element)
+		} else {
+			fieldEntries = append(fieldEntries, ce)
+		}
+	}
+
+	// Arrays are comparable in go and may be used as map keys, but maps and slices are not.
+	// So we can special case small numbers of key props as arrays and fall back to serialization
+	// for larger numbers of key props
+	if len(fieldEntries) == 1 {
+		return fieldEntries[0].GetFrom(element).Interface()
+	}
+	if len(fieldEntries) == 2 {
+		return [2]interface{}{fieldEntries[0].GetFrom(element).Interface(), fieldEntries[1].GetFrom(element).Interface()}
+	}
+	if len(fieldEntries) == 3 {
+		return [3]interface{}{fieldEntries[0].GetFrom(element).Interface(), fieldEntries[1].GetFrom(element).Interface(), fieldEntries[3].GetFrom(element).Interface()}
+	}
+
+	key := make([]interface{}, len(fieldEntries))
+	for i := range fieldEntries {
+		key[i] = fieldEntries[i].GetFrom(element).Interface()
+	}
+	return fmt.Sprintf("%v", key)
+}
+
+// Equal on a map list ignores list element order.
+func (t *typedMapList) Equal(other ref.Val) ref.Val {
+	oMapList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	sz := types.Int(t.value.Len())
+	if sz != oMapList.Size() {
+		return types.False
+	}
+	tMap := t.getMap()
+	for it := oMapList.Iterator(); it.HasNext() == types.True; {
+		v := it.Next()
+		k := t.toMapKey(reflect.ValueOf(v.Value()))
+		tVal, ok := tMap[k]
+		if !ok {
+			return types.False
+		}
+		eq := TypedToVal(tVal, t.itemsSchema).Equal(v)
+		if eq != types.True {
+			return eq // either false or error
+		}
+	}
+	return types.True
+}
+
+// Add for a map list `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
+// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
+// non-intersecting keys are appended, retaining their partial order.
+func (t *typedMapList) Add(other ref.Val) ref.Val {
+	sliceType := t.value.Type()
+	elementType := sliceType.Elem()
+	oMapList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	sz := t.value.Len()
+	elements := reflect.MakeSlice(sliceType, sz, sz)
+	keyToIdx := map[interface{}]int{}
+	for i := 0; i < sz; i++ {
+		e := t.Get(types.Int(i)).Value()
+		re := reflect.ValueOf(e)
+		k := t.toMapKey(re)
+		keyToIdx[k] = i
+		elements.Index(i).Set(re.Convert(elementType))
+	}
+	for it := oMapList.Iterator(); it.HasNext() == types.True; {
+		e := it.Next()
+		re := reflect.ValueOf(e.Value())
+		k := t.toMapKey(re)
+		if overwritePosition, ok := keyToIdx[k]; ok {
+			elements.Index(overwritePosition).Set(re)
+		} else {
+			elements = reflect.Append(elements, re.Convert(elementType))
+		}
+	}
+	return &typedMapList{
+		typedList:       typedList{value: elements, itemsSchema: t.itemsSchema},
+		escapedKeyProps: t.escapedKeyProps,
+	}
+}
+
+type typedSetList struct {
+	typedList
+
+	sync.Once // for lazy load of setOfList since it is only needed if Equals is called
+	set       map[interface{}]struct{}
+}
+
+func (t *typedSetList) getSet() map[interface{}]struct{} {
+	// sets are only allowed to contain scalar elements, which are comparable in go, and can safely be used as
+	// golang map keys
+	t.Do(func() {
+		sz := t.value.Len()
+		t.set = make(map[interface{}]struct{}, sz)
+		for i := types.Int(0); i < types.Int(sz); i++ {
+			e := t.Get(i).Value()
+			t.set[e] = struct{}{}
+		}
+	})
+	return t.set
+}
+
+// Equal on a map list ignores list element order.
+func (t *typedSetList) Equal(other ref.Val) ref.Val {
+	oSetList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	sz := types.Int(t.value.Len())
+	if sz != oSetList.Size() {
+		return types.False
+	}
+	tSet := t.getSet()
+	for it := oSetList.Iterator(); it.HasNext() == types.True; {
+		next := it.Next().Value()
+		_, ok := tSet[next]
+		if !ok {
+			return types.False
+		}
+	}
+	return types.True
+}
+
+// Add for a set list `X + Y` performs a union where the array positions of all elements in `X` are preserved and
+// non-intersecting elements in `Y` are appended, retaining their partial order.
+func (t *typedSetList) Add(other ref.Val) ref.Val {
+	setType := t.value.Type()
+	elementType := setType.Elem()
+	oSetList, ok := other.(traits.Lister)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	sz := t.value.Len()
+	elements := reflect.MakeSlice(setType, sz, sz)
+	for i := 0; i < sz; i++ {
+		e := t.Get(types.Int(i)).Value()
+		re := reflect.ValueOf(e)
+		elements.Index(i).Set(re.Convert(elementType))
+	}
+	set := t.getSet()
+	for it := oSetList.Iterator(); it.HasNext() == types.True; {
+		e := it.Next().Value()
+		re := reflect.ValueOf(e)
+		if _, ok := set[e]; !ok {
+			set[e] = struct{}{}
+			elements = reflect.Append(elements, re.Convert(elementType))
+		}
+	}
+	return &typedSetList{
+		typedList: typedList{value: elements, itemsSchema: t.itemsSchema},
+	}
+}
+
+type typedMap struct {
+	value reflect.Value // Kind is required to be: reflect.Map
+
+	valuesSchema Schema
+}
+
+func (t *typedMap) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	switch typeDesc.Kind() {
+	case reflect.Map:
+		return t.value, nil
+	default:
+		return nil, fmt.Errorf("type conversion error from '%s' to '%s'", t.Type(), typeDesc)
+	}
+}
+
+func (t *typedMap) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case types.MapType:
+		return t
+	case types.TypeType:
+		return types.MapType
+	}
+	return types.NewErr("type conversion error from '%s' to '%s'", t.Type(), typeValue.TypeName())
+}
+
+func (t *typedMap) Equal(other ref.Val) ref.Val {
+	oMap, isMap := other.(traits.Mapper)
+	if !isMap {
+		return types.MaybeNoSuchOverloadErr(other)
+	}
+	if types.Int(t.value.Len()) != oMap.Size() {
+		return types.False
+	}
+	for it := t.value.MapRange(); it.Next(); {
+		key := it.Key()
+		value := it.Value()
+		ov, found := oMap.Find(types.String(key.String()))
+		if !found {
+			return types.False
+		}
+		v := TypedToVal(value.Interface(), t.valuesSchema)
+		vEq := v.Equal(ov)
+		if vEq != types.True {
+			return vEq // either false or error
+		}
+	}
+	return types.True
+}
+
+func (t *typedMap) Type() ref.Type {
+	return types.MapType
+}
+
+func (t *typedMap) Value() interface{} {
+	return t.value
+}
+
+func (t *typedMap) Contains(key ref.Val) ref.Val {
+	v, found := t.Find(key)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+
+	return types.Bool(found)
+}
+
+func (t *typedMap) Get(key ref.Val) ref.Val {
+	v, found := t.Find(key)
+	if found {
+		return v
+	}
+	return types.ValOrErr(key, "no such key: %v", key)
+}
+
+func (t *typedMap) Size() ref.Val {
+	return types.Int(t.value.Len())
+}
+
+func (t *typedMap) Find(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	k := keyStr.Value().(string)
+	if v := t.value.MapIndex(reflect.ValueOf(k)); v.IsValid() {
+		return TypedToVal(v.Interface(), t.valuesSchema), true
+	}
+	return nil, false
+}
+
+func (t *typedMap) Iterator() traits.Iterator {
+	keys := make([]ref.Val, t.value.Len())
+	for i, k := range t.value.MapKeys() {
+		keys[i] = types.String(k.String())
+	}
+	return &mapIter{typedMap: t, keys: keys}
+}
+
+type mapIter struct {
+	*typedMap
+	keys []ref.Val
+	idx  int
+}
+
+func (it *mapIter) HasNext() ref.Val {
+	return types.Bool(it.idx < len(it.keys))
+}
+
+func (it *mapIter) Next() ref.Val {
+	key := it.keys[it.idx]
+	it.idx++
+	return key
+}
diff --git a/staging/src/k8s.io/apiserver/pkg/cel/common/values.go b/staging/src/k8s.io/apiserver/pkg/cel/common/valuesunstructured.go
similarity index 98%
rename from staging/src/k8s.io/apiserver/pkg/cel/common/values.go
rename to staging/src/k8s.io/apiserver/pkg/cel/common/valuesunstructured.go
index c8279f01371bf..0b8d52001c3c4 100644
--- a/staging/src/k8s.io/apiserver/pkg/cel/common/values.go
+++ b/staging/src/k8s.io/apiserver/pkg/cel/common/valuesunstructured.go
@@ -33,7 +33,7 @@ import (
 )
 
 // UnstructuredToVal converts a Kubernetes unstructured data element to a CEL Val.
-// The root schema of custom resource schema is expected contain type meta and object meta schemas.
+// The root schema of custom resource schemas is expected to contain type meta and object meta schemas.
 // If Embedded resources do not contain type meta and object meta schemas, they will be added automatically.
 func UnstructuredToVal(unstructured interface{}, schema Schema) ref.Val {
 	if unstructured == nil {
@@ -358,9 +358,8 @@ func escapeKeyProps(idents []string) []string {
 // unstructuredSetList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=set.
 type unstructuredSetList struct {
 	unstructuredList
-	escapedKeyProps []string
 
-	sync.Once // for for lazy load of setOfList since it is only needed if Equals is called
+	sync.Once // for lazy load of setOfList since it is only needed if Equals is called
 	set       map[interface{}]struct{}
 }
 
@@ -415,7 +414,6 @@ func (t *unstructuredSetList) Add(other ref.Val) ref.Val {
 	}
 	return &unstructuredSetList{
 		unstructuredList: unstructuredList{elements: elements, itemsSchema: t.itemsSchema},
-		escapedKeyProps:  t.escapedKeyProps,
 	}
 }
 


Reviews:
user: login: hashim21223445
state: APPROVED
author_association: NONE
submitted_at: 2025-04-25T16:43:42Z
user: login: jpbetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T17:37:26Z
user: login: aaron-prindle
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T18:45:43Z
user: login: jpbetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T19:43:23Z
user: login: liggitt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T19:45:30Z
user: login: jpbetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T19:53:28Z
user: login: jpbetz
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-30T05:14:10Z

Review comments:
diff_hunk: @@ -358,9 +358,8 @@ func escapeKeyProps(idents []string) []string {
 // unstructuredSetList represents an unstructured data instance of an OpenAPI array with x-kubernetes-list-type=set.
 type unstructuredSetList struct {
 	unstructuredList
-	escapedKeyProps []string
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesunstructured.go
user: login: jpbetz
body: This is unused. It got copied over from unstructuredMapList but is not needed for sets.
created_at: 2025-04-29T17:37:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
user: login: aaron-prindle
body: If a field is null, should it count as finding the value here (eg: return nil, true IIUC - currently this case is `return nil, false`)

If I use the CEL playground:

```
has(spec.explicitlyNullField)
// has(spec.absentField)
```

```
spec:
  explicitlyNullField: null
  # The'absentField' missing spec
```

I get:

has(spec.explicitlyNullField) -> true
has(spec.absentField) -> false

Would this implementation also return this?  Trying to understand if there is a delta and if so if it is intended.  Might make sense to add a comment here if so related to non-existing value and null value both returning false.

https://playcel.undistro.io/?content=H4sIAAAAAAAAA6tWSk7NUbJSykgs1iguSE3WS60oyMlMzizJqfQrzclxy0zNSdGMydPXV4CrSEwqTs0rgcgo6SilJJYkeuYVlJYATQHJW8XkKShgMcVKIQ%2FIBEkqK4RkpKojGaOukJtZXJyZl64AMgBoZm5%2BSirQOJDTagGIfws3ogAAAA%3D%3D
created_at: 2025-04-29T18:45:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
user: login: jpbetz
body: https://github.com/google/cel-spec/blob/master/doc/langdef.md#macros is our source of truth.  I don't think the playground is doing the right thing here.  CRD validation rules operate on the semantic of "A field is available if it's value is present (non-nil)".  This interacts well with users that guard dereferences with `has()` checks, so I'd like to continue with that approach.
created_at: 2025-04-29T19:43:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
user: login: liggitt
body: in places where we're switching from `tounstructured()` to this (https://github.com/kubernetes/kubernetes/issues/131520), let's spot test the behavior of `has()` doesn't change

I would *guess* that places where we were omitting null stanzas / attributes ([example](https://github.com/kubernetes/kubernetes/blob/release-1.33/staging/src/k8s.io/apiserver/pkg/authorization/cel/compile.go#L282)) will work the same, but we should verify that
created_at: 2025-04-29T19:45:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
user: login: jpbetz
body: Yes, the test suite in use here tests both the unstructured and this new "reflective" approach and ensure they behave the same, so we've aligning with unstructured.. we just need to make sure our test coverage is solid.  I'll do a sweep specifically around has() and different stanza representations (pointer/non-pointer,zero-valued,embedded,inlined...)
created_at: 2025-04-29T19:53:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,678 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	apiequality "k8s.io/apimachinery/pkg/api/equality"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"reflect"
+	"sigs.k8s.io/structured-merge-diff/v4/value"
+	"sync"
+
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/common/types/traits"
+)
+
+// TypedToVal wraps "typed" Go value as CEL ref.Val types using reflection.
+// "typed" values must be values declared by Kubernetes API types.go definitions.
+func TypedToVal(val interface{}, schema Schema) ref.Val {
+	if val == nil {
+		return types.NullValue
+	}
+	v := reflect.ValueOf(val)
+	if !v.IsValid() {
+		return types.NewErr("invalid data, got invalid reflect value: %v", v)
+	}
+	for v.Kind() == reflect.Pointer {
+		if v.IsNil() {
+			return types.NullValue
+		}
+		v = v.Elem()
+	}
+	val = v.Interface()
+
+	switch typedVal := val.(type) {
+	case bool:
+		return types.Bool(typedVal)
+	case int:
+		return types.Int(typedVal)
+	case int32:
+		return types.Int(typedVal)
+	case int64:
+		return types.Int(typedVal)
+	case float32:
+		return types.Double(typedVal)
+	case float64:
+		return types.Double(typedVal)
+	case string:
+		return types.String(typedVal)
+	case []byte:
+		if typedVal == nil {
+			return types.NullValue
+		}
+		return types.Bytes(typedVal)
+	case metav1.Time:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.MicroTime:
+		return types.Timestamp{Time: typedVal.Time}
+	case metav1.Duration:
+		return types.Duration{Duration: typedVal.Duration}
+	case intstr.IntOrString:
+		switch typedVal.Type {
+		case intstr.Int:
+			return types.Int(typedVal.IntVal)
+		case intstr.String:
+			return types.String(typedVal.StrVal)
+		}
+	case resource.Quantity:
+		// For compatibility with CRD Validation rules, represent quantity as a plain string.
+		return types.String(typedVal.String())
+	case json.Marshaler:
+		// All JSON marshaled types must be mapped to a CEL type in the above switch.
+		// This ensures that all types are purposefully mapped to CEL types.
+		return types.NewErr("unsupported Go type for CEL: %T", typedVal)
+	default:
+		// continue on to the next switch
+	}
+
+	switch v.Kind() {
+	case reflect.Slice:
+		if schema.Items() == nil {
+			return types.NewErr("invalid schema for slice type: %v", schema)
+		}
+		typedList := typedList{value: v, itemsSchema: schema.Items()}
+		listType := schema.XListType()
+		if listType != "" {
+			switch listType {
+			case "map":
+				mapKeys := schema.XListMapKeys()
+				return &typedMapList{typedList: typedList, escapedKeyProps: escapeKeyProps(mapKeys)}
+			case "set":
+				return &typedSetList{typedList: typedList}
+			case "atomic":
+				return &typedList
+			default:
+				return types.NewErr("invalid x-kubernetes-list-type, expected 'map', 'set' or 'atomic' but got %s", listType)
+			}
+		}
+		return &typedList
+	case reflect.Map:
+		if schema.AdditionalProperties() == nil || schema.AdditionalProperties().Schema() == nil {
+			return types.NewErr("invalid schema for map type: %v", schema)
+		}
+		return &typedMap{value: v, valuesSchema: schema.AdditionalProperties().Schema()}
+	case reflect.Struct:
+		if schema.Properties() == nil {
+			return types.NewErr("invalid schema for struct type: %v", schema)
+		}
+		return &typedStruct{
+			value: v,
+			propSchema: func(key string) (Schema, bool) {
+				if schema, ok := schema.Properties()[key]; ok {
+					return schema, true
+				}
+				return nil, false
+			},
+		}
+	// Match type aliases to primitives by kind
+	case reflect.Bool:
+		return types.Bool(v.Bool())
+	case reflect.String:
+		return types.String(v.String())
+	case reflect.Int, reflect.Int32, reflect.Int64:
+		return types.Int(v.Int())
+	case reflect.Float32, reflect.Float64:
+		return types.Double(v.Float())
+	default:
+		return types.NewErr("unsupported Go type for CEL: %v", v.Type())
+	}
+}
+
+// typedStruct wraps a struct as a CEL ref.Val and provides lazy access to fields via reflection.
+type typedStruct struct {
+	value reflect.Value // Kind is required to be: reflect.Struct
+
+	// propSchema finds the schema to use for a particular map key.
+	propSchema func(key string) (Schema, bool)
+}
+
+func (s *typedStruct) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
+	if s.value.Type().AssignableTo(typeDesc) {
+		return s.value.Interface(), nil
+	}
+	return nil, fmt.Errorf("type conversion error from struct type %v to %v", s.value.Type(), typeDesc)
+}
+
+func (s *typedStruct) ConvertToType(typeValue ref.Type) ref.Val {
+	switch typeValue {
+	case s.Type():
+		return s
+	case types.MapType:
+		return s
+	case types.TypeType:
+		return s.objType()
+	}
+	return types.NewErr("type conversion error from struct %s to %s", s.Type().TypeName(), typeValue.TypeName())
+}
+
+func (s *typedStruct) Equal(other ref.Val) ref.Val {
+	otherStruct, ok := other.(*typedStruct)
+	if ok {
+		return types.Bool(apiequality.Semantic.DeepEqual(s.value.Interface(), otherStruct.value.Interface()))
+	}
+	return types.MaybeNoSuchOverloadErr(other)
+}
+
+func (s *typedStruct) Type() ref.Type {
+	return s.objType()
+}
+
+func (s *typedStruct) objType() *types.Type {
+	typeName := s.value.Type().Name()
+	if pkgPath := s.value.Type().PkgPath(); pkgPath != "" {
+		typeName = pkgPath + "." + typeName
+	}
+	return types.NewObjectType(typeName)
+}
+
+func (s *typedStruct) Value() interface{} {
+	return s.value.Interface()
+}
+
+func (s *typedStruct) IsSet(field ref.Val) ref.Val {
+	v, found := s.lookupField(field)
+	if v != nil && types.IsUnknownOrError(v) {
+		return v
+	}
+	return types.Bool(found)
+}
+
+func (s *typedStruct) Get(key ref.Val) ref.Val {
+	v, found := s.lookupField(key)
+	if !found {
+		return types.NewErr("no such key: %v", key)
+	}
+	return v
+}
+
+func (s *typedStruct) lookupField(key ref.Val) (ref.Val, bool) {
+	keyStr, ok := key.(types.String)
+	if !ok {
+		return types.MaybeNoSuchOverloadErr(key), true
+	}
+	fieldName := keyStr.Value().(string)
+
+	cacheEntry := value.TypeReflectEntryOf(s.value.Type())
+	fieldCache, ok := cacheEntry.Fields()[fieldName]
+	if !ok {
+		return nil, false
+	}
+
+	if e := fieldCache.GetFrom(s.value); !fieldCache.CanOmit(e) {
+		if propSchema, ok := s.propSchema(fieldName); ok {
+			v := TypedToVal(e.Interface(), propSchema)
+			if v == types.NullValue {
path: staging/src/k8s.io/apiserver/pkg/cel/common/valuesreflect.go
user: login: jpbetz
body: Did more has() testing. https://github.com/kubernetes/kubernetes/pull/130989 will help. I've verified that once that PR merges, `omitzero` will be picked up by both unstructured and typed CEL wrappers.

I'll open a PR for additional tests once https://github.com/kubernetes/kubernetes/pull/130989 merges to demonstrate this.
created_at: 2025-04-30T05:14:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: k8s-ci-robot
created_at: 2025-04-25T04:18:49Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/kubernetes/pull/131460#" title="Author self-approved">jpbetz</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fkubernetes).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[staging/src/k8s.io/apiserver/pkg/cel/OWNERS](https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/cel/OWNERS)~~ [jpbetz]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR
user: login: jpbetz
created_at: 2025-04-25T11:48:50Z
body: /assign @cici37 @aaron-prindle 
cc @TristonianJones

/sig api-machinery
/priority important-longterm
/triage accepted
author_association: CONTRIBUTOR
user: login: aaron-prindle
created_at: 2025-04-29T20:00:16Z
body: /lgtm
author_association: CONTRIBUTOR
user: login: k8s-ci-robot
created_at: 2025-04-29T20:00:23Z
body: LGTM label has been added.  <details>Git tree hash: 1678803b922169038abceb087eeb28440c2ed303</details>
author_association: CONTRIBUTOR
user: login: jpbetz
created_at: 2025-04-29T21:39:31Z
body: This merged faster than I expected.  It's not in use yet.  I'll follow up with a PR for the tests @liggitt mentioned

@cici37 I'd still appreciate your feedback.  I'll apply suggestions in a follow up
author_association: CONTRIBUTOR
user: login: cici37
created_at: 2025-04-30T22:06:02Z
body: The conversion looks great. Thanks for the issue tracking as well^^
author_association: CONTRIBUTOR