Files
ucan/pkg/container/writer.go

146 lines
4.2 KiB
Go
Raw Permalink Normal View History

2024-10-02 11:56:32 +02:00
package container
import (
"bytes"
2024-10-02 11:56:32 +02:00
"io"
2025-12-03 12:19:27 +00:00
"slices"
2024-10-02 11:56:32 +02:00
"github.com/ipld/go-ipld-prime"
2025-01-07 18:16:27 +01:00
"github.com/ipld/go-ipld-prime/codec/cbor"
2024-10-02 11:56:32 +02:00
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/fluent/qp"
"github.com/ipld/go-ipld-prime/node/basicnode"
)
// Writer is a token container writer. It provides a convenient way to aggregate and serialize tokens together.
type Writer map[string]struct{}
2024-10-02 11:56:32 +02:00
func NewWriter() Writer {
return make(Writer)
}
// AddSealed includes a "sealed" token (serialized with a ToSealed* function) in the container.
func (ctn Writer) AddSealed(data []byte) {
ctn[string(data)] = struct{}{}
2024-10-02 11:56:32 +02:00
}
// ToBytes encode the container into raw bytes.
func (ctn Writer) ToBytes() ([]byte, error) {
return ctn.toBytes(headerRawBytes)
}
2024-10-02 11:56:32 +02:00
// ToBytesWriter is the same as ToBytes, but with an io.Writer.
func (ctn Writer) ToBytesWriter(w io.Writer) error {
return ctn.toWriter(headerRawBytes, w)
2024-10-02 11:56:32 +02:00
}
// ToBytesGzipped encode the container into gzipped bytes.
func (ctn Writer) ToBytesGzipped() ([]byte, error) {
return ctn.toBytes(headerRawBytesGzip)
2024-10-02 11:56:32 +02:00
}
// ToBytesGzippedWriter is the same as ToBytesGzipped, but with an io.Writer.
func (ctn Writer) ToBytesGzippedWriter(w io.Writer) error {
return ctn.toWriter(headerRawBytesGzip, w)
}
// ToBase64StdPadding encode the container into a base64 string, with standard encoding and padding.
func (ctn Writer) ToBase64StdPadding() (string, error) {
return ctn.toString(headerBase64StdPadding)
}
// ToBase64StdPaddingWriter is the same as ToBase64StdPadding, but with an io.Writer.
func (ctn Writer) ToBase64StdPaddingWriter(w io.Writer) error {
return ctn.toWriter(headerBase64StdPadding, w)
}
// ToBase64StdPaddingGzipped encode the container into a pre-gzipped base64 string, with standard encoding and padding.
func (ctn Writer) ToBase64StdPaddingGzipped() (string, error) {
return ctn.toString(headerBase64StdPaddingGzip)
}
// ToBase64StdPaddingGzippedWriter is the same as ToBase64StdPaddingGzipped, but with an io.Writer.
func (ctn Writer) ToBase64StdPaddingGzippedWriter(w io.Writer) error {
return ctn.toWriter(headerBase64StdPaddingGzip, w)
}
// ToBase64URL encode the container into base64 string, with URL-safe encoding and no padding.
func (ctn Writer) ToBase64URL() (string, error) {
return ctn.toString(headerBase64URL)
}
// ToBase64URLWriter is the same as ToBase64URL, but with an io.Writer.
func (ctn Writer) ToBase64URLWriter(w io.Writer) error {
return ctn.toWriter(headerBase64URL, w)
}
2025-08-05 12:11:20 +02:00
// ToBase64URLGzipped encode the container into pre-gzipped base64 string, with URL-safe encoding and no padding.
func (ctn Writer) ToBase64URLGzipped() (string, error) {
return ctn.toString(headerBase64URLGzip)
}
2025-08-05 12:11:20 +02:00
// ToBase64URLGzipWriter is the same as ToBase64URL, but with an io.Writer.
func (ctn Writer) ToBase64URLGzipWriter(w io.Writer) error {
return ctn.toWriter(headerBase64URLGzip, w)
}
func (ctn Writer) toBytes(header header) ([]byte, error) {
var buf bytes.Buffer
err := ctn.toWriter(header, &buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (ctn Writer) toString(header header) (string, error) {
var buf bytes.Buffer
err := ctn.toWriter(header, &buf)
if err != nil {
2024-12-11 16:05:16 +01:00
return "", err
}
2024-12-11 16:05:16 +01:00
return buf.String(), nil
}
func (ctn Writer) toWriter(header header, w io.Writer) (err error) {
encoder := header.encoder(w)
defer func() {
err = encoder.Close()
}()
node, err := qp.BuildMap(basicnode.Prototype.Any, 1, func(ma datamodel.MapAssembler) {
qp.MapEntry(ma, containerVersionTag, qp.List(int64(len(ctn)), func(la datamodel.ListAssembler) {
2025-12-03 12:19:27 +00:00
tokens := make([][]byte, 0, len(ctn))
for data := range ctn {
tokens = append(tokens, []byte(data))
}
slices.SortFunc(tokens, bytes.Compare)
for _, data := range tokens {
qp.ListEntry(la, qp.Bytes(data))
}
}))
})
if err != nil {
return err
}
return ipld.EncodeStreaming(encoder, node, cbor.Encode)
2024-10-02 11:56:32 +02:00
}
2025-02-27 14:27:02 +01:00
// ToReader convert a container Writer into a Reader.
// Most likely, you only want to use this in tests for convenience.
// This is not optimized and can panic.
func (ctn Writer) ToReader() Reader {
data, err := ctn.ToBytes()
if err != nil {
panic(err)
}
reader, err := FromBytes(data)
if err != nil {
panic(err)
}
return reader
}