Files
mstore/vendor/go.yaml.in/yaml/v4/internal/libyaml/loader.go
T
2026-06-16 08:02:19 +02:00

299 lines
8.5 KiB
Go

// Copyright 2025 The go-yaml Project Contributors
// SPDX-License-Identifier: Apache-2.0
// This file contains the Loader API for reading YAML documents.
//
// Primary functions:
// - Load: Decode YAML document(s) into a value (use WithAll for multi-doc)
// - NewLoader: Create a streaming loader from io.Reader
package libyaml
import (
"bytes"
"errors"
"io"
"reflect"
)
// A Loader reads and loads YAML values from an input stream with configurable
// options.
type Loader struct {
composer *Composer
resolver *Resolver
constructor *Constructor
options *Options
docCount int
}
// NewLoader returns a new Loader that reads from r with the given options.
//
// The Loader introduces its own buffering and may read data from r beyond the
// YAML values requested.
func NewLoader(r io.Reader, opts ...Option) (*Loader, error) {
o, err := ApplyOptions(opts...)
if err != nil {
return nil, err
}
c := NewComposerFromReader(r, o)
c.SetStreamNodes(o.StreamNodes)
return &Loader{
composer: c,
resolver: NewResolver(o),
constructor: NewConstructor(o),
options: o,
}, nil
}
// Load loads YAML document(s) with the given options.
//
// By default, Load requires exactly one document in the input.
// If zero documents are found, it returns an error.
// If multiple documents are found, it returns an error.
//
// Use WithAllDocuments() to load all documents into a slice:
//
// var configs []Config
// yaml.Load(multiDocYAML, &configs, yaml.WithAllDocuments())
//
// When WithAllDocuments is used, out must be a pointer to a slice.
// Each document is loaded into the slice element type.
// Zero documents results in an empty slice (no error).
//
// Maps and pointers (to a struct, string, int, etc) are accepted as out
// values. If an internal pointer within a struct is not initialized,
// the yaml package will initialize it if necessary. The out parameter
// must not be nil.
//
// The type of the loaded values should be compatible with the respective
// values in out. If one or more values cannot be loaded due to type
// mismatches, decoding continues partially until the end of the YAML
// content, and a *yaml.LoadErrors is returned with details for all
// missed values.
//
// Struct fields are only loaded if they are exported (have an upper case
// first letter), and are loaded using the field name lowercased as the
// default key. Custom keys may be defined via the "yaml" name in the field
// tag: the content preceding the first comma is used as the key, and the
// following comma-separated options control the loading and dumping behavior.
//
// For example:
//
// type T struct {
// F int `yaml:"a,omitempty"`
// B int
// }
// var t T
// yaml.Load([]byte("a: 1\nb: 2"), &t)
//
// See the documentation of Dump for the format of tags and a list of
// supported tag options.
func Load(in []byte, out any, opts ...Option) error {
o, err := ApplyOptions(opts...)
if err != nil {
return err
}
if o.AllDocuments {
// Multi-document mode: out must be pointer to slice
return loadAll(in, out, o)
}
// Single-document mode: exactly one document required
return loadSingle(in, out, o)
}
// Load reads the next YAML-encoded document from its input and stores it
// in the value pointed to by v.
//
// Returns [io.EOF] when there are no more documents to read.
// If WithSingleDocument option was set and a document was already read,
// subsequent calls return [io.EOF].
//
// Maps and pointers (to a struct, string, int, etc) are accepted as v
// values. If an internal pointer within a struct is not initialized,
// the yaml package will initialize it if necessary. The v parameter
// must not be nil.
//
// Struct fields are only loaded if they are exported (have an upper case
// first letter), and are loaded using the field name lowercased as the
// default key. Custom keys may be defined via the "yaml" name in the field
// tag: the content preceding the first comma is used as the key, and the
// following comma-separated options control the loading and dumping behavior.
//
// See the documentation of the package-level Load function for more details
// about YAML to Go conversion and tag options.
func (l *Loader) Load(v any) (err error) {
defer handleErr(&err)
if l.options.SingleDocument && l.docCount > 0 {
return io.EOF
}
// Stage 1: Compose - parse events into node tree (unresolved tags)
node := l.composer.Compose() // *Node
if node == nil {
return io.EOF
}
l.docCount++
// Stage 2: Resolve - determine implicit types for untagged scalars
l.resolver.Resolve(node)
// Stage 3: Construct - convert node tree to Go values
out := reflect.ValueOf(v)
if out.Kind() == reflect.Pointer && !out.IsNil() {
out = out.Elem()
}
l.constructor.Construct(node, out)
if len(l.constructor.TypeErrors) > 0 {
typeErrors := l.constructor.TypeErrors
l.constructor.TypeErrors = nil
return &LoadErrors{Errors: typeErrors}
}
return nil
}
// loadAll loads all documents from the input into a slice.
// The out parameter must be a non-nil pointer to a slice.
// Each document is appended to the slice as an element.
func loadAll(in []byte, out any, opts *Options) error {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Pointer || outVal.IsNil() {
msg := "yaml: WithAllDocuments requires a non-nil pointer to a slice"
return &LoadErrors{Errors: []*LoadError{{
Stage: ConstructorStage,
Message: msg,
err: errors.New(msg),
}}}
}
sliceVal := outVal.Elem()
if sliceVal.Kind() != reflect.Slice {
msg := "yaml: WithAllDocuments requires a pointer to a slice"
return &LoadErrors{Errors: []*LoadError{{
Stage: ConstructorStage,
Message: msg,
err: errors.New(msg),
}}}
}
// Create a new slice (clear existing content)
sliceVal.Set(reflect.MakeSlice(sliceVal.Type(), 0, 0))
l, err := NewLoader(bytes.NewReader(in), func(o *Options) error {
*o = *opts // Copy options
return nil
})
if err != nil {
return err
}
elemType := sliceVal.Type().Elem()
for {
// Create new element of slice's element type
elemPtr := reflect.New(elemType)
err := l.Load(elemPtr.Interface())
if err == io.EOF {
break
}
if err != nil {
return err
}
// Append loaded element to slice
sliceVal.Set(reflect.Append(sliceVal, elemPtr.Elem()))
}
return nil
}
// loadSingle loads exactly one document from the input.
// Returns an error if the input contains zero or multiple documents
// (unless FromLegacy option is set for backward compatibility).
func loadSingle(in []byte, out any, opts *Options) error {
l, err := NewLoader(bytes.NewReader(in), func(o *Options) error {
*o = *opts // Copy options
return nil
})
if err != nil {
return err
}
// Load first document
err = l.Load(out)
if err == io.EOF {
msg := "yaml: no documents in stream"
return &LoadErrors{Errors: []*LoadError{{
Stage: ConstructorStage,
Message: msg,
err: errors.New(msg),
}}}
}
if err != nil {
return err
}
// Skip trailing document check for legacy Unmarshal() compatibility
if opts.FromLegacy {
return nil
}
// Check for additional documents
var dummy any
err = l.Load(&dummy)
if err != io.EOF {
if err != nil {
// Some other error occurred
return err
}
// Successfully loaded a second document - this is an error in strict mode
msg := "yaml: expected single document, found multiple"
return &LoadErrors{Errors: []*LoadError{{
Stage: ConstructorStage,
Message: msg,
err: errors.New(msg),
}}}
}
return nil
}
// SetKnownFields enables or disables strict field checking for subsequent Load
// calls.
// This is used by the legacy Decoder.KnownFields() method.
func (l *Loader) SetKnownFields(enable bool) {
l.constructor.KnownFields = enable
}
// ComposeAndResolve composes and resolves the next document from the input
// and returns the node without constructing Go values. This is used by
// Unmarshal() to support the Unmarshaler interface.
func (l *Loader) ComposeAndResolve() *Node {
if l.options.SingleDocument && l.docCount > 0 {
return nil
}
// Stage 1: Compose - parse events into node tree (unresolved tags)
node := l.composer.Compose()
if node == nil {
return nil
}
l.docCount++
// Stage 2: Resolve - determine implicit types for untagged scalars
l.resolver.Resolve(node)
return node
}
// LoadAny parses YAML data into generic Go structures (map[string]any, []any).
//
// Useful for test data loading where the structure is unknown at compile time.
// This is a convenience wrapper around Load with an any target.
func LoadAny(data []byte) (any, error) {
var result any
if err := Load(data, &result); err != nil {
return nil, err
}
return result, nil
}