errbox

package
v0.0.18 Latest Latest
Warning

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

Go to latest
Published: Dec 19, 2024 License: MIT Imports: 8 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 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.

Box converts input iterator stateful form by calling iter.Pull2. *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 V paired to the error. *Box.Err returns that error otherwise nil.

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

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.

*Box.Stop must be called to release resource regardless of usage.

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 stateful; breaking and calling seq again continues its iteration without replaying data. If the iterator finds an error, it stops iteration and will no longer produce any data. In that case the error can be inspected by calling *Box.Err.

func (*Box[V]) Stop

func (b *Box[V]) Stop()

Stop releases resources allocated by New. After calling Stop, iterators returned from Box.IntoIter produce no more data.

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)))
	defer dec.Stop()

	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),
			),
		),
	)
	defer dec.Stop()

	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")),
			),
		),
	)
	defer dec.Stop()

	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)
	defer boxed.Stop()

	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)
		},
	)
	defer boxed.Stop()

	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)
	defer boxed.Stop()

	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))))
	defer dec.Stop()

	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)),
			),
		),
	)
	defer dec.Stop()

	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")),
			),
		),
	)
	defer dec.Stop()

	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