diff --git a/.chloggen/any-value.yaml b/.chloggen/any-value.yaml new file mode 100644 index 000000000000..c177c1068659 --- /dev/null +++ b/.chloggen/any-value.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: xpdata + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add Serialization and Deserialization of AnyValue + +# One or more tracking issues or pull requests related to the change +issues: [12826] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: + +# Optional: The change log or logs in which this entry should be included. +# e.g. '[user]' or '[user, api]' +# Include 'user' if the change is relevant to end users. +# Include 'api' if there is a change to a library API. +# Default: '[user]' +change_logs: [] diff --git a/pdata/xpdata/fuzz_test.go b/pdata/xpdata/fuzz_test.go new file mode 100644 index 000000000000..aa1131dad063 --- /dev/null +++ b/pdata/xpdata/fuzz_test.go @@ -0,0 +1,35 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xpdata + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +var unexpectedBytes = "expected the same bytes from unmarshaling and marshaling." + +func FuzzUnmarshalJSONAnyValue(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + u1 := &JSONUnmarshaler{} + ld1, err := u1.UnmarshalAnyValue(data) + if err != nil { + return + } + m1 := &JSONMarshaler{} + b1, err := m1.MarshalAnyValue(ld1) + require.NoError(t, err, "failed to marshal valid struct") + + u2 := &JSONUnmarshaler{} + ld2, err := u2.UnmarshalAnyValue(b1) + require.NoError(t, err, "failed to unmarshal valid bytes") + m2 := &JSONMarshaler{} + b2, err := m2.MarshalAnyValue(ld2) + require.NoError(t, err, "failed to marshal valid struct") + + require.True(t, bytes.Equal(b1, b2), "%s. \nexpected %d but got %d\n", unexpectedBytes, b1, b2) + }) +} diff --git a/pdata/xpdata/json.go b/pdata/xpdata/json.go new file mode 100644 index 000000000000..247e81186b0b --- /dev/null +++ b/pdata/xpdata/json.go @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xpdata // import "go.opentelemetry.io/collector/pdata/xpdata" + +import ( + "slices" + + "go.opentelemetry.io/collector/pdata/internal" + otlpcommon "go.opentelemetry.io/collector/pdata/internal/data/protogen/common/v1" + "go.opentelemetry.io/collector/pdata/internal/json" +) + +type JSONMarshaler struct{} + +func (*JSONMarshaler) MarshalAnyValue(value *otlpcommon.AnyValue) ([]byte, error) { + dest := json.BorrowStream(nil) + defer json.ReturnStream(dest) + internal.MarshalJSONOrigAnyValue(value, dest) + if dest.Error() != nil { + return nil, dest.Error() + } + return slices.Clone(dest.Buffer()), nil +} + +type JSONUnmarshaler struct{} + +func (*JSONUnmarshaler) UnmarshalAnyValue(buf []byte) (*otlpcommon.AnyValue, error) { + iter := json.BorrowIterator(buf) + defer json.ReturnIterator(iter) + value := &otlpcommon.AnyValue{} + internal.UnmarshalJSONOrigAnyValue(value, iter) + if iter.Error() != nil { + return nil, iter.Error() + } + return value, nil +} diff --git a/pdata/xpdata/json_test.go b/pdata/xpdata/json_test.go new file mode 100644 index 000000000000..ce6ce7a77357 --- /dev/null +++ b/pdata/xpdata/json_test.go @@ -0,0 +1,58 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package xpdata + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/collector/pdata/internal" + otlpcommon "go.opentelemetry.io/collector/pdata/internal/data/protogen/common/v1" +) + +func TestMarshalAndUnmarshalAnyValue(t *testing.T) { + for name, src := range genTestEncodingValuesAnyValue() { + t.Run(name, func(t *testing.T) { + m := &JSONMarshaler{} + b, err := m.MarshalAnyValue(src) + require.NoError(t, err) + + u := &JSONUnmarshaler{} + dest, err := u.UnmarshalAnyValue(b) + require.NoError(t, err) + + require.Equal(t, src, dest) + }) + } +} + +func TestUnmarshalAnyValueUnknown(t *testing.T) { + m := &JSONUnmarshaler{} + + b, err := m.UnmarshalAnyValue([]byte(`{"unknown": "string"}`)) + require.NoError(t, err) + assert.Equal(t, internal.NewOrigAnyValue(), b) +} + +func genTestEncodingValuesAnyValue() map[string]*otlpcommon.AnyValue { + return map[string]*otlpcommon.AnyValue{ + "empty": internal.NewOrigAnyValue(), + "StringValue/default": {Value: &otlpcommon.AnyValue_StringValue{StringValue: ""}}, + "StringValue/test": {Value: &otlpcommon.AnyValue_StringValue{StringValue: "test_stringvalue"}}, + "BoolValue/default": {Value: &otlpcommon.AnyValue_BoolValue{BoolValue: false}}, + "BoolValue/test": {Value: &otlpcommon.AnyValue_BoolValue{BoolValue: true}}, + "IntValue/default": {Value: &otlpcommon.AnyValue_IntValue{IntValue: int64(0)}}, + "IntValue/test": {Value: &otlpcommon.AnyValue_IntValue{IntValue: int64(13)}}, + "DoubleValue/default": {Value: &otlpcommon.AnyValue_DoubleValue{DoubleValue: float64(0)}}, + "DoubleValue/test": {Value: &otlpcommon.AnyValue_DoubleValue{DoubleValue: float64(3.1415926)}}, + "ArrayValue/default": {Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: &otlpcommon.ArrayValue{}}}, + "ArrayValue/test": {Value: &otlpcommon.AnyValue_ArrayValue{ArrayValue: internal.GenTestOrigArrayValue()}}, + "KvlistValue/default": {Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: &otlpcommon.KeyValueList{}}}, + "KvlistValue/test": {Value: &otlpcommon.AnyValue_KvlistValue{KvlistValue: internal.GenTestOrigKeyValueList()}}, + "BytesValue/default": {Value: &otlpcommon.AnyValue_BytesValue{BytesValue: nil}}, + "BytesValue/test": {Value: &otlpcommon.AnyValue_BytesValue{BytesValue: []byte{1, 2, 3}}}, + } +}