Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/tall-worms-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@livekit/protocol": patch
"github.com/livekit/protocol": patch
---

Allow adding, removing items and clearing list fields.
33 changes: 30 additions & 3 deletions livekit/livekit_models.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 51 additions & 3 deletions livekit/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"
"io"
"slices"

"buf.build/go/protoyaml"
"github.com/dennwc/iters"
Expand Down Expand Up @@ -226,14 +227,42 @@ func (p *ListUpdate) Validate() error {
if p == nil {
return nil
}
change := len(p.Set)+len(p.Add)+len(p.Del) > 0
if !p.Clear && !change {
return fmt.Errorf("unsupported list update operation")
}
if p.Clear && change {
return fmt.Errorf("cannot clear and change the list at the same time")
}
if len(p.Set) > 0 && len(p.Add)+len(p.Del) > 0 {
return fmt.Errorf("cannot set and change the list at the same time")
}
for _, v := range p.Set {
if v == "" {
return fmt.Errorf("empty element in the list")
}
}
for _, v := range p.Add {
if v == "" {
return fmt.Errorf("empty element in the list")
}
}
for _, v := range p.Del {
if v == "" {
return fmt.Errorf("empty element in the list")
}
}
return nil
}

func (p *ListUpdate) Apply(arr []string) ([]string, error) {
if err := p.Validate(); err != nil {
return arr, err
}
applyListUpdate(&arr, p)
return arr, nil
}

func applyUpdate[T any](dst *T, set *T) {
if set != nil {
*dst = *set
Expand All @@ -250,9 +279,28 @@ func applyListUpdate[T ~string](dst *[]T, u *ListUpdate) {
if u == nil {
return
}
arr := make([]T, 0, len(u.Set))
for _, v := range u.Set {
arr = append(arr, T(v))
if u.Clear {
*dst = nil
return
}
if len(u.Set) != 0 {
arr := make([]T, 0, len(u.Set))
for _, v := range u.Set {
arr = append(arr, T(v))
}
*dst = arr
return
}
arr := slices.Clone(*dst)
for _, v := range u.Del {
if i := slices.Index(arr, T(v)); i >= 0 {
arr = slices.Delete(arr, i, i+1)
}
}
for _, v := range u.Add {
if i := slices.Index(arr, T(v)); i < 0 {
arr = append(arr, T(v))
}
}
*dst = arr
}
Expand Down
81 changes: 81 additions & 0 deletions livekit/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package livekit
import (
"context"
"fmt"
"slices"
"testing"

"github.com/dennwc/iters"
Expand Down Expand Up @@ -238,3 +239,83 @@ func TestListPageIter(t *testing.T) {
require.Equal(t, []testPageItem(nil), got)
})
}

func TestListUpdate(t *testing.T) {
var cases = []struct {
Name string
Arr []string
Update *ListUpdate
Exp []string
Err bool
}{
{
Name: "empty update",
Update: &ListUpdate{},
Err: true,
},
{
Name: "clear and set",
Update: &ListUpdate{Clear: true, Set: []string{"a"}},
Err: true,
},
{
Name: "clear and add",
Update: &ListUpdate{Clear: true, Add: []string{"a"}},
Err: true,
},
{
Name: "set and add",
Update: &ListUpdate{Set: []string{"a"}, Add: []string{"b"}},
Err: true,
},
{
Name: "set and del",
Update: &ListUpdate{Set: []string{"a"}, Del: []string{"b"}},
Err: true,
},
{
Name: "clear",
Arr: []string{"a", "b"},
Update: &ListUpdate{Clear: true},
Exp: nil,
},
{
Name: "set",
Arr: []string{"a"},
Update: &ListUpdate{Set: []string{"b"}},
Exp: []string{"b"},
},
{
Name: "add",
Arr: []string{"a", "b"},
Update: &ListUpdate{Add: []string{"b", "c"}},
Exp: []string{"a", "b", "c"},
},
{
Name: "del",
Arr: []string{"a", "b"},
Update: &ListUpdate{Del: []string{"b", "c"}},
Exp: []string{"a"},
},
{
Name: "add and del",
Arr: []string{"a", "b", "c"},
Update: &ListUpdate{Add: []string{"b", "d"}, Del: []string{"c", "e"}},
Exp: []string{"a", "b", "d"},
},
}

for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
prev := slices.Clone(c.Arr)
out, err := c.Update.Apply(c.Arr)
if c.Err {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, c.Exp, out)
require.Equal(t, prev, c.Arr)
})
}
}
3 changes: 3 additions & 0 deletions protobufs/livekit_models.proto
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ message Pagination {
// ListUpdate is used for updated APIs where 'repeated string' field is modified.
message ListUpdate {
repeated string set = 1; // set the field to a new list
repeated string add = 2; // append items to a list, avoiding duplicates
repeated string del = 3; // delete items from a list
bool clear = 4; // sets the list to an empty list
}

message Room {
Expand Down
Loading