diff --git a/object/patch.go b/object/patch.go index 1cc30fc..824513f 100644 --- a/object/patch.go +++ b/object/patch.go @@ -78,6 +78,42 @@ type PayloadPatch struct { Chunk []byte } +// SplitByMaxChunkLength splits a payload patch into a few payload patches if its `Chunk` size +// exceeds `maxChunkLen`. +func (payloadPatch *PayloadPatch) SplitByMaxChunkLength(maxChunkLen int) []*PayloadPatch { + if len(payloadPatch.Chunk) <= maxChunkLen { + return []*PayloadPatch{payloadPatch} + } + + var result []*PayloadPatch + remainingChunk := payloadPatch.Chunk + currentOffset := payloadPatch.Range.GetOffset() + remainingLength := payloadPatch.Range.GetLength() + + for len(remainingChunk) > 0 { + chunkSize := uint64(min(len(remainingChunk), maxChunkLen)) + newLength := min(remainingLength, chunkSize) + + rng := NewRange() + rng.SetOffset(currentOffset) + rng.SetLength(newLength) + + newPatch := &PayloadPatch{ + Range: rng, + Chunk: remainingChunk[:chunkSize], + } + result = append(result, newPatch) + + remainingChunk = remainingChunk[chunkSize:] + currentOffset += chunkSize + if remainingLength > 0 { + remainingLength -= newLength + } + } + + return result +} + func (p *PayloadPatch) ToV2() *v2object.PatchRequestBodyPatch { if p == nil { return nil diff --git a/object/patch_test.go b/object/patch_test.go index f66fd29..a0c6e60 100644 --- a/object/patch_test.go +++ b/object/patch_test.go @@ -10,6 +10,73 @@ import ( "github.com/stretchr/testify/require" ) +func newRange(offest, length uint64) *Range { + rng := &Range{} + rng.SetOffset(offest) + rng.SetLength(length) + return rng +} + +func TestSplitByMaxChunkLength(t *testing.T) { + t.Run("no break down", func(t *testing.T) { + payloadPatch := &PayloadPatch{ + Range: newRange(0, 42), + Chunk: []byte("100000|010000|001000|000100|000010|000001|"), + } + + payloadPatches := payloadPatch.SplitByMaxChunkLength(42) + + require.Len(t, payloadPatches, 1) + require.Equal(t, []byte("100000|010000|001000|000100|000010|000001|"), payloadPatches[0].Chunk) + + require.Equal(t, uint64(0), payloadPatches[0].Range.GetOffset()) + require.Equal(t, uint64(42), payloadPatches[0].Range.GetLength()) + }) + + t.Run("one replacing and two inserting patches", func(t *testing.T) { + payloadPatch := &PayloadPatch{ + Range: newRange(0, 15), + Chunk: []byte("100000|010000|001000|000100|000010|000001|"), + } + + payloadPatches := payloadPatch.SplitByMaxChunkLength(15) + + require.Len(t, payloadPatches, 3) + + require.Equal(t, []byte("100000|010000|0"), payloadPatches[0].Chunk) + require.Equal(t, uint64(0), payloadPatches[0].Range.GetOffset()) + require.Equal(t, uint64(15), payloadPatches[0].Range.GetLength()) + + require.Equal(t, []byte("01000|000100|00"), payloadPatches[1].Chunk) + require.Equal(t, uint64(15), payloadPatches[1].Range.GetOffset()) + require.Equal(t, uint64(0), payloadPatches[1].Range.GetLength()) + + require.Equal(t, []byte("0010|000001|"), payloadPatches[2].Chunk) + require.Equal(t, uint64(30), payloadPatches[2].Range.GetOffset()) + require.Equal(t, uint64(0), payloadPatches[2].Range.GetLength()) + }) + + t.Run("one replacing and one inserting patches", func(t *testing.T) { + payloadPatch := &PayloadPatch{ + Range: newRange(0, 30), + Chunk: []byte("100000|010000|001000|000100|000010|000001|"), + } + + payloadPatches := payloadPatch.SplitByMaxChunkLength(30) + + require.Len(t, payloadPatches, 2) + require.Equal(t, []byte("100000|010000|001000|000100|00"), payloadPatches[0].Chunk) + + require.Equal(t, uint64(0), payloadPatches[0].Range.GetOffset()) + require.Equal(t, uint64(30), payloadPatches[0].Range.GetLength()) + + require.Equal(t, []byte("0010|000001|"), payloadPatches[1].Chunk) + + require.Equal(t, uint64(30), payloadPatches[1].Range.GetOffset()) + require.Equal(t, uint64(0), payloadPatches[1].Range.GetLength()) + }) +} + func TestPatch(t *testing.T) { t.Run("to v2", func(t *testing.T) { t.Run("only attributes", func(t *testing.T) {