integrity

package
v1.6.1 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: BSD-2-Clause Imports: 20 Imported by: 3

Documentation

Overview

Package integrity provides typed storage with built-in data integrity protection. It automatically computes and verifies hashes and signatures for stored values.

See TypedBuilder for configuration options and Typed for available operations.

Index

Examples

Constants

View Source
const ModRevisionEmpty = 0

ModRevisionEmpty is used to initialize the ModRevision field by default.

Variables

View Source
var (
	ErrBranchNotFired     = errors.New("integrity: transaction branch did not fire")
	ErrTxNotCommitted     = errors.New("integrity: transaction has not been committed")
	ErrTxAlreadyCommitted = errors.New("integrity: transaction has already been committed")
)

Sentinel errors for Tx operations.

View Source
var (
	ErrNotFound                  = errors.New("not found")
	ErrMoreThanOneResult         = errors.New("more than one result was returned")
	ErrInvalidPredicateValueType = errors.New("invalid predicate value type")
	ErrNoValueKey                = errors.New("no value key found in generated keys")
	// ErrPredicateFailed is returned by Put or Delete when predicates are specified
	// but the transaction predicate check fails (i.e., the conditions are not met).
	// Use [WithPutPredicates] or [WithDeletePredicates] to specify predicates.
	ErrPredicateFailed = errors.New("predicate check failed")
)
View Source
var ErrInvalidName = InvalidNameError{/* contains filtered or unexported fields */}

ErrInvalidName is a sentinel error for invalid names.

View Source
var ErrSingleHashCompactCardinality = errors.New(
	"codec: WithSingleHashCompact requires exactly one hasher")

ErrSingleHashCompactCardinality is returned when WithSingleHashCompact is set but the codec is not configured with exactly one hasher.

View Source
var ErrSingleSigCompactCardinality = errors.New(
	"codec: WithSingleSigCompact requires exactly one unique signer/verifier")

ErrSingleSigCompactCardinality is returned when WithSingleSigCompact is set but the union of signers and verifiers (deduped by name) is not of size one.

View Source
var ErrUnknownHasherLocation = errors.New("codec: WithHashLocation key does not match any configured hasher")

ErrUnknownHasherLocation is returned when a WithHashLocation key does not match any configured hasher.

View Source
var ErrUnknownSignerLocation = errors.New(
	"codec: WithSignatureLocation key does not match any configured signer or verifier")

ErrUnknownSignerLocation is returned when a WithSignatureLocation key does not match any configured signer or verifier.

Functions

func IgnoreMoreThanOneResult

func IgnoreMoreThanOneResult() options.OptionCallback[getOptions]

IgnoreMoreThanOneResult returns an option that allows Get operation to succeed when multiple results are returned for a single name. By default, Get returns ErrMoreThanOneResult in such cases.

func IgnoreVerificationError

func IgnoreVerificationError() options.OptionCallback[getOptions]

IgnoreVerificationError returns an option that allows Get and Range operations to return results even if hash or signature verification fails. The returned result will still contain the Error field with verification details.

func WithDeletePredicates added in v1.1.0

func WithDeletePredicates(predicates ...Predicate) options.OptionCallback[deleteOptions]

WithDeletePredicates configures predicates for conditional Delete operations. The Delete operation will only succeed if all predicates evaluate to true. If predicates are specified but fail, ErrPredicateFailed is returned.

func WithPrefix added in v1.1.0

func WithPrefix() options.OptionCallback[deleteOptions]

WithPrefix configures the ability to delete keys by a prefix.

func WithPutPredicates added in v1.1.0

func WithPutPredicates(predicates ...Predicate) options.OptionCallback[putOptions]

WithPutPredicates configures predicates for conditional Put operations. The Put operation will only succeed if all predicates evaluate to true. If predicates are specified but fail, ErrPredicateFailed is returned.

Types

type Branch added in v1.3.0

type Branch struct {
	// contains filtered or unexported fields
}

Branch holds a list of operations and spans for one side of an If/Then/Else. Branch is not goroutine-safe.

type Branchable added in v1.3.0

type Branchable interface {
	// contains filtered or unexported methods
}

Branchable is satisfied by both *Tx (which routes to thenBranch) and *Branch (which routes to itself). The method is unexported so only types in this package can satisfy the interface.

type Codec added in v1.3.0

type Codec[T any] struct {
	// contains filtered or unexported fields
}

Codec holds the integrity schema for type T: marshaller, hashers, signers/verifiers, and three namer instances (generator, validator, and a top-level union). It carries no storage handle and no namespace prefix — namespace scoping is the storage layer's concern.

func (*Codec[T]) Bind added in v1.3.0

func (c *Codec[T]) Bind(s storage.Storage) *Store[T]

Bind creates a new Store[T] by binding c to the given storage. Bind is a cheap struct literal — no caching, no validation.

func (*Codec[T]) BindPredicate added in v1.3.0

func (c *Codec[T]) BindPredicate(name string, pred Predicate) (predicate.Predicate, error)

BindPredicate resolves the value-layer key for name and calls p with it, returning the concrete predicate.Predicate ready for use in Tx.If.

Returns ErrInvalidName for empty, leading-slash, or trailing-slash names — the same rule applied by Put/Delete/Get/Watch — and ErrNoValueKey if the namer emits no value-layer key.

func (*Codec[T]) BindSingleton added in v1.4.0

func (c *Codec[T]) BindSingleton(s storage.Storage, name string) (*SingletonStore[T], error)

BindSingleton binds c to s and bakes name into every operation of the returned SingletonStore[T]. The name is validated eagerly with the same rules as Store[T] operations; an invalid name returns ErrInvalidName.

Example

ExampleCodec_BindSingleton shows the canonical fixed-key configuration pattern: a single object at "/settings/auth" instead of a directory of "/settings/auth/<id>" items. The bound name is supplied once; operations take no name parameter.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type authConfig struct {
	Issuer string `yaml:"issuer"`
}

// ExampleCodec_BindSingleton shows the canonical fixed-key configuration
// pattern: a single object at "/settings/auth" instead of a directory of
// "/settings/auth/<id>" items. The bound name is supplied once; operations
// take no name parameter.
func main() {
	ctx := context.Background()

	codec, err := integrity.NewCodecBuilder[authConfig]().
		WithObjectLocation("settings").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	auth, err := codec.BindSingleton(storage.NewStorage(dummy.New()), "auth")
	if err != nil {
		log.Fatalf("bind singleton: %v", err)
	}

	err = auth.Put(ctx, authConfig{Issuer: "tarantool"})
	if err != nil {
		log.Fatalf("put: %v", err)
	}

	res, err := auth.Get(ctx)
	if err != nil {
		log.Fatalf("get: %v", err)
	}

	fmt.Println("issuer:", res.Value.Unwrap().Issuer)

}
Output:
issuer: tarantool

func (*Codec[T]) FullKeys added in v1.6.0

func (c *Codec[T]) FullKeys(name string) ([]string, error)

FullKeys returns every key the codec's namer would emit for name — the value-layer key plus one key per configured hash and signature layer. The order matches namer.Namer.GenerateNames: value first, then hashes, then signatures.

Returned keys are namer-relative — use Store.FullKeys for keys that include any storage.Prefixed wrapper's prefix.

Returns ErrInvalidName for empty, leading-slash, or trailing-slash names.

func (*Codec[T]) TxDelete added in v1.3.0

func (c *Codec[T]) TxDelete(
	b Branchable,
	name string,
	opts ...options.OptionCallback[deleteOptions],
) error

TxDelete enqueues Delete operations for name onto branch b.

Example

ExampleCodec_TxDelete enqueues a Delete onto the Then branch and removes every key that belongs to the named value (value plus all hash/sig keys).

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type txExampleValue struct {
	Field string `yaml:"field"`
}

func newTxExampleCodec() *integrity.Codec[txExampleValue] {
	codec, err := integrity.NewCodecBuilder[txExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	store := storage.NewStorage(dummy.New())
	codec := newTxExampleCodec()

	seedTx := integrity.NewTx(store)

	err := codec.TxPut(seedTx, "victim", txExampleValue{Field: "x"})
	if err != nil {
		log.Fatalf("seed: %v", err)
	}

	_, err = seedTx.Commit(ctx)
	if err != nil {
		log.Fatalf("seed commit: %v", err)
	}

	delTx := integrity.NewTx(store)

	err = codec.TxDelete(delTx, "victim")
	if err != nil {
		log.Fatalf("delete: %v", err)
	}

	_, err = delTx.Commit(ctx)
	if err != nil {
		log.Fatalf("delete commit: %v", err)
	}

	getTx := integrity.NewTx(store)
	fut := codec.TxGet(getTx, "victim")

	_, err = getTx.Commit(ctx)
	if err != nil {
		log.Fatalf("get commit: %v", err)
	}

	_, err = fut.Result()
	fmt.Println("not found:", errors.Is(err, integrity.ErrNotFound))

}
Output:
not found: true

func (*Codec[T]) TxGet added in v1.3.0

func (c *Codec[T]) TxGet(
	b Branchable,
	name string,
	opts ...options.OptionCallback[getOptions],
) *GetFuture[T]

TxGet enqueues a Get operation for name onto branch b. Returns a *GetFuture[T] whose Result() is populated after Commit.

func (*Codec[T]) TxPut added in v1.3.0

func (c *Codec[T]) TxPut(b Branchable, name string, value T) error

TxPut enqueues Put operations for name/value onto branch b. Returns the first error encountered during build (if any); subsequent enqueue calls after an error are no-ops returning nil.

func (*Codec[T]) TxRange added in v1.3.0

func (c *Codec[T]) TxRange(
	b Branchable,
	name string,
	opts ...options.OptionCallback[getOptions],
) *RangeFuture[T]

TxRange enqueues a range-Get operation for name onto branch b. name == "" fetches everything under the object location.

func (*Codec[T]) ValueEqual added in v1.3.0

func (c *Codec[T]) ValueEqual(value T) (Predicate, error)

ValueEqual creates a predicate that checks if a key's value equals the specified value.

Example

ExampleCodec_ValueEqual produces a Codec-bound predicate that can be passed to Tx.If via Codec.BindPredicate, or used through Store helpers like WithPutPredicates.

package main

import (
	"fmt"
	"log"

	"github.com/tarantool/go-storage/integrity"
)

type codecExampleConfig struct {
	Name string `yaml:"name"`
	N    int    `yaml:"n"`
}

func main() {
	codec, err := integrity.NewCodecBuilder[codecExampleConfig]().WithObjectLocation("objects").Build()
	if err != nil {
		log.Fatalf("build: %v", err)
	}

	pred, err := codec.ValueEqual(codecExampleConfig{Name: "alice", N: 1})
	if err != nil {
		log.Fatalf("predicate: %v", err)
	}

	bound, err := codec.BindPredicate("alice", pred)
	if err != nil {
		log.Fatalf("bind: %v", err)
	}

	// The bound predicate targets the value-layer key for "alice".
	fmt.Printf("predicate key: %s\n", bound.Key())

}
Output:
predicate key: /objects/alice

func (*Codec[T]) ValueKey added in v1.6.0

func (c *Codec[T]) ValueKey(name string) (string, error)

ValueKey returns the value-layer key for name as produced by the codec's namer — e.g. "/objects/<name>" for a codec built with WithObjectLocation("objects"), or "/<name>" for an unnamed codec.

The returned key is namer-relative: it does NOT include any prefix added by a storage.Prefixed wrapper — for the on-disk key, use Store.ValueKey. Hash and signature keys are not included; use Codec.FullKeys for those.

Returns ErrInvalidName for empty, leading-slash, or trailing-slash names, and ErrNoValueKey if the namer emits no value-layer key.

func (*Codec[T]) ValueNotEqual added in v1.3.0

func (c *Codec[T]) ValueNotEqual(value T) (Predicate, error)

ValueNotEqual creates a predicate that checks if a key's value is not equal to the specified value.

func (*Codec[T]) VersionEqual added in v1.3.0

func (c *Codec[T]) VersionEqual(v int64) Predicate

VersionEqual creates a predicate that checks if a key's version equals the specified version.

func (*Codec[T]) VersionGreater added in v1.3.0

func (c *Codec[T]) VersionGreater(v int64) Predicate

VersionGreater creates a predicate that checks if a key's version is greater than the specified version.

func (*Codec[T]) VersionLess added in v1.3.0

func (c *Codec[T]) VersionLess(v int64) Predicate

VersionLess creates a predicate that checks if a key's version is less than the specified version.

func (*Codec[T]) VersionNotEqual added in v1.3.0

func (c *Codec[T]) VersionNotEqual(v int64) Predicate

VersionNotEqual creates a predicate that checks if a key's version is not equal to the specified version.

type CodecBuilder added in v1.3.0

type CodecBuilder[T any] struct {
	// contains filtered or unexported fields
}

CodecBuilder[T] is a fluent, value-receiver builder for Codec[T]. Each setter returns a copy of the builder (copy-on-write), so the original builder is not mutated.

func NewCodecBuilder added in v1.3.0

func NewCodecBuilder[T any]() CodecBuilder[T]

NewCodecBuilder returns a new CodecBuilder with sensible defaults. Default marshaller: TypedYamlMarshaller[T]. Default objectLocation: namer.ObjectLocationMissing (unnamed codec — keys are emitted without the per-codec location segment). Call WithObjectLocation to opt into a named layout like /<location>/<name>. Default namerFunc: wraps namer.NewLayeredNamer.

Example

ExampleNewCodecBuilder builds a Codec[T] using the fluent builder. Each WithX call returns a copy, so the original builder is never mutated.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type codecExampleConfig struct {
	Name string `yaml:"name"`
	N    int    `yaml:"n"`
}

func main() {
	codec, err := integrity.NewCodecBuilder[codecExampleConfig]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	store := codec.Bind(storage.NewStorage(dummy.New()))

	ctx := context.Background()

	err = store.Put(ctx, "alice", codecExampleConfig{Name: "alice", N: 42})
	if err != nil {
		log.Fatalf("put: %v", err)
	}

	res, err := store.Get(ctx, "alice")
	if err != nil {
		log.Fatalf("get: %v", err)
	}

	got := res.Value.Unwrap()
	fmt.Printf("Name=%s N=%d\n", got.Name, got.N)

}
Output:
Name=alice N=42
Example (Immutability)

ExampleNewCodecBuilder_immutability demonstrates copy-on-write: the original builder is unchanged after a setter call, so two divergent codecs can be built from the same base.

package main

import (
	"fmt"
	"log"

	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type codecExampleConfig struct {
	Name string `yaml:"name"`
	N    int    `yaml:"n"`
}

func main() {
	base := integrity.NewCodecBuilder[codecExampleConfig]().WithObjectLocation("objects")

	withHasher := base.WithHasher(hasher.NewSHA256Hasher())
	withoutHasher := base // unchanged.

	codecHashed, err := withHasher.Build()
	if err != nil {
		log.Fatalf("codecHashed: %v", err)
	}

	codecPlain, err := withoutHasher.Build()
	if err != nil {
		log.Fatalf("codecPlain: %v", err)
	}

	fmt.Println("two distinct codecs:", codecHashed != codecPlain)

}
Output:
two distinct codecs: true

func (CodecBuilder[T]) Build added in v1.3.0

func (b CodecBuilder[T]) Build() (*Codec[T], error)

Build constructs a *Codec[T] from the current builder state.

It returns an error if any location segment is invalid, if any pair of segments collides, or if a WithHashLocation/WithSignatureLocation key does not match a configured hasher/signer/verifier (catches typos that would otherwise be silently ignored).

func (CodecBuilder[T]) WithHashLocation added in v1.3.0

func (b CodecBuilder[T]) WithHashLocation(hasherName, loc string) CodecBuilder[T]

WithHashLocation overrides the location segment for the named hasher. hasherName must match the hasher's Name() method. If not called, the location defaults to the hasher's Name().

func (CodecBuilder[T]) WithHasher added in v1.3.0

func (b CodecBuilder[T]) WithHasher(h hasher.Hasher) CodecBuilder[T]

WithHasher adds a hasher to the codec.

func (CodecBuilder[T]) WithKeyPrefix added in v1.6.0

func (b CodecBuilder[T]) WithKeyPrefix(prefix string) CodecBuilder[T]

WithKeyPrefix prepends the given path prefix to every key the codec emits and parses. This lets multiple codecs share a single storage.Storage while keeping their keys in disjoint sub-trees — and therefore stay atomic in a single integrity.Tx, which cannot span multiple storage handles.

The prefix must start with '/' and must not end with '/'; empty is a no-op. The validation runs in Build via the underlying namer, which returns namer.ErrKeyPrefixNoLeadingSlash or namer.ErrKeyPrefixTrailingSlash on malformed input.

Custom namers passed through WithNamer receive the prefix as an extra namer.LayeredOption. Namers that ignore LayeredOptions silently drop the prefix; the default namer.NewLayeredNamer honours it.

func (CodecBuilder[T]) WithMarshaller added in v1.3.0

func (b CodecBuilder[T]) WithMarshaller(m marshaller.TypedMarshaller[T]) CodecBuilder[T]

WithMarshaller sets a custom marshaller.

func (CodecBuilder[T]) WithNamer added in v1.3.0

func (b CodecBuilder[T]) WithNamer(f CodecNamerConstructor) CodecBuilder[T]

WithNamer sets a custom CodecNamerConstructor.

func (CodecBuilder[T]) WithObjectLocation added in v1.3.0

func (b CodecBuilder[T]) WithObjectLocation(loc string) CodecBuilder[T]

WithObjectLocation sets the location segment for value keys. If not called, the codec is built in unnamed mode (no per-codec location segment); see NewCodecBuilder.

Example

ExampleCodecBuilder_WithObjectLocation shows overriding the value-layer location segment. The default is "objects".

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type codecExampleConfig struct {
	Name string `yaml:"name"`
	N    int    `yaml:"n"`
}

func main() {
	codec, err := integrity.NewCodecBuilder[codecExampleConfig]().WithObjectLocation("objects").
		WithObjectLocation("users").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	store := codec.Bind(storage.NewStorage(dummy.New()))

	ctx := context.Background()

	err = store.Put(ctx, "alice", codecExampleConfig{Name: "alice", N: 1})
	if err != nil {
		log.Fatalf("put: %v", err)
	}

	// The value lands under /users/alice instead of /objects/alice.
	res, err := store.Get(ctx, "alice")
	if err != nil {
		log.Fatalf("get: %v", err)
	}

	fmt.Println("name:", res.Value.Unwrap().Name)

}
Output:
name: alice

func (CodecBuilder[T]) WithSignatureLocation added in v1.3.0

func (b CodecBuilder[T]) WithSignatureLocation(signerName, loc string) CodecBuilder[T]

WithSignatureLocation overrides the location segment for the named signer/verifier. signerName must match the signer's or verifier's Name() method. If not called, the location defaults to the signer's Name().

func (CodecBuilder[T]) WithSigner added in v1.3.0

func (b CodecBuilder[T]) WithSigner(s crypto.Signer) CodecBuilder[T]

WithSigner adds a signer to the codec.

func (CodecBuilder[T]) WithSignerVerifier added in v1.3.0

func (b CodecBuilder[T]) WithSignerVerifier(sv crypto.SignerVerifier) CodecBuilder[T]

WithSignerVerifier adds a combined signer/verifier to both the signer and verifier lists.

Example

ExampleCodecBuilder_WithSignerVerifier configures a codec with both a hasher and an RSA-PSS signer/verifier. Put writes value, hash, and signature keys; Get verifies all of them on read.

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/crypto"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type codecExampleConfig struct {
	Name string `yaml:"name"`
	N    int    `yaml:"n"`
}

func main() {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatalf("genkey: %v", err)
	}

	codec, err := integrity.NewCodecBuilder[codecExampleConfig]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		WithSignerVerifier(crypto.NewRSAPSSSignerVerifier(*priv)).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	store := codec.Bind(storage.NewStorage(dummy.New()))

	ctx := context.Background()

	err = store.Put(ctx, "alice", codecExampleConfig{Name: "alice", N: 7})
	if err != nil {
		log.Fatalf("put: %v", err)
	}

	res, err := store.Get(ctx, "alice")
	if err != nil {
		log.Fatalf("get: %v", err)
	}

	fmt.Printf("verified Name=%s N=%d\n", res.Value.Unwrap().Name, res.Value.Unwrap().N)

}
Output:
verified Name=alice N=7

func (CodecBuilder[T]) WithSingleHashCompact added in v1.3.0

func (b CodecBuilder[T]) WithSingleHashCompact() CodecBuilder[T]

WithSingleHashCompact opts the codec into the compact hash key layout (/hashes/<objectLocation>/<name>, dropping the per-hasher segment). Build returns ErrSingleHashCompactCardinality if exactly one hasher is not configured.

func (CodecBuilder[T]) WithSingleSigCompact added in v1.3.0

func (b CodecBuilder[T]) WithSingleSigCompact() CodecBuilder[T]

WithSingleSigCompact opts the codec into the compact sig key layout (/sig/<objectLocation>/<name>, dropping the per-signer segment). Build returns ErrSingleSigCompactCardinality if the union of signers and verifiers (deduped by name) is not of size one.

func (CodecBuilder[T]) WithVerifier added in v1.3.0

func (b CodecBuilder[T]) WithVerifier(v crypto.Verifier) CodecBuilder[T]

WithVerifier adds a verifier to the codec.

type CodecNamerConstructor added in v1.3.0

type CodecNamerConstructor func(
	objectLocation string,
	hashLocations []namer.LayeredHashLocation,
	sigLocations []namer.LayeredSigLocation,
	opts ...namer.LayeredOption,
) (namer.Namer, error)

CodecNamerConstructor builds a namer for a Codec given the resolved object/hash/sig location bindings and any LayeredOptions threaded through from the builder (e.g. compact-mode flags).

type FailedToComputeHashError

type FailedToComputeHashError struct {
	// contains filtered or unexported fields
}

FailedToComputeHashError represents an error when hash computation fails.

func (FailedToComputeHashError) Error

func (e FailedToComputeHashError) Error() string

Error returns a string representation of the hash computation error.

func (FailedToComputeHashError) Unwrap

func (e FailedToComputeHashError) Unwrap() error

Unwrap returns the underlying error that caused the hash computation failure.

type FailedToGenerateKeysError

type FailedToGenerateKeysError struct {
	// contains filtered or unexported fields
}

FailedToGenerateKeysError represents an error when key generation fails.

func (FailedToGenerateKeysError) Error

Error returns a string representation of the key generation error.

func (FailedToGenerateKeysError) Unwrap

func (e FailedToGenerateKeysError) Unwrap() error

Unwrap returns the underlying error that caused the key generation failure.

type FailedToGenerateSignatureError

type FailedToGenerateSignatureError struct {
	// contains filtered or unexported fields
}

FailedToGenerateSignatureError represents an error when signature generation fails.

func (FailedToGenerateSignatureError) Error

Error returns a string representation of the signature generation error.

func (FailedToGenerateSignatureError) Unwrap

Unwrap returns the underlying error that caused the signature generation failure.

type FailedToMarshalValueError

type FailedToMarshalValueError struct {
	// contains filtered or unexported fields
}

FailedToMarshalValueError represents an error when value marshalling fails.

func (FailedToMarshalValueError) Error

Error returns a string representation of the marshalling error.

func (FailedToMarshalValueError) Unwrap

func (e FailedToMarshalValueError) Unwrap() error

Unwrap returns the underlying error that caused the marshalling failure.

type FailedToValidateAggregatedError

type FailedToValidateAggregatedError struct {
	// contains filtered or unexported fields
}

FailedToValidateAggregatedError represents aggregated validation errors.

func (*FailedToValidateAggregatedError) Append

func (e *FailedToValidateAggregatedError) Append(err error)

Append adds an error to the aggregated error.

func (*FailedToValidateAggregatedError) Error

Error returns a string representation of the aggregated error.

func (*FailedToValidateAggregatedError) Finalize

func (e *FailedToValidateAggregatedError) Finalize() error

Finalize returns nil if there are no errors, otherwise returns error or the aggregated error.

func (*FailedToValidateAggregatedError) Unwrap

func (e *FailedToValidateAggregatedError) Unwrap() []error

Unwrap returns the underlying slice of errors.

type Generator

type Generator[T any] struct {
	// contains filtered or unexported fields
}

Generator creates integrity-protected key-value pairs for storage.

func NewGenerator

func NewGenerator[T any](
	namer namer.Namer,
	marshaller marshaller.TypedMarshaller[T],
	hashers []hasher.Hasher,
	signers []crypto.Signer,
) Generator[T]

NewGenerator creates a new Generator instance.

func (Generator[T]) Generate

func (g Generator[T]) Generate(name string, value T) ([]kv.KeyValue, error)

Generate creates integrity-protected key-value pairs for the given object.

type GetFuture added in v1.3.0

type GetFuture[T any] struct {
	// contains filtered or unexported fields
}

GetFuture holds the result of a TxGet enqueue. Call Result() after Commit.

Example

ExampleGetFuture shows how TxGet hands back a *GetFuture[T] whose Result() is populated only after Commit.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type txExampleValue struct {
	Field string `yaml:"field"`
}

func newTxExampleCodec() *integrity.Codec[txExampleValue] {
	codec, err := integrity.NewCodecBuilder[txExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	store := storage.NewStorage(dummy.New())
	codec := newTxExampleCodec()

	seedTx := integrity.NewTx(store)

	err := codec.TxPut(seedTx, "alice", txExampleValue{Field: "hello"})
	if err != nil {
		log.Fatalf("seed: %v", err)
	}

	_, err = seedTx.Commit(ctx)
	if err != nil {
		log.Fatalf("seed commit: %v", err)
	}

	readTx := integrity.NewTx(store)
	fut := codec.TxGet(readTx, "alice")

	// Calling Result() before Commit returns ErrTxNotCommitted.
	_, err = fut.Result()
	fmt.Println("before commit:", errors.Is(err, integrity.ErrTxNotCommitted))

	_, err = readTx.Commit(ctx)
	if err != nil {
		log.Fatalf("read commit: %v", err)
	}

	res, err := fut.Result()
	if err != nil {
		log.Fatalf("result: %v", err)
	}

	fmt.Println("field:", res.Value.Unwrap().Field)

}
Output:
before commit: true
field: hello

func (*GetFuture[T]) Result added in v1.3.0

func (f *GetFuture[T]) Result() (ValidatedResult[T], error)

Result returns the validated result after the Tx has been committed.

Returns ErrTxNotCommitted if called before Commit. Returns the stored buildErr if Commit surfaced a build error. Returns ErrBranchNotFired if this future's branch did not fire.

type ImpossibleError

type ImpossibleError struct {
	// contains filtered or unexported fields
}

ImpossibleError represents an error when an integrity operation cannot be performed due to internal problems.

func (ImpossibleError) Error

func (e ImpossibleError) Error() string

type InvalidNameError

type InvalidNameError struct {
	// contains filtered or unexported fields
}

InvalidNameError represents an error when a name is invalid.

func (InvalidNameError) Error

func (e InvalidNameError) Error() string

Error returns a string representation of the invalid name error.

type NamerConstructor

type NamerConstructor func(prefix string, hashNames []string, sigNames []string) namer.Namer

type Predicate added in v1.1.0

type Predicate func(key []byte) predicate.Predicate

type RangeFuture added in v1.3.0

type RangeFuture[T any] struct {
	// contains filtered or unexported fields
}

RangeFuture holds the results of a TxRange enqueue. Call Result() after Commit.

Example

ExampleRangeFuture shows TxRange returning every value under a name prefix in a single transaction. Use "" to fetch every value the codec owns; pass a non-empty prefix (must end with "/") to scope.

package main

import (
	"context"
	"fmt"
	"log"
	"sort"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type txExampleValue struct {
	Field string `yaml:"field"`
}

func newTxExampleCodec() *integrity.Codec[txExampleValue] {
	codec, err := integrity.NewCodecBuilder[txExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	store := storage.NewStorage(dummy.New())
	codec := newTxExampleCodec()

	seedTx := integrity.NewTx(store)
	for _, name := range []string{"users/alice", "users/bob"} {
		err := codec.TxPut(seedTx, name, txExampleValue{Field: name})
		if err != nil {
			log.Fatalf("seed %s: %v", name, err)
		}
	}

	_, err := seedTx.Commit(ctx)
	if err != nil {
		log.Fatalf("seed commit: %v", err)
	}

	readTx := integrity.NewTx(store)
	fut := codec.TxRange(readTx, "users/")

	_, err = readTx.Commit(ctx)
	if err != nil {
		log.Fatalf("read commit: %v", err)
	}

	results, err := fut.Result()
	if err != nil {
		log.Fatalf("result: %v", err)
	}

	names := make([]string, 0, len(results))
	for _, r := range results {
		names = append(names, r.Name)
	}

	sort.Strings(names)

	for _, n := range names {
		fmt.Println(n)
	}

}
Output:
users/alice
users/bob

func (*RangeFuture[T]) Result added in v1.3.0

func (f *RangeFuture[T]) Result() ([]ValidatedResult[T], error)

Result returns the validated results after the Tx has been committed.

type Response added in v1.3.0

type Response struct {
	Succeeded bool
}

Response is the result of a Tx.Commit call. It deliberately exposes less than tx.Response so the integrity layer stays storage-agnostic.

type SingletonStore added in v1.4.0

type SingletonStore[T any] struct {
	// contains filtered or unexported fields
}

SingletonStore[T] is a Store[T] bound to one fixed name. It is intended for configuration-style objects that live at a single, known key (e.g. "/settings/auth") rather than under a directory of "/settings/auth/<id>" items. The name is supplied once at bind time and threaded through every operation, so call sites no longer carry the noisy identifier.

Construct one via Codec[T].BindSingleton. The wire layout is identical to what Store[T] produces for the same name — no separate codec, no separate namer.

func (*SingletonStore[T]) BindPredicate added in v1.4.0

func (s *SingletonStore[T]) BindPredicate(pred Predicate) (predicate.Predicate, error)

BindPredicate resolves the singleton's value-layer key and applies p to it, returning a concrete predicate.Predicate ready for use in Tx.If.

func (*SingletonStore[T]) Delete added in v1.4.0

func (s *SingletonStore[T]) Delete(
	ctx context.Context,
	opts ...options.OptionCallback[deleteOptions],
) error

Delete removes the singleton's value, hash, and signature keys. WithDeletePredicates(...) makes the delete conditional; ErrPredicateFailed is returned if any predicate fails.

func (*SingletonStore[T]) FullKeys added in v1.6.0

func (s *SingletonStore[T]) FullKeys() ([]string, error)

FullKeys returns every on-disk key the bound codec would produce for the singleton (value + hashes + signatures). See Store.FullKeys.

func (*SingletonStore[T]) Get added in v1.4.0

func (s *SingletonStore[T]) Get(
	ctx context.Context,
	opts ...options.OptionCallback[getOptions],
) (ValidatedResult[T], error)

Get reads the singleton's value and verifies its hashes/signatures.

func (*SingletonStore[T]) Put added in v1.4.0

func (s *SingletonStore[T]) Put(
	ctx context.Context,
	value T,
	opts ...options.OptionCallback[putOptions],
) error

Put writes value under the singleton's bound name with integrity protection. WithPutPredicates(...) makes the write conditional; ErrPredicateFailed is returned if any predicate fails.

func (*SingletonStore[T]) TxDelete added in v1.4.0

func (s *SingletonStore[T]) TxDelete(
	b Branchable,
	opts ...options.OptionCallback[deleteOptions],
) error

TxDelete enqueues a Delete for the singleton onto branch b. Mirrors Codec[T].TxDelete without the name parameter.

func (*SingletonStore[T]) TxGet added in v1.4.0

func (s *SingletonStore[T]) TxGet(
	b Branchable,
	opts ...options.OptionCallback[getOptions],
) *GetFuture[T]

TxGet enqueues a Get for the singleton onto branch b, returning a future whose Result() is populated after Commit. Mirrors Codec[T].TxGet without the name parameter — the bound name is used.

func (*SingletonStore[T]) TxPut added in v1.4.0

func (s *SingletonStore[T]) TxPut(b Branchable, value T) error

TxPut enqueues a Put for the singleton onto branch b. Mirrors Codec[T].TxPut without the name parameter.

func (*SingletonStore[T]) ValueKey added in v1.6.0

func (s *SingletonStore[T]) ValueKey() (string, error)

ValueKey returns the on-disk value-layer key for the singleton's bound name. See Store.ValueKey.

func (*SingletonStore[T]) Watch added in v1.4.0

func (s *SingletonStore[T]) Watch(ctx context.Context) (<-chan watch.Event, error)

Watch returns a channel that receives events when the singleton's value-layer key changes. Hash and signature key changes are not surfaced — consumers re-fetch on signal, which re-runs verification.

type Store added in v1.3.0

type Store[T any] struct {
	// contains filtered or unexported fields
}

Store[T] is a Codec[T] bound to a concrete storage.Storage. Each method is a thin wrapper that builds a *Tx, enqueues one op via the codec, commits, and returns the future's result — observably indistinguishable from a single-op Tx.Commit. Store[T] holds nothing beyond the codec and storage handle, so binding the same codec to different storages is cheap.

Example

ExampleStore demonstrates a Put/Get round-trip via Store[T]. Each method is a thin single-op wrapper around a Tx — observably indistinguishable from a single-op Tx.Commit.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type storeExampleValue struct {
	Field string `yaml:"field"`
}

func newExampleCodec() *integrity.Codec[storeExampleValue] {
	codec, err := integrity.NewCodecBuilder[storeExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func newExampleStore() *integrity.Store[storeExampleValue] {
	return newExampleCodec().Bind(storage.NewStorage(dummy.New()))
}

func main() {
	ctx := context.Background()
	store := newExampleStore()

	err := store.Put(ctx, "alice", storeExampleValue{Field: "hello"})
	if err != nil {
		log.Fatalf("put: %v", err)
	}

	res, err := store.Get(ctx, "alice")
	if err != nil {
		log.Fatalf("get: %v", err)
	}

	fmt.Println("name:", res.Name)
	fmt.Println("field:", res.Value.Unwrap().Field)

}
Output:
name: alice
field: hello

func (*Store[T]) Delete added in v1.3.0

func (s *Store[T]) Delete(
	ctx context.Context,
	name string,
	vOpts ...options.OptionCallback[deleteOptions],
) error

Delete removes a named value with integrity protection. Accepts WithDeletePredicates(...) and WithPrefix(); if any predicate fails, ErrPredicateFailed is returned.

Example (WithPrefix)

ExampleStore_Delete_withPrefix removes every value (and its hashes) whose name starts with the given prefix. The prefix must end with '/'.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type storeExampleValue struct {
	Field string `yaml:"field"`
}

func newExampleCodec() *integrity.Codec[storeExampleValue] {
	codec, err := integrity.NewCodecBuilder[storeExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func newExampleStore() *integrity.Store[storeExampleValue] {
	return newExampleCodec().Bind(storage.NewStorage(dummy.New()))
}

func main() {
	ctx := context.Background()
	store := newExampleStore()

	for _, name := range []string{"tmp/a", "tmp/b"} {
		err := store.Put(ctx, name, storeExampleValue{Field: name})
		if err != nil {
			log.Fatalf("put %s: %v", name, err)
		}
	}

	before, err := store.Range(ctx, "tmp/")
	if err != nil {
		log.Fatalf("range before: %v", err)
	}

	fmt.Println("before:", len(before))

	err = store.Delete(ctx, "tmp/", integrity.WithPrefix())
	if err != nil {
		log.Fatalf("delete: %v", err)
	}

	after, err := store.Range(ctx, "tmp/")
	if err != nil {
		log.Fatalf("range after: %v", err)
	}

	fmt.Println("after:", len(after))

}
Output:
before: 2
after: 0

func (*Store[T]) FullKeys added in v1.6.0

func (s *Store[T]) FullKeys(name string) ([]string, error)

FullKeys returns every on-disk key the bound codec would produce for name (value + hashes + signatures), each prefixed with the bound storage's prefix if it is wrapped with storage.Prefixed. See Codec.FullKeys for the namer-relative form.

func (*Store[T]) Get added in v1.3.0

func (s *Store[T]) Get(
	ctx context.Context,
	name string,
	opts ...options.OptionCallback[getOptions],
) (ValidatedResult[T], error)

Get retrieves and validates a single named value from storage.

func (*Store[T]) Put added in v1.3.0

func (s *Store[T]) Put(
	ctx context.Context,
	name string,
	value T,
	vOpts ...options.OptionCallback[putOptions],
) error

Put stores a named value with integrity protection. Accepts WithPutPredicates(...) to add conditional predicates; if any predicate fails, ErrPredicateFailed is returned.

Example (Predicate)

ExampleStore_Put_predicate uses WithPutPredicates to make the write conditional. ErrPredicateFailed is returned when the predicate does not hold.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type storeExampleValue struct {
	Field string `yaml:"field"`
}

func newExampleCodec() *integrity.Codec[storeExampleValue] {
	codec, err := integrity.NewCodecBuilder[storeExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	codec := newExampleCodec()
	store := codec.Bind(storage.NewStorage(dummy.New()))

	err := store.Put(ctx, "k", storeExampleValue{Field: "v1"})
	if err != nil {
		log.Fatalf("seed: %v", err)
	}

	pred, err := codec.ValueEqual(storeExampleValue{Field: "v1"})
	if err != nil {
		log.Fatalf("predicate: %v", err)
	}

	// Predicate matches: this update succeeds.
	err = store.Put(ctx, "k", storeExampleValue{Field: "v2"},
		integrity.WithPutPredicates(pred))
	fmt.Println("first update err:", err)

	// Predicate no longer matches (current value is "v2"): ErrPredicateFailed.
	err = store.Put(ctx, "k", storeExampleValue{Field: "v3"},
		integrity.WithPutPredicates(pred))
	fmt.Println("second update is ErrPredicateFailed:", errors.Is(err, integrity.ErrPredicateFailed))

}
Output:
first update err: <nil>
second update is ErrPredicateFailed: true

func (*Store[T]) Range added in v1.3.0

func (s *Store[T]) Range(
	ctx context.Context,
	name string,
	opts ...options.OptionCallback[getOptions],
) ([]ValidatedResult[T], error)

Range retrieves and validates all values under the given name prefix.

Example

ExampleStore_Range fetches and validates every value under a name prefix. Pass "" to fetch everything under the codec's object location.

package main

import (
	"context"
	"fmt"
	"log"
	"sort"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type storeExampleValue struct {
	Field string `yaml:"field"`
}

func newExampleCodec() *integrity.Codec[storeExampleValue] {
	codec, err := integrity.NewCodecBuilder[storeExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func newExampleStore() *integrity.Store[storeExampleValue] {
	return newExampleCodec().Bind(storage.NewStorage(dummy.New()))
}

func main() {
	ctx := context.Background()
	store := newExampleStore()

	for _, name := range []string{"users/alice", "users/bob", "groups/admins"} {
		err := store.Put(ctx, name, storeExampleValue{Field: name})
		if err != nil {
			log.Fatalf("put %s: %v", name, err)
		}
	}

	results, err := store.Range(ctx, "users/")
	if err != nil {
		log.Fatalf("range: %v", err)
	}

	names := make([]string, 0, len(results))
	for _, r := range results {
		names = append(names, r.Name)
	}

	sort.Strings(names)

	for _, n := range names {
		fmt.Println(n)
	}

}
Output:
users/alice
users/bob

func (*Store[T]) ValueKey added in v1.6.0

func (s *Store[T]) ValueKey(name string) (string, error)

ValueKey returns the on-disk value-layer key for name — the namer's value key prefixed with the bound storage's prefix (if it is wrapped with storage.Prefixed). See Codec.ValueKey for the namer-relative form.

func (*Store[T]) Watch added in v1.3.0

func (s *Store[T]) Watch(ctx context.Context, name string) (<-chan watch.Event, error)

Watch returns a channel that receives events for values under the given name prefix. Watch is not transactional and bypasses Tx; it calls s.storage.Watch directly.

Under the signal-only Event.Prefix contract every event carries the watched prefix verbatim (driver-stripped of its trailing "/"), so filtering on event.Prefix is a no-op — callers must Range/Get to learn what changed.

Example

ExampleStore_Watch streams events for changes to a specific name. The emitted event Prefix is the codec-internal absolute key; Watch filters out events whose key does not belong to this codec's namespace.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type storeExampleValue struct {
	Field string `yaml:"field"`
}

func newExampleCodec() *integrity.Codec[storeExampleValue] {
	codec, err := integrity.NewCodecBuilder[storeExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func newExampleStore() *integrity.Store[storeExampleValue] {
	return newExampleCodec().Bind(storage.NewStorage(dummy.New()))
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())

	store := newExampleStore()

	events, err := store.Watch(ctx, "alice")
	if err != nil {
		cancel()
		log.Fatalf("watch: %v", err)
	}

	go func() {
		_ = store.Put(ctx, "alice", storeExampleValue{Field: "v"})
	}()

	ev := <-events

	cancel()

	fmt.Println("event:", string(ev.Prefix))

}
Output:
event: /objects/alice

type Tx added in v1.3.0

type Tx struct {
	// contains filtered or unexported fields
}

Tx is the user-facing multi-op, multi-codec transactional accumulator. Tx is not goroutine-safe.

Example (ThenElse)

ExampleTx_thenElse routes operations to the Then or Else branch, depending on which side fires after If predicates are evaluated. Futures attached to a branch that did not fire return ErrBranchNotFired.

package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type txExampleValue struct {
	Field string `yaml:"field"`
}

func newTxExampleCodec() *integrity.Codec[txExampleValue] {
	codec, err := integrity.NewCodecBuilder[txExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	store := storage.NewStorage(dummy.New())
	codec := newTxExampleCodec()

	// Seed a value the Else branch will read.
	seedTx := integrity.NewTx(store)

	err := codec.TxPut(seedTx, "fallback", txExampleValue{Field: "fallback-value"})
	if err != nil {
		log.Fatalf("seed: %v", err)
	}

	_, err = seedTx.Commit(ctx)
	if err != nil {
		log.Fatalf("seed commit: %v", err)
	}

	// Build a tx whose If can never hold.
	mainTx := integrity.NewTx(store)

	pred, err := codec.BindPredicate("missing", codec.VersionGreater(999))
	if err != nil {
		log.Fatalf("bind: %v", err)
	}

	mainTx.If(pred)

	thenFut := codec.TxGet(mainTx.Then(), "missing")
	elseFut := codec.TxGet(mainTx.Else(), "fallback")

	resp, err := mainTx.Commit(ctx)
	if err != nil {
		log.Fatalf("commit: %v", err)
	}

	fmt.Println("succeeded:", resp.Succeeded)

	_, thenErr := thenFut.Result()
	if errors.Is(thenErr, integrity.ErrBranchNotFired) {
		fmt.Println("then branch did not fire")
	}

	res, err := elseFut.Result()
	if err != nil {
		log.Fatalf("else result: %v", err)
	}

	fmt.Println("else field:", res.Value.Unwrap().Field)

}
Output:
succeeded: false
then branch did not fire
else field: fallback-value

func NewTx added in v1.3.0

func NewTx(s storage.Storage) *Tx

NewTx creates a new Tx backed by the given storage.

Example

ExampleNewTx demonstrates a multi-codec, multi-op transaction. Two TxPut calls are accumulated, then committed atomically with a single storage call.

package main

import (
	"context"
	"fmt"
	"log"

	storage "github.com/tarantool/go-storage"
	"github.com/tarantool/go-storage/driver/dummy"
	"github.com/tarantool/go-storage/hasher"
	"github.com/tarantool/go-storage/integrity"
)

type txExampleValue struct {
	Field string `yaml:"field"`
}

func newTxExampleCodec() *integrity.Codec[txExampleValue] {
	codec, err := integrity.NewCodecBuilder[txExampleValue]().WithObjectLocation("objects").
		WithHasher(hasher.NewSHA256Hasher()).
		Build()
	if err != nil {
		log.Fatalf("build codec: %v", err)
	}

	return codec
}

func main() {
	ctx := context.Background()
	store := storage.NewStorage(dummy.New())
	codec := newTxExampleCodec()

	txn := integrity.NewTx(store)

	err := codec.TxPut(txn, "alice", txExampleValue{Field: "a"})
	if err != nil {
		log.Fatalf("put alice: %v", err)
	}

	err = codec.TxPut(txn, "bob", txExampleValue{Field: "b"})
	if err != nil {
		log.Fatalf("put bob: %v", err)
	}

	resp, err := txn.Commit(ctx)
	if err != nil {
		log.Fatalf("commit: %v", err)
	}

	fmt.Println("succeeded:", resp.Succeeded)

}
Output:
succeeded: true

func (*Tx) Commit added in v1.3.0

func (t *Tx) Commit(ctx context.Context) (Response, error)

Commit executes the accumulated operations as a single atomic storage call. A second call returns ErrTxAlreadyCommitted without touching storage.

func (*Tx) Else added in v1.3.0

func (t *Tx) Else() *Branch

Else lazily creates and returns the Else branch. Idempotent.

func (*Tx) If added in v1.3.0

func (t *Tx) If(preds ...predicate.Predicate) *Tx

If appends predicates to the transaction condition. Multiple calls accumulate, they do not replace. Returns the receiver for chaining.

func (*Tx) Then added in v1.3.0

func (t *Tx) Then() *Branch

Then returns the Then branch. Always returns the same *Branch.

type Typed

type Typed[T any] struct {
	// contains filtered or unexported fields
}

Typed provides integrity-protected storage operations for typed values.

func (*Typed[T]) Delete

func (t *Typed[T]) Delete(ctx context.Context, name string, vOpts ...options.OptionCallback[deleteOptions]) error

Delete removes a named value with integrity protection. Use WithPrefix to delete all values under a prefix. Use WithDeletePredicates to specify conditions that must be met for the operation to succeed. If predicates are specified but fail, ErrPredicateFailed is returned.

func (*Typed[T]) Get

func (t *Typed[T]) Get(
	ctx context.Context,
	name string,
	vOpts ...options.OptionCallback[getOptions],
) (ValidatedResult[T], error)

Get retrieves and validates a single named value from storage.

func (*Typed[T]) Put

func (t *Typed[T]) Put(ctx context.Context, name string, val T, vOpts ...options.OptionCallback[putOptions]) error

Put stores a named value with integrity protection. Use WithPutPredicates to specify conditions that must be met for the operation to succeed. If predicates are specified but fail, ErrPredicateFailed is returned.

func (*Typed[T]) Range

func (t *Typed[T]) Range(
	ctx context.Context,
	name string,
	vOpts ...options.OptionCallback[getOptions],
) ([]ValidatedResult[T], error)

Range retrieves and validates all values under the given name prefix.

func (*Typed[T]) ValueEqual added in v1.1.0

func (t *Typed[T]) ValueEqual(value T) (Predicate, error)

ValueEqual creates a predicate that checks if a key's value equals the specified value.

func (*Typed[T]) ValueNotEqual added in v1.1.0

func (t *Typed[T]) ValueNotEqual(value T) (Predicate, error)

ValueNotEqual creates a predicate that checks if a key's value is not equal to the specified value.

func (*Typed[T]) VersionEqual added in v1.1.0

func (t *Typed[T]) VersionEqual(value int64) Predicate

VersionEqual creates a predicate that checks if a key's version equals the specified version.

func (*Typed[T]) VersionGreater added in v1.1.0

func (t *Typed[T]) VersionGreater(value int64) Predicate

VersionGreater creates a predicate that checks if a key's version is greater than the specified version.

func (*Typed[T]) VersionLess added in v1.1.0

func (t *Typed[T]) VersionLess(value int64) Predicate

VersionLess creates a predicate that checks if a key's version is less than the specified version.

func (*Typed[T]) VersionNotEqual added in v1.1.0

func (t *Typed[T]) VersionNotEqual(value int64) Predicate

VersionNotEqual creates a predicate that checks if a key's version is not equal to the specified version.

func (*Typed[T]) Watch

func (t *Typed[T]) Watch(ctx context.Context, name string) (<-chan watch.Event, error)

Watch returns a channel for watching changes to values under the given name prefix.

Under the signal-only Event.Prefix contract every event carries the watched prefix verbatim (driver-stripped of its trailing "/"), so filtering on event.Prefix is a no-op — callers must Range/Get to learn what changed.

type TypedBuilder

type TypedBuilder[T any] struct {
	// contains filtered or unexported fields
}

TypedBuilder builds typed storage instances with integrity protection.

func NewTypedBuilder

func NewTypedBuilder[T any](storageInstance storage.Storage) TypedBuilder[T]

NewTypedBuilder creates a new TypedBuilder for the given storage instance.

func (TypedBuilder[T]) Build

func (s TypedBuilder[T]) Build() *Typed[T]

Build creates a new Typed storage instance with the configured options.

func (TypedBuilder[T]) WithHasher

func (s TypedBuilder[T]) WithHasher(h hasher.Hasher) TypedBuilder[T]

WithHasher adds a hasher to the builder.

func (TypedBuilder[T]) WithMarshaller

func (s TypedBuilder[T]) WithMarshaller(marshaller marshaller.TypedMarshaller[T]) TypedBuilder[T]

WithMarshaller sets the marshaller for the builder.

func (TypedBuilder[T]) WithNamer

func (s TypedBuilder[T]) WithNamer(namerFunc NamerConstructor) TypedBuilder[T]

WithNamer sets the namer for the builder using a constructor function. The constructor function will be called during Build() with the current prefix.

func (TypedBuilder[T]) WithPrefix

func (s TypedBuilder[T]) WithPrefix(prefix string) TypedBuilder[T]

WithPrefix sets the key prefix for the builder.

func (TypedBuilder[T]) WithSigner

func (s TypedBuilder[T]) WithSigner(signer crypto.Signer) TypedBuilder[T]

WithSigner adds a signer to the builder.

func (TypedBuilder[T]) WithSignerVerifier

func (s TypedBuilder[T]) WithSignerVerifier(sv crypto.SignerVerifier) TypedBuilder[T]

WithSignerVerifier adds a signer/verifier to the builder.

func (TypedBuilder[T]) WithVerifier

func (s TypedBuilder[T]) WithVerifier(verifier crypto.Verifier) TypedBuilder[T]

WithVerifier adds a verifier to the builder.

type ValidatedResult

type ValidatedResult[T any] struct {
	// Name is the object identifier under which the value was stored.
	Name string
	// Value contains the unmarshalled value if decoding succeeded.
	Value option.Generic[T]
	// ModRevision is the storage revision when this value was last modified.
	ModRevision int64
	// Error contains validation errors if integrity verification failed.
	Error error
}

ValidatedResult represents a validated named value.

type ValidationError

type ValidationError struct {
	// contains filtered or unexported fields
}

ValidationError represents an error when validation fails.

func (ValidationError) Error

func (e ValidationError) Error() string

Error returns a string representation of the validation error.

func (ValidationError) Unpack

func (e ValidationError) Unpack() error

type Validator

type Validator[T any] struct {
	// contains filtered or unexported fields
}

Validator verifies integrity-protected key-value pairs.

func NewValidator

func NewValidator[T any](
	namer namer.Namer,
	marshaller marshaller.TypedMarshaller[T],
	hashers []hasher.Hasher,
	verifiers []crypto.Verifier,
) Validator[T]

NewValidator creates a new Validator instance.

func (Validator[T]) Validate

func (v Validator[T]) Validate(kvs []kv.KeyValue) ([]ValidatedResult[T], error)

Validate verifies integrity-protected key-value pairs and returns the validated value.

Jump to

Keyboard shortcuts

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