Documentation
¶
Overview ¶
Package omnist is the root package of the omnist-go module.
Example (AlgebraCompatibleWith) ¶
Example_algebraCompatibleWith backs reference.md's `algebra` section: §6.6's own worked example. A has an optional "nick" field B doesn't; compatible_with(A, B) is false (A may emit nick, B is closed) while compatible_with(B, A) is true (everything B emits, A accepts).
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
a, err := osd.Read(`record User {
"id": string,
"name": string,
"nick" [0,1]: string,
} root User`)
if err != nil {
panic(err)
}
b, err := osd.Read(`record User {
"id": string,
"name": string,
} root User`)
if err != nil {
panic(err)
}
fmt.Println(algebra.CompatibleWith(a, b))
fmt.Println(algebra.CompatibleWith(b, a))
}
Output: false true
Example (AlgebraEquivalent) ¶
Example_algebraEquivalent backs reference.md's `algebra` section: Equivalent is strictly stronger than CompatibleWith in one direction -- two schemas that only differ in record naming and declaration order are equivalent even though they are not structurally equal.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
a, err := osd.Read(`
record Person { "id": string, "name": string }
root Person
`)
if err != nil {
panic(err)
}
b, err := osd.Read(`
record User { "id": string, "name": string }
root User
`)
if err != nil {
panic(err)
}
fmt.Println(algebra.Equivalent(a, b))
}
Output: true
Example (AlgebraExtract) ¶
Example_algebraExtract backs reference.md's `algebra` section: Extract trims a schema down to only the reachable records/fields needed to keep a chosen field set, erroring if the root itself is invalidated.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
s, err := osd.Read(`
record Root { "keep": string, "drop" [0,1]: string }
root Root
`)
if err != nil {
panic(err)
}
out, err := algebra.Extract(s, map[string]bool{"keep": true})
if err != nil {
panic(err)
}
root := out.Env[out.Root]
fmt.Println(len(root.Fields))
}
Output: 1
Example (AlgebraInfer) ¶
Example_algebraInfer backs reference.md's `algebra` section: Infer drafts a Schema from sample Documents -- here, two JSON samples that disagree on whether "tags" is present, producing an optional [0,1] field in the inferred schema.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/formats/json"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
s1, err := json.Read(`{"name": "Ann", "tags": ["a"]}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
s2, err := json.Read(`{"name": "Bo"}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
schema, err := algebra.Infer([]omnist.Document{s1, s2}, "", false)
if err != nil {
panic(err)
}
text, err := osd.Write(schema, true)
if err != nil {
panic(err)
}
fmt.Println(text)
}
Output: record Root { "name": string, "tags" [0,1]: string } root Root
Example (AlgebraLint) ¶
Example_algebraLint backs reference.md's `algebra` section: Lint reports schema diagnostics -- here, an unreachable record no field ever references.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
s, err := osd.Read(`
record Root { "id": string }
record Orphan { "id": string, "note": string }
root Root
`)
if err != nil {
panic(err)
}
findings := algebra.Lint(s)
for _, f := range findings {
fmt.Println(f.Code, f.Location)
}
}
Output: lint.unreachable-record Orphan
Example (AlgebraNormalize) ¶
Example_algebraNormalize backs reference.md's `algebra` section: Normalize collapses two structurally-identical records (same fields, different names) to a canonical shared form.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
s, err := osd.Read(`
record A { "id": string }
record B { "id": string }
record Root { "a": A, "b": B }
root Root
`)
if err != nil {
panic(err)
}
classes := algebra.EquivalenceClasses(algebra.Normalize(s))
fmt.Println(len(classes))
}
Output: 2
Example (AlgebraPrune) ¶
Example_algebraPrune backs reference.md's `algebra` section: Prune removes a field that can never be emitted (optional, referencing a record that is itself unsatisfiable) and, as a consequence, the now-unreachable record it alone referenced.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/algebra"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
s, err := osd.Read(`
record Root { "id": string, "dead" [0,1]: Orphan }
record Orphan { "self": Orphan }
root Root
`)
if err != nil {
panic(err)
}
pruned := algebra.Prune(s)
root := pruned.Env[pruned.Root]
fmt.Println(len(root.Fields))
fmt.Println(len(pruned.Env))
}
Output: 1 1
Example (ConvertFormats) ¶
Example_convertFormats backs the fourth code block in docs/getting-started.md ("Convert between formats"): writers are schema-free and serialize whatever Document they're given.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
"github.com/omnist-dev/omnist-go/formats/yaml"
)
func main() {
doc, _ := json.Read(`{"name": "Ann"}`, omnist.DefaultLimits())
text, diagnostics, err := yaml.Write(doc)
if err != nil {
panic(err)
}
if len(diagnostics) != 0 {
panic("unexpected diagnostics")
}
fmt.Print(text)
}
Output: "name": "Ann"
Example (DocumentsEqual) ¶
Example_documentsEqual backs reference.md's Operations section: DocumentsEqual is order-sensitive -- two documents with the same edges in different order are not equal.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/oml"
)
func main() {
a, err := oml.Read(`x: "1"
y: "2"
`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
b, err := oml.Read(`y: "2"
x: "1"
`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
fmt.Println(omnist.DocumentsEqual(a, a))
fmt.Println(omnist.DocumentsEqual(a, b))
}
Output: true false
Example (JsonRoundTrip) ¶
Example_jsonRoundTrip backs reference.md's `formats/json` section.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
)
func main() {
doc, err := json.Read(`{"name": "Ann"}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
text, diagnostics, err := json.Write(doc)
if err != nil {
panic(err)
}
if len(diagnostics) != 0 {
panic("unexpected diagnostics")
}
fmt.Print(text)
}
Output: {"name": "Ann"}
Example (LimitsValidate) ¶
Example_limitsValidate backs reference.md's Limits section: Validate checks that configured limits are strictly positive and do not exceed sane recommended safety bounds.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
)
func main() {
// DefaultLimits() satisfies all recommended bounds:
fmt.Println(omnist.DefaultLimits().Validate())
// Setting astronomically large limits triggers an error:
huge := omnist.Limits{
MaxDepth: 200,
MaxNodes: 200_000_000, // exceeds MaxRecommendedNodes (100,000,000)
MaxIntDigits: 4300,
}
fmt.Println(huge.Validate())
}
Output: <nil> MaxNodes 200000000 exceeds recommended safety ceiling (100000000)
Example (Materialize) ¶
Example_materialize backs reference.md's Operations section: Materialize upgrades a leaf scalar to its schema-declared kind only when the conversion is value-exact -- a JSON string that looks like a date becomes a real `date` scalar.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
schema, err := osd.Read(`
record Event { "when": date }
root Event
`)
if err != nil {
panic(err)
}
doc, err := json.Read(`{"when": "2024-01-01"}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
result, diagnostics, err := omnist.Materialize(doc, schema)
if err != nil {
panic(err)
}
if len(diagnostics) != 0 {
panic("unexpected diagnostics")
}
edge := result.Node.Edges[0]
v, _ := edge.Target.Value()
d := v.Scalar.Date
fmt.Printf("%s %04d-%02d-%02d\n", v.Scalar.Kind, d.Year, d.Month, d.Day)
}
Output: date 2024-01-01
Example (NewIntegerScalar) ¶
Example_newIntegerScalar backs reference.md's Document model section: integer scalars use *big.Int, not int64, to support the spec's 4,300-decimal-digit limit -- constructing one from a small literal still goes through big.NewInt.
package main
import (
"fmt"
"math/big"
omnist "github.com/omnist-dev/omnist-go"
)
func main() {
s := omnist.NewIntegerScalar(big.NewInt(42))
fmt.Println(s.Kind, s.Int)
}
Output: integer 42
Example (OmlRoundTrip) ¶
Example_omlRoundTrip backs reference.md's `oml` section: reading OML text to a Document, then writing it back out compact.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/oml"
)
func main() {
doc, err := oml.Read(`name: "Ann"
tags: "a"
tags: "b"
`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
text, diagnostics := oml.WriteCompact(doc)
if len(diagnostics) != 0 {
panic("unexpected diagnostics")
}
fmt.Println(text)
}
Output: name: "Ann"; tags: "a"; tags: "b"
Example (OsdRoundTrip) ¶
Example_osdRoundTrip backs reference.md's `osd` section: parsing an OSD schema definition, then writing it back out.
package main
import (
"fmt"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
schema, err := osd.Read(`
record Person { "name": string, "tags" [0,]: string }
root Person
`)
if err != nil {
panic(err)
}
text, err := osd.Write(schema, true)
if err != nil {
panic(err)
}
fmt.Println(text)
}
Output: record Person { "name": string, "tags" [0,]: string } root Person
Example (ParseError) ¶
Example_parseError backs reference.md's Diagnostics and errors section: a stage-1 reader's failure is a *ParseError with a real Line/Col text position, not yet a Document-relative Path (no Document exists yet).
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
)
func main() {
_, err := json.Read(`{"name": }`, omnist.DefaultLimits())
perr, ok := err.(*omnist.ParseError)
if !ok {
panic("expected a *ParseError")
}
fmt.Println(perr.Line, perr.Col, perr.Code)
}
Output: 1 10 parse.codec-syntax
Example (ReadDocument) ¶
Example_readDocument backs the first code block in docs/getting-started.md ("Read a document, no schema"): reading JSON produces a flat edge list where a JSON array becomes repeated edges sharing one label, not one edge holding a list.
package main
import (
"fmt"
"strconv"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
)
// formatDocument renders a flat (non-nested) Node document as a
// comma-separated list of (label,value) pairs, in edge order, matching the
// style used in docs/getting-started.md's prose. Only the scalar kinds
// exercised by these examples are handled.
func formatDocument(d omnist.Document) string {
if !d.IsNode {
return formatValue(d.Value)
}
parts := make([]string, 0, len(d.Node.Edges))
for _, e := range d.Node.Edges {
v, ok := e.Target.Value()
if !ok {
parts = append(parts, fmt.Sprintf("(%s,<node>)", e.Label))
continue
}
parts = append(parts, fmt.Sprintf("(%s,%s)", e.Label, formatValue(v)))
}
out := "["
for i, p := range parts {
if i > 0 {
out += ", "
}
out += p
}
return out + "]"
}
func formatValue(v omnist.Value) string {
if v.IsNull {
return "null"
}
switch v.Scalar.Kind {
case omnist.KindString:
return strconv.Quote(v.Scalar.Str)
case omnist.KindBoolean:
return strconv.FormatBool(v.Scalar.Bool)
default:
return "<scalar>"
}
}
func main() {
doc, err := json.Read(`{"name": "Ann", "tags": ["a", "b"]}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
fmt.Println(formatDocument(doc))
}
Output: [(name,"Ann"), (tags,"a"), (tags,"b")]
Example (SchemasEqual) ¶
Example_schemasEqual backs reference.md's Operations section: SchemasEqual's two modes differ on record naming -- ModeExact requires matching record names, ModeIsomorphic accepts structurally identical schemas that merely name their records differently.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
a, err := osd.Read(`record Person { "id": string } root Person`)
if err != nil {
panic(err)
}
b, err := osd.Read(`record User { "id": string } root User`)
if err != nil {
panic(err)
}
fmt.Println(omnist.SchemasEqual(a, b, omnist.ModeExact))
fmt.Println(omnist.SchemasEqual(a, b, omnist.ModeIsomorphic))
}
Output: false true
Example (TomlWrite) ¶
Example_tomlWrite backs reference.md's `formats/toml` section, covering a codec beyond JSON/YAML: writers are schema-free and serialize whatever Document they're given.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/toml"
"github.com/omnist-dev/omnist-go/oml"
)
func main() {
doc, err := oml.Read(`name: "Ann"`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
text, diagnostics, err := toml.Write(doc)
if err != nil {
panic(err)
}
if len(diagnostics) != 0 {
panic("unexpected diagnostics")
}
fmt.Print(text)
}
Output: "name" = "Ann"
Example (Validate) ¶
Example_validate backs reference.md's Operations section: Validate checks shape and cardinality without ever converting a value's type -- here a schema-typed "age" field rejects a JSON string, producing a populated Diagnostic with a real Path/Code/Message.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
schema, err := osd.Read(`
record Person { "name": string, "age": integer }
root Person
`)
if err != nil {
panic(err)
}
doc, err := json.Read(`{"name": "Ann", "age": "42"}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
diagnostics := omnist.Validate(doc, schema)
for _, d := range diagnostics {
fmt.Println(d.Path, d.Code, d.Severity)
}
}
Output: $.age validate.type-mismatch error
Example (ValidateDocument) ¶
Example_validateDocument backs the second code block in docs/getting-started.md ("Validate against a schema").
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/json"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
schema, err := osd.Read(`
record Person { "name": string, "tags" [0,]: string }
root Person
`)
if err != nil {
panic(err)
}
doc, err := json.Read(`{"name": "Ann", "tags": ["a", "b"]}`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
diagnostics := omnist.Validate(doc, schema)
if len(diagnostics) == 0 {
fmt.Println("valid")
}
}
Output: valid
Example (XmlLeafTyping) ¶
Example_xmlLeafTyping backs reference.md's `formats/xml` section: XML carries no type information at all, so every leaf arrives as a string scalar -- unlike JSON/YAML/TOML, `42` inside an element is never resolved to an integer.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/xml"
)
func main() {
doc, _, err := xml.Read(`<root><age>42</age></root>`, omnist.DefaultLimits())
if err != nil {
panic(err)
}
ageNode, _ := doc.Node.Edges[0].Target.Node()
v, _ := ageNode.Edges[0].Target.Value()
fmt.Println(v.Scalar.Kind, v.Scalar.Str)
}
Output: string 42
Example (XmlReadWithSchema) ¶
Example_xmlReadWithSchema backs reference.md's `formats/xml` section: XML carries no native type information, so plain `Read` leaves every leaf as a string. `ReadWithSchema` uses an OSD schema to pre-type numeric, boolean, and temporal leaves during ingestion per omnist-spec#44.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/xml"
"github.com/omnist-dev/omnist-go/osd"
)
func main() {
schema, err := osd.Read(`
record User { "name": string, "age": integer, "active": boolean }
root User
`)
if err != nil {
panic(err)
}
src := `<User><name>Ann</name><age>42</age><active>true</active></User>`
doc, _, err := xml.ReadWithSchema(src, &schema, omnist.DefaultLimits())
if err != nil {
panic(err)
}
rootNode, _ := doc.Node.Edges[0].Target.Node()
for _, edge := range rootNode.Edges {
v, _ := edge.Target.Value()
fmt.Printf("%s: %s\n", edge.Label, v.Scalar.Kind)
}
}
Output: name: string age: integer active: boolean
Example (YamlSexagesimal) ¶
Example_yamlSexagesimal backs reference.md's `formats/yaml` section: YAML 1.1's sexagesimal-integer sharp edge -- a bare `1:30:00` resolves to the base-60 integer 5400, NOT a time value, even though it looks like one.
package main
import (
"fmt"
omnist "github.com/omnist-dev/omnist-go"
"github.com/omnist-dev/omnist-go/formats/yaml"
)
func main() {
doc, err := yaml.Read("n: 1:30:00\n", omnist.DefaultLimits())
if err != nil {
panic(err)
}
v, _ := doc.Node.Edges[0].Target.Value()
fmt.Println(v.Scalar.Kind, v.Scalar.Int)
}
Output: integer 5400
Index ¶
- Constants
- Variables
- func DocumentsEqual(a, b Document) bool
- func FormatISODate(d DateValue) string
- func FormatISOFraction(ns int) string
- func FormatISOTime(t TimeValue) string
- func FracToNanos(digits string) int
- func MatchesISOKind(s string, kind TemporalKind) bool
- func Materialize(doc Document, s Schema) (Document, []Diagnostic, error)
- func PathIndexInNode(node *Node, edgeIndex int) (occurrence int, repeated bool)
- func SchemasEqual(a, b Schema, mode SchemaEqualityMode) bool
- func ValidDate(d DateValue) bool
- func ValidOffsetText(s string) bool
- func ValidTime(t TimeValue) bool
- type Cardinality
- type Code
- type DateTimeValue
- type DateValue
- type Diagnostic
- type Document
- type Edge
- type Field
- type FieldIndex
- type LimitChecker
- type Limits
- type Node
- type ParseError
- type Path
- type Record
- type Resolved
- type ResolvedKind
- type Scalar
- type ScalarKind
- type Schema
- type SchemaEqualityMode
- type Severity
- type Target
- type TemporalKind
- type TimeValue
- type Type
- type TypeKind
- type Value
Examples ¶
- Package (AlgebraCompatibleWith)
- Package (AlgebraEquivalent)
- Package (AlgebraExtract)
- Package (AlgebraInfer)
- Package (AlgebraLint)
- Package (AlgebraNormalize)
- Package (AlgebraPrune)
- Package (ConvertFormats)
- Package (DocumentsEqual)
- Package (JsonRoundTrip)
- Package (LimitsValidate)
- Package (Materialize)
- Package (NewIntegerScalar)
- Package (OmlRoundTrip)
- Package (OsdRoundTrip)
- Package (ParseError)
- Package (ReadDocument)
- Package (SchemasEqual)
- Package (TomlWrite)
- Package (Validate)
- Package (ValidateDocument)
- Package (XmlLeafTyping)
- Package (XmlReadWithSchema)
- Package (YamlSexagesimal)
Constants ¶
const ( // MaxRecommendedDepth is the upper bound above which recursive algorithms risk stack exhaustion. MaxRecommendedDepth = 10_000 // MaxRecommendedNodes is the upper bound above which node materialization risks heap exhaustion. MaxRecommendedNodes = 100_000_000 // MaxRecommendedIntDigits is the upper bound above which arbitrary-precision integer parsing risks CPU exhaustion. MaxRecommendedIntDigits = 1_000_000 )
Recommended limit ceilings for Limits.Validate: While spec §2.4 mandates that every implementation must enforce finite positive limits for MaxDepth, MaxNodes, and MaxIntDigits, setting astronomically large limits (e.g. math.MaxInt) practically defeats the safety purpose of resource bounding.
const SpecVersion = "v0.21.0-beta"
SpecVersion is the omnist-spec version this module targets, pinned via the vendor/omnist-spec git submodule. See docs/limitations.md.
Variables ¶
var ( ISODateTimeRegexp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?([+-]\d{2}:\d{2})?`) ISODateRegexp = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}`) ISOTimeRegexp = regexp.MustCompile(`^\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?([+-]\d{2}:\d{2})?`) )
ISODateTimeRegexp, ISODateRegexp, and ISOTimeRegexp are the exact, start-anchored regexes the OML lexer uses (spec §4.2's DATETIME/DATE/ TIME productions) to recognize the three temporal token kinds. They are exported because multiple packages beyond the OML lexer itself need the identical spelling: a codec reader deciding whether a source string is "exactly" a date/time/datetime (see MatchesISOKind), or any future package that needs to recognize this exact literal shape without duplicating — and risking drifting from — the OML grammar's own regex.
Each regex is anchored only at the start ('^'), not the end: that is correct for the OML lexer's own use (FindString on a remaining-input tail, consuming a prefix while scanning forward through a longer document) but means a caller that needs a *whole-string* match — is this entire string exactly a DATE, not just prefixed by one — must check that explicitly, e.g. via MatchesISOKind, rather than trusting a non-empty FindString result on its own.
Functions ¶
func DocumentsEqual ¶
DocumentsEqual reports whether a and b are the same Document: the same shape (node vs. bare value), the same edges in the same order (edge order is data, per spec §2.3 D-1/D-3 — this is why DocumentsEqual is order-sensitive, unlike SchemasEqual's field-set comparison), and the same scalar values including kind. Two NaN-valued KindNumber scalars compare equal here (plain Scalar.Equal, and Go's == underneath it, does not: NaN != NaN), matching the reserved "nan" OML spelling's intended round-trip semantics.
func FormatISODate ¶
FormatISODate renders a DateValue per ISO-8601's calendar-date form (YYYY-MM-DD). Promoted here (issue #45) from json_writer.go, which originally defined it privately as formatISODate; yaml_writer.go, toml_writer.go, and xml_writer.go all called that same private function directly (a real, load-bearing cross-codec dependency this project's package-restructuring plan had described as not existing — discovered while moving JSON into its own formats/json package, since that move is what first made the dependency across package boundaries visible). oml_writer.go's formatOMLDate is a separate, OML-specific copy and is deliberately not merged into this one: OML's writer lives in its own already-moved package and duplicating one four-line function is preferable to adding a needless production dependency from oml on the root package's temporal.go beyond what it already uses.
func FormatISOFraction ¶
FormatISOFraction converts a Nanosecond field to the shortest 1-6 digit fraction string that reproduces it. Nanosecond is always a product of FracToNanos above, which right-pads to 9 digits, so it is always an exact multiple of 1000, and trimming trailing zeros from its 6-digit microsecond form can never trim down to nothing given the caller's guard that Nanosecond != 0. Promoted here (issue #45) alongside FormatISODate/FormatISOTime, for the same cross-codec reason.
func FormatISOTime ¶
FormatISOTime renders a TimeValue per ISO-8601's time-of-day form. Seconds are emitted only when Second or Nanosecond is nonzero, and the fractional part only when Nanosecond is nonzero. Promoted here (issue #45) alongside FormatISODate, for the same cross-codec reason.
func FracToNanos ¶
func MatchesISOKind ¶
func MatchesISOKind(s string, kind TemporalKind) bool
MatchesISOKind reports whether s is *exactly* (start to end) the spelling ISODateRegexp/ISOTimeRegexp/ISODateTimeRegexp accepts for kind — the "exact spelling matches_kind() accepts for that specific kind, not merely parseable by a looser library function" rule from spec §7.2's try_upgrade notes. The underlying regexes are only start-anchored (correct for a lexer scanning forward through a longer document via FindString on a remaining-input tail, which pins the *start* of a match but not the end), so this wraps FindString with an explicit "the match is the whole string" comparison rather than introducing new regexes with different anchoring — that would be exactly the kind of drift spec §7.2 warns against: reuse the same format-matching logic rather than a new, possibly looser or stricter parser.
This is deliberately the OML lexer's spelling (§4's grammar), not, for example, a TOML/YAML reader's own wider temporal parsing (TOML and YAML both allow separators/offsets OML's grammar does not). Callers reading from an arbitrary Document (JSON, YAML, or direct construction) should use this canonical spelling per §7.2's rule of a single canonical spelling per kind — the OML lexer's spelling is that spelling, since OML is the format whose native literal syntax the temporal scalar kinds were designed around (§2.2.1/§4).
func Materialize ¶
func Materialize(doc Document, s Schema) (Document, []Diagnostic, error)
Materialize walks doc against s, upgrading leaves to their declared scalar kinds and checking record shape, collecting every problem found (not failing fast, matching Validate). On success (no diagnostics) the returned Document is the materialized result. On failure the returned Document is a best-effort partial materialization — not part of this function's contract, since a caller with a non-empty diagnostics slice has no ok Document per §7.1 ("fails-with-diagnostics on any problem") and must not use it. err is always nil today; it is part of the signature for symmetry with the two-stage read model's stage-1 codec readers (json_reader.go et al.), which do report structural errors distinct from diagnostics, and to leave room for a future internal failure mode without a breaking signature change.
func PathIndexInNode ¶
PathIndexInNode reports the zero-based occurrence index of the edge at edgeIndex within node.Edges, counting only edges sharing that edge's label, and whether that label occurs more than once in node overall. It is the helper a reader walking a Node uses to build the (index, repeated) arguments Path.Child needs, so the "index present iff label repeats" rule (spec §8.4) is computed in one place rather than re-derived at every call site.
Panics if edgeIndex is out of range, since that indicates a caller bug rather than a reportable condition.
func SchemasEqual ¶
func SchemasEqual(a, b Schema, mode SchemaEqualityMode) bool
SchemasEqual reports whether a and b are the same Schema under mode.
ModeExact: the root name must match, and every record in a's env must have a same-named counterpart in b's env (and vice versa, via the length check) whose fields are equal as a *set* keyed by label — field declaration order does not participate (spec §3.1; referee self-test case 01). A field is equal when its Type and Cardinality are equal; Type equality for TypeRefKind compares RefName literally in this mode (no renaming is permitted), which is what makes case 04 (same shape, different record names) correctly compare not-equal even though case 03's identical shape compares equal under ModeIsomorphic.
ModeIsomorphic: same structure up to a consistent renaming of records, checked by walking both schemas' reference graphs from their respective roots in lock-step and building a bijection between record names as new ones are encountered (see schemaIsomorphic's doc comment for the algorithm and its scope).
func ValidDate ¶
ValidDate reports whether d is a valid proleptic Gregorian calendar date: month 01-12, and day valid for that month/year.
func ValidOffsetText ¶
ValidOffsetText reports whether s -- text already matched by ISOTimeRegexp or ISODateTimeRegexp -- has no tz-offset suffix, or has one whose hour/minute are each within TIME's own range (00-23, 00-59) -- deliberately the SAME range check ValidTime/validClockRange applies to TIME itself, per spec section 4.2.4.
This checks the offset's raw source digits directly, not TimeValue.OffsetSeconds: an out-of-range minute like "+00:60" folds silently into a normalized total of 3600 seconds (60 minutes) once converted to OffsetSeconds -- indistinguishable, after the fact, from a correctly-written "+01:00". That was a real, previously-undetected bug (confirmed live against the Python reference): "+00:60" was silently accepted and normalized, even though a bare "00:60:00" TIME literal was correctly rejected. Checking the raw digits before they are ever folded into a total is the only way to catch it.
Types ¶
type Cardinality ¶
Cardinality is a closed integer range [Min, Max] bounding the count of edges carrying a field's label in a node (spec §3.3, §3.4). Max is meaningful only when Unbounded is false.
Per the issue's design-continuity note: cardinality bounds are plain non-negative integers or "unbounded", not arbitrary-precision — unlike §2.4's integer *literal digit* limit, nothing in the spec calls for arbitrary precision here, so a uint64 with an explicit Unbounded sentinel is used instead of *big.Int.
func DefaultCardinality ¶
func DefaultCardinality() Cardinality
DefaultCardinality returns the OSD default cardinality [1,1] used when a field declares none (spec §5.5).
type Code ¶
type Code string
Code is a diagnostic code from the spec §8.3 taxonomy: a lowercase, dot-separated path whose first segment is the family. Codes are stable identifiers; once published, a code's meaning MUST NOT change.
const ( CodeParseUnexpectedToken Code = "parse.unexpected-token" CodeParseTrailingContent Code = "parse.trailing-content" CodeParseUnterminatedString Code = "parse.unterminated-string" CodeParseInvalidEscape Code = "parse.invalid-escape" CodeParseUnpairedSurrogate Code = "parse.unpaired-surrogate" CodeParseControlCharacter Code = "parse.control-character" CodeParseReservedWordLabel Code = "parse.reserved-word-label" CodeParseBareWord Code = "parse.bare-word" CodeParseEmptyArray Code = "parse.empty-array" CodeParseNestedArray Code = "parse.nested-array" CodeParseSeparatorInArray Code = "parse.separator-in-array" // CodeParseLeadingZero is raised when a NUMBER/INTEGER literal's // integer part has a leading zero (e.g. "01", "00.5"). Per spec // section 4.2.3 (added 2026-08-29): int-part = "0" / (a nonzero // digit followed by any digits) -- a bare "0" alone, or "-0", is // never a leading zero and remains valid. CodeParseLeadingZero Code = "parse.leading-zero" // CodeParseInvalidDate is raised when a DATE token (or DATETIME's // date portion) is a valid ISO-8601 shape but not a valid calendar // date -- month out of 01-12, or day invalid for month/year // (including leap years). Per spec section 4.2.4 (added // 2026-08-29). CodeParseInvalidDate Code = "parse.invalid-date" // CodeParseInvalidTime is raised when a TIME token (or DATETIME's // time portion, or a tz-offset) is a valid ISO-8601 shape but not a // valid clock value -- hour out of 00-23, minute or second out of // 00-59 (no leap-second spelling), or -- for a tz-offset -- the // same hour/minute ranges TIME itself uses. Per spec section 4.2.4 // (added 2026-08-29): tz-offset shares TIME's exact range check, // not a separately implemented one. CodeParseInvalidTime Code = "parse.invalid-time" // CodeParseCodecSyntax is raised when a codec (JSON, YAML, TOML, XML) // cannot accept its input: the text is not well-formed in its own source // format, or it fails the one precondition spec §8.3.1's E-24 imposes ahead of // the codec (D-21's rejection of a second leading U+FEFF, run on text that // has already decoded cleanly; invalid UTF-8 is CodeParseInvalidEncoding). One code covers all four codecs deliberately. CodeParseCodecSyntax Code = "parse.codec-syntax" // CodeParseInvalidEncoding is raised when the input is not valid UTF-8 // (spec §2.5 D-14). Its path is always "1:1", on every surface, whatever // byte failed: a fixed value, not a computed position. It is checked before // D-15's BOM strip and D-21's second-BOM check, so invalid UTF-8 is never // parse.codec-syntax, whatever the codec's parsing library would have said. CodeParseInvalidEncoding Code = "parse.invalid-encoding" )
parse.* — text to Document, stage 1 (spec §8.3.1).
const ( CodeDocumentLimitDepth Code = "document.limit.depth" CodeDocumentLimitNodes Code = "document.limit.nodes" CodeDocumentLimitIntDigits Code = "document.limit.int-digits" CodeDocumentUnlabeledElement Code = "document.unlabeled-element" )
document.* — building and limits (spec §8.3.2).
const ( CodeSchemaNoRoot Code = "schema.no-root" CodeSchemaUnknownType Code = "schema.unknown-type" CodeSchemaDuplicateRecord Code = "schema.duplicate-record" CodeSchemaDuplicateField Code = "schema.duplicate-field" CodeSchemaReservedName Code = "schema.reserved-name" CodeSchemaInvalidCardinality Code = "schema.invalid-cardinality" CodeSchemaNonIntegerCardinality Code = "schema.non-integer-cardinality" CodeSchemaEmptyCardinality Code = "schema.empty-cardinality" CodeSchemaUnquotedLabel Code = "schema.unquoted-label" CodeSchemaNullableRef Code = "schema.nullable-ref" CodeSchemaNullableAny Code = "schema.nullable-any" // CodeSchemaQuotedType is the reverse of CodeSchemaUnquotedLabel, per // spec §5.2's quoting rule (added upstream via omnist-spec#35): a // quoted string in type position is a data string and can never // legally appear there, since type position only ever accepts a bare // schema name (a scalar keyword, `any`, or a reference). CodeSchemaQuotedType Code = "schema.quoted-type" // CodeSchemaDuplicateRoot is raised when a schema contains more than // one `root` declaration. Per spec §5.8 (updated 2026-08-23, closing // chapter 9 divergence-ledger D-2), this is normatively an error, not // an implementation-defined choice. CodeSchemaDuplicateRoot Code = "schema.duplicate-root" // CodeSchemaEmptyLabel is raised when a field label is the empty // string. Per spec section 5.4 (added 2026-08-29): a label is an // identifier, not a value -- an empty label names nothing a caller // could ever reference. Path is the enclosing record, the same // convention CodeSchemaUnquotedLabel uses when the label itself is // the problem. CodeSchemaEmptyLabel Code = "schema.empty-label" // CodeSchemaBracketInLabel is raised when a field label contains a // literal '[' or ']' character. Per spec section 5.4 (added // 2026-08-29): section 3.6.1's validate() pseudocode appends "[i]" // to a repeated label's second and later occurrences when building a // diagnostic path, so a label containing a literal bracket can // collide with that convention (e.g. a repeatable field "a" and a // separately declared field literally named "a[1]" can both path as // $.a[1]). Rejecting the character vocabulary in labels is the // narrowest fix. Path is the enclosing record, same convention as // CodeSchemaEmptyLabel/CodeSchemaUnquotedLabel. CodeSchemaBracketInLabel Code = "schema.bracket-in-label" )
schema.* — schema well-formedness (spec §8.3.3).
const ( CodeValidateShapeMismatch Code = "validate.shape-mismatch" CodeValidateTypeMismatch Code = "validate.type-mismatch" CodeValidateNullNotAllowed Code = "validate.null-not-allowed" CodeValidateUnexpectedField Code = "validate.unexpected-field" CodeValidateCardinality Code = "validate.cardinality" )
validate.* — document against schema (spec §8.3.4).
const ( CodeAlgebraExtractInvalidatesRoot Code = "algebra.extract-invalidates-root" CodeAlgebraInferNoSamples Code = "algebra.infer-no-samples" CodeAlgebraInferScalarRoot Code = "algebra.infer-scalar-root" CodeAlgebraInferConflictingScalars Code = "algebra.infer-conflicting-scalars" // CodeAlgebraInferMixedShape is raised when a label is a node in some // samples and a scalar in others (allow_any=false). The spec's §8.3.6 // taxonomy table does not list a dedicated code for this failure -- // only infer-no-samples, infer-scalar-root, and infer-conflicting- // scalars are enumerated there, even though §6.10's infer_type // pseudocode has two distinct hard-failure branches (mixed shape, and // conflicting scalar kinds). This is the plainly-correct reading of // that gap: mint a fourth algebra.infer-* code following the same // naming convention, rather than overloading infer-conflicting-scalars // (whose taxonomy description is specifically "disagree on a scalar // kind") for a shape mismatch that isn't a scalar-kind disagreement at // all. CodeAlgebraInferMixedShape Code = "algebra.infer-mixed-shape" )
algebra.* — operations over schemas (spec §8.3.6).
const ( CodeLintUnsatisfiableRecord Code = "lint.unsatisfiable-record" CodeLintUnreachableRecord Code = "lint.unreachable-record" CodeLintDuplicateRecord Code = "lint.duplicate-record" CodeLintAnyField Code = "lint.any-field" )
lint.* — schema diagnostics (spec §8.3.7).
const ( CodeFormatTemporalStringified Code = "format.temporal-stringified" CodeFormatFloatSpecial Code = "format.float-special" CodeFormatNullUnrepresentable Code = "format.null-unrepresentable" CodeFormatAttributeDropped Code = "format.attribute-dropped" CodeFormatNamespaceDropped Code = "format.namespace-dropped" CodeFormatInterleavingLost Code = "format.interleaving-lost" CodeFormatMultipleRoots Code = "format.multiple-roots" // The three data-XML profile refusals (docs/formats/xml.md). Each is // reported at path "$" and only for input that is well-formed XML: a // malformed document is CodeParseCodecSyntax, never one of these. CodeFormatDTDForbidden Code = "format.dtd-forbidden" CodeFormatEntityForbidden Code = "format.entity-forbidden" CodeFormatMixedContent Code = "format.mixed-content" )
format.* — codec adjustments (spec §8.3.8).
const (
CodeMaterializeInexactConversion Code = "materialize.inexact-conversion"
)
materialize.* — schema-directed deserialization (spec §8.3.5).
const (
CodeWriteUnsupportedValue Code = "write.unsupported-value"
)
write.* (spec §8.3.9).
type DateTimeValue ¶
DateTimeValue is a date and a time of day, joined (spec §2.2.1 `datetime`).
func ParseISODateTime ¶
func ParseISODateTime(s string) DateTimeValue
ParseISODateTime parses s, which must already match ISODateTimeRegexp, into a DateTimeValue.
type DateValue ¶
DateValue is a calendar date: year, month, day (spec §2.2.1 `date`).
func ParseISODate ¶
ParseISODate parses s, which must already match ISODateRegexp, into a DateValue.
type Diagnostic ¶
Diagnostic is a single reported problem, carrying at least the four fields spec §8.2 requires: code, path, message, severity.
func Validate ¶
func Validate(doc Document, s Schema) []Diagnostic
Validate checks doc against s and returns every diagnostic found. An empty (non-nil) slice means doc conforms to s (spec §3.6: "an empty list means valid").
validate MUST run within the depth limit of §2.4 (spec §3.6's own text); exceeding it produces a document.limit.depth diagnostic (via the existing LimitChecker from issue #1's limits.go) rather than a validate.* finding, and descent stops at that point.
func (Diagnostic) Error ¶
func (d Diagnostic) Error() string
Error implements the error interface so a Diagnostic can be used wherever a plain error is expected.
type Document ¶
Document is a node or a bare value (spec §2.2: `Document = node | value`). Exactly one of Node or Value is meaningful, selected by IsNode.
Validity contract (issue #77): A Document represents an immutable, finite, acyclic tree. Operations throughout this package (validation, materialization, algebra, serialization) assume this contract and do not perform cyclic-graph checks during recursive descent.
func ValueDocument ¶
ValueDocument wraps a Value as a Document (a bare-value Document).
type Edge ¶
Edge is a single (label, target) pair within a Node's ordered edge list.
Validity contract (issue #77): in a valid Document, edges form a strictly finite, acyclic tree with no shared or cyclic references.
type Field ¶
type Field struct {
Label string
Type Type
Cardinality Cardinality
}
Field is one label a Record allows, per spec §3.3: `Field = (label, type, cardinality)`.
type FieldIndex ¶
FieldIndex maps field labels to *Field for O(1) lookups on a Record.
func (FieldIndex) Field ¶
func (idx FieldIndex) Field(label string) *Field
Field returns the *Field with the given label from the indexed view, or nil if not present.
type LimitChecker ¶
type LimitChecker struct {
// contains filtered or unexported fields
}
LimitChecker tracks running depth and node count as a tree is walked, and validates integer literal digit counts, against a fixed Limits configuration. It is stateful and not safe for concurrent use.
Every format reader in this repository (OML, OSD, JSON, YAML, TOML, XML) constructs one LimitChecker per Document it builds and invokes EnterNode, LeaveNode, and CheckIntDigits as it walks input.
func NewLimitChecker ¶
func NewLimitChecker(limits Limits) *LimitChecker
NewLimitChecker returns a LimitChecker enforcing limits.
func (*LimitChecker) CheckIntDigits ¶
func (c *LimitChecker) CheckIntDigits(path string, digitCount int) *Diagnostic
CheckIntDigits validates that digitCount (the number of decimal digits in an integer literal, sign excluded) does not exceed the configured limit. Returns a Diagnostic with code CodeDocumentLimitIntDigits if it does, otherwise nil.
func (*LimitChecker) Depth ¶
func (c *LimitChecker) Depth() int
Depth returns the current nesting depth.
func (*LimitChecker) EnterNode ¶
func (c *LimitChecker) EnterNode(path string) *Diagnostic
EnterNode records descending into one more level of nesting and materializing one more node. Call it when a reader starts building a new node (including the root). Call LeaveNode when done with that node's children. Returns a Diagnostic with code CodeDocumentLimitDepth or CodeDocumentLimitNodes if the corresponding limit is exceeded, otherwise nil.
func (*LimitChecker) LeaveNode ¶
func (c *LimitChecker) LeaveNode()
LeaveNode records ascending back out of one level of nesting entered via EnterNode. Callers MUST call it exactly once for each successful EnterNode call, after that node's children have all been processed.
func (*LimitChecker) NodeCount ¶
func (c *LimitChecker) NodeCount() int
NodeCount returns the number of nodes entered so far.
type Limits ¶
type Limits struct {
// MaxDepth is the maximum levels of node nesting, counted from the
// Document root.
MaxDepth int
// MaxNodes is the maximum nodes materialized while building one
// Document.
MaxNodes int
// MaxIntDigits is the maximum decimal digits in an integer literal,
// sign excluded.
MaxIntDigits int
}
Limits bounds the work a Document builder will do before refusing to continue, per spec §2.4. The existence and meaning of the three limits is normative; the specific numbers are not, so Limits is a configurable struct rather than package-level constants. Every conformant implementation MUST enforce a finite limit on all three — "no limit" is not a legal value for any field.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits returns the spec §2.4 reference defaults: depth 200, node count 1,000,000, integer digits 4,300.
func (Limits) Validate ¶
Validate checks that l specifies strictly positive values within sane, recommended safety bounds (issue #78). It returns an error if any field is <= 0 or exceeds recommended ceilings.
Validate is purely opt-in: NewLimitChecker does not enforce it, so callers who require custom or unusually large limits retain full control. For standard production use, DefaultLimits() is recommended.
type Node ¶
type Node struct {
Edges []Edge
}
Node is an ordered list of labeled edges (spec §2.1/§2.2). Labels MAY repeat; nothing in the Document model constrains uniqueness, ordering, or the relationship between repeated labels (spec §2.2.2).
Validity contract (issue #77): A Node and its descendant edges must form a strictly finite, acyclic tree. Nodes must not be mutated concurrently once constructed. Trees produced by omnist-go's format readers are guaranteed acyclic by construction; callers constructing Node trees programmatically are responsible for preserving acyclicity.
Per the design decision recorded in CONTRIBUTING.md §2.3, Node stays edge-list-native everywhere: there is no separate map-collapsed type. Callers append to Edges directly to build a Document; this preserves invariant D-1 (edge order is exactly construction order) and D-2 (repeated labels remain separate edges, never merged into a list) by construction, since there is no map-shaped alternative to accidentally use instead.
func (*Node) AddNode ¶
AddNode appends an edge with the given label pointing at a node target, in place, and returns the node for chaining.
func (*Node) AddValue ¶
AddValue appends an edge with the given label pointing at a value target, in place, and returns the node for chaining.
func (*Node) HasLostInterleaving ¶
HasLostInterleaving reports whether grouping n's edges by label (spec §7.3.1's `groups` construction, shared by every JSON-family writer — formats/json, formats/yaml, formats/toml) would lose cross-label interleaving: some label's edges are not all contiguous in n.Edges.
This is the shared home for the check because all three writer packages already depend on this root-level omnist package (the same placement precedent as schema.go's FieldIndex), and the algorithm itself is format-independent — it only inspects n.Edges' label order, never any format-specific rendering.
The distinction that matters (spec §8.3.8, D-3): a label reappearing immediately after its own prior occurrence is NOT interleaving loss — [(m,A),(m,B),(x,X)] groups to {"m":[A,B],"x":X} with nothing lost, since m's two edges were already contiguous before grouping. Only a genuine interruption — a different label's edge appearing between two edges of the same label, e.g. [(m,A),(x,X),(m,B)] — loses information: grouping produces {"m":[A,B],"x":X}, which can no longer tell that X originally sat between A and B.
Detection walks n.Edges once, tracking the most recently seen label at each position and the set of labels considered "closed" (a different label has appeared since we last saw them). If a closed label reappears, its edges were not contiguous, so interleaving was lost.
type ParseError ¶
ParseError is the structured error a stage-1 (text to Document) reader reports, per the design decision recorded in CONTRIBUTING.md §2.4. Its Path field MUST be a text-position path per spec §8.4 (e.g. "14:8"), since a parse.* diagnostic fires before any Document exists to descend a Document-shaped path into.
func PrepareInput ¶
func PrepareInput(text string, bomCode Code) (string, *ParseError)
PrepareInput is the one place every read surface (OML, OSD, JSON, YAML, TOML, XML) applies spec §2.5's input rules, in the order §2.5 states them:
- D-14: the input must be valid UTF-8. Go's string is a byte sequence, so a reader taking one is a byte-oriented entry point, and §2.5 states the testable rule for it: reject every s for which utf8.ValidString(s) is false. The failure is a *ParseError with code CodeParseInvalidEncoding at the fixed path "1:1" -- not a computed offset, whatever byte failed -- and there is exactly one per input. Nothing is repaired or replaced.
- D-15/D-21: StripLeadingBOM.
The order is not cosmetic: a byte-order mark is EF BB BF, so a truncated one is malformed UTF-8 and step 1, not step 2, is the rule that fires on it. bomCode is the code D-21 requires for a second leading mark on this surface.
func StripLeadingBOM ¶
func StripLeadingBOM(text string, code Code) (string, *ParseError)
StripLeadingBOM is the one place every read surface (OML, OSD, JSON, YAML, TOML, XML) applies spec §2.5's byte-order-mark rules to its input text. Nothing else in this repository strips or rejects a U+FEFF; a reader calls this first and hands the returned text to its own lexer or library.
D-15: a U+FEFF at offset zero is consumed and contributes nothing. It is consumed exactly once.
D-21: if a second U+FEFF still stands at offset zero of what remains, the read fails with a *ParseError at text position 1:1 -- computed on the text after the strip, not on the original input -- carrying code, which is CodeParseUnexpectedToken for OML and OSD and CodeParseCodecSyntax for the four codecs (§8.3.1). The check runs here, on the raw text, before any library sees it, because YAML and XML libraries would otherwise discard the second mark themselves; that is a second undeclared strip.
A U+FEFF anywhere other than offset zero is ordinary content and is never touched, so a reader that calls this cannot corrupt a label or a value.
type Path ¶
type Path struct {
// contains filtered or unexported fields
}
Path is a Document or Schema path, per spec §8.4: it starts at "$" and descends by label, disambiguating a repeated label with a zero-based occurrence index in brackets. The index is present if and only if the label occurs more than once in the node it appears in — Path never decides that on its own; callers supply it (typically via PathIndexInNode) because only the caller walking a Node knows how many edges in it share a label.
The zero Path is the root path "$".
func RootPath ¶
func RootPath() Path
RootPath returns the path to the whole Document or schema: "$".
func (Path) Child ¶
Child returns a new Path formed by descending one edge labeled label. If repeated is true, index is rendered as a bracketed occurrence index (e.g. "$.item[2]"); if repeated is false, index is ignored and no bracket is rendered (e.g. "$.name"). Per spec §8.4 the index MUST be present exactly when the label occurs more than once in that node, so callers must determine repeated (e.g. via PathIndexInNode) rather than always passing true.
type Record ¶
Record is a closed set of fields (spec §3.3, §3.1): only the labels listed are allowed, and nothing else — there is no wildcard.
func (*Record) Index ¶
func (r *Record) Index() FieldIndex
Index returns a FieldIndex mapping each declared field label to its *Field. If r is nil, returns nil.
type Resolved ¶
type Resolved struct {
Kind ResolvedKind
ScalarKind ScalarKind
Nullable bool
Record *Record
}
Resolved is S.resolve(t): a Type resolved through the schema's env to exactly one of a scalar declaration, a Record, or `any`. A Ref whose name is absent from Env resolves to a nil Record; that indicates a not-well-formed schema (spec §3.3 S-6 requires every reference to resolve), which is a schema.* well-formedness concern from issue #5, not something validate re-checks — conformRecord treats a nil Record as "no fields, closed", so any node there reports unexpected-field for every edge rather than panicking.
func ResolveType ¶
ResolveType implements S.resolve(t) from the §3.6.1 pseudocode.
type ResolvedKind ¶
type ResolvedKind int
ResolvedKind identifies which of Type's three alternatives S.resolve(t) produced (spec §3.3's `Type = Scalar | Ref | Any`, via §6.2's S.resolve notation).
const ( ResolvedScalar ResolvedKind = iota ResolvedRecord ResolvedAny )
type Scalar ¶
type Scalar struct {
Kind ScalarKind
Str string
Int *big.Int
Num float64
Bool bool
Date DateValue
Time TimeValue
DateTime DateTimeValue
}
Scalar is a tagged value holding exactly one of the seven scalar kinds. Only the field matching Kind is meaningful; the others are zero.
Per the design decision recorded in CONTRIBUTING.md §2.2, integer uses *big.Int (not int64) because spec §2.4 requires supporting integer literals up to 4,300 decimal digits.
func NewBooleanScalar ¶
NewBooleanScalar constructs a boolean-kind Scalar.
func NewDateScalar ¶
NewDateScalar constructs a date-kind Scalar.
func NewDateTimeScalar ¶
func NewDateTimeScalar(dt DateTimeValue) Scalar
NewDateTimeScalar constructs a datetime-kind Scalar.
func NewIntegerScalar ¶
NewIntegerScalar constructs an integer-kind Scalar. It copies v so the caller's *big.Int may be safely mutated afterward.
func NewNumberScalar ¶
NewNumberScalar constructs a number-kind Scalar.
func NewStringScalar ¶
NewStringScalar constructs a string-kind Scalar.
func NewTimeScalar ¶
NewTimeScalar constructs a time-kind Scalar.
func (Scalar) Equal ¶
Equal reports whether s and other are the same scalar per spec D-5: two scalars are equal when their kinds AND values are equal. An integer and a number of the same magnitude are distinct scalars in the Document model even though integer is a subtype of number in the Schema model (§6) — so this method deliberately does not do numeric cross-kind comparison. Go's built-in == cannot be used for Scalar equality: the Int field is a *big.Int pointer, so == would compare pointer identity rather than value, and even a value-correct == would not by itself express the kind-strictness rule this method exists to enforce.
type ScalarKind ¶
type ScalarKind int
ScalarKind identifies which of the seven scalar kinds a Scalar holds.
Spec §2.2.1 defines exactly seven scalar kinds and is explicit that implementations MUST NOT add or collapse kinds, since doing so changes the Schema Algebra's subtyping lattice and therefore changes conformance results. Do not add an eighth constant here.
const ( // KindString is a sequence of Unicode code points. KindString ScalarKind = iota // KindInteger is an arbitrary-precision signed integer, subject to the // §2.4 safety limit on digit count. KindInteger // KindNumber is a real number, represented as IEEE 754 binary64. KindNumber // KindBoolean is true or false. KindBoolean // KindDate is a calendar date: year, month, day. KindDate // KindTime is a time of day, with optional sub-second precision and // optional UTC offset. KindTime // KindDateTime is a date and a time of day, joined. KindDateTime )
func (ScalarKind) String ¶
func (k ScalarKind) String() string
String returns the taxonomy-style lowercase name of the kind (e.g. "integer"), matching the kind names used in spec §2.2.1's table.
type Schema ¶
Schema is a graph of named records plus a distinguished root record name (spec §3.3: `Schema = (root: Ref, env: Name -> Record)`).
EnvOrder holds the declaration order of Env's keys. The schema algebra (spec ch.6, e.g. §6.4's satisfiable_set and §6.5's prune) requires deterministic, declaration-order iteration over env wherever output ordering is observable — Go's map iteration is deliberately randomized, so Env alone cannot satisfy that on its own. Any code that builds a new Schema (the OSD parser, and later prune/normalize/extract) MUST keep EnvOrder consistent with Env's keys — same set, declaration order.
type SchemaEqualityMode ¶
type SchemaEqualityMode string
SchemaEqualityMode selects one of the two schema-comparison modes the porting guide calls for: ModeExact requires every record name and field to match; ModeIsomorphic requires the same structure up to consistent record renaming.
const ( // ModeExact is used for normalize/prune/extract, whose output naming // is spec-determined (§6's operations fix record names // deterministically, so a naming difference is a real divergence). ModeExact SchemaEqualityMode = "exact" // ModeIsomorphic is used only for infer, since §6.10's infer_type // never normalizes its output — two schemas that differ only in which // arbitrary names infer picked for its records are still the same // answer. ModeIsomorphic SchemaEqualityMode = "isomorphic" )
type Target ¶
type Target struct {
// contains filtered or unexported fields
}
Target is what an Edge points to: exactly a Value or a Node, per spec D-4 ("A target is a value or a node. No third case exists."). The zero Target is invalid; construct one with ValueTarget or NodeTarget.
Target is deliberately not an interface. An interface satisfied by two concrete types can always be satisfied by a third one added later by a caller outside this package, which would let a list-valued or otherwise illegal target escape into user-visible Documents — exactly what D-4 forbids. A closed struct with an internal discriminant cannot be extended from outside the package.
func NodeTarget ¶
NodeTarget constructs a Target holding a node. Panics if n is nil, since a nil node is not a legal target (it is neither a value nor a node).
func ValueTarget ¶
ValueTarget constructs a Target holding a value.
type TemporalKind ¶
type TemporalKind int
TemporalKind selects which of ISODateRegexp/ISOTimeRegexp/ ISODateTimeRegexp MatchesISOKind checks a string against.
const ( TemporalDate TemporalKind = iota TemporalTime TemporalDateTime )
type TimeValue ¶
type TimeValue struct {
Hour int
Minute int
Second int
Nanosecond int
// HasOffset reports whether Offset is meaningful. A time value need not
// carry a UTC offset at all.
HasOffset bool
// OffsetSeconds is the UTC offset in seconds when HasOffset is true.
OffsetSeconds int
}
TimeValue is a time of day, with optional sub-second precision and optional UTC offset (spec §2.2.1 `time`).
func ParseISOTime ¶
ParseISOTime parses s, which must already match ISOTimeRegexp, into a TimeValue.
type Type ¶
type Type struct {
Kind TypeKind
// ScalarKind and Nullable are meaningful only when Kind == TypeScalarKind.
ScalarKind ScalarKind
Nullable bool
// RefName is meaningful only when Kind == TypeRefKind. It names a
// record in the schema's env; resolution happens by lookup (spec §3.3
// S-6), not eagerly at Type-construction time, since forward references
// and mutual recursion are both legal.
RefName string
}
Type is a field's type: exactly one scalar kind (optionally nullable), a reference to another record (by name), or the `any` type — never a choice between candidates (spec §3.1, §3.3). Only the fields relevant to Kind are meaningful; the others are zero.
Like Target in document.go, this is a closed struct rather than an interface, for the same reason: an interface satisfied by two concrete types is always extensible by a third from outside the package, which would let a shape outside the spec's closed `Scalar | Ref | Any` union leak into a well-formed Schema.
func ScalarType ¶
func ScalarType(kind ScalarKind, nullable bool) Type
ScalarType constructs a scalar Type. nullable corresponds to a trailing `?` in OSD source (spec §5.6).
type TypeKind ¶
type TypeKind int
TypeKind identifies which of the three Type alternatives (spec §3.3's `Type = Scalar | Ref | Any`) a Type holds.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
omnist
command
Package main implements omnist, a thin CLI wrapper over this repo's exported library functions: the stage-1 readers/writers, OSD read/write, Validate, Materialize, and the schema algebra (Normalize, Prune, Extract, CompatibleWith, Equivalent, IsEmpty, Infer, Lint).
|
Package main implements omnist, a thin CLI wrapper over this repo's exported library functions: the stage-1 readers/writers, OSD read/write, Validate, Materialize, and the schema algebra (Normalize, Prune, Extract, CompatibleWith, Equivalent, IsEmpty, Infer, Lint). |
|
formats
|
|
|
tools
|
|
|
check_doc_examples
command
Command check_doc_examples is the CI gate for issue #62: every fenced code block in docs/*.md must carry an HTML-comment marker, directly above or below the block, declaring how it's verified:
|
Command check_doc_examples is the CI gate for issue #62: every fenced code block in docs/*.md must carry an HTML-comment marker, directly above or below the block, declaring how it's verified: |
|
conformance
This file is Track 1 (fixture-based) of the conformance harness, per issue #55 and vendor/omnist-spec/docs/conformance-harness.md.
|
This file is Track 1 (fixture-based) of the conformance harness, per issue #55 and vendor/omnist-spec/docs/conformance-harness.md. |
|
conformance/cmd/conformance
command
Command conformance runs omnist-go against every Track 2 (JSON-vector) test in vendor/omnist-spec/test-suite/, per spec §8.5 and issue #31.
|
Command conformance runs omnist-go against every Track 2 (JSON-vector) test in vendor/omnist-spec/test-suite/, per spec §8.5 and issue #31. |
|
conformance/cmd/conformance-fixtures
command
Command conformance-fixtures runs omnist-go against every Track 1 (fixture-based) test in vendor/omnist-spec/conformance/fixtures/, per spec docs/conformance-harness.md and issue #55.
|
Command conformance-fixtures runs omnist-go against every Track 1 (fixture-based) test in vendor/omnist-spec/conformance/fixtures/, per spec docs/conformance-harness.md and issue #55. |