Skip to content

Commit 8cf93fe

Browse files
authored
Allow adding, removing items and clearing list fields. (#1142)
1 parent 34c7a0e commit 8cf93fe

File tree

5 files changed

+171
-6
lines changed

5 files changed

+171
-6
lines changed

.changeset/tall-worms-compare.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@livekit/protocol": patch
3+
"github.com/livekit/protocol": patch
4+
---
5+
6+
Allow adding, removing items and clearing list fields.

livekit/livekit_models.pb.go

Lines changed: 30 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

livekit/types.go

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"io"
21+
"slices"
2122

2223
"buf.build/go/protoyaml"
2324
"github.com/dennwc/iters"
@@ -226,14 +227,42 @@ func (p *ListUpdate) Validate() error {
226227
if p == nil {
227228
return nil
228229
}
230+
change := len(p.Set)+len(p.Add)+len(p.Del) > 0
231+
if !p.Clear && !change {
232+
return fmt.Errorf("unsupported list update operation")
233+
}
234+
if p.Clear && change {
235+
return fmt.Errorf("cannot clear and change the list at the same time")
236+
}
237+
if len(p.Set) > 0 && len(p.Add)+len(p.Del) > 0 {
238+
return fmt.Errorf("cannot set and change the list at the same time")
239+
}
229240
for _, v := range p.Set {
230241
if v == "" {
231242
return fmt.Errorf("empty element in the list")
232243
}
233244
}
245+
for _, v := range p.Add {
246+
if v == "" {
247+
return fmt.Errorf("empty element in the list")
248+
}
249+
}
250+
for _, v := range p.Del {
251+
if v == "" {
252+
return fmt.Errorf("empty element in the list")
253+
}
254+
}
234255
return nil
235256
}
236257

258+
func (p *ListUpdate) Apply(arr []string) ([]string, error) {
259+
if err := p.Validate(); err != nil {
260+
return arr, err
261+
}
262+
applyListUpdate(&arr, p)
263+
return arr, nil
264+
}
265+
237266
func applyUpdate[T any](dst *T, set *T) {
238267
if set != nil {
239268
*dst = *set
@@ -250,9 +279,28 @@ func applyListUpdate[T ~string](dst *[]T, u *ListUpdate) {
250279
if u == nil {
251280
return
252281
}
253-
arr := make([]T, 0, len(u.Set))
254-
for _, v := range u.Set {
255-
arr = append(arr, T(v))
282+
if u.Clear {
283+
*dst = nil
284+
return
285+
}
286+
if len(u.Set) != 0 {
287+
arr := make([]T, 0, len(u.Set))
288+
for _, v := range u.Set {
289+
arr = append(arr, T(v))
290+
}
291+
*dst = arr
292+
return
293+
}
294+
arr := slices.Clone(*dst)
295+
for _, v := range u.Del {
296+
if i := slices.Index(arr, T(v)); i >= 0 {
297+
arr = slices.Delete(arr, i, i+1)
298+
}
299+
}
300+
for _, v := range u.Add {
301+
if i := slices.Index(arr, T(v)); i < 0 {
302+
arr = append(arr, T(v))
303+
}
256304
}
257305
*dst = arr
258306
}

livekit/types_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package livekit
33
import (
44
"context"
55
"fmt"
6+
"slices"
67
"testing"
78

89
"github.com/dennwc/iters"
@@ -238,3 +239,83 @@ func TestListPageIter(t *testing.T) {
238239
require.Equal(t, []testPageItem(nil), got)
239240
})
240241
}
242+
243+
func TestListUpdate(t *testing.T) {
244+
var cases = []struct {
245+
Name string
246+
Arr []string
247+
Update *ListUpdate
248+
Exp []string
249+
Err bool
250+
}{
251+
{
252+
Name: "empty update",
253+
Update: &ListUpdate{},
254+
Err: true,
255+
},
256+
{
257+
Name: "clear and set",
258+
Update: &ListUpdate{Clear: true, Set: []string{"a"}},
259+
Err: true,
260+
},
261+
{
262+
Name: "clear and add",
263+
Update: &ListUpdate{Clear: true, Add: []string{"a"}},
264+
Err: true,
265+
},
266+
{
267+
Name: "set and add",
268+
Update: &ListUpdate{Set: []string{"a"}, Add: []string{"b"}},
269+
Err: true,
270+
},
271+
{
272+
Name: "set and del",
273+
Update: &ListUpdate{Set: []string{"a"}, Del: []string{"b"}},
274+
Err: true,
275+
},
276+
{
277+
Name: "clear",
278+
Arr: []string{"a", "b"},
279+
Update: &ListUpdate{Clear: true},
280+
Exp: nil,
281+
},
282+
{
283+
Name: "set",
284+
Arr: []string{"a"},
285+
Update: &ListUpdate{Set: []string{"b"}},
286+
Exp: []string{"b"},
287+
},
288+
{
289+
Name: "add",
290+
Arr: []string{"a", "b"},
291+
Update: &ListUpdate{Add: []string{"b", "c"}},
292+
Exp: []string{"a", "b", "c"},
293+
},
294+
{
295+
Name: "del",
296+
Arr: []string{"a", "b"},
297+
Update: &ListUpdate{Del: []string{"b", "c"}},
298+
Exp: []string{"a"},
299+
},
300+
{
301+
Name: "add and del",
302+
Arr: []string{"a", "b", "c"},
303+
Update: &ListUpdate{Add: []string{"b", "d"}, Del: []string{"c", "e"}},
304+
Exp: []string{"a", "b", "d"},
305+
},
306+
}
307+
308+
for _, c := range cases {
309+
t.Run(c.Name, func(t *testing.T) {
310+
prev := slices.Clone(c.Arr)
311+
out, err := c.Update.Apply(c.Arr)
312+
if c.Err {
313+
require.Error(t, err)
314+
return
315+
}
316+
require.NoError(t, err)
317+
require.Equal(t, c.Exp, out)
318+
require.Equal(t, prev, c.Arr)
319+
})
320+
}
321+
}

protobufs/livekit_models.proto

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ message Pagination {
3131
// ListUpdate is used for updated APIs where 'repeated string' field is modified.
3232
message ListUpdate {
3333
repeated string set = 1; // set the field to a new list
34+
repeated string add = 2; // append items to a list, avoiding duplicates
35+
repeated string del = 3; // delete items from a list
36+
bool clear = 4; // sets the list to an empty list
3437
}
3538

3639
message Room {

0 commit comments

Comments
 (0)