microsoft / microsoft/yardl

Embedded schema type definitions are namespace-unqualified

Open
#298 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
35
Forks
14
Avg merge
5d 17h
Merged PRs (30d)
1

Description

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 #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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in tooling/pkg/dsl/validation_type_resolution.go, especially the fully qualified reference serialization around lines 32 and 157, then inspect DefinitionMeta in tooling/pkg/dsl/types.go around lines 223-224. Reproduce the two-namespace Foo model and inspect its embedded schema. Done means every definition is uniquely resolvable and the schema-only reader correctly reads the reproduction model, while accounting for the schema compatibility concern in #297.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.