Skip to content

Refuse unsafe documents

Some YAML cannot be round-tripped safely by the underlying parser. yamldoc detects those constructs and reports them. It does not decide what to do about them — that is yours.

f, err := yamldoc.Parse(src)
if err != nil {
    return err
}

if u := f.Unsupported(); len(u) > 0 {
    return fmt.Errorf("cannot safely edit %s: %s", path, u[0])
}

Each report names the reason and the line:

line 12: multi-line flow collection with interior comments (collection spans lines 12-15)

Tell the two reasons apart

There are two, and they call for different responses. Branch on the Reason constant rather than the message text:

for _, u := range f.Unsupported() {
    switch u.Reason {
    case yamldoc.ReasonFlowComment:
        // the document is fine, its layout is not: offer to rewrite it
    case yamldoc.ReasonUndecodable:
        // the document is broken: tell whoever wrote it
    }
}

ReasonFlowComment is a formatting problem with a mechanical fix, described below. ReasonUndecodable means the file parses but does not mean anything — in practice an *alias pointing at an anchor that does not exist:

line 1: document parses but does not decode (could not find alias "nope")

No rewrite makes that document safe. The full catalogue, including what is deliberately not flagged, is in Unsupported constructs.

You cannot forget to check

Emitting a file that contains an unsupported construct returns ErrUnsupported rather than bytes:

out, err := f.Bytes()
if errors.Is(err, yamldoc.ErrUnsupported) {
    // nothing was written, and nothing was damaged
}

Checking up front is better — the caller has already made their edits by the time Bytes is reached, and failing then looks arbitrary — but the guard is there either way.

Why a multi-line flow collection with comments is refused

A flow collection spanning multiple lines with comments inside it:

bounds: {
  min: 1,   # lower bound
  max: 10   # upper bound
}

Re-emitting that swallows the closing brace into the comment, producing YAML that does not parse. Rather than emit a broken document, yamldoc refuses.

Safe flow style is not affected. Single-line flow mappings and sequences, comments above or beside a flow node, and flow collections without interior comments all round-trip normally:

voice: &voice {id: abc, stability: 0.6}   # fine
tiers: [core, ingress, 'tier-1']          # fine
# describes the bounds
bounds: {min: 1, max: 10}                 # fine

Block scalars are not flow collections. Braces and # inside a | or > block scalar are literal content, not structure, so a JSON- or template-bearing block scalar round-trips normally and is never flagged:

template: |                               # fine
  {
    "id": 1, # not a YAML comment
  }

Working around it

Rewrite the collection in block style, which has no such problem:

bounds:
  min: 1    # lower bound
  max: 10   # upper bound