Skip to content

OpenAPI writer drops field metadata beside a $ref, losing it on round-trip and for named types #93

Description

@wallstromsimon

Summary

lower_object attaches field-owned keywords only when the property is not a bare $ref:

// crates/gnr8-core/src/lower/mod.rs
if prop.schema_ref.is_none() {
    // description, example, apply_field_meta(...)
}

So a property whose type is a named schema drops all of its field-owned metadata —
constraints, description, example, default, format and vendor extensions. The two
write_schema implementations reinforce it: both return after emitting the $ref key alone
(lower/yaml.rs, lower/json.rs).

Why this is a bug rather than a limitation

The import side already reads that metadata beside a $ref. In openapi_source.rs,
type_from_schema resolves the $ref for the type, while description, example and
field_meta_from_schema(property_schema) are read off the same property object independently
(crates/gnr8-core/src/sdk/openapi_source.rs:2029-2037).

gnr8 can therefore read minItems next to a $ref but cannot write it. Importing a
document with that shape as a Source and re-emitting it silently loses the keyword, so
OpenApiOpenApi is lossy for any metadata a named-type property carries.

This is also valid output to produce: gnr8 emits openapi: 3.1.0, and under JSON Schema
2020-12 $ref is an ordinary keyword whose siblings apply as an intersection. minItems: 1
beside $ref: '#/components/schemas/Tags' means exactly "matches Tags and has at least one
item". The sibling-keys-are-ignored rule the code comments cite is the OpenAPI 3.0 rule.

Second symptom: named types lose source-declared constraints

The same guard is why a Go named collection loses cardinality that its unnamed equivalent keeps:

Names []string `json:"names" validate:"min=1"`  // minItems: 1
Names Tags     `json:"names" validate:"min=1"`  // nothing   (type Tags []string)

Extraction is not the blocker here — fixing the extractor alone would be inert, because the
field meta is discarded during lowering. The same applies to named strings and named ints
(type Name string with min=2, type Rank int with min=1).

Scope

  • lower_object's schema_ref.is_none() guard (crates/gnr8-core/src/lower/mod.rs)
  • the bare-$ref early return in write_schema in both crates/gnr8-core/src/lower/yaml.rs
    and crates/gnr8-core/src/lower/json.rs
  • write_schema_seq_item's compact $ref variant rendering in lower/yaml.rs
  • the nullable-$ref oneOf path already receives field meta, so it needs checking for
    double-application

Blast radius

Output changes for every named-type property that carries any field metadata, in all three
source languages — not only the Go collection case. That includes description and default,
so generated SDK doc comments move too. Worth its own release note, framed as the round-trip
fix rather than a Go tag fix.

Suggested starting point

A failing OpenApiOpenApi round-trip test over a property with $ref plus minItems and
description. That pins the behaviour independently of the Go extractor and demonstrates the
loss without needing a Go fixture.


Found while reviewing #92, which fixes Go collection cardinality for unnamed slices, arrays and
maps. That PR deliberately leaves this alone: it is a spec-writer change with a much wider blast
radius than the extraction work there.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions