feat: add OffsetDateTime, nesting limits and uniform Marshaler dispatch
Test / test (push) Successful in 2m18s
Test / test (push) Successful in 2m18s
Assisted-by: DeepSeek V4.1 Flash
This commit is contained in:
+59
-11
@@ -104,15 +104,33 @@ func Parse(data []byte) (map[string]any, error) {
|
||||
// ctx. The context is checked between top-level statements so cancellation is
|
||||
// honoured before the parser has done substantial work.
|
||||
func ParseContext(ctx context.Context, data []byte) (map[string]any, error) {
|
||||
return parseWithOptions(ctx, data, parseOptions{})
|
||||
}
|
||||
|
||||
// parseOptions bound the work one parse may do. A zero field takes the
|
||||
// default.
|
||||
type parseOptions struct {
|
||||
maxDepth int
|
||||
maxInputSize int
|
||||
}
|
||||
|
||||
func parseWithOptions(ctx context.Context, data []byte, opts parseOptions) (map[string]any, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.maxInputSize > 0 && len(data) > opts.maxInputSize {
|
||||
return nil, fmt.Errorf("interpres: input is %d bytes, over the limit of %d", len(data), opts.maxInputSize)
|
||||
}
|
||||
if !utf8.Valid(data) {
|
||||
return nil, &SyntaxError{Line: 1, Msg: "input is not valid UTF-8"}
|
||||
}
|
||||
maxDepth := opts.maxDepth
|
||||
if maxDepth <= 0 {
|
||||
maxDepth = maxNestingDepth
|
||||
}
|
||||
// The parser scans data in place; it only reads the buffer, and every
|
||||
// string it stores in the tree is copied out of it.
|
||||
p := &parser{src: data, line: 1, ctx: ctx}
|
||||
p := &parser{src: data, line: 1, ctx: ctx, maxDepth: maxDepth}
|
||||
return p.parse()
|
||||
}
|
||||
|
||||
@@ -143,9 +161,11 @@ func UnmarshalContext(ctx context.Context, data []byte, v any) error {
|
||||
}
|
||||
|
||||
// A Decoder decodes a TOML document into a Go value with configurable
|
||||
// strictness.
|
||||
// strictness and configurable limits on the parse it performs.
|
||||
type Decoder struct {
|
||||
disallowUnknown bool
|
||||
maxDepth int
|
||||
maxInputSize int
|
||||
}
|
||||
|
||||
// NewDecoder returns a Decoder.
|
||||
@@ -158,6 +178,28 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
|
||||
return d
|
||||
}
|
||||
|
||||
// MaxDepth bounds how deeply arrays and inline tables may nest in a document
|
||||
// this decoder accepts. The parser is a recursive descent, so a document that
|
||||
// nests without bound would exhaust the stack; one that nests deeper than the
|
||||
// limit is rejected with a SyntaxError naming it instead. Use 0 or any
|
||||
// negative value for the default of 10000, which no hand-written document
|
||||
// approaches.
|
||||
func (d *Decoder) MaxDepth(depth int) *Decoder {
|
||||
d.maxDepth = depth
|
||||
return d
|
||||
}
|
||||
|
||||
// MaxInputSize bounds the size of a document this decoder accepts, in bytes; a
|
||||
// larger one is rejected before parsing starts. Use 0 or any negative value for
|
||||
// no limit, which is the default: the caller already holds the bytes, so the
|
||||
// size is a policy the caller sets rather than a protection the library
|
||||
// imposes on its own. Parse and ParseContext take no limit beyond the nesting
|
||||
// default.
|
||||
func (d *Decoder) MaxInputSize(size int) *Decoder {
|
||||
d.maxInputSize = size
|
||||
return d
|
||||
}
|
||||
|
||||
// Decode parses data and stores the result in the value pointed to by v,
|
||||
// honouring the decoder's strictness settings.
|
||||
//
|
||||
@@ -168,7 +210,10 @@ func (d *Decoder) Decode(data []byte, v any) error {
|
||||
|
||||
// DecodeContext is the cancellable variant of Decode.
|
||||
func (d *Decoder) DecodeContext(ctx context.Context, data []byte, v any) error {
|
||||
tree, err := ParseContext(ctx, data)
|
||||
tree, err := parseWithOptions(ctx, data, parseOptions{
|
||||
maxDepth: d.maxDepth,
|
||||
maxInputSize: d.maxInputSize,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,12 +238,15 @@ type Marshaler interface {
|
||||
// Unmarshaler is the inverse of Marshaler: a type that wants control over
|
||||
// how it is decoded from a TOML value may implement UnmarshalTOML. The data
|
||||
// argument is whatever the parser produced for that key: one of string,
|
||||
// bool, int64, float64, time.Time, LocalDateTime, LocalDate, LocalTime,
|
||||
// []any, or map[string]any. UnmarshalTOML may parse, inspect, or transform
|
||||
// the value however it likes, then store the result by mutating its
|
||||
// receiver through the standard pointer-indirection rules of the reflect
|
||||
// package (i.e. via reflect.Value.Set or by reassigning fields through a
|
||||
// pointer the receiver holds).
|
||||
// bool, int64, float64, OffsetDateTime, LocalDateTime, LocalDate, LocalTime,
|
||||
// []any, or map[string]any. A tree built by hand may carry a plain time.Time
|
||||
// where the parser would put an OffsetDateTime.
|
||||
//
|
||||
// UnmarshalTOML may parse, inspect, or transform the value however it likes,
|
||||
// then store the result by mutating its receiver through the standard
|
||||
// pointer-indirection rules of the reflect package (i.e. via
|
||||
// reflect.Value.Set or by reassigning fields through a pointer the receiver
|
||||
// holds).
|
||||
//
|
||||
// UnmarshalTOML is invoked from (*Decoder).Decode / Unmarshal when the
|
||||
// destination type implements the interface. The decoder does not need to
|
||||
@@ -231,8 +279,8 @@ type Unmarshaler interface {
|
||||
// value array (for example an inline table in a mixed array) emits as an
|
||||
// inline table.
|
||||
// - Scalars encode as TOML scalars: bool, int64, float64, string, time.Time
|
||||
// (offset date-time), and LocalDateTime/LocalDate/LocalTime (local
|
||||
// variants). A date-time writes its seconds only when the value carries
|
||||
// and OffsetDateTime (offset date-time), and LocalDateTime/LocalDate/
|
||||
// LocalTime (local variants). A date-time writes its seconds only when the value carries
|
||||
// them, and drops the trailing zeros of a fractional second.
|
||||
// - A table element of a value array, and a sub-table inlined by
|
||||
// Encoder.InlineTables, is written as an inline table, across lines when it
|
||||
|
||||
Reference in New Issue
Block a user