Skip to content

Commit 0677103

Browse files
committed
corpus update
Signed-off-by: Phil Hassey <phil@strongdm.com>
1 parent a4d0ae8 commit 0677103

29 files changed

+677
-220
lines changed

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ linters:
99
golangci-lint run
1010
go run github.com/alecthomas/go-check-sumtype/cmd/go-check-sumtype@latest -default-signifies-exhaustive=false ./...
1111
go test -coverprofile=coverage.out ./...
12-
sed -i '' '/^github.com\/cedar-policy\/cedar-go\/internal\/schema\/parser\/cedarschema.go/d' coverage.out
13-
go tool cover -func=coverage.out | sed 's/%$$//' | awk '$$2 == "isCedarType" { next } $$2 == "Entity" && $$1 ~ /entity\.go/ { next } $$2 == "typeOfExtensionCall" { next } { if ($$3 < 100.0) { printf "Insufficient code coverage for %s\n", $$0; failed=1 } } END { exit failed }'
12+
go tool cover -func=coverage.out | sed 's/%$$//' | awk '{ if ($$3 < 100.0) { printf "Insufficient code coverage for %s\n", $$0; failed=1 } } END { exit failed }'
1413

1514
# Download the latest corpus tests tarball and overwrite corpus-tests.tar.gz if changed
1615
check-upstream-corpus:

corpus-tests-json-schemas.tar.gz

-155 KB
Binary file not shown.

corpus-tests-validation.tar.gz

45.6 KB
Binary file not shown.

corpus-tests.tar.gz

-350 KB
Binary file not shown.

x/exp/schema/validate/cedar_type.go

Lines changed: 93 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validate
22

33
import (
4+
"cmp"
45
"fmt"
56
"slices"
67
"strings"
@@ -36,6 +37,28 @@ type entityAttrSource struct {
3637
type typeEntity struct{ lub entityLUB } // Entity with LUB of types
3738
type typeExtension struct{ name types.Ident } // Extension type (ipaddr, decimal, etc.)
3839

40+
var cedarTypeKindRanks = map[string]int{
41+
fmt.Sprintf("%T", typeTrue{}): 0,
42+
fmt.Sprintf("%T", typeFalse{}): 1,
43+
fmt.Sprintf("%T", typeBool{}): 2,
44+
fmt.Sprintf("%T", typeNever{}): 3,
45+
fmt.Sprintf("%T", typeLong{}): 4,
46+
fmt.Sprintf("%T", typeString{}): 5,
47+
fmt.Sprintf("%T", typeSet{}): 6,
48+
fmt.Sprintf("%T", typeRecord{}): 7,
49+
fmt.Sprintf("%T", typeEntity{}): 8,
50+
fmt.Sprintf("%T", typeExtension{}): 9,
51+
}
52+
53+
var cedarPrimitiveTypeNames = map[string]string{
54+
fmt.Sprintf("%T", typeNever{}): "__cedar::internal::Any",
55+
fmt.Sprintf("%T", typeTrue{}): "__cedar::internal::True",
56+
fmt.Sprintf("%T", typeFalse{}): "__cedar::internal::False",
57+
fmt.Sprintf("%T", typeBool{}): "Bool",
58+
fmt.Sprintf("%T", typeLong{}): "Long",
59+
fmt.Sprintf("%T", typeString{}): "String",
60+
}
61+
3962
func (typeNever) isCedarType() { _ = "hack for code coverage" }
4063
func (typeTrue) isCedarType() { _ = "hack for code coverage" }
4164
func (typeFalse) isCedarType() { _ = "hack for code coverage" }
@@ -51,7 +74,7 @@ func (typeExtension) isCedarType() { _ = "hack for code coverage" }
5174
func typeIncompatErr(a, b cedarType) *typeIncompatError {
5275
nameA := cedarTypeName(a)
5376
nameB := cedarTypeName(b)
54-
if cedarTypeSortKey(a) > cedarTypeSortKey(b) {
77+
if compareCedarType(a, b) > 0 {
5578
nameA, nameB = nameB, nameA
5679
}
5780
return &typeIncompatError{msg: fmt.Sprintf("the types %s and %s are not compatible", nameA, nameB)}
@@ -64,14 +87,7 @@ func typeIncompatErrMulti(types []cedarType) *typeIncompatError {
6487
sorted := make([]cedarType, len(types))
6588
copy(sorted, types)
6689
slices.SortFunc(sorted, func(a, b cedarType) int {
67-
ka, kb := cedarTypeSortKey(a), cedarTypeSortKey(b)
68-
if ka < kb {
69-
return -1
70-
}
71-
if ka > kb {
72-
return 1
73-
}
74-
return 0
90+
return compareCedarType(a, b)
7591
})
7692
names := make([]string, len(sorted))
7793
for i, t := range sorted {
@@ -99,74 +115,85 @@ func typeIncompatErrMulti(types []cedarType) *typeIncompatError {
99115
return &typeIncompatError{msg: sb.String()}
100116
}
101117

102-
// cedarTypeSortKey returns a sort key for ordering types in error messages.
103-
// Matches Rust's structural type ordering (True < False < Never < Long < String < Set < Record < Entity < Extension).
104-
func cedarTypeSortKey(t cedarType) string {
105-
switch tv := t.(type) {
106-
case typeTrue:
107-
return "0a"
108-
case typeFalse:
109-
return "0b"
110-
case typeBool:
111-
return "0c"
112-
case typeNever:
113-
return "1"
114-
case typeLong:
115-
return "2"
116-
case typeString:
117-
return "3"
118-
case typeSet:
119-
return "4:" + cedarTypeSortKey(tv.element)
120-
case typeRecord:
121-
// Sort by attribute keys/types (matches Rust BTreeMap ordering)
122-
key := "5"
123-
keys := make([]string, 0, len(tv.attrs))
124-
for k := range tv.attrs {
125-
keys = append(keys, string(k))
126-
}
127-
slices.Sort(keys)
128-
for _, k := range keys {
129-
at := tv.attrs[types.String(k)]
130-
key += ":" + k + ":" + cedarTypeSortKey(at.typ)
131-
}
132-
return key
133-
case typeEntity:
134-
return "6:" + cedarEntityTypeName(tv.lub)
135-
case typeExtension:
118+
func compareCedarType(a, b cedarType) int {
119+
ak, bk := cedarTypeKindRank(a), cedarTypeKindRank(b)
120+
if ak != bk {
121+
return ak - bk
122+
}
123+
124+
if av, ok := a.(typeSet); ok {
125+
return compareCedarType(av.element, b.(typeSet).element)
126+
}
127+
if av, ok := a.(typeRecord); ok {
128+
return compareRecordTypes(av, b.(typeRecord))
129+
}
130+
if av, ok := a.(typeEntity); ok {
131+
return compareEntityLUB(av.lub, b.(typeEntity).lub)
132+
}
133+
return strings.Compare(cedarTypeName(a), cedarTypeName(b))
134+
}
135+
136+
func cedarTypeKindRank(t cedarType) int {
137+
return cedarTypeKindRanks[fmt.Sprintf("%T", t)]
138+
}
139+
140+
func compareRecordTypes(a, b typeRecord) int {
141+
ak := make([]string, 0, len(a.attrs))
142+
for k := range a.attrs {
143+
ak = append(ak, string(k))
136144
}
137-
return "7:" + string(t.(typeExtension).name)
145+
bk := make([]string, 0, len(b.attrs))
146+
for k := range b.attrs {
147+
bk = append(bk, string(k))
148+
}
149+
slices.Sort(ak)
150+
slices.Sort(bk)
151+
152+
n := len(ak)
153+
if len(bk) < n {
154+
n = len(bk)
155+
}
156+
for i := 0; i < n; i++ {
157+
if c := strings.Compare(ak[i], bk[i]); c != 0 {
158+
return c
159+
}
160+
aat := a.attrs[types.String(ak[i])]
161+
bat := b.attrs[types.String(bk[i])]
162+
if c := compareCedarType(aat.typ, bat.typ); c != 0 {
163+
return c
164+
}
165+
}
166+
return cmp.Compare(len(ak), len(bk))
167+
}
168+
169+
func compareEntityLUB(a, b entityLUB) int {
170+
n := min(len(a.elements), len(b.elements))
171+
for i := 0; i < n; i++ {
172+
as, bs := string(a.elements[i]), string(b.elements[i])
173+
if c := strings.Compare(as, bs); c != 0 {
174+
return c
175+
}
176+
}
177+
return cmp.Compare(len(a.elements), len(b.elements))
138178
}
139179

140180
// cedarTypeName returns the Rust Cedar display name for a type.
141181
func cedarTypeName(t cedarType) string {
142182
switch tv := t.(type) {
143-
case typeNever:
144-
return "__cedar::internal::Any"
145-
case typeTrue:
146-
return "__cedar::internal::True"
147-
case typeFalse:
148-
return "__cedar::internal::False"
149-
case typeBool:
150-
return "Bool"
151-
case typeLong:
152-
return "Long"
153-
case typeString:
154-
return "String"
183+
case typeNever, typeTrue, typeFalse, typeBool, typeLong, typeString:
155184
case typeSet:
156185
return "Set<" + cedarTypeName(tv.element) + ">"
157186
case typeRecord:
158187
return cedarRecordTypeName(tv)
159188
case typeEntity:
160189
return cedarEntityTypeName(tv.lub)
161190
case typeExtension:
191+
return string(tv.name)
162192
}
163-
return string(t.(typeExtension).name)
193+
return cedarPrimitiveTypeNames[fmt.Sprintf("%T", t)]
164194
}
165195

166196
func cedarEntityTypeName(lub entityLUB) string {
167-
if len(lub.elements) == 0 {
168-
return "__cedar::internal::AnyEntity"
169-
}
170197
if len(lub.elements) == 1 {
171198
return string(lub.elements[0])
172199
}
@@ -192,6 +219,9 @@ func cedarRecordTypeName(r typeRecord) string {
192219
for _, k := range keys {
193220
at := r.attrs[types.String(k)]
194221
sb.WriteString(k)
222+
if !at.required {
223+
sb.WriteRune('?')
224+
}
195225
sb.WriteString(": ")
196226
sb.WriteString(cedarTypeName(at.typ))
197227
sb.WriteRune(',')
@@ -254,14 +284,13 @@ func (v *Validator) isSubtype(a, b cedarType) bool {
254284

255285
// leastUpperBound computes the LUB of two types.
256286
func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
257-
if _, ok := a.(typeNever); ok {
258-
return b, nil
259-
}
260287
if _, ok := b.(typeNever); ok {
261288
return a, nil
262289
}
263290

264291
switch av := a.(type) {
292+
case typeNever:
293+
return b, nil
265294
case typeTrue:
266295
switch b.(type) {
267296
case typeTrue:
@@ -279,10 +308,8 @@ func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
279308
case typeNever, typeLong, typeString, typeSet, typeRecord, typeEntity, typeExtension:
280309
}
281310
case typeBool:
282-
switch b.(type) {
283-
case typeTrue, typeFalse, typeBool:
311+
if isBoolType(b) {
284312
return typeBool{}, nil
285-
case typeNever, typeLong, typeString, typeSet, typeRecord, typeEntity, typeExtension:
286313
}
287314
case typeLong:
288315
if _, ok := b.(typeLong); ok {
@@ -312,8 +339,6 @@ func (v *Validator) leastUpperBound(a, b cedarType) (cedarType, error) {
312339
if bv, ok := b.(typeExtension); ok && av.name == bv.name {
313340
return av, nil
314341
}
315-
case typeNever:
316-
// Already handled above; unreachable.
317342
}
318343

319344
return nil, fmt.Errorf("incompatible types for least upper bound")

x/exp/schema/validate/marker_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,4 @@ func TestMarkerMethods(t *testing.T) {
1515
typeRecord{}.isCedarType()
1616
typeEntity{}.isCedarType()
1717
typeExtension{}.isCedarType()
18-
19-
// Defensive type name/sort key paths for types that rarely appear in error messages
20-
cedarTypeSortKey(typeNever{})
21-
cedarTypeSortKey(typeBool{})
22-
cedarTypeName(typeNever{})
23-
cedarEntityTypeName(entityLUB{})
24-
2518
}

x/exp/schema/validate/policy.go

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"slices"
7+
"strings"
78

89
"github.com/cedar-policy/cedar-go/types"
910
"github.com/cedar-policy/cedar-go/x/exp/ast"
@@ -39,24 +40,26 @@ func (v *Validator) Policy(policyID string, policy *ast.Policy) error {
3940
}
4041

4142
// Check action application
42-
if err := v.validateActionApplication(principalTypes, resourceTypes, actionUIDs); err != nil {
43-
errs = append(errs, err)
43+
actionAppErr := v.validateActionApplication(principalTypes, resourceTypes, actionUIDs)
44+
if actionAppErr != nil {
45+
errs = append(errs, actionAppErr)
4446
}
4547

4648
// Expression type checking
4749
allEnvs := v.generateRequestEnvs()
4850
envs := v.filterEnvsForPolicy(allEnvs, principalTypes, resourceTypes, actionUIDs)
4951

50-
if len(envs) > 0 {
51-
// Check for empty action set literal in strict mode. This matches Rust
52-
// where the scope is part of the typechecked condition — the empty set
53-
// check only fires when prior scope constraints don't short-circuit.
54-
if v.strict {
55-
if sc, ok := policy.Action.(ast.ScopeTypeInSet); ok && len(sc.Entities) == 0 {
56-
errs = append(errs, fmt.Errorf("empty set literals are forbidden in policies"))
57-
}
52+
// In strict mode, empty action set scopes always report this error.
53+
if v.strict {
54+
if sc, ok := policy.Action.(ast.ScopeTypeInSet); ok && len(sc.Entities) == 0 {
55+
errs = append(errs, fmt.Errorf("empty set literals are forbidden in policies"))
5856
}
59-
if len(policy.Conditions) > 0 {
57+
}
58+
59+
if len(envs) > 0 {
60+
// In permissive mode, if action applicability already failed, Rust does
61+
// not typecheck policy conditions.
62+
if len(policy.Conditions) > 0 && (v.strict || actionAppErr == nil) {
6063
if err := v.typecheckConditions(envs, policy.Conditions); err != nil {
6164
errs = append(errs, flattenErrors(err)...)
6265
}
@@ -338,14 +341,18 @@ func (v *Validator) typecheckConditions(envs []requestEnv, conditions []ast.Cond
338341
var allErrs []error
339342
for _, cond := range conditions {
340343
// Collect error multisets per environment and merge (element-wise max count).
341-
// This deduplicates identical errors across environments while preserving
342-
// duplicates from different expression positions within the same environment.
344+
// For dynamic tag-key diagnostics:
345+
// - `principal.*` tag keys aggregate by principal type
346+
// - `resource.*` tag keys aggregate by resource type
343347
type errEntry struct {
344348
err error
345349
count int
346350
}
347351
merged := map[string]*errEntry{}
348352
var mergedOrder []string
353+
const unsafeTagPrefix = "unable to guarantee safety of access to tag `"
354+
principalTagByType := map[string]map[types.EntityType]int{}
355+
resourceTagByType := map[string]map[types.EntityType]int{}
349356
for _, env := range envs {
350357
caps := newCapabilitySet()
351358
t, _, err := v.typeOfExpr(&env, cond.Body, caps)
@@ -370,14 +377,53 @@ func (v *Validator) typecheckConditions(envs []requestEnv, conditions []ast.Cond
370377
envCounts[msg] = &envErr{err: e, count: 1}
371378
}
372379
}
373-
// Merge: deduplicate across environments, preserving per-environment counts.
374-
// The same expression evaluated in different type contexts produces the same
375-
// count for any shared error message, so first-seen count is sufficient.
376380
for msg, ee := range envCounts {
377381
if _, ok := merged[msg]; !ok {
378382
mergedOrder = append(mergedOrder, msg)
379383
merged[msg] = &errEntry{err: ee.err, count: ee.count}
380384
}
385+
merged[msg].count = max(merged[msg].count, ee.count)
386+
if strings.HasPrefix(msg, unsafeTagPrefix) && strings.Contains(msg, "`principal.") {
387+
byType, ok := principalTagByType[msg]
388+
if !ok {
389+
byType = map[types.EntityType]int{}
390+
principalTagByType[msg] = byType
391+
}
392+
if ee.count > byType[env.principalType] {
393+
byType[env.principalType] = ee.count
394+
}
395+
}
396+
if strings.HasPrefix(msg, unsafeTagPrefix) && strings.Contains(msg, "`resource.") {
397+
byType, ok := resourceTagByType[msg]
398+
if !ok {
399+
byType = map[types.EntityType]int{}
400+
resourceTagByType[msg] = byType
401+
}
402+
if ee.count > byType[env.resourceType] {
403+
byType[env.resourceType] = ee.count
404+
}
405+
}
406+
}
407+
}
408+
for _, msg := range mergedOrder {
409+
if byType, ok := principalTagByType[msg]; ok {
410+
total := 0
411+
for _, count := range byType {
412+
total += count
413+
}
414+
if total > 0 {
415+
merged[msg].count = total
416+
}
417+
continue
418+
}
419+
if byType, ok := resourceTagByType[msg]; ok {
420+
total := 0
421+
for _, count := range byType {
422+
total += count
423+
}
424+
if total > 0 {
425+
merged[msg].count = total
426+
}
381427
}
382428
}
383429
// Emit merged errors preserving first-seen order and original error types

0 commit comments

Comments
 (0)