errbox

package
v0.0.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 21, 2025 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

errbox boxes iter.Seq[V, error] and converts to iter.Seq[V]. The occurrence of the error stops the boxed iterator. The error can be later inspected through method.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Box

type Box[V any] struct {
	// contains filtered or unexported fields
}

Box boxes an input iter.Seq2[V, error] to iter.Seq[V] by stripping nil errors from values over the input iterator. The first non-nil error causes the boxed iterator to be stopped and Box stores the error. Later the error can be examined by *Box.Err.

Caveats: Box remembers the first non-nil error but it DOES NOT mean that Box converts an input iterator to be stateful. In case users need Box.IntoIter to be break-ed and resumed, users must ensure that the input is stateful, or is ok to replay it. To convert iterators to be stateful, github.com/ngicks/go-iterator-helper/hiter/iterable.NewResumable is useful.

*Box.IntoIter returns the iterator as iter.Seq[V]. While consuming values from the iterator, it might conditionally yield a non-nil error. In that case Box stores the error and stops without yielding the value paired to the error. *Box.Err returns that error otherwise nil. After the first non-nil error, *Box.IntoIter return an iterator that yields nothing.

The zero Box is invalid and it must be allocated by New.

func Map added in v0.0.19

func Map[V1, V2 any](mapper func(v V1) (V2, error), seq iter.Seq[V1]) *Box[V2]

Map maps values over the input iterator and put them in Box.

See doc comment for Box for detailed caveats.

func New

func New[V any](seq iter.Seq2[V, error]) *Box[V]

New returns a newly allocated Box.

When a pair from seq contains non-nil error, Box discards a former value of that pair(V), then the iterator returned from Box.IntoIter stops.

See doc comment for Box for detailed caveats.

func (*Box[V]) Err

func (b *Box[V]) Err() error

Err returns an error the input iterator has returned. If the iterator has not yet encountered an error, Err returns nil.

func (*Box[V]) IntoIter added in v0.0.11

func (b *Box[V]) IntoIter() iter.Seq[V]

IntoIter returns an iterator which yields values from the input iterator.

As the name IntoIter suggests, the iterator is (partially) stateful; If the iterator produce a non-nil error, it stops iteration without yielding paired value(V) and will no longer produce any data. In that case the error can be inspected by calling *Box.Err.

type Box2 added in v0.0.19

type Box2[K, V any] struct {
	// contains filtered or unexported fields
}

Box2 is like Box but with iter.Seq2.

See doc comment for Box for detail.

func Map2 added in v0.0.19

func Map2[K1, V1, K2, V2 any](mapper func(K1, V1) (K2, V2, error), seq iter.Seq2[K1, V1]) *Box2[K2, V2]

Map2 maps pairs of values over the input iterator using mapper and put them in Box2.

See doc comment for Box for detailed caveats.

func New2 added in v0.0.19

func New2[K, V any](seq iter.Seq2[hiter.KeyValue[K, V], error]) *Box2[K, V]

New2 is like New but returns Box2.

func (*Box2[K, V]) Err added in v0.0.19

func (b *Box2[K, V]) Err() error

Err returns an error the input iterator has returned. If the iterator has not yet encountered an error, Err returns nil.

func (*Box2[K, V]) IntoIter2 added in v0.0.19

func (b *Box2[K, V]) IntoIter2() iter.Seq2[K, V]

IntoIter2 is like Box.IntoIter but is for Box2.

type JsonDecoder

type JsonDecoder struct {
	*Box[json.Token]
	Dec *json.Decoder
}

func NewJsonDecoder

func NewJsonDecoder(dec *json.Decoder) *JsonDecoder
Example (A_semantically_broken)

ExampleNewJsonDecoder_a_semantically_broken demonstrates raw decoder can be accessed while iterating over tokens. Also calling Decode is safe and not a race condition. Failing to decode does not affect its iteration. After the iterator stops, no error is stored.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const semanticallyBroken = `{
		"foo": "bar",
		"baz": ["yay", "nay", 5, "wow"]
	}`

	dec := errbox.NewJsonDecoder(json.NewDecoder(strings.NewReader(semanticallyBroken)))

	var depth int
	for t := range dec.IntoIter() {
		if depth == 1 && t == "baz" {
			// read opening [.
			t, err := dec.Dec.Token()
			if err != nil {
				panic(err)
			}
			if t != json.Delim('[') {
				panic("??")
			}
			var yayyay string
			for dec.Dec.More() {
				err = dec.Dec.Decode(&yayyay)
				if err == nil {
					fmt.Printf("yay? = %s\n", yayyay)
				} else {
					fmt.Printf("yay err = %v\n", err)
				}
			}
			// read closing ].
			t, err = dec.Dec.Token()
			if err != nil {
				panic(err)
			}
			if t != json.Delim(']') {
				panic("??")
			}
		}
		switch t {
		case json.Delim('{'), json.Delim('['):
			depth++
		case json.Delim('}'), json.Delim(']'):
			depth--
		}
	}
	fmt.Printf("stored error: %v\n", dec.Err())
	_, err := dec.Dec.Token()
	fmt.Printf("eof: %t\n", err == io.EOF)
}
Output:

yay? = yay
yay? = nay
yay err = json: cannot unmarshal number into Go value of type string
yay? = wow
stored error: <nil>
eof: true
Example (B_syntactically_broken)

ExampleNewJsonDecoder_b_syntactically_broken demonstrates that syntactically broken json inputs cause no error on reading. Also it works well with some reader implementation where final non-empty data come with io.EOF error.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"testing/iotest"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const syntacticallyBroken = `{
		"foo": "bar",
		"baz": ["yay", "nay", 5, "wow"],
		"broken": {
	}`

	dec := errbox.NewJsonDecoder(
		json.NewDecoder(
			iotest.DataErrReader(
				strings.NewReader(syntacticallyBroken),
			),
		),
	)

	for t := range dec.IntoIter() {
		fmt.Printf("%v\n", t)
	}
	fmt.Printf("stored error: %v\n", dec.Err())
	_, err := dec.Dec.Token()
	fmt.Printf("eof: %t\n", err == io.EOF)
}
Output:

{
foo
bar
baz
[
yay
nay
5
wow
]
broken
{
}
stored error: <nil>
eof: true
Example (C_reader_broken)

ExampleNewJsonDecoder_c_reader_broken demonstrates an error returned from the decoder can be inspected through Err method.

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"strings"
	"testing/iotest"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const readerBroken = `{
		"foo": "bar",
		"baz": ["yay", "nay", 5, "wow"]`

	dec := errbox.NewJsonDecoder(
		json.NewDecoder(
			io.MultiReader(
				strings.NewReader(readerBroken),
				iotest.ErrReader(errors.New("sample")),
			),
		),
	)

	for t := range dec.IntoIter() {
		fmt.Printf("%v\n", t)
	}
	fmt.Printf("stored error: %v\n", dec.Err())
}
Output:

{
foo
bar
baz
[
yay
nay
5
wow
]
stored error: sample

type Nexter added in v0.0.14

type Nexter[V any] struct {
	*Box[V]
}

func NewNexter added in v0.0.14

func NewNexter[
	V any,
	N interface {
		Next() bool
		Err() error
	},
](n N, scanner func(N) (V, error)) *Nexter[V]

type SqlRows

type SqlRows[V any] struct {
	*Box[V]
}

func NewSqlRows

func NewSqlRows[V any](rows *sql.Rows, scanner func(*sql.Rows) (V, error)) *SqlRows[V]
Example (Row_error)
package main

import (
	"fmt"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
	"github.com/ngicks/go-iterator-helper/internal/testhelper"
)

func main() {
	mock := testhelper.OpenMockDB(true)
	defer mock.Close()

	boxed := errbox.NewSqlRows(testhelper.QueryRows(mock), testhelper.Scan)

	for row := range boxed.IntoIter() {
		fmt.Printf("row = %#v\n", row)
	}
	fmt.Printf("stored err: %v\n", boxed.Err())
}
Output:

row = testhelper.TestRow{Id:1, Title:"post 1", Body:"hello"}
row = testhelper.TestRow{Id:2, Title:"post 2", Body:"world"}
stored err: mock error
Example (Scan_error)
package main

import (
	"database/sql"
	"errors"
	"fmt"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
	"github.com/ngicks/go-iterator-helper/internal/testhelper"
)

func main() {
	scanErr := errors.New("scan")

	mock := testhelper.OpenMockDB(true)
	defer mock.Close()

	var count int
	boxed := errbox.NewSqlRows(
		testhelper.QueryRows(mock),
		func(r *sql.Rows) (testhelper.TestRow, error) {
			count++
			if count > 1 {
				return *new(testhelper.TestRow), scanErr
			}
			return testhelper.Scan(r)
		},
	)

	for row := range boxed.IntoIter() {
		fmt.Printf("row = %#v\n", row)
	}
	fmt.Printf("stored err: %v\n", boxed.Err())
}
Output:

row = testhelper.TestRow{Id:1, Title:"post 1", Body:"hello"}
stored err: scan
Example (Successful)
package main

import (
	"database/sql"
	"fmt"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
	"github.com/ngicks/go-iterator-helper/internal/testhelper"
)

func main() {
	type TestRow struct {
		Id    int
		Title string
		Body  string
	}

	mock := testhelper.OpenMockDB(false)
	defer mock.Close()

	rows, err := mock.Query("SELECT id, title, body FROM posts")
	if err != nil {
		panic(err)
	}

	scanner := func(r *sql.Rows) (TestRow, error) {
		var t TestRow
		err := r.Scan(&t.Id, &t.Title, &t.Body)
		return t, err
	}

	boxed := errbox.NewSqlRows(rows, scanner)

	for row := range boxed.IntoIter() {
		fmt.Printf("row = %#v\n", row)
	}
	fmt.Printf("stored err: %v\n", boxed.Err())
}
Output:

row = errbox_test.TestRow{Id:1, Title:"post 1", Body:"hello"}
row = errbox_test.TestRow{Id:2, Title:"post 2", Body:"world"}
row = errbox_test.TestRow{Id:3, Title:"post 3", Body:"iter"}
stored err: <nil>

type XmlDecoder

type XmlDecoder struct {
	*Box[xml.Token]
	Dec *xml.Decoder
}

func NewXmlDecoder

func NewXmlDecoder(dec *xml.Decoder) *XmlDecoder
Example (A_semantically_broken)

ExampleNewXmlDecoder_a_semantically_broken demonstrates raw decoder can be accessed while iterating over tokens. Also calling DecodeElement is safe and not a race condition. Failing to decode does not affect its iteration. After the iterator stops, no error is stored.

package main

import (
	"encoding/xml"
	"fmt"
	"io"
	"strings"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const semanticallyBroken = `
	<root>
		<self/>
		<foo>bar</foo>
		<baz>5</baz>
		<baz>23</baz>
		<baz>yay</baz>
		<baz>49</baz>
	</root>`

	dec := errbox.NewXmlDecoder(xml.NewDecoder(strings.NewReader(strings.TrimSpace(semanticallyBroken))))

	var depth int
	for t := range dec.IntoIter() {
		var ok bool
		tok, ok := t.(xml.StartElement)
		if ok {
			if depth == 1 && tok.Name.Local == "baz" {
				var yayyay int
				err := dec.Dec.DecodeElement(&yayyay, &tok)
				if err == nil {
					fmt.Printf("yay? = %d\n", yayyay)
				} else {
					fmt.Printf("yay err = %v\n", err)
				}
				continue
			}
			depth++
		}
		_, ok = t.(xml.EndElement)
		if ok {
			depth--
		}
	}
	fmt.Printf("stored error: %v\n", dec.Err())
	_, err := dec.Dec.Token()
	fmt.Printf("eof: %t\n", err == io.EOF)
}
Output:

yay? = 5
yay? = 23
yay err = strconv.ParseInt: parsing "yay": invalid syntax
yay? = 49
stored error: <nil>
eof: true
Example (B_syntactically_broken)

ExampleNewXmlDecoder_b_syntactically_broken demonstrates that syntactically broken xml inputs cause io.UnexpectedEOF error. Also it works well with some reader implementation where final non-empty data come with io.EOF error.

package main

import (
	"encoding/xml"
	"fmt"
	"strings"
	"testing/iotest"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const syntacticallyBroken = `
	<root>
		<self/>
		<foo>bar</foo>
		<baz>5</baz>
		<baz>23</baz>
		<baz>yay</baz>
		<baz>49`

	dec := errbox.NewXmlDecoder(
		xml.NewDecoder(
			iotest.DataErrReader(
				strings.NewReader(strings.TrimSpace(syntacticallyBroken)),
			),
		),
	)

	for t := range dec.IntoIter() {
		fmt.Printf("%#v\n", t)
	}
	fmt.Printf("stored err: %v\n", dec.Err())
}
Output:

xml.StartElement{Name:xml.Name{Space:"", Local:"root"}, Attr:[]xml.Attr{}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"self"}, Attr:[]xml.Attr{}}
xml.EndElement{Name:xml.Name{Space:"", Local:"self"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"foo"}, Attr:[]xml.Attr{}}
xml.CharData{0x62, 0x61, 0x72}
xml.EndElement{Name:xml.Name{Space:"", Local:"foo"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x35}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x32, 0x33}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x79, 0x61, 0x79}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x34, 0x39}
stored err: XML syntax error on line 7: unexpected EOF
Example (C_reader_broken)

ExampleNewXmlDecoder_c_reader_broken demonstrates an error returned from the decoder can be inspected through Err method.

package main

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"strings"
	"testing/iotest"

	"github.com/ngicks/go-iterator-helper/hiter/errbox"
)

func main() {
	const readerBroken = `
	<root>
		<self/>
		<foo>bar</foo>
		<baz>5</baz>
		<baz>23</baz>
		<baz>yay</baz>
		<baz>49`

	dec := errbox.NewXmlDecoder(
		xml.NewDecoder(
			io.MultiReader(
				strings.NewReader(strings.TrimSpace(readerBroken)),
				iotest.ErrReader(errors.New("sample")),
			),
		),
	)

	for t := range dec.IntoIter() {
		fmt.Printf("%#v\n", t)
	}
	fmt.Printf("stored err: %v\n", dec.Err())
}
Output:

xml.StartElement{Name:xml.Name{Space:"", Local:"root"}, Attr:[]xml.Attr{}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"self"}, Attr:[]xml.Attr{}}
xml.EndElement{Name:xml.Name{Space:"", Local:"self"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"foo"}, Attr:[]xml.Attr{}}
xml.CharData{0x62, 0x61, 0x72}
xml.EndElement{Name:xml.Name{Space:"", Local:"foo"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x35}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x32, 0x33}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x79, 0x61, 0x79}
xml.EndElement{Name:xml.Name{Space:"", Local:"baz"}}
xml.CharData{0xa, 0x9, 0x9}
xml.StartElement{Name:xml.Name{Space:"", Local:"baz"}, Attr:[]xml.Attr{}}
xml.CharData{0x34, 0x39}
stored err: sample

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL