diff --git a/.changeset/rare-carrots-grab.md b/.changeset/rare-carrots-grab.md new file mode 100644 index 000000000..e2c208ea0 --- /dev/null +++ b/.changeset/rare-carrots-grab.md @@ -0,0 +1,5 @@ +--- + +--- + +Consolidate on go.yaml.in/yaml/v3 diff --git a/auth/provider.go b/auth/provider.go index 1503a44ab..dde8f2b73 100644 --- a/auth/provider.go +++ b/auth/provider.go @@ -17,7 +17,7 @@ package auth import ( "io" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) type FileBasedKeyProvider struct { diff --git a/go.mod b/go.mod index 058aad339..3048626ca 100644 --- a/go.mod +++ b/go.mod @@ -40,6 +40,7 @@ require ( go.uber.org/multierr v1.11.0 go.uber.org/zap v1.28.0 go.uber.org/zap/exp v0.3.0 + go.yaml.in/yaml/v3 v3.0.4 golang.org/x/exp v0.0.0-20260603202125-055de637280b golang.org/x/mod v0.38.0 golang.org/x/sys v0.47.0 @@ -90,7 +91,6 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.53.0 // indirect golang.org/x/net v0.56.0 // indirect golang.org/x/sync v0.22.0 // indirect diff --git a/go.sum b/go.sum index c2d8c9f45..4330d4a19 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,6 @@ github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkI github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= -github.com/livekit/psrpc v0.7.2 h1:6oZ+NODJ2pLyaT6VqDq1F4Qc/3TpDUSpyphj/P9MhQc= -github.com/livekit/psrpc v0.7.2/go.mod h1:rAI+m2+/cb4x9RXhLRtUx5ZwdfjjXOl4zi46IjEetaw= github.com/livekit/psrpc v0.7.3 h1:bekuZt/ZQzg8+/M8G6G5jq7bvV9fAKdPHSOZeTwrIIc= github.com/livekit/psrpc v0.7.3/go.mod h1:rAI+m2+/cb4x9RXhLRtUx5ZwdfjjXOl4zi46IjEetaw= github.com/mackerelio/go-osstat v0.2.8 h1:I2duicTaCGWoM53XwAwA9OIe1inu0xnVs8/pqOWWVr4= diff --git a/livekit/types.go b/livekit/types.go index 3904f2dfa..868ea520d 100644 --- a/livekit/types.go +++ b/livekit/types.go @@ -27,8 +27,8 @@ import ( "buf.build/go/protoyaml" "github.com/dennwc/iters" "go.opentelemetry.io/otel/attribute" + "go.yaml.in/yaml/v3" proto "google.golang.org/protobuf/proto" - "gopkg.in/yaml.v3" ) const ( @@ -139,48 +139,53 @@ type Guid interface { type GuidBlock [9]byte -func (r *RoomConfiguration) UnmarshalYAML(value *yaml.Node) error { - // Marshall the Node back to yaml to pass it to the protobuf specific unmarshaller - str, err := yaml.Marshal(value) - if err != nil { - return err - } - - return protoyaml.Unmarshal(str, r) +func (r *RoomConfiguration) UnmarshalYAML(unmarshal func(interface{}) error) error { + return unmarshalProto(unmarshal, r) } func (r *RoomConfiguration) MarshalYAML() (interface{}, error) { return marshalProto(r) } -func (r *RoomEgress) UnmarshalYAML(value *yaml.Node) error { - // Marshall the Node back to yaml to pass it to the protobuf specific unmarshaller - str, err := yaml.Marshal(value) - if err != nil { - return err - } - - return protoyaml.Unmarshal(str, r) +func (r *RoomEgress) UnmarshalYAML(unmarshal func(interface{}) error) error { + return unmarshalProto(unmarshal, r) } func (r *RoomEgress) MarshalYAML() (interface{}, error) { return marshalProto(r) } -func (r *RoomAgent) UnmarshalYAML(value *yaml.Node) error { - // Marshall the Node back to yaml to pass it to the protobuf specific unmarshaller - str, err := yaml.Marshal(value) - if err != nil { - return err - } - - return protoyaml.Unmarshal(str, r) +func (r *RoomAgent) UnmarshalYAML(unmarshal func(interface{}) error) error { + return unmarshalProto(unmarshal, r) } func (r *RoomAgent) MarshalYAML() (interface{}, error) { return marshalProto(r) } +// unmarshalProto decodes a yaml value into a protobuf message using the protobuf +// specific unmarshaller. +// +// The func-based unmarshal signature is deliberate: unlike UnmarshalYAML(*yaml.Node), +// it names no types from any yaml package, so decoders from both gopkg.in/yaml.v3 and +// go.yaml.in/yaml/v3 recognize it. Taking a *yaml.Node would bind these types to +// whichever package protocol imports, and callers using the other one would silently +// fall back to reflective struct decoding. +func unmarshalProto(unmarshal func(interface{}) error, o proto.Message) error { + var v interface{} + if err := unmarshal(&v); err != nil { + return err + } + + // Marshall the value back to yaml to pass it to the protobuf specific unmarshaller + str, err := yaml.Marshal(v) + if err != nil { + return err + } + + return protoyaml.Unmarshal(str, o) +} + func marshalProto(o proto.Message) (map[string]interface{}, error) { // Marshall the Node to yaml using the protobuf specific marshaller to ensure the proper field names are used str, err := protoyaml.MarshalOptions{UseProtoNames: true}.Marshal(o) diff --git a/livekit/types_test.go b/livekit/types_test.go index 729d6536a..254ad37d8 100644 --- a/livekit/types_test.go +++ b/livekit/types_test.go @@ -9,8 +9,9 @@ import ( "github.com/dennwc/iters" "github.com/stretchr/testify/require" + "go.yaml.in/yaml/v3" proto "google.golang.org/protobuf/proto" - "gopkg.in/yaml.v3" + gopkgyaml "gopkg.in/yaml.v3" ) func TestUnmarshallRoomConfiguration(t *testing.T) { @@ -44,6 +45,51 @@ a: } +// TestUnmarshallRoomConfigurationBothYAMLPackages guards the func-based UnmarshalYAML +// signature on RoomConfiguration/RoomEgress/RoomAgent. +// +// Callers decode protocol types with either gopkg.in/yaml.v3 or go.yaml.in/yaml/v3. +// A UnmarshalYAML(*yaml.Node) method is only recognized by the package that owns the +// Node type; the other package silently falls back to reflective struct decoding, +// which leaves proto fields such as min_playout_delay at their zero value rather than +// returning an error. Both decoders must therefore keep producing a populated message. +func TestUnmarshallRoomConfigurationBothYAMLPackages(t *testing.T) { + y := ` +name: room_name +egress: + room: + room_name: egress_room +agents: + - agent_name: ag + metadata: mm +min_playout_delay: 42 +` + + check := func(t *testing.T, rc *RoomConfiguration) { + t.Helper() + require.Equal(t, "room_name", rc.Name) + // zero here means the custom unmarshaller was skipped + require.Equal(t, uint32(42), rc.MinPlayoutDelay) + require.NotNil(t, rc.Egress) + require.Equal(t, "egress_room", rc.Egress.Room.RoomName) + require.Equal(t, 1, len(rc.Agents)) + require.Equal(t, "ag", rc.Agents[0].AgentName) + require.Equal(t, "mm", rc.Agents[0].Metadata) + } + + t.Run("go.yaml.in/yaml/v3", func(t *testing.T) { + var rc RoomConfiguration + require.NoError(t, yaml.Unmarshal([]byte(y), &rc)) + check(t, &rc) + }) + + t.Run("gopkg.in/yaml.v3", func(t *testing.T) { + var rc RoomConfiguration + require.NoError(t, gopkgyaml.Unmarshal([]byte(y), &rc)) + check(t, &rc) + }) +} + func TestMarshallRoomConfiguration(t *testing.T) { r := &RoomConfiguration{ Name: "name", diff --git a/utils/configutil/observer.go b/utils/configutil/observer.go index 6eb027a24..730423969 100644 --- a/utils/configutil/observer.go +++ b/utils/configutil/observer.go @@ -23,7 +23,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/atomic" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" "github.com/livekit/protocol/logger" "github.com/livekit/protocol/utils/events"