Completes issue #11. Tagged.Scalar.timestampValue parses a !!timestamp scalar under the YAML 1.1 grammar into the pure-Swift Tagged.Timestamp (calendar components, timezone offset, secondsSinceEpoch via the days-from-civil closed form; epoch test expectations measured against Python's independent implementation). Tagged.Scalar.binaryValue base64 decodes a !!binary scalar into bytes, accepting unpadded and block scalar wrapped content and rejecting corrupt input whole. Yams hands back Foundation Date/Data; this package is mechanically Foundation-free, so the bridges are caller-owned one-liners: Date(timeIntervalSince1970:) and Data(_:). !!set and !!omap stay skipped since Model.Mapping already preserves order and duplicates. Closes #11
133 lines
11 KiB
Markdown
133 lines
11 KiB
Markdown
# Migration and YAML Support Boundaries
|
|
|
|
PureYAML can replace a C-backed YAML parser when the caller needs dependency-free
|
|
Swift parsing, emitting, validation, and typed conversion for the currently
|
|
tested YAML subset.
|
|
|
|
It should not be treated as a drop-in full YAML implementation yet. The current
|
|
contract is the behavior pinned by the Swift Testing corpus.
|
|
|
|
## Attribution Boundary
|
|
|
|
PureYAML is informed by the behavior and public API shape of Yams and its bundled
|
|
`CYaml` / libyaml-derived parser. The public implementation is original Swift
|
|
code in this repository. Do not copy Yams, CYaml, or libyaml implementation code
|
|
into `Sources/`.
|
|
|
|
See [../ATTRIBUTION.md](../ATTRIBUTION.md) for the project attribution.
|
|
|
|
## Migration Map
|
|
|
|
| Existing need | PureYAML path | Notes |
|
|
|---|---|---|
|
|
| Compose YAML into a native YAML tree | `PureYAML.parse(_:)` | Returns `PureYAML.Model.Value`, preserving mapping order and duplicate keys. |
|
|
| Compose all YAML documents in a stream | `PureYAML.parseStream(_:)` | Returns indexed `PureYAML.Stream.Document` values. |
|
|
| Preserve explicit YAML tags for analysis | `PureYAML.parseTagged(_:)` or `PureYAML.parseTaggedStream(_:)` | Returns tag-preserving nodes without Foundation-backed construction. |
|
|
| Serialize a YAML tree | `PureYAML.dump(_:options:)` | Emits deterministic YAML with explicit block, flow, and scalar policies. |
|
|
| Serialize all YAML documents in a stream | `PureYAML.dump(_:options:)` with `[PureYAML.Stream.Document]` | Emits deterministic explicit document starts and preserves array order. |
|
|
| Decode a `Decodable` type from YAML | `PureYAML.decode(_:from:)` | Runs default validation before typed decoding. |
|
|
| Encode an `Encodable` value to YAML | `PureYAML.encodeToYAML(_:options:)` | Encodes through `PureYAML.Model.Value`, then dumps. |
|
|
| Validate document structure | `PureYAML.validate(_:using:strict:)` | Default rule reports duplicate mapping keys with exact paths. |
|
|
| Validate every document in a stream | `PureYAML.validate(_:using:strict:)` with stream documents | Reports `PureYAML.Stream.Issue` values that preserve document indexes. |
|
|
| Validate explicit tag usage | `PureYAML.Tagged.Validator` | Reports unsupported built-in tags and kind-inconsistent built-in tags with exact paths. |
|
|
| Construct caller-owned values from explicit tags | `PureYAML.Tagged.Constructor` | Empty by default; callers register exact tag/kind handlers or an explicit fallback. |
|
|
| Load arbitrary `Any` dictionaries and arrays | Use `Model.Value` or typed `Codable` | There is deliberately no public arbitrary `Any` conversion API. Dictionary-shaped values erase YAML diagnostics. |
|
|
|
|
## Arbitrary Any Decision
|
|
|
|
PureYAML does not expose `loadAny`, `dumpAny`, or dictionary-and-array `Any`
|
|
entry points. That is a deliberate compatibility boundary, not a missing
|
|
wrapper.
|
|
|
|
The research implementation's arbitrary-value constructor is useful to study,
|
|
but its observable shape conflicts with PureYAML's first principles:
|
|
|
|
- mapping conversion becomes dictionary-shaped, so duplicate keys and insertion
|
|
order are not a first-class contract;
|
|
- merge-key behavior can be hidden inside construction rather than exposed as an
|
|
explicit parser contract;
|
|
- scalar construction includes Foundation-specific values such as binary data,
|
|
dates, and null objects;
|
|
- custom constructors can hide whether a document was parsed, normalized,
|
|
flattened, or validated.
|
|
|
|
For unknown schemas, use `PureYAML.Model.Value` and validate before projecting
|
|
into project-specific values. For known schemas, use typed `Codable`. If a
|
|
caller truly needs a JSON-like dictionary after validation, build that projection
|
|
in the application layer where duplicate-key and unsupported-shape policies are
|
|
explicit. If a caller needs custom tag construction, use
|
|
`PureYAML.Tagged.Constructor` so unsupported tags, wrong-kind tags, and
|
|
tag-erasing fallbacks stay explicit.
|
|
|
|
## Supported Today
|
|
|
|
The supported list below is covered by tests. When adding a new migration case,
|
|
add a fixture beside the closest existing suite.
|
|
|
|
| Area | Current behavior | Test coverage |
|
|
|---|---|---|
|
|
| Block mappings and sequences | Parsed into ordered mappings and sequences. | `ParsingTests`, `CollectionCompatibilityFixtures` |
|
|
| Common scalars | Null, booleans, integers, doubles, and strings resolve into model values. | `ParsingTests`, `ScalarCompatibilityFixtures` |
|
|
| Quoted strings | Single and double quoted strings keep string intent. | `ParsingTests`, `ScalarCompatibilityFixtures` |
|
|
| Comments | Comments outside quoted strings are ignored. Hash characters inside quoted strings remain content. | `ParsingTests` |
|
|
| Flow collections | Flow sequences and flow mappings compose into model values. | `ParsingTests`, `CollectionCompatibilityFixtures` |
|
|
| Literal and folded blocks | Block scalar text is preserved according to current parser rules. | `ParsingTests`, `DumperTests` |
|
|
| Anchors and aliases | Scalar and mapping anchors can be reused through aliases. Undefined aliases throw exact parse errors. | `ParsingTests`, `CollectionCompatibilityFixtures` |
|
|
| Merge keys | Plain `<<` and explicit `!!merge` keys expand mapping values and sequence-of-mapping values. Local keys override inherited keys; duplicate local keys remain visible to validation. | `MergeKeyCompatibilityTests`, `ParsingCompatibilityTests`, `DownstreamDocumentTests` |
|
|
| YAML directives and document markers | `%YAML 1.2`, selected `%TAG` expansion, `---`, and `...` are supported. | `ParsingCompatibilityTests`, `StreamParsingTests` |
|
|
| Multi-document streams | `PureYAML.parseStream(_:)` parses implicit and explicit documents, empty explicit documents as `null`, `...` document ends, and trailing comments before the next `---`. | `StreamParsingTests` |
|
|
| Multi-document stream dumping | `PureYAML.dump(_:)` with stream documents emits explicit `---` before every document, preserves array order, and round-trips through `parseStream(_:)`. | `StreamDumpingTests` |
|
|
| Stream validation | Stream validation preserves document indexes while keeping validation paths document-local. | `StreamParsingTests` |
|
|
| Built-in scalar tags | `!!str`, `!!int`, `!!float`, `!!bool`, and `!!null` are applied when valid. | `ScalarCompatibilityFixtures` |
|
|
| Tag-preserving parsing | `parseTagged(_:)` and `parseTaggedStream(_:)` preserve explicit scalar and collection tags for compatibility analysis. | `TaggedCompatibilityTests` |
|
|
| Tagged validation | Unsupported built-in tags and built-in tags applied to the wrong node kind are reported with exact paths. Project-specific tags are allowed by default. | `TaggedCompatibilityTests` |
|
|
| Tagged construction | `PureYAML.Tagged.Constructor` supports scalar, sequence, and mapping handlers, exact missing-constructor errors, exact kind-mismatch errors, recursive path-aware construction, and an explicit model-value fallback that erases tags without collapsing duplicate keys. | `TaggedConstructorTests` |
|
|
| Complex mapping keys | Sequence and mapping keys parse into `Model.Pair.keyNode`, validate with exact paths, and dump as explicit YAML keys. String-keyed APIs remain string-keyed. | `ComplexMappingKeyParsingTests`, `ComplexMappingKeyValidationTests`, `ComplexMappingKeyDumperTests`, `ComplexMappingKeyCodableTests` |
|
|
| Emitting | Deterministic block output by default, with opt-in flow collections and conservative literal blocks. | `DumperTests`, `EmitterCorpusTests` |
|
|
| Typed conversion | Scalar, keyed, unkeyed, nested, sequence, dynamic-key, and super-coder cases are covered. | `CodingTests`, `CodableCompatibilityTests` |
|
|
| Validation | Duplicate mapping keys are reported with exact issue paths. | `ValidationTests`, `ValidationModelTests` |
|
|
| Downstream-shaped documents | API-style and service-configuration documents parse and decode through representative paths. | `DownstreamDocumentTests` |
|
|
|
|
## Current Unsupported Behavior
|
|
|
|
Unsupported behavior is either rejected with an exact error or preserved as an
|
|
explicit fallback value tree. This avoids silent compatibility drift.
|
|
|
|
| YAML pattern | Current PureYAML behavior | Test coverage |
|
|
|---|---|---|
|
|
| Passing a multi-document stream to `PureYAML.parse(_:)` | Throws `unsupportedMultiDocumentStream`; use `PureYAML.parseStream(_:)` when multiple documents are expected. | `UnsupportedYAMLGapsFixtures.parseErrors`, `StreamParsingTests` |
|
|
| Content after explicit document end | Throws `unsupportedMultiDocumentStream`; trailing comments after `...` remain non-content. | `UnsupportedYAMLGapsFixtures` |
|
|
| Unknown directives such as `%FOO` | Throws `unsupportedDirective`. | `UnsupportedYAMLGapsFixtures.parseErrors` |
|
|
| `%TAG` after content or after document start | Throws `unsupportedDirective`. | `UnsupportedYAMLGapsFixtures.parseErrors` |
|
|
| Undefined aliases | Throws `undefinedAlias`. | `CollectionCompatibilityFixtures.parseErrors` |
|
|
| Invalid explicit scalar tag values | Throws `invalidTaggedScalar`. | `ScalarCompatibilityFixtures.invalidExplicitScalarTags` |
|
|
| Invalid merge values | Throws `invalidMergeValue` when `<<` is not followed by a mapping or sequence of mappings. | `MergeKeyCompatibilityTests` |
|
|
| Unsupported built-in tags such as `!!timestamp`, `!!binary`, `!!set`, and `!!omap` | `parse(_:)` keeps parseable value trees without special tag construction. `parseTagged(_:)` preserves the tags, and tagged validation reports them as unsupported built-in tags. | `UnsupportedYAMLGapsFixtures.fallbackValues`, `TaggedCompatibilityTests` |
|
|
| Sexagesimal-style scalars and date-looking plain scalars | Keep string values for currently unsupported spellings such as `1:20`, `2002-04-28`, and `09`. | `ScalarCompatibilityFixtures.unsupportedScalars` |
|
|
| Custom resolvers and environment expansion | Not implemented. Build project-specific conversion on top of `Model.Value`, tagged nodes, typed `Codable`, or `PureYAML.Tagged.Constructor`. | Future compatibility work |
|
|
| Foundation-specific scalar conversions such as `Data`, `Date`, or `URL` strategies | `Tagged.Scalar.timestampValue` parses `!!timestamp` into the pure-Swift `Tagged.Timestamp` (bridge with `Date(timeIntervalSince1970: stamp.secondsSinceEpoch)`) and `Tagged.Scalar.binaryValue` base64-decodes `!!binary` into `[UInt8]` (bridge with `Data(bytes)`). The package stays Foundation-free; `URL` and other conversions remain caller-owned. | `TaggedBuiltinScalarTests` |
|
|
|
|
## Migration Checklist
|
|
|
|
1. Audit the YAML corpus the caller actually reads and writes.
|
|
2. Classify each document against the supported and unsupported tables above.
|
|
3. Add representative fixtures for project-specific documents before switching
|
|
call sites.
|
|
4. Parse through `PureYAML.parse(_:)` and run `PureYAML.validate(_:)` before
|
|
typed decoding when duplicate-key visibility matters.
|
|
5. Prefer typed `Codable` for known schemas and `PureYAML.Model.Value` for
|
|
inspection, migration tooling, or unsupported-shape triage.
|
|
6. Run macOS, Linux, Windows, and WASM gates before publishing the migration.
|
|
|
|
## When Not to Migrate Yet
|
|
|
|
Do not migrate a caller yet if it requires:
|
|
|
|
- binary or timestamp conversion into Foundation types;
|
|
- arbitrary `Any` load and dump APIs;
|
|
- custom resolvers;
|
|
- a full YAML 1.2 compliance claim.
|
|
|
|
In those cases, add a fixture and a child issue before changing production call
|
|
sites.
|