📖 4 min read (~ 800 words).

Descriptions beside a $ref

When a struct field’s Go type resolves to a named model, the field becomes a $ref. Strict JSON Schema draft 4 (the dialect OpenAPI 2.0 is built on) says a $ref replaces its siblings — so a description, a validation, or an x-* extension written on that field cannot simply sit next to the $ref.

codescan’s default is to preserve those decorations by wrapping the reference in an allOf compound, which is the draft-4-correct shape. Three options tune this behaviour. The decorations split into two classes:

  • description & extensionssiblings-eligible: modern tooling (OpenAPI 3.1 / JSON Schema 2020-12, most Swagger-UI renderers) reads them directly beside a $ref.
  • validations & externalDocscompound-only: they have no valid bare-$ref form, so they can only ride an allOf compound.
// Address is a referenced model.
//
// swagger:model
type Address struct {
	// Street is the street line.
	Street string `json:"street"`
}

// Person references Address through a field decorated with a description and a
// vendor extension — both can, in principle, sit beside the $ref. How they are
// rendered depends on the options.
//
// swagger:model
type Person struct {
	// Home is where the person lives.
	//
	// extensions:
	//   x-ui-order: 3
	Home Address `json:"home"`
}

Full source: docs/examples/shaping/refsiblings/refsiblings.go

The default — an allOf wrapper

With no options set, the field’s description and extension are preserved by wrapping the $ref as the single member of an allOf; the decorations ride the outer schema:

{
  "description": "Home is where the person lives.",
  "allOf": [
    {
      "$ref": "#/definitions/Address"
    }
  ],
  "x-go-name": "Home",
  "x-ui-order": 3
}

Full source: docs/examples/shaping/refsiblings/testdata/default.json

This is the always-correct shape and needs no configuration — see also Decorating a $ref in the Model definitions tutorial.

Emit siblings directly — EmitRefSiblings

Set Options.EmitRefSiblings to render the description and extensions as direct siblings of the $ref, with no allOf wrapper — the leaner shape modern tools expect:

codescan.Run(&codescan.Options{
    Packages:        []string{"./..."},
    ScanModels:      true,
    EmitRefSiblings: true,
})
Default — allOf wrapper
{
  "description": "Home is where the person lives.",
  "allOf": [
    {
      "$ref": "#/definitions/Address"
    }
  ],
  "x-go-name": "Home",
  "x-ui-order": 3
}

Full source: docs/examples/shaping/refsiblings/testdata/default.json

EmitRefSiblings: true
{
  "description": "Home is where the person lives.",
  "x-ui-order": 3,
  "$ref": "#/definitions/Address"
}

Full source: docs/examples/shaping/refsiblings/testdata/siblings.json

Info

EmitRefSiblings only changes the cases where nothing else forces a compound. When the field also carries a validation or externalDocs (which cannot live beside a bare $ref), the allOf wrapper is still emitted and the description / extensions ride its outer schema.

Drop the compound entirely — SkipAllOfCompounding

Some downstream consumers — notably go-swagger’s code generator — expect a field that points at a model to be a bare $ref and do not handle the allOf-compounded shape. Set Options.SkipAllOfCompounding to never emit an allOf compound:

codescan.Run(&codescan.Options{
    Packages:             []string{"./..."},
    ScanModels:           true,
    SkipAllOfCompounding: true,
})

No compound is produced, so validations and externalDocs are dropped, and the description and extension go with them — leaving a bare $ref:

{
  "$ref": "#/definitions/Address"
}

Full source: docs/examples/shaping/refsiblings/testdata/skip.json

Every dropped decoration is reported through Options.OnDiagnostic (code validate.dropped-ref-sibling), so the loss is never silent. Combine it with EmitRefSiblings to keep the description and extensions as siblings while still dropping the compound-only validations:

codescan.Run(&codescan.Options{
    Packages:             []string{"./..."},
    ScanModels:           true,
    EmitRefSiblings:      true, // keep description / x-* as $ref siblings
    SkipAllOfCompounding: true, // drop validations / externalDocs, no allOf
})
Note

required: is never affected by any of these options. It is a property of the parent object (it lands in the parent’s required list), not a sibling of the $ref, so it is always preserved.

DescWithRef (deprecated)

Options.DescWithRef predates EmitRefSiblings and covers only the narrow description-only case: a $ref’d field whose sole decoration is a description. By default that description is dropped; DescWithRef preserves it by wrapping the $ref in a single-arm allOf.

Default — description dropped
{
  "description": "Person references Address through a field whose only decoration is a\ndescription.",
  "type": "object",
  "properties": {
    "home": {
      "$ref": "#/definitions/Address"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"
}

Full source: docs/examples/shaping/descref/testdata/off.json

DescWithRef: true
{
  "description": "Person references Address through a field whose only decoration is a\ndescription.",
  "type": "object",
  "properties": {
    "home": {
      "description": "Home is where the person lives.",
      "allOf": [
        {
          "$ref": "#/definitions/Address"
        }
      ],
      "x-go-name": "Home"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"
}

Full source: docs/examples/shaping/descref/testdata/on.json

Warning

DescWithRef is deprecated — prefer EmitRefSiblings, which preserves both descriptions and extensions (as direct siblings). DescWithRef keeps its original behaviour for compatibility and is a no-op when EmitRefSiblings is set.