Skip to content

Embedded schema type definitions are namespace-unqualified #298

Description

@naegelejd

Summary

In the schema string embedded in every Yardl file, type references are fully qualified (Namespace.Type) but type definitions carry only their unqualified name ("name":"Foo"). Because Yardl enforces name uniqueness only within a namespace, two different types from different namespaces can share an unqualified name in the same embedded schema. Any consumer that resolves the schema by definition name — e.g. a schema-only/dynamic reader, or a third-party tool — cannot disambiguate them and will misread the data.

Generated SDKs are unaffected (they use compile-time type knowledge and never resolve by name at runtime). This only affects consumers that interpret the embedded schema.

Root cause

  • Uniqueness is checked on the fully-qualified name, so cross-namespace collisions are legal:
    tooling/pkg/dsl/validation_type_resolution.go:32fullName := fmt.Sprintf("%s.%s", namespace, meta.Name)
  • References are serialized qualified:
    tooling/pkg/dsl/validation_type_resolution.go:157simpleType.Name = meta.GetQualifiedName()
  • Definitions serialize only the unqualified name (DefinitionMeta: Name json:"name", Namespace json:"-"tooling/pkg/dsl/types.go:223-224).

Reproduction

Two namespaces each defining a different, non-alias record named Foo:

# imported/_package.yml: namespace: Imported
Foo: !record
  fields: { a: int32 }
# base/_package.yml: namespace: Base, imports: [../imported]
Foo: !record
  fields: { b: string }

P: !protocol
  sequence:
    localFoo: Foo
    importedFoo: Imported.Foo

yardl generate succeeds, and the embedded schema contains two same-named, different-bodied definitions:

// references (qualified):
localFoo    -> "Base.Foo"
importedFoo -> "Imported.Foo"
// definitions (unqualified — ambiguous):
{"name":"Foo","fields":[{"name":"b","type":"string"}]}
{"name":"Foo","fields":[{"name":"a","type":"int32"}]}

A reader that keys definitions by unqualified name resolves Imported.Foo to the wrong body and misreads the stream (in practice: reads the int32 as a string length → EOFError). The generated SDK reads the same bytes correctly.

Note: the existing TestModel schemas already contain unqualified-name collisions (e.g. Image — the real Image.Image generic vs. a TestModel.Image re-export alias). Those happen to be alias/real pairs resolving to the same structure, so they're benign today — but nothing guarantees that; the case above is a genuine, silent mismatch.

Proposed fix

Qualify definition names for imported types in the embedded schema (leave base-model types unqualified), matching the already-qualified references. Because within-namespace names are unique, every possible collision is cross-namespace, so qualifying the imported side makes the key sets provably disjoint (dot-free base names can never equal dotted imported names). Consumers then resolve via exact match, falling back to the unqualified name only for base-namespace references.

Alternative: fully qualify all definition names. Simpler for consumers (pure exact-match; no fallback), at the cost of changing every embedded schema string.

Notes

  • Either option changes the embedded schema string and thus interacts with the same back-compat concern raised in Fix embedded schema to distinguish between flags and enums #297 (readers do exact string-equality on the schema). A structural/canonical schema comparison would address both together.
  • Assumes one namespace per package (true today). If a package could ever span multiple namespaces, "base = unqualified" breaks and full qualification would be required.

Acceptance criteria

  • The embedded schema uniquely identifies every type definition even when unqualified names collide across imported namespaces.
  • A schema-only reader can correctly resolve and read the reproduction model above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions