Files
gserver/vendor/go.yaml.in/yaml/v4/loader.go

232 lines
6.6 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 yaml
import (
"bytes"
"errors"
"io"
"reflect"
"go.yaml.in/yaml/v4/internal/libyaml"
)
// Load decodes 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 decoded 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 decoded values should be compatible with the respective
// values in out. If one or more values cannot be decoded 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 := libyaml.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)
}
// loadAll loads all documents into a slice
func loadAll(in []byte, out any, opts *libyaml.Options) error {
outVal := reflect.ValueOf(out)
if outVal.Kind() != reflect.Pointer || outVal.IsNil() {
return &LoadErrors{Errors: []*libyaml.ConstructError{{
Err: errors.New("yaml: WithAllDocuments requires a non-nil pointer to a slice"),
}}}
}
sliceVal := outVal.Elem()
if sliceVal.Kind() != reflect.Slice {
return &LoadErrors{Errors: []*libyaml.ConstructError{{
Err: errors.New("yaml: WithAllDocuments requires a pointer to a slice"),
}}}
}
// Create a new slice (clear existing content)
sliceVal.Set(reflect.MakeSlice(sliceVal.Type(), 0, 0))
l, err := NewLoader(bytes.NewReader(in), func(o *libyaml.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 decoded element to slice
sliceVal.Set(reflect.Append(sliceVal, elemPtr.Elem()))
}
return nil
}
// loadSingle loads exactly one document (strict)
func loadSingle(in []byte, out any, opts *libyaml.Options) error {
l, err := NewLoader(bytes.NewReader(in), func(o *libyaml.Options) error {
*o = *opts // Copy options
return nil
})
if err != nil {
return err
}
// Load first document
err = l.Load(out)
if err == io.EOF {
return &LoadErrors{Errors: []*libyaml.ConstructError{{
Err: errors.New("yaml: no documents in stream"),
}}}
}
if err != nil {
return err
}
// 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
return &LoadErrors{Errors: []*libyaml.ConstructError{{
Err: errors.New("yaml: expected single document, found multiple"),
}}}
}
return nil
}
// A Loader reads and decodes YAML values from an input stream with configurable
// options.
type Loader struct {
composer *libyaml.Composer
decoder *libyaml.Constructor
opts *libyaml.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 := libyaml.ApplyOptions(opts...)
if err != nil {
return nil, err
}
c := libyaml.NewComposerFromReader(r)
c.SetStreamNodes(o.StreamNodes)
return &Loader{
composer: c,
decoder: libyaml.NewConstructor(o),
opts: o,
}, nil
}
// 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.opts.SingleDocument && l.docCount > 0 {
return io.EOF
}
node := l.composer.Parse() // *libyaml.Node
if node == nil {
return io.EOF
}
l.docCount++
out := reflect.ValueOf(v)
if out.Kind() == reflect.Pointer && !out.IsNil() {
out = out.Elem()
}
l.decoder.Construct(node, out) // Pass libyaml.Node directly
if len(l.decoder.TypeErrors) > 0 {
typeErrors := l.decoder.TypeErrors
l.decoder.TypeErrors = nil
return &LoadErrors{Errors: typeErrors}
}
return nil
}