Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions hasher/hasher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ func TestSHA256Hasher(t *testing.T) {
t.Parallel()

tests := []struct {
name string
in []byte
out string
name string
in []byte
expected string
}{
{"empty", []byte(""), "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},
{"abc", []byte("abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"},
Expand All @@ -77,7 +77,7 @@ func TestSHA256Hasher(t *testing.T) {

result, _ := h.Hash(test.in)

assert.Equal(t, test.out, hex.EncodeToString(result))
assert.Equal(t, test.expected, hex.EncodeToString(result))
})
}
}
Expand Down
1 change: 0 additions & 1 deletion kv/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ type KeyValue struct {
Key []byte
// Value is the serialized representation of the value.
Value []byte

// ModRevision is the revision number of the last modification to this key.
ModRevision int64
}
8 changes: 4 additions & 4 deletions marshaller/marshaller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,24 @@ var ErrUnmarshall = errors.New("failed to unmarshal")
// implements one time for all objects.
// Required for `integrity.Storage` to set marshalling format for any type object
// and as recommendation for developers of `Storage` wrappers.
type DefaultMarshaller interface {
type DefaultMarshaller interface { //nolint:iface
Marshal(data any) ([]byte, error)
Unmarshal(data []byte, out any) error
}

// Marshallable - custom object serialization, implements for each object.
// Required for `integrity.Storage` type to set marshalling format to specific object
// and as recommendation for developers of `Storage` wrappers.
type Marshallable interface {
Marshal() ([]byte, error)
type Marshallable interface { //nolint:iface
Marshal(data any) ([]byte, error)
Unmarshal(data []byte, out any) error
}

// YAMLMarshaller struct represent realization.
type YAMLMarshaller struct{}

// NewYamlMarshaller creates new NewYamlMarshaller object.
func NewYamlMarshaller() YAMLMarshaller {
func NewYamlMarshaller() Marshallable {
return YAMLMarshaller{}
}

Expand Down
59 changes: 59 additions & 0 deletions namer/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package namer

// KeyType represents key types.
type KeyType int

const (
// KeyTypeValue represents data type.
KeyTypeValue KeyType = iota + 1
// KeyTypeHash represents hash of the data type.
KeyTypeHash
// KeyTypeSignature represents signature of the data type.
KeyTypeSignature
)

// Key defines the minimal interface required by keys.
type Key interface {
Name() string // Get object name.
Type() KeyType // Get key type.
Property() string // Get metadata (e.g., algorithm version).
Build() string // Reconstruct raw key string.
}

// DefaultKey implements default realization.
type DefaultKey struct {
name string // Object identifier.
keyType KeyType // Type of object (hash/signature/value).
property string // Additional metadata (version/algorithm).
raw string // Raw key string.
}

// NewDefaultKey returns new Key object.
func NewDefaultKey(name string, keytype KeyType, property string, raw string) DefaultKey {
return DefaultKey{
name: name,
keyType: keytype,
property: property,
raw: raw,
}
}

// Name returns name of the key.
func (k DefaultKey) Name() string {
return k.name
}

// Type returns type of the key.
func (k DefaultKey) Type() KeyType {
return k.keyType
}

// Property returns property of the key.
func (k DefaultKey) Property() string {
return k.property
}

// Build reconstructs key string.
func (k DefaultKey) Build() string {
return k.raw
}
260 changes: 234 additions & 26 deletions namer/namer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,249 @@
package namer

import (
"github.com/tarantool/go-storage/kv"
"fmt"
"iter"
"strings"
)

// KeyType represents key types.
type KeyType int

const (
// KeyTypeValue represents data type.
KeyTypeValue KeyType = iota + 1
// KeyTypeHash represents hash of the data type.
KeyTypeHash
// KeyTypeSignature represents signature of the data type.
KeyTypeSignature
hashName = "hash"
signatureName = "sig"
)

// Key implements internal realization.
type Key struct {
Name string // Object identificator.
Type KeyType // Type of the object.
Property string // Additional information (version/algorithm).
type InvalidKeyError struct {

Check failure on line 15 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

exported: exported type InvalidKeyError should have comment or be unexported (revive)

Check failure on line 15 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

exported: exported type InvalidKeyError should have comment or be unexported (revive)
Key string
Problem string
}

func (e InvalidKeyError) Error() string {
return fmt.Sprintf("invalid key '%s': %s", e.Key, e.Problem)
}

func errInvalidKey(key string, problem string) error {
return InvalidKeyError{
Key: key,
Problem: problem,
}
}

type InvalidNameError struct {
Name string
Problem string
}

func (e InvalidNameError) Error() string {
return fmt.Sprintf("invalid name '%s': %s", e.Name, e.Problem)
}

func errInvalidName(name string, problem string) error {
return InvalidNameError{
Name: name,
Problem: problem,
}
}

// Results represents Namer working result.
type Results struct {
isSingle bool // True if result contains only one object name.
isSingleName string // Cached name when isSingle=true.
result map[string][]Key // Grouped keys: object name -> key list.
}

func getFirstFromMap(m map[string][]Key) string {
for name := range m {
return name
}
return ""
}

func NewResults(initial map[string][]Key) Results {
return Results{
isSingle: len(initial) == 1,
isSingleName: getFirstFromMap(initial),
result: initial,
}
}

// SelectSingle gets keys for single-name case (if applicable).
func (r *Results) SelectSingle() ([]Key, bool) {
if r.isSingle {
return r.result[r.isSingleName], true
}

return nil, false
}

// Items return iterator over all name->keys groups.
func (r *Results) Items() iter.Seq2[string, []Key] {
return func(yield func(str string, res []Key) bool) {
for i, v := range r.result {
if !yield(i, v) {
return
}
}
}
}

func (r *Results) Result() map[string][]Key {
return r.result
}

// Namer represents keys naming strategy.
type Namer interface {
GenerateNames(name string) []string // Object's keys generation.
ParseNames(names []string) []Key // Convert names into keys.
// Select gets keys for a specific object name.
func (r *Results) Select(name string) ([]Key, bool) {
if i, ok := r.result[name]; ok {
return i, true
}

return nil, false
}

// Len returns the number of unique object names.
func (r *Results) Len() int {
return len(r.result)
}

// DefaultNamer represents default namer.
type DefaultNamer struct {
prefix string // Key prefix (e.g., "storage/").
hashNames []string
sigNames []string
}

// NewDefaultNamer returns new DefaultNamer object with hash/signature names configuration.
func NewDefaultNamer(prefix string, hashNames []string, sigNames []string) *DefaultNamer {
return &DefaultNamer{
prefix: strings.Trim(prefix, "/"),
hashNames: hashNames,
sigNames: sigNames,
}
}

// GenerateNames all keys for an object name.
func (n *DefaultNamer) GenerateNames(name string) ([]Key, error) {
switch {
case name == "":
return nil, errInvalidName(name, "should not be empty")
case strings.HasSuffix(name, "/"):
return nil, errInvalidName(name, "should not be prefix")
}

name = strings.TrimPrefix(name, "/")

out := make([]Key, 0, len(n.hashNames)+len(n.sigNames)+1)

out = append(out,
NewDefaultKey(
name,
KeyTypeValue,
"",
fmt.Sprintf("/%s/%s", n.prefix, name),
))

for _, hash := range n.hashNames {
out = append(out,
NewDefaultKey(
name,
KeyTypeHash,
hash,
fmt.Sprintf("/%s/%s/%s/%s", n.prefix, hashName, hash, name),
),
)
}

for _, sig := range n.sigNames {
out = append(out,
NewDefaultKey(
name,
KeyTypeSignature,
sig,
fmt.Sprintf("/%s/%s/%s/%s", n.prefix, signatureName, sig, name),
),
)
}

return out, nil
}

// Generator generates signer K/V pairs.
// Implementation should use `generic` and will used for strong typing of the solution.
type Generator[T any] interface {
Generate(name string, value T) ([]kv.KeyValue, error)
func (n *DefaultNamer) ParseKey(name string) (DefaultKey, error) {
originalName := name

name, found := strings.CutPrefix(strings.TrimPrefix(name, "/"), n.prefix)
if !found {
return DefaultKey{}, errInvalidKey(originalName, "prefix not found")
}

name = strings.TrimPrefix(name, "/")
nameParts := strings.SplitN(name, "/", 3)

Check failure on line 178 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <argument> detected (mnd)

Check failure on line 178 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <argument> detected (mnd)

switch {
case len(nameParts) <= 0 || len(nameParts) > 3:

Check failure on line 181 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

sloppyLen: len(nameParts) <= 0 can be len(nameParts) == 0 (gocritic)

Check failure on line 181 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

sloppyLen: len(nameParts) <= 0 can be len(nameParts) == 0 (gocritic)
panic("illegal state") // Unreachable.

case nameParts[0] == signatureName:
switch {
case len(nameParts) != 3:

Check failure on line 186 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <case> detected (mnd)

Check failure on line 186 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <case> detected (mnd)
return DefaultKey{}, errInvalidKey(originalName, "found sig prefix, but key name is incomplete")
case len(nameParts[1]) == 0:
return DefaultKey{}, errInvalidKey(originalName, "found sig prefix, but hash name is invalid")
case len(nameParts[2]) == 0:
return DefaultKey{}, errInvalidKey(originalName, "found sig prefix, but key name is invalid")
case strings.HasSuffix(nameParts[2], "/"):
return DefaultKey{}, errInvalidKey(originalName, "found hash prefix, but key name is prefix")
}

return NewDefaultKey(
nameParts[2],
KeyTypeSignature,
nameParts[1],
originalName,
), nil
case nameParts[0] == hashName:
switch {
case len(nameParts) != 3:

Check failure on line 204 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <case> detected (mnd)

Check failure on line 204 in namer/namer.go

View workflow job for this annotation

GitHub Actions / golangci-lint

Magic number: 3, in <case> detected (mnd)
return DefaultKey{}, errInvalidKey(originalName, "found hash prefix, but key name is incomplete")
case len(nameParts[1]) == 0:
return DefaultKey{}, errInvalidKey(originalName, "found hash prefix, but hash name is invalid")
case len(nameParts[2]) == 0:
return DefaultKey{}, errInvalidKey(originalName, "found hash prefix, but key name is invalid")
case strings.HasSuffix(nameParts[2], "/"):
return DefaultKey{}, errInvalidKey(originalName, "found hash prefix, but key name is prefix")
}

return NewDefaultKey(
nameParts[2],
KeyTypeHash,
nameParts[1],
originalName,
), nil
default:
if strings.HasSuffix(name, "/") {
return DefaultKey{}, errInvalidKey(originalName, "key name should not be prefix")
}

return NewDefaultKey(
name,
KeyTypeValue,
"",
originalName,
), nil
}
}

// Validator validates and build the object from K/V.
type Validator[T any] interface {
Validate(pairs []kv.KeyValue) (T, error)
// ParseKeys combine multiple raw keys into grouped results.
func (n *DefaultNamer) ParseKeys(names []string, ignoreError bool) (Results, error) {
out := map[string][]Key{}

for _, name := range names {
key, err := n.ParseKey(name)
switch {
case err != nil && ignoreError:
continue
case err != nil:
return Results{}, err
}
out[key.name] = append(out[key.name], key)
}

return NewResults(out), nil
}
Loading
Loading