Skip to content

Commit 39a8f04

Browse files
authored
fix: Translate and preserve metadata in schema/v2 (#2767)
1 parent f4ea9ee commit 39a8f04

File tree

5 files changed

+511
-0
lines changed

5 files changed

+511
-0
lines changed

pkg/schema/v2/convert.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@ import (
99
)
1010

1111
func convertDefinition(def *corev1.NamespaceDefinition) (*Definition, error) {
12+
// Parse metadata
13+
metadata, err := parseMetadata(def.GetMetadata())
14+
if err != nil {
15+
return nil, fmt.Errorf("failed to parse definition metadata: %w", err)
16+
}
17+
1218
out := &Definition{
1319
name: def.GetName(),
1420
relations: make(map[string]*Relation),
1521
permissions: make(map[string]*Permission),
22+
metadata: metadata,
1623
}
1724
for _, r := range def.GetRelation() {
1825
if userset := r.GetUsersetRewrite(); userset != nil {
@@ -30,17 +37,32 @@ func convertDefinition(def *corev1.NamespaceDefinition) (*Definition, error) {
3037
}
3138
rel.parent = out
3239
rel.name = r.GetName()
40+
41+
// Parse relation metadata
42+
relMetadata, err := parseMetadata(r.GetMetadata())
43+
if err != nil {
44+
return nil, fmt.Errorf("failed to parse relation metadata: %w", err)
45+
}
46+
rel.metadata = relMetadata
47+
3348
out.relations[r.GetName()] = rel
3449
}
3550
}
3651
return out, nil
3752
}
3853

3954
func convertCaveat(def *corev1.CaveatDefinition) (*Caveat, error) {
55+
// Parse metadata
56+
metadata, err := parseMetadata(def.GetMetadata())
57+
if err != nil {
58+
return nil, fmt.Errorf("failed to parse caveat metadata: %w", err)
59+
}
60+
4061
out := &Caveat{
4162
name: def.GetName(),
4263
expression: string(def.GetSerializedExpression()),
4364
parameters: make([]CaveatParameter, 0, len(def.GetParameterTypes())),
65+
metadata: metadata,
4466
}
4567

4668
for paramName, paramType := range def.GetParameterTypes() {

pkg/schema/v2/convert_test.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"testing"
55

66
"github.com/stretchr/testify/require"
7+
"google.golang.org/protobuf/types/known/anypb"
78

89
corev1 "github.com/authzed/spicedb/pkg/proto/core/v1"
10+
implv1 "github.com/authzed/spicedb/pkg/proto/impl/v1"
911
)
1012

1113
func TestConvertDefinitionEdgeCases(t *testing.T) {
@@ -831,3 +833,271 @@ func TestConvertFunctionType(t *testing.T) {
831833
})
832834
}
833835
}
836+
837+
func TestMetadataPreservation(t *testing.T) {
838+
t.Parallel()
839+
840+
t.Run("namespace definition metadata with doc comments is preserved", func(t *testing.T) {
841+
t.Parallel()
842+
843+
// Create doc comment metadata
844+
docComment := &implv1.DocComment{
845+
Comment: "This is a test resource",
846+
}
847+
docCommentAny, err := anypb.New(docComment)
848+
require.NoError(t, err)
849+
850+
originalMetadata := &corev1.Metadata{
851+
MetadataMessage: []*anypb.Any{docCommentAny},
852+
}
853+
854+
originalDef := &corev1.NamespaceDefinition{
855+
Name: "test_resource",
856+
Relation: []*corev1.Relation{
857+
{
858+
Name: "viewer",
859+
TypeInformation: &corev1.TypeInformation{
860+
AllowedDirectRelations: []*corev1.AllowedRelation{
861+
{
862+
Namespace: "user",
863+
RelationOrWildcard: &corev1.AllowedRelation_Relation{
864+
Relation: "",
865+
},
866+
},
867+
},
868+
},
869+
},
870+
},
871+
Metadata: originalMetadata,
872+
}
873+
874+
// Convert to v2 schema
875+
schema, err := BuildSchemaFromDefinitions([]*corev1.NamespaceDefinition{originalDef}, nil)
876+
require.NoError(t, err)
877+
878+
// Verify internal representation
879+
def, ok := schema.Definitions()["test_resource"]
880+
require.True(t, ok)
881+
require.NotNil(t, def.metadata)
882+
require.Equal(t, []string{"This is a test resource"}, def.metadata.Comments())
883+
884+
// Convert back to corev1
885+
defs, caveats, err := schema.ToDefinitions()
886+
require.NoError(t, err)
887+
require.Len(t, defs, 1)
888+
require.Len(t, caveats, 0)
889+
890+
// Verify metadata is preserved
891+
require.NotNil(t, defs[0].Metadata, "Metadata should be preserved during round-trip conversion")
892+
require.Len(t, defs[0].Metadata.MetadataMessage, 1)
893+
894+
// Verify doc comment
895+
var roundTrippedComment implv1.DocComment
896+
err = defs[0].Metadata.MetadataMessage[0].UnmarshalTo(&roundTrippedComment)
897+
require.NoError(t, err)
898+
require.Equal(t, "This is a test resource", roundTrippedComment.Comment)
899+
})
900+
901+
t.Run("caveat definition metadata with doc comments is preserved", func(t *testing.T) {
902+
t.Parallel()
903+
904+
// Create doc comment metadata
905+
docComment := &implv1.DocComment{
906+
Comment: "This is a test caveat",
907+
}
908+
docCommentAny, err := anypb.New(docComment)
909+
require.NoError(t, err)
910+
911+
originalMetadata := &corev1.Metadata{
912+
MetadataMessage: []*anypb.Any{docCommentAny},
913+
}
914+
915+
originalCaveat := &corev1.CaveatDefinition{
916+
Name: "test_caveat",
917+
SerializedExpression: []byte("x == 1"),
918+
ParameterTypes: map[string]*corev1.CaveatTypeReference{
919+
"x": {
920+
TypeName: "int",
921+
},
922+
},
923+
Metadata: originalMetadata,
924+
}
925+
926+
// Convert to v2 schema
927+
schema, err := BuildSchemaFromDefinitions(nil, []*corev1.CaveatDefinition{originalCaveat})
928+
require.NoError(t, err)
929+
930+
// Verify internal representation
931+
caveat, ok := schema.Caveats()["test_caveat"]
932+
require.True(t, ok)
933+
require.NotNil(t, caveat.metadata)
934+
require.Equal(t, []string{"This is a test caveat"}, caveat.metadata.Comments())
935+
936+
// Convert back to corev1
937+
defs, caveats, err := schema.ToDefinitions()
938+
require.NoError(t, err)
939+
require.Len(t, defs, 0)
940+
require.Len(t, caveats, 1)
941+
942+
// Verify metadata is preserved
943+
require.NotNil(t, caveats[0].Metadata, "Caveat metadata should be preserved during round-trip conversion")
944+
945+
// Verify doc comment
946+
var roundTrippedComment implv1.DocComment
947+
err = caveats[0].Metadata.MetadataMessage[0].UnmarshalTo(&roundTrippedComment)
948+
require.NoError(t, err)
949+
require.Equal(t, "This is a test caveat", roundTrippedComment.Comment)
950+
})
951+
952+
t.Run("relation metadata with doc comments and relation kind is preserved", func(t *testing.T) {
953+
t.Parallel()
954+
955+
// Create doc comment metadata
956+
docComment := &implv1.DocComment{
957+
Comment: "This is a viewer relation",
958+
}
959+
docCommentAny, err := anypb.New(docComment)
960+
require.NoError(t, err)
961+
962+
// Create relation metadata
963+
relationMetadata := &implv1.RelationMetadata{
964+
Kind: implv1.RelationMetadata_RELATION,
965+
}
966+
relationMetadataAny, err := anypb.New(relationMetadata)
967+
require.NoError(t, err)
968+
969+
combinedMetadata := &corev1.Metadata{
970+
MetadataMessage: []*anypb.Any{docCommentAny, relationMetadataAny},
971+
}
972+
973+
originalDef := &corev1.NamespaceDefinition{
974+
Name: "test_resource",
975+
Relation: []*corev1.Relation{
976+
{
977+
Name: "viewer",
978+
TypeInformation: &corev1.TypeInformation{
979+
AllowedDirectRelations: []*corev1.AllowedRelation{
980+
{
981+
Namespace: "user",
982+
RelationOrWildcard: &corev1.AllowedRelation_Relation{
983+
Relation: "",
984+
},
985+
},
986+
},
987+
},
988+
Metadata: combinedMetadata,
989+
},
990+
},
991+
}
992+
993+
// Convert to v2 schema
994+
schema, err := BuildSchemaFromDefinitions([]*corev1.NamespaceDefinition{originalDef}, nil)
995+
require.NoError(t, err)
996+
997+
// Verify internal representation
998+
def, ok := schema.Definitions()["test_resource"]
999+
require.True(t, ok)
1000+
rel, ok := def.Relations()["viewer"]
1001+
require.True(t, ok)
1002+
require.NotNil(t, rel.metadata)
1003+
require.Equal(t, []string{"This is a viewer relation"}, rel.metadata.Comments())
1004+
require.Equal(t, RelationKindRelation, rel.metadata.RelationKind())
1005+
1006+
// Convert back to corev1
1007+
defs, _, err := schema.ToDefinitions()
1008+
require.NoError(t, err)
1009+
require.Len(t, defs, 1)
1010+
require.Len(t, defs[0].Relation, 1)
1011+
1012+
// Verify relation metadata is preserved
1013+
require.NotNil(t, defs[0].Relation[0].Metadata, "Relation metadata should be preserved during round-trip conversion")
1014+
require.Len(t, defs[0].Relation[0].Metadata.MetadataMessage, 2)
1015+
1016+
// Verify both doc comment and relation metadata are preserved
1017+
var foundDocComment, foundRelationMetadata bool
1018+
for _, msg := range defs[0].Relation[0].Metadata.MetadataMessage {
1019+
var dc implv1.DocComment
1020+
if err := msg.UnmarshalTo(&dc); err == nil {
1021+
require.Equal(t, "This is a viewer relation", dc.Comment)
1022+
foundDocComment = true
1023+
continue
1024+
}
1025+
1026+
var rm implv1.RelationMetadata
1027+
if err := msg.UnmarshalTo(&rm); err == nil {
1028+
require.Equal(t, implv1.RelationMetadata_RELATION, rm.Kind)
1029+
foundRelationMetadata = true
1030+
}
1031+
}
1032+
1033+
require.True(t, foundDocComment, "Doc comment should be preserved")
1034+
require.True(t, foundRelationMetadata, "Relation metadata should be preserved")
1035+
})
1036+
1037+
t.Run("permission metadata with type annotations is preserved", func(t *testing.T) {
1038+
t.Parallel()
1039+
1040+
// Create permission metadata with type annotations
1041+
permissionMetadata := &implv1.RelationMetadata{
1042+
Kind: implv1.RelationMetadata_PERMISSION,
1043+
TypeAnnotations: &implv1.TypeAnnotations{
1044+
Types: []string{"user", "group"},
1045+
},
1046+
}
1047+
permissionMetadataAny, err := anypb.New(permissionMetadata)
1048+
require.NoError(t, err)
1049+
1050+
metadata := &corev1.Metadata{
1051+
MetadataMessage: []*anypb.Any{permissionMetadataAny},
1052+
}
1053+
1054+
originalDef := &corev1.NamespaceDefinition{
1055+
Name: "test_resource",
1056+
Relation: []*corev1.Relation{
1057+
{
1058+
Name: "viewer",
1059+
TypeInformation: &corev1.TypeInformation{
1060+
AllowedDirectRelations: []*corev1.AllowedRelation{
1061+
{
1062+
Namespace: "user",
1063+
RelationOrWildcard: &corev1.AllowedRelation_Relation{
1064+
Relation: "",
1065+
},
1066+
},
1067+
},
1068+
},
1069+
Metadata: metadata,
1070+
},
1071+
},
1072+
}
1073+
1074+
// Convert to v2 schema
1075+
schema, err := BuildSchemaFromDefinitions([]*corev1.NamespaceDefinition{originalDef}, nil)
1076+
require.NoError(t, err)
1077+
1078+
// Verify internal representation
1079+
def, ok := schema.Definitions()["test_resource"]
1080+
require.True(t, ok)
1081+
rel, ok := def.Relations()["viewer"]
1082+
require.True(t, ok)
1083+
require.NotNil(t, rel.metadata)
1084+
require.Equal(t, RelationKindPermission, rel.metadata.RelationKind())
1085+
require.Equal(t, []string{"user", "group"}, rel.metadata.TypeAnnotations())
1086+
1087+
// Convert back to corev1
1088+
defs, _, err := schema.ToDefinitions()
1089+
require.NoError(t, err)
1090+
require.Len(t, defs, 1)
1091+
require.Len(t, defs[0].Relation, 1)
1092+
1093+
// Verify permission metadata with type annotations is preserved
1094+
require.NotNil(t, defs[0].Relation[0].Metadata)
1095+
1096+
var rm implv1.RelationMetadata
1097+
err = defs[0].Relation[0].Metadata.MetadataMessage[0].UnmarshalTo(&rm)
1098+
require.NoError(t, err)
1099+
require.Equal(t, implv1.RelationMetadata_PERMISSION, rm.Kind)
1100+
require.NotNil(t, rm.TypeAnnotations)
1101+
require.Equal(t, []string{"user", "group"}, rm.TypeAnnotations.Types)
1102+
})
1103+
}

0 commit comments

Comments
 (0)