Skip to content

Commit b9480ad

Browse files
vitorbariclaude
andcommitted
fix: carry the dotted-name rule in the meta-schema only
Drops the Go walk in favour of the meta-schema rule. Coverage is narrower — a name hidden under $defs, allOf, or items is not caught — but the rule now lives with the dialect, so an editor flags it while authoring rather than the server rejecting it after the fact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 6472704 commit b9480ad

3 files changed

Lines changed: 5 additions & 87 deletions

File tree

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
---
22
"@zitadel/server": minor
3+
"@zitadel/config": minor
34
---
45

5-
A user schema property name may no longer contain a dot. Nested values are stored
6-
under their dotted path, so `{"a.b": …}` and `{"a": {"b": …}}` would produce the
7-
same attribute key and become indistinguishable.
6+
A user schema property name must be a single attribute name and may no longer
7+
contain a dot. The rule lives in the user-schema meta-schema, so an editor
8+
validating against the shipped dialect flags it while authoring, and the server
9+
rejects it on create.

internal/domain/json_schema.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,6 @@ func NewJSONSchema(projectID string, schemabs []byte) (_ *JSONSchema, err error)
8181
}
8282
}
8383

84-
if err := rejectDottedPropertyNames(schema); err != nil {
85-
return nil, err
86-
}
87-
8884
var objectType *string
8985
if ot, ok := maputil.Get[string](schema, "objectType"); ok {
9086
objectType = &ot
@@ -99,35 +95,6 @@ func NewJSONSchema(projectID string, schemabs []byte) (_ *JSONSchema, err error)
9995
}, nil
10096
}
10197

102-
// rejectDottedPropertyNames rejects a property name holding a dot. The
103-
// user-schema meta-schema carries the same rule so an editor flags it
104-
// while authoring; this copy enforces it, and reaches `$defs`, `allOf`,
105-
// and `items`, which the meta-schema leaves unconstrained.
106-
func rejectDottedPropertyNames(node any) error {
107-
switch v := node.(type) {
108-
case map[string]any:
109-
if props, ok := v["properties"].(map[string]any); ok {
110-
for name := range props {
111-
if strings.Contains(name, ".") {
112-
return ErrJSONSchemaInvalid().WithMessage(fmt.Sprintf("schema property %q cannot contain a dot", name))
113-
}
114-
}
115-
}
116-
for _, val := range v {
117-
if err := rejectDottedPropertyNames(val); err != nil {
118-
return err
119-
}
120-
}
121-
case []any:
122-
for _, item := range v {
123-
if err := rejectDottedPropertyNames(item); err != nil {
124-
return err
125-
}
126-
}
127-
}
128-
return nil
129-
}
130-
13198
//go:generate go tool mockgen -typed -package domainmock -destination ./mock/json_schema.mock.go . JSONSchemaStore
13299

133100
// JSONSchemaStore is the persistence port for JSON schemas used by

internal/domain/json_schema_test.go

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -474,57 +474,6 @@ func TestNewJSONSchema_ReservedProperties(t *testing.T) {
474474
}
475475
})
476476

477-
// A nested value is keyed by its dotted path in the attribute store and
478-
// addressed by that same path in a flow step's fields, so a literal dot
479-
// in a property name would be indistinguishable from nesting.
480-
t.Run("dotted property names", func(t *testing.T) {
481-
t.Run("rejected", func(t *testing.T) {
482-
tests := []struct {
483-
name string
484-
schema string
485-
message string
486-
}{
487-
{
488-
name: "at the top level",
489-
schema: `{"type":"object","properties":{"address.street":{"type":"string"}}}`,
490-
message: `schema property "address.street" cannot contain a dot`,
491-
},
492-
{
493-
name: "inside a nested object",
494-
schema: `{"type":"object","properties":{"address":{"type":"object","properties":{"zip.code":{"type":"string"}}}}}`,
495-
message: `schema property "zip.code" cannot contain a dot`,
496-
},
497-
{
498-
name: "under $defs behind a $ref",
499-
schema: `{"type":"object","$defs":{"address":{"type":"object","properties":{"zip.code":{"type":"string"}}}},"properties":{"address":{"$ref":"#/$defs/address"}}}`,
500-
message: `schema property "zip.code" cannot contain a dot`,
501-
},
502-
{
503-
name: "inside an allOf branch",
504-
schema: `{"type":"object","allOf":[{"properties":{"zip.code":{"type":"string"}}}]}`,
505-
message: `schema property "zip.code" cannot contain a dot`,
506-
},
507-
}
508-
509-
for _, tt := range tests {
510-
t.Run(tt.name, func(t *testing.T) {
511-
schema, err := domain.NewJSONSchema(projectID, []byte(tt.schema))
512-
require.Error(t, err)
513-
assert.Nil(t, schema)
514-
assert.ErrorIs(t, err, domain.ErrJSONSchemaInvalid())
515-
assert.EqualError(t, err, tt.message)
516-
})
517-
}
518-
})
519-
520-
t.Run("an ordinary nested schema is accepted", func(t *testing.T) {
521-
const content = `{"type":"object","properties":{"address":{"type":"object","properties":{"street":{"type":"string"}}}}}`
522-
schema, err := domain.NewJSONSchema(projectID, []byte(content))
523-
require.NoError(t, err)
524-
require.NotNil(t, schema)
525-
})
526-
})
527-
528477
t.Run("a reserved key with a null value is not rejected", func(t *testing.T) {
529478
// Characterisation test, not an endorsement: maputil.Get[any] reports a
530479
// JSON null as absent, because a type assertion on a nil interface

0 commit comments

Comments
 (0)