Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Swift-JSON requires Swift tools/compiler 6.4+, Swift language mode 6 and
macOS/SDK 27+. Read README.md, CONTRIBUTING.md and the relevant contracts in
Documentation/Architecture before changing behavior.

- Keep five public products: JSON, JSONSchema, JSONStreaming, JSONPatch and JSONHTML.
- Keep six public products: JSON, JSONSchema, JSONStreaming, JSONPatch, JSONHTML and JSONBridges.
JSONHTML depends on JSON only; the maintainer-approved 1.3 addition preserves historical four-product releases.
The E2-approved opt-in JSONBridges depends on JSON only and is never re-exported by core.
JSONRegexRuntime is package-private. Root package dependencies remain empty.
- Preserve exact bounded decimals, decoded Unicode scalar identity, ordered
objects with order-independent equality, one strict UTF-8 grammar, native
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

## Unreleased

- Add E2's shallow JSON parsing, checked object construction, decimal-text and
pre-parsed Pointer entry points; exact-key membership and three-state presence
composition; and direct validation from an immutable compiled schema plan.
- Add the opt-in JSONBridges product with trusted integer construction, checked
integer projection, explicitly named Double round-trip-text admission and
policy-explicit Double/Foundation Decimal projection. Core imports remain
isolated. The generic JSON.number integer overload is deferred because it
changes existing literal overload resolution. Exact-binary source conversion,
Float-family and Decimal source bridges remain deferred.

- Add typed caller diagnostics and coherent JSONDocument byte-span rendering to
JSONHTML, with exact target identity, explicitly missing-parent attachment and
finite cumulative annotation/path/message accounting. Rendering never asserts
Expand Down
13 changes: 11 additions & 2 deletions Documentation/Architecture/API.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
# Public API contract

This document describes the implemented public interface. Native compiler symbol
graphs supply the complete declarations in the five DocC catalogs. Examples
graphs supply the complete declarations in the six DocC catalogs. Examples
in the [README](../../README.md) are compiled and executed by the public consumer.

## Products and ownership

| Product | Main entry points | Owned implementation boundary |
| --- | --- | --- |
| JSON | JSON, JSONNumber, JSONObject, JSONParser, JSONWriter, JSONValueEncoder, JSONValueDecoder, JSONPointer, JSONDocument | Shared strict codec, exact decimal model, bounded traversal and native coding |
| JSONBridges | BinaryInteger.jsonNumber, FixedWidthInteger/Double/Decimal.init(jsonNumber:), Double.jsonNumberFromRoundTripText | Optional one-delegate numeric conversions; explicit fidelity, no new authority |
| JSONSchema | JSONSchemaDocument, JSONSchemaRegistry, JSONSchemaCompiler, CompiledJSONSchema, JSONSchemaValidator | Offline immutable plan, invocation-owned evaluation and private native regex |
| JSONStreaming | JSONEventSequence, JSONValueSequence, NDJSONValueSequence, JSONTopLevelArraySequence, JSONAsyncWriter | Exclusive pull state, shared codec, strict framing and awaited handoffs |
| JSONPatch | JSONPatch, JSONPatchOperation, JSONPatchResult, JSONMergePatch | Transaction staging, exact comparison and iterative editing |
| JSONHTML | JSONHTMLRenderer, JSONHTMLRenderingOptions, JSONHTMLLimits, JSONHTMLRenderReport, JSONHTMLAnnotation, JSONExportDocument, JSONExportOptions, JSONExportReport | Exact HTML, native Apple transfer, immutable prepared JSON/text/Markdown/static HTML/opt-in styled RTF and cumulative budgets |

JSON imports no siblings. JSONSchema depends on JSON and package-private
JSONRegexRuntime. Streaming, Patch and HTML depend on JSON. There are five public
JSONRegexRuntime. Streaming, Patch, HTML and Bridges depend on JSON. There are six public
products and no external root-package dependencies. Runtime code uses no HTTP,
UI, MCP or documentation-tool dependencies.

Expand All @@ -25,6 +26,14 @@ actual stored state and result types. See [concurrency](Concurrency.md).

## Exact values and construction

The additive E2 development surface is documented in the
[ergonomics crosswalk](../Ergonomics/README.md). JSON.parse forwards String, Data
and byte arrays to their respective parser overloads; buildObject, number(parsing:)
and value(at:) wrap one existing authority each. JSONObject.containsKey is an
unmetered exact-key lookup; JSONPresence.map/fold preserve all three cases and
rethrow caller errors. CompiledJSONSchema.validate evaluates its existing offline
plan. No raw-string Pointer composition or generic number factory is added.

JSON has six cases: object, array, string, number, boolean and null. Numbers compare
mathematically, text/keys compare decoded Unicode scalars, and object equality
ignores insertion order. JSONObject iteration and writing preserve insertion
Expand Down
2 changes: 1 addition & 1 deletion Documentation/Architecture/NumbersAndUnicode.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ has the target's fixed precision/exponent bounds; exact policy verifies equality

JSONNumber.fromFiniteDouble preserves Swift's round-trip decimal rendering of the
supplied finite binary value. It cannot recover an earlier source token or the full
infinite binary-to-decimal expansion. NaN and infinities fail. Native Codable integer
terminating decimal expansion of its binary rational. NaN and infinities fail. Native Codable integer
projections are exact; Float/Double projections round to their requested finite type.
Foreign coders determine the fidelity of values they supply.

Expand Down
11 changes: 6 additions & 5 deletions Documentation/DocumentationTooling.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Building and maintaining the DocC catalogs

Swift source comments are the API documentation authority. Native compiler symbol
graphs supply the declarations and links for five Swift DocC catalogs. Each catalog
graphs supply the declarations and links for six Swift DocC catalogs. Each catalog
combines the product reference with curated Topics and a focused usage guide.
See the [Swift DocC documentation](https://www.swift.org/documentation/docc/).

Expand All @@ -19,7 +19,7 @@ python3 Scripts/ci.py --lane docs
The lane records native preflight, extracts fresh public symbol graphs, requires
summaries and named-parameter documentation on every authored public declaration,
checks README Swift fences against their executable consumer source, runs that
consumer, and compiles all five DocC catalogs with warnings as errors. Zero public
consumer, and compiles all six DocC catalogs with warnings as errors. Zero public
symbols, missing comments, stale examples or a failed catalog fail the lane.

| Output | Purpose |
Expand All @@ -29,12 +29,13 @@ symbols, missing comments, stale examples or a failed catalog fail the lane.
| `.build/handoff/docc/JSONStreaming.doccarchive` | Streaming API and completion/backpressure guide |
| `.build/handoff/docc/JSONHTML.doccarchive` | Exact formatted HTML API and rendering guide |
| `.build/handoff/docc/JSONPatch.doccarchive` | Patch API and choosing/applying patches guide |
| `.build/handoff/docc/JSONBridges.doccarchive` | Opt-in native numeric conversion and fidelity guide |
| `.build/handoff/docc/symbols/` | Public compiler symbol graphs grouped by product |
| `.build/handoff/docc/coverage.json` | Counts and documentation-coverage failures |
| `.build/handoff/docc/receipt.json` | Commands, toolchain, counts, exits and diagnostics |
| `.build/handoff/docc/compiled-catalogs.tar.gz` | All four compiled catalogs with their exact native filenames |
| `.build/handoff/docc/compiled-catalogs.tar.gz` | All six compiled catalogs with their exact native filenames |
| `.build/handoff/docc/compiled-catalogs.MANIFEST.sha256` | Independently verifiable hashes for every archived catalog file |
| `.build/handoff/pages/site/` | Static site, landing page and all five references under the project base path |
| `.build/handoff/pages/site/` | Static site, landing page and all six references under the project base path |
| `.build/handoff/pages/receipt.json` | Native transformations, input/output hashes and route/asset verification |

Open a `.doccarchive` in Xcode for the compiled reference. To preview a source
Expand Down Expand Up @@ -63,7 +64,7 @@ The manifest and receipt preserve file-level and archive-level digests.

The docs lane uses native `docc process-archive transform-for-static-hosting` to
prepare each qualified archive under `/Swift-JSON/<product>`. A shared landing
page links all five references and guides. The build verifies product/guide routes,
page links all six references and guides. The build verifies product/guide routes,
local HTML links and assets, rejects private-path/credential markers and symlinks,
and records a file manifest. Only the generated site is packaged for Pages;
source graphs, command logs and private maintainer records are not website content.
Expand Down
62 changes: 62 additions & 0 deletions Documentation/Ergonomics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Additive ergonomics E2

These development APIs are opt-in additions to the existing semantic authorities.
Existing calls remain supported. No tagged release is changed by this feature branch.

| Task | Before | After | Authority and boundary |
| --- | --- | --- | --- |
| Strict parsing | `JSONParser(options: options).parse(input)` | `JSON.parse(input, options: options)` | Same overload/input/options, including owned-copy accounting |
| Build object | `.object(try JSONObject.build(body))` | `try JSON.buildObject(body)` | Same exclusive accumulator and cumulative meter |
| Exact decimal | `.number(try JSONNumber(parsing: text))` | `try JSON.number(parsing: text)` | Same strict admission and original lexeme |
| Navigate Pointer | `try pointer.resolve(in: root)` | `try root.value(at: pointer)` | Resolution only; no hidden Pointer parsing |
| Exact key membership | `object[key] != nil` | `object.containsKey(key)` | Unmetered exact indexed lookup; null is present |
| Presence transformation | Exhaustive switch | `presence.map(transform)` / `fold(absent:null:value:)` | Exactly one case; callbacks are caller work and rethrow |
| Reuse schema plan | `try JSONSchemaValidator(options: options).validate(instance, against: plan)` | `try plan.validate(instance, options: options)` | Same evaluation; invalidity is false, operational failures throw |
| Trusted integer | `JSONNumber(integer: count)` | `count.jsonNumber()` | JSONBridges only; exact, trusted and unmetered |
| Checked integer | `try number.integer(as: UInt64.self)` | `try UInt64(jsonNumber: number)` | Same finite integral/range/sign projection |
| Adopt Double text | `try JSONNumber.fromFiniteDouble(value)` | `try value.jsonNumberFromRoundTripText()` | Same finite shortest round-trip decimal text, not exact binary rational |
| Project binary64 | `try number.doubleValue(policy: policy)` | `try Double(jsonNumber: number, policy: policy)` | Exact default, finite rounding opt-in |
| Project Foundation Decimal | `try number.decimalValue(policy: policy)` | `try Decimal(jsonNumber: number, policy: policy)` | Same fixed target precision/range and policy |

The three full guides have public-only executable counterparts in
[ErgonomicsExamples.swift](../../Examples/Consumer/Sources/Consumer/ErgonomicsExamples.swift).
The native/Linux CI lanes verify their fences, compile old callsites, test bridge
import isolation in debug/release, and execute the consumer. README fences are
separately synchronized and executed by the documentation lane.

- [Entry points, builders, scoped reads and presence](../../Sources/JSON/Documentation.docc/QuickEntryPoints.md)
- [Numeric fidelity and optional imports](../../Sources/JSONBridges/Documentation.docc/NumericFidelity.md)
- [Compiled-plan validation](../../Sources/JSONSchema/Documentation.docc/CompiledPlanConvenience.md)
- [Core differential cases](../../Tests/JSONTests/ErgonomicsTests.swift)
- [Bridge differential cases](../../Tests/JSONBridgesTests/ConversionTests.swift)
- [Schema differential cases](../../Tests/JSONSchemaTests/ErgonomicsTests.swift)

## Decisions and exclusions

The Swift 6.4 compiler accepts a generic JSON.number integer overload but selects
it for existing `.number(42)` calls, replacing the enum-case declaration. E2's
source-resolution gate therefore defers it. Keep explicit JSONNumber construction
or opt into `.number(count.jsonNumber())`.

Only JSONKey membership is added: string literals already work and dynamic String
conversion remains explicit. No String-key overload proliferation, keys/values
copied arrays, nested aliases, umbrella imports, writer synonyms, static Codable
synonyms or raw-string Pointer composition is needed for this wave.

JSONBridges is a separate sixth product depending on JSON only. Core imports no
leaf product and does not re-export bridges. Numeric source conversion for Float,
Float16, Float80, CGFloat and Foundation Decimal remains deferred. The existing
package-only exactBinary Double routine remains private pending separate finite
admission, retained-digit, nonfinite and extreme-subnormal correctness review.

Any/NSNumber/dictionary/Optional bridges, implicit float/dictionary literals,
null-on-error navigation and public mutation setters are excluded. No parser,
regex, schema dialect, stream completion, Patch transaction, source map, profile,
callback ownership or cursor lifetime implementation changes are made. Existing
reports remain the route for cumulative successful accounting; errors/cancellation
are terminal when no successful report accounts for the work. Logical bytes are
not RSS and owned work units do not meter arbitrary caller computation.

No runtime speed, zero-allocation or measured human-usability improvement is
claimed. Compiler symbol graphs and compiled task examples establish surface
shape and validity; platform execution receipts establish only their stated scope.
3 changes: 2 additions & 1 deletion Examples/Consumer/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ let package = Package(
.product(name: "JSONSchema", package: "Swift-JSON"),
.product(name: "JSONStreaming", package: "Swift-JSON"),
.product(name: "JSONPatch", package: "Swift-JSON"),
.product(name: "JSONHTML", package: "Swift-JSON")
.product(name: "JSONHTML", package: "Swift-JSON"),
.product(name: "JSONBridges", package: "Swift-JSON")
])],
swiftLanguageModes: [.v6]
)
5 changes: 5 additions & 0 deletions Examples/Consumer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ use the public APIs documented in README and the product DocC catalogs.

The fifth product, JSONHTML, additionally checks exact highlighted output, checked
Sendable reports, one admission and a cumulative work refusal using public APIs.

The sixth optional product, JSONBridges, is imported explicitly for the E2 numeric
examples. Three synchronized guide fences execute alongside all README examples.
Core-only and unsupported-Float compiler rejections are checked separately by
Scripts/qualify_ergonomics.py in debug and release configurations.
56 changes: 56 additions & 0 deletions Examples/Consumer/Sources/Consumer/ErgonomicsExamples.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: Apache-2.0
// Public-only before/after examples; guide fences are verified by qualify_ergonomics.py.
import Foundation
import JSON
import JSONBridges
import JSONSchema

func runE2CoreExamples() throws {
let source = #"{"n":9007199254740993,"label":null}"#
let before = try JSONParser().parse(source)
let after = try JSON.parse(source)
precondition(before == after)
let built = try JSON.buildObject { try $0.append("n", value: 42) }
let builtBefore = JSON.object(try JSONObject.build { try $0.append("n", value: 42) })
precondition(built == builtBefore)
let decimal = try JSON.number(parsing: "0.1")
let decimalBefore = JSON.number(try JSONNumber(parsing: "0.1"))
precondition(decimal == decimalBefore)
let pointer = try JSONPointer(parsing: "/n")
let selected = try after.value(at: pointer)
let selectedBefore = try pointer.resolve(in: before)
precondition(selected == selectedBefore)
let object = try after.read { try $0.requiredObject() }
precondition(object.containsKey("label") && !object.containsKey("missing"))
let label = try after.presence(of: "label").map { try $0.decode(String.self) }
precondition(label.fold(absent: { "missing" }, null: { "null" }, value: { $0 }) == "null")
let scoped = try after.readWithReport { try $0["n"].requiredInteger(as: UInt64.self) }
precondition(scoped.result == 9_007_199_254_740_993 && scoped.statistics[.workUnits] > 0)
}

func runE2BridgeExamples() throws {
let count: UInt64 = 9_007_199_254_740_993
let before = JSONNumber(integer: count)
let after = count.jsonNumber() // Trusted construction, without an operation meter.
precondition(before == after)
let recovered = try UInt64(jsonNumber: after)
precondition(recovered == count)
let tenth = try JSONNumber(parsing: "0.1")
let rounded = try Double(jsonNumber: tenth, policy: .rounded)
let adoptedText = try rounded.jsonNumberFromRoundTripText()
precondition(adoptedText == tenth)
let decimal = try Decimal(jsonNumber: tenth)
precondition(decimal == Decimal(string: "0.1"))
}

func runE2SchemaExamples() throws {
let document = try JSONSchemaDocument(JSON.parse(#"{"type":"integer","minimum":0}"#))
let plan = try JSONSchemaCompiler().compile(document)
let before = try JSONSchemaValidator().validate(42, against: plan)
let after = try plan.validate(42)
precondition(before.isValid && after.isValid)
let invalid = try plan.validate(-1)
precondition(!invalid.isValid)
let report = try JSONSchemaValidator().validateWithReport(42, against: plan)
precondition(report.result.isValid && report.statistics[.evaluationWorkUnits] > 0)
}
15 changes: 15 additions & 0 deletions Examples/Consumer/Sources/Consumer/READMEExamples.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Compiled README examples; checked by the documentation lane.
import JSONBridges
import JSON
import Foundation
import JSONSchema
Expand All @@ -26,6 +27,20 @@ where Source.Element == JSONByteChunk {
}

func runREADMEExamples() throws {
let quick = try JSON.parse(#"{"n":9007199254740993,"note":null}"#)
let quickCount = try quick.read { try $0["n"].requiredInteger(as: UInt64.self) }
precondition(quickCount == 9_007_199_254_740_993)
let quickDecimal = try JSON.number(parsing: "0.1")
precondition(quickDecimal.numberValue != nil)

let bridgeCount: UInt64 = 9_007_199_254_740_993
let bridgeNumber = bridgeCount.jsonNumber() // Trusted, unmetered integer construction.
let bridgeRecovered = try UInt64(jsonNumber: bridgeNumber)
precondition(bridgeRecovered == bridgeCount)
let bridgeTenth = try JSONNumber(parsing: "0.1")
let bridgeRounded = try Double(jsonNumber: bridgeTenth, policy: .rounded)
precondition(bridgeRounded == 0.1)

let parser = JSONParser()
let value = try parser.parse(#"{"count":9007199254740993,"note":null}"#)
let count = try JSONPointer(parsing: "/count").resolve(in: value)
Expand Down
5 changes: 5 additions & 0 deletions Examples/Consumer/Sources/Consumer/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ precondition(diagnosticReport.result.contains("Missing target; nearest existing
let unmappedDiagnosticHTML = try diagnosticRenderer.renderString(mappedDocument.replacingRoot(value))
precondition(!unmappedDiagnosticHTML.contains("source span:"))
print("JSONHTML_ANNOTATION_PUBLIC_CONSUMER passed typed_targets=2 coherent_spans=2 replaced_spans=0")

try runE2CoreExamples()
try runE2BridgeExamples()
try runE2SchemaExamples()
print("ERGONOMICS_PUBLIC_CONSUMER passed guides=3")
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ let package = Package(
.library(name: "JSONSchema", targets: ["JSONSchema"]),
.library(name: "JSONStreaming", targets: ["JSONStreaming"]),
.library(name: "JSONPatch", targets: ["JSONPatch"]),
.library(name: "JSONHTML", targets: ["JSONHTML"])
.library(name: "JSONHTML", targets: ["JSONHTML"]),
.library(name: "JSONBridges", targets: ["JSONBridges"])
],
targets: [
.target(name: "JSON"),
.target(name: "JSONBridges", dependencies: ["JSON"]),
.testTarget(name: "JSONBridgesTests", dependencies: ["JSONBridges", "JSON"]),
.target(name: "JSONRegexRuntime", dependencies: ["JSON"]),
.target(name: "JSONSchema", dependencies: ["JSON", "JSONRegexRuntime"], resources: [.copy("Resources/Draft2020-12")]),
.target(name: "JSONStreaming", dependencies: ["JSON"]),
Expand Down
Loading