aboutsummaryrefslogtreecommitdiff
path: root/object/store
diff options
context:
space:
mode:
Diffstat (limited to 'object/store')
-rw-r--r--object/store/memory/add.go13
-rw-r--r--object/store/memory/write_bytes.go35
-rw-r--r--object/store/memory/write_reader.go55
-rw-r--r--object/store/memory/write_test.go170
4 files changed, 265 insertions, 8 deletions
diff --git a/object/store/memory/add.go b/object/store/memory/add.go
index 3b27f52d..3946c89e 100644
--- a/object/store/memory/add.go
+++ b/object/store/memory/add.go
@@ -1,21 +1,18 @@
package memory
import (
- objectheader "codeberg.org/lindenii/furgit/object/header"
objectid "codeberg.org/lindenii/furgit/object/id"
objecttype "codeberg.org/lindenii/furgit/object/type"
)
// AddObject stores one object body and returns its object ID.
+//
+//go:fix inline
func (store *Store) AddObject(ty objecttype.Type, body []byte) objectid.ObjectID {
- header, ok := objectheader.Encode(ty, int64(len(body)))
- if !ok {
- panic("failed to encode object header")
+ id, err := store.WriteBytesContent(ty, body)
+ if err != nil {
+ panic(err)
}
- raw := append(append([]byte(nil), header...), body...)
- id := store.algo.Sum(raw)
- store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), body...)}
-
return id
}
diff --git a/object/store/memory/write_bytes.go b/object/store/memory/write_bytes.go
new file mode 100644
index 00000000..241169d9
--- /dev/null
+++ b/object/store/memory/write_bytes.go
@@ -0,0 +1,35 @@
+package memory
+
+import (
+ "bytes"
+
+ objectheader "codeberg.org/lindenii/furgit/object/header"
+ objectid "codeberg.org/lindenii/furgit/object/id"
+ objecttype "codeberg.org/lindenii/furgit/object/type"
+)
+
+// WriteBytesContent writes one typed object content byte slice.
+func (store *Store) WriteBytesContent(ty objecttype.Type, content []byte) (objectid.ObjectID, error) {
+ id := store.algo.Sum(buildRawObject(ty, content))
+ store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), content...)}
+
+ return id, nil
+}
+
+// WriteBytesFull writes one full serialized object byte slice as "type size\0content".
+func (store *Store) WriteBytesFull(raw []byte) (objectid.ObjectID, error) {
+ return store.WriteReaderFull(bytes.NewReader(raw))
+}
+
+func buildRawObject(ty objecttype.Type, body []byte) []byte {
+ header, ok := objectheader.Encode(ty, int64(len(body)))
+ if !ok {
+ panic("failed to encode object header")
+ }
+
+ raw := make([]byte, len(header)+len(body))
+ copy(raw, header)
+ copy(raw[len(header):], body)
+
+ return raw
+}
diff --git a/object/store/memory/write_reader.go b/object/store/memory/write_reader.go
new file mode 100644
index 00000000..0fa6a13f
--- /dev/null
+++ b/object/store/memory/write_reader.go
@@ -0,0 +1,55 @@
+package memory
+
+import (
+ "errors"
+ "fmt"
+ "io"
+
+ objectheader "codeberg.org/lindenii/furgit/object/header"
+ objectid "codeberg.org/lindenii/furgit/object/id"
+ objecttype "codeberg.org/lindenii/furgit/object/type"
+)
+
+// WriteReaderContent writes one typed object content stream.
+func (store *Store) WriteReaderContent(ty objecttype.Type, size int64, src io.Reader) (objectid.ObjectID, error) {
+ if size < 0 {
+ return objectid.ObjectID{}, fmt.Errorf("objectstore/memory: negative content size: %d", size)
+ }
+
+ content, err := io.ReadAll(io.LimitReader(src, size+1))
+ if err != nil {
+ return objectid.ObjectID{}, err
+ }
+
+ switch {
+ case int64(len(content)) > size:
+ return objectid.ObjectID{}, errors.New("objectstore/memory: object content longer than declared size")
+ case int64(len(content)) < size:
+ return objectid.ObjectID{}, errors.New("objectstore/memory: object content shorter than declared size")
+ }
+
+ return store.WriteBytesContent(ty, content)
+}
+
+// WriteReaderFull writes one full serialized object stream as "type size\0content".
+func (store *Store) WriteReaderFull(src io.Reader) (objectid.ObjectID, error) {
+ raw, err := io.ReadAll(src)
+ if err != nil {
+ return objectid.ObjectID{}, err
+ }
+
+ ty, size, headerLen, ok := objectheader.Parse(raw)
+ if !ok {
+ return objectid.ObjectID{}, errors.New("objectstore/memory: malformed object header")
+ }
+
+ content := raw[headerLen:]
+ if int64(len(content)) != size {
+ return objectid.ObjectID{}, errors.New("objectstore/memory: object header size/content mismatch")
+ }
+
+ id := store.algo.Sum(raw)
+ store.objects[id] = storedObject{ty: ty, content: append([]byte(nil), content...)}
+
+ return id, nil
+}
diff --git a/object/store/memory/write_test.go b/object/store/memory/write_test.go
new file mode 100644
index 00000000..5fe5f893
--- /dev/null
+++ b/object/store/memory/write_test.go
@@ -0,0 +1,170 @@
+package memory
+
+import (
+ "bytes"
+ "testing"
+
+ "codeberg.org/lindenii/furgit/internal/testgit"
+ objectheader "codeberg.org/lindenii/furgit/object/header"
+ objectid "codeberg.org/lindenii/furgit/object/id"
+ objecttype "codeberg.org/lindenii/furgit/object/type"
+)
+
+func TestStoreWriteReaderContent(t *testing.T) {
+ t.Parallel()
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ store := New(algo)
+ content := []byte("memory-content\n")
+
+ gotID, err := store.WriteReaderContent(objecttype.TypeBlob, int64(len(content)), bytes.NewReader(content))
+ if err != nil {
+ t.Fatalf("WriteReaderContent: %v", err)
+ }
+
+ wantID := algo.Sum(buildRawObject(objecttype.TypeBlob, content))
+ if gotID != wantID {
+ t.Fatalf("WriteReaderContent id = %s, want %s", gotID, wantID)
+ }
+
+ gotType, gotContent, err := store.ReadBytesContent(gotID)
+ if err != nil {
+ t.Fatalf("ReadBytesContent: %v", err)
+ }
+
+ if gotType != objecttype.TypeBlob {
+ t.Fatalf("ReadBytesContent type = %v, want %v", gotType, objecttype.TypeBlob)
+ }
+
+ if !bytes.Equal(gotContent, content) {
+ t.Fatalf("ReadBytesContent content mismatch")
+ }
+ })
+}
+
+func TestStoreWriteReaderFull(t *testing.T) {
+ t.Parallel()
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ store := New(algo)
+ content := []byte("memory-full\n")
+ raw := buildRawObject(objecttype.TypeBlob, content)
+
+ gotID, err := store.WriteReaderFull(bytes.NewReader(raw))
+ if err != nil {
+ t.Fatalf("WriteReaderFull: %v", err)
+ }
+
+ wantID := algo.Sum(raw)
+ if gotID != wantID {
+ t.Fatalf("WriteReaderFull id = %s, want %s", gotID, wantID)
+ }
+
+ gotRaw, err := store.ReadBytesFull(gotID)
+ if err != nil {
+ t.Fatalf("ReadBytesFull: %v", err)
+ }
+
+ if !bytes.Equal(gotRaw, raw) {
+ t.Fatalf("ReadBytesFull mismatch")
+ }
+ })
+}
+
+func TestStoreWriteBytes(t *testing.T) {
+ t.Parallel()
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ store := New(algo)
+ content := []byte("memory-bytes\n")
+
+ gotID, err := store.WriteBytesContent(objecttype.TypeBlob, content)
+ if err != nil {
+ t.Fatalf("WriteBytesContent: %v", err)
+ }
+
+ wantID := algo.Sum(buildRawObject(objecttype.TypeBlob, content))
+ if gotID != wantID {
+ t.Fatalf("WriteBytesContent id = %s, want %s", gotID, wantID)
+ }
+
+ raw := buildRawObject(objecttype.TypeBlob, content)
+
+ gotID2, err := store.WriteBytesFull(raw)
+ if err != nil {
+ t.Fatalf("WriteBytesFull: %v", err)
+ }
+
+ if gotID2 != wantID {
+ t.Fatalf("WriteBytesFull id = %s, want %s", gotID2, wantID)
+ }
+ })
+}
+
+func TestStoreWriteReaderValidationErrors(t *testing.T) {
+ t.Parallel()
+ testgit.ForEachAlgorithm(t, func(t *testing.T, algo objectid.Algorithm) { //nolint:thelper
+ t.Run("content overflow", func(t *testing.T) {
+ t.Parallel()
+ store := New(algo)
+
+ _, err := store.WriteReaderContent(objecttype.TypeBlob, 1, bytes.NewReader([]byte("hello")))
+ if err == nil {
+ t.Fatalf("expected error after overflow")
+ }
+ })
+
+ t.Run("content short", func(t *testing.T) {
+ t.Parallel()
+ store := New(algo)
+
+ _, err := store.WriteReaderContent(objecttype.TypeBlob, 5, bytes.NewReader([]byte("x")))
+ if err == nil {
+ t.Fatalf("expected error for short content")
+ }
+ })
+
+ t.Run("full malformed header", func(t *testing.T) {
+ t.Parallel()
+ store := New(algo)
+
+ _, err := store.WriteReaderFull(bytes.NewReader([]byte("not-a-header")))
+ if err == nil {
+ t.Fatalf("expected error for malformed header")
+ }
+ })
+
+ t.Run("full size mismatch", func(t *testing.T) {
+ t.Parallel()
+ store := New(algo)
+
+ _, err := store.WriteReaderFull(bytes.NewReader([]byte("blob 1\x00hello")))
+ if err == nil {
+ t.Fatalf("expected error after mismatch")
+ }
+ })
+
+ t.Run("bytes malformed header", func(t *testing.T) {
+ t.Parallel()
+ store := New(algo)
+
+ _, err := store.WriteBytesFull([]byte("not-a-header"))
+ if err == nil {
+ t.Fatalf("expected error for malformed byte header")
+ }
+ })
+ })
+}
+
+func TestBuildRawObjectMatchesObjectHeaderEncode(t *testing.T) {
+ t.Parallel()
+
+ content := []byte("body")
+ raw := buildRawObject(objecttype.TypeBlob, content)
+ header, ok := objectheader.Encode(objecttype.TypeBlob, int64(len(content)))
+ if !ok {
+ t.Fatalf("objectheader.Encode failed")
+ }
+
+ want := append(append([]byte(nil), header...), content...)
+ if !bytes.Equal(raw, want) {
+ t.Fatalf("buildRawObject mismatch")
+ }
+}