You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-08-10 22:31:50 +02:00
Remove deprecated Array from attribute package (#2235)
* Remove deprecated Array from attribute pkg * Add changes to changelog Co-authored-by: Anthony Mirabella <a9@aneurysm9.com>
This commit is contained in:
@@ -36,15 +36,6 @@ var (
|
||||
outStrSlice []string
|
||||
)
|
||||
|
||||
func benchmarkAny(k string, v interface{}) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Any(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkEmit(kv attribute.KeyValue) func(*testing.B) {
|
||||
return func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -54,23 +45,9 @@ func benchmarkEmit(kv attribute.KeyValue) func(*testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkArray(k string, v interface{}) func(*testing.B) {
|
||||
a := attribute.Array(k, v)
|
||||
return func(b *testing.B) {
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Array(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(a))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBool(b *testing.B) {
|
||||
k, v := "bool", true
|
||||
kv := attribute.Bool(k, v)
|
||||
array := []bool{true, false, true}
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -90,9 +67,7 @@ func BenchmarkBool(b *testing.B) {
|
||||
outBool = kv.Value.AsBool()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Array", benchmarkArray(k, array))
|
||||
}
|
||||
|
||||
func BenchmarkBoolSlice(b *testing.B) {
|
||||
@@ -117,14 +92,12 @@ func BenchmarkBoolSlice(b *testing.B) {
|
||||
outBoolSlice = kv.Value.AsBoolSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
}
|
||||
|
||||
func BenchmarkInt(b *testing.B) {
|
||||
k, v := "int", int(42)
|
||||
kv := attribute.Int(k, v)
|
||||
array := []int{42, -3, 12}
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -138,9 +111,7 @@ func BenchmarkInt(b *testing.B) {
|
||||
outKV = attribute.Int(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Array", benchmarkArray(k, array))
|
||||
}
|
||||
|
||||
func BenchmarkIntSlice(b *testing.B) {
|
||||
@@ -159,26 +130,12 @@ func BenchmarkIntSlice(b *testing.B) {
|
||||
outKV = attribute.IntSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
}
|
||||
|
||||
func BenchmarkInt8(b *testing.B) {
|
||||
b.Run("Any", benchmarkAny("int8", int8(42)))
|
||||
}
|
||||
|
||||
func BenchmarkInt16(b *testing.B) {
|
||||
b.Run("Any", benchmarkAny("int16", int16(42)))
|
||||
}
|
||||
|
||||
func BenchmarkInt32(b *testing.B) {
|
||||
b.Run("Any", benchmarkAny("int32", int32(42)))
|
||||
}
|
||||
|
||||
func BenchmarkInt64(b *testing.B) {
|
||||
k, v := "int64", int64(42)
|
||||
kv := attribute.Int64(k, v)
|
||||
array := []int64{42, -3, 12}
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -198,9 +155,7 @@ func BenchmarkInt64(b *testing.B) {
|
||||
outInt64 = kv.Value.AsInt64()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Array", benchmarkArray(k, array))
|
||||
}
|
||||
|
||||
func BenchmarkInt64Slice(b *testing.B) {
|
||||
@@ -225,14 +180,12 @@ func BenchmarkInt64Slice(b *testing.B) {
|
||||
outInt64Slice = kv.Value.AsInt64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
}
|
||||
|
||||
func BenchmarkFloat64(b *testing.B) {
|
||||
k, v := "float64", float64(42)
|
||||
kv := attribute.Float64(k, v)
|
||||
array := []float64{42, -3, 12}
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -252,9 +205,7 @@ func BenchmarkFloat64(b *testing.B) {
|
||||
outFloat64 = kv.Value.AsFloat64()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Array", benchmarkArray(k, array))
|
||||
}
|
||||
|
||||
func BenchmarkFloat64Slice(b *testing.B) {
|
||||
@@ -279,14 +230,12 @@ func BenchmarkFloat64Slice(b *testing.B) {
|
||||
outFloat64Slice = kv.Value.AsFloat64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
}
|
||||
|
||||
func BenchmarkString(b *testing.B) {
|
||||
k, v := "string", "42"
|
||||
kv := attribute.String(k, v)
|
||||
array := []string{"forty-two", "negative three", "twelve"}
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -306,9 +255,7 @@ func BenchmarkString(b *testing.B) {
|
||||
outStr = kv.Value.AsString()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Array", benchmarkArray(k, array))
|
||||
}
|
||||
|
||||
func BenchmarkStringSlice(b *testing.B) {
|
||||
@@ -333,10 +280,5 @@ func BenchmarkStringSlice(b *testing.B) {
|
||||
outStrSlice = kv.Value.AsStringSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Any", benchmarkAny(k, v))
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
}
|
||||
|
||||
func BenchmarkBytes(b *testing.B) {
|
||||
b.Run("Any", benchmarkAny("bytes", []byte("bytes")))
|
||||
}
|
||||
|
@@ -128,19 +128,6 @@ func (k Key) StringSlice(v []string) KeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
// Array creates a KeyValue instance with an ARRAY Value.
|
||||
//
|
||||
// If creating both a key and value at the same time, use the provided
|
||||
// convenience function instead -- Array(name, value).
|
||||
//
|
||||
// Deprecated: Use the typed *Slice methods instead.
|
||||
func (k Key) Array(v interface{}) KeyValue {
|
||||
return KeyValue{
|
||||
Key: k,
|
||||
Value: ArrayValue(v),
|
||||
}
|
||||
}
|
||||
|
||||
// Defined returns true for non-empty keys.
|
||||
func (k Key) Defined() bool {
|
||||
return len(k) != 0
|
||||
|
@@ -15,9 +15,7 @@
|
||||
package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// KeyValue holds a key and value pair.
|
||||
@@ -86,60 +84,3 @@ func StringSlice(k string, v []string) KeyValue {
|
||||
func Stringer(k string, v fmt.Stringer) KeyValue {
|
||||
return Key(k).String(v.String())
|
||||
}
|
||||
|
||||
// Array creates a new key-value pair with a passed name and a array.
|
||||
// Only arrays of primitive type are supported.
|
||||
//
|
||||
// Deprecated: Use the typed *Slice functions instead.
|
||||
func Array(k string, v interface{}) KeyValue {
|
||||
return Key(k).Array(v)
|
||||
}
|
||||
|
||||
// Any creates a new key-value pair instance with a passed name and
|
||||
// automatic type inference. This is slower, and not type-safe.
|
||||
//
|
||||
// Deprecated: Use the typed functions instead.
|
||||
func Any(k string, value interface{}) KeyValue {
|
||||
if value == nil {
|
||||
return String(k, "<nil>")
|
||||
}
|
||||
|
||||
if stringer, ok := value.(fmt.Stringer); ok {
|
||||
return String(k, stringer.String())
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(value)
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Array:
|
||||
rv = rv.Slice(0, rv.Len())
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
switch reflect.TypeOf(value).Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
return BoolSlice(k, rv.Interface().([]bool))
|
||||
case reflect.Int:
|
||||
return IntSlice(k, rv.Interface().([]int))
|
||||
case reflect.Int64:
|
||||
return Int64Slice(k, rv.Interface().([]int64))
|
||||
case reflect.Float64:
|
||||
return Float64Slice(k, rv.Interface().([]float64))
|
||||
case reflect.String:
|
||||
return StringSlice(k, rv.Interface().([]string))
|
||||
default:
|
||||
return KeyValue{Key: Key(k), Value: Value{vtype: INVALID}}
|
||||
}
|
||||
case reflect.Bool:
|
||||
return Bool(k, rv.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return Int64(k, rv.Int())
|
||||
case reflect.Float64:
|
||||
return Float64(k, rv.Float())
|
||||
case reflect.String:
|
||||
return String(k, rv.String())
|
||||
}
|
||||
if b, err := json.Marshal(value); b != nil && err == nil {
|
||||
return String(k, string(b))
|
||||
}
|
||||
return String(k, fmt.Sprint(value))
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -80,87 +79,6 @@ func TestKeyValueConstructors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString("foo")
|
||||
jsonifyStruct := struct {
|
||||
Public string
|
||||
private string
|
||||
Tagged string `json:"tagName"`
|
||||
Empty string
|
||||
OmitEmpty string `json:",omitempty"`
|
||||
Omit string `json:"-"`
|
||||
}{"foo", "bar", "baz", "", "", "omitted"}
|
||||
invalidStruct := struct {
|
||||
N complex64
|
||||
}{complex(0, 0)}
|
||||
for _, testcase := range []struct {
|
||||
key string
|
||||
value interface{}
|
||||
wantType attribute.Type
|
||||
wantValue interface{}
|
||||
}{
|
||||
{
|
||||
key: "bool type inferred",
|
||||
value: true,
|
||||
wantType: attribute.BOOL,
|
||||
wantValue: true,
|
||||
},
|
||||
{
|
||||
key: "int64 type inferred",
|
||||
value: int64(42),
|
||||
wantType: attribute.INT64,
|
||||
wantValue: int64(42),
|
||||
},
|
||||
{
|
||||
key: "float64 type inferred",
|
||||
value: float64(42.1),
|
||||
wantType: attribute.FLOAT64,
|
||||
wantValue: 42.1,
|
||||
},
|
||||
{
|
||||
key: "string type inferred",
|
||||
value: "foo",
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "foo",
|
||||
},
|
||||
{
|
||||
key: "stringer type inferred",
|
||||
value: builder,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "foo",
|
||||
},
|
||||
{
|
||||
key: "unknown value serialized as %v",
|
||||
value: nil,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "<nil>",
|
||||
},
|
||||
{
|
||||
key: "JSON struct serialized correctly",
|
||||
value: &jsonifyStruct,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: `{"Public":"foo","tagName":"baz","Empty":""}`,
|
||||
},
|
||||
{
|
||||
key: "Invalid JSON struct falls back to string",
|
||||
value: &invalidStruct,
|
||||
wantType: attribute.STRING,
|
||||
wantValue: "&{(0+0i)}",
|
||||
},
|
||||
} {
|
||||
t.Logf("Running test case %s", testcase.key)
|
||||
keyValue := attribute.Any(testcase.key, testcase.value)
|
||||
if keyValue.Value.Type() != testcase.wantType {
|
||||
t.Errorf("wrong value type, got %#v, expected %#v", keyValue.Value.Type(), testcase.wantType)
|
||||
}
|
||||
got := keyValue.Value.AsInterface()
|
||||
if diff := cmp.Diff(testcase.wantValue, got); diff != "" {
|
||||
t.Errorf("+got, -want: %s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyValueValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
@@ -206,11 +124,6 @@ func TestKeyValueValid(t *testing.T) {
|
||||
valid: true,
|
||||
kv: attribute.String("string", ""),
|
||||
},
|
||||
{
|
||||
desc: "non-empty key with ARRAY type Value should be valid",
|
||||
valid: true,
|
||||
kv: attribute.Array("array", []int{}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@@ -17,12 +17,11 @@ func _() {
|
||||
_ = x[INT64SLICE-6]
|
||||
_ = x[FLOAT64SLICE-7]
|
||||
_ = x[STRINGSLICE-8]
|
||||
_ = x[ARRAY-9]
|
||||
}
|
||||
|
||||
const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICEARRAY"
|
||||
const _Type_name = "INVALIDBOOLINT64FLOAT64STRINGBOOLSLICEINT64SLICEFLOAT64SLICESTRINGSLICE"
|
||||
|
||||
var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 38, 48, 60, 71, 76}
|
||||
var _Type_index = [...]uint8{0, 7, 11, 16, 23, 29, 38, 48, 60, 71}
|
||||
|
||||
func (i Type) String() string {
|
||||
if i < 0 || i >= Type(len(_Type_index)-1) {
|
||||
|
@@ -17,7 +17,6 @@ package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"go.opentelemetry.io/otel/internal"
|
||||
@@ -55,12 +54,6 @@ const (
|
||||
FLOAT64SLICE
|
||||
// STRINGSLICE is a slice of strings Type Value.
|
||||
STRINGSLICE
|
||||
// ARRAY is an array Type Value used to store 1-dimensional slices or
|
||||
// arrays of bool, int, int32, int64, uint, uint32, uint64, float,
|
||||
// float32, float64, or string types.
|
||||
//
|
||||
// Deprecated: Use slice types instead.
|
||||
ARRAY
|
||||
)
|
||||
|
||||
// BoolValue creates a BOOL Value.
|
||||
@@ -152,37 +145,6 @@ func StringSliceValue(v []string) Value {
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayValue creates an ARRAY value from an array or slice.
|
||||
// Only arrays or slices of bool, int, int64, float, float64, or string types are allowed.
|
||||
// Specifically, arrays and slices can not contain other arrays, slices, structs, or non-standard
|
||||
// types. If the passed value is not an array or slice of these types an
|
||||
// INVALID value is returned.
|
||||
//
|
||||
// Deprecated: Use the typed *SliceValue functions instead.
|
||||
func ArrayValue(v interface{}) Value {
|
||||
switch reflect.TypeOf(v).Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
// get array type regardless of dimensions
|
||||
typ := reflect.TypeOf(v).Elem()
|
||||
kind := typ.Kind()
|
||||
switch kind {
|
||||
case reflect.Bool, reflect.Int, reflect.Int64,
|
||||
reflect.Float64, reflect.String:
|
||||
val := reflect.ValueOf(v)
|
||||
length := val.Len()
|
||||
frozen := reflect.Indirect(reflect.New(reflect.ArrayOf(length, typ)))
|
||||
reflect.Copy(frozen, val)
|
||||
return Value{
|
||||
vtype: ARRAY,
|
||||
slice: frozen.Interface(),
|
||||
}
|
||||
default:
|
||||
return Value{vtype: INVALID}
|
||||
}
|
||||
}
|
||||
return Value{vtype: INVALID}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
func (v Value) Type() Type {
|
||||
return v.vtype
|
||||
@@ -248,20 +210,11 @@ func (v Value) AsStringSlice() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsArray returns the array Value as an interface{}.
|
||||
//
|
||||
// Deprecated: Use the typed As*Slice functions instead.
|
||||
func (v Value) AsArray() interface{} {
|
||||
return v.slice
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
// AsInterface returns Value's data as interface{}.
|
||||
func (v Value) AsInterface() interface{} {
|
||||
switch v.Type() {
|
||||
case ARRAY:
|
||||
return v.AsArray()
|
||||
case BOOL:
|
||||
return v.AsBool()
|
||||
case BOOLSLICE:
|
||||
@@ -285,8 +238,6 @@ func (v Value) AsInterface() interface{} {
|
||||
// Emit returns a string representation of Value's data.
|
||||
func (v Value) Emit() string {
|
||||
switch v.Type() {
|
||||
case ARRAY:
|
||||
return fmt.Sprint(v.slice)
|
||||
case BOOLSLICE:
|
||||
return fmt.Sprint(*(v.slice.(*[]bool)))
|
||||
case BOOL:
|
||||
|
@@ -15,7 +15,6 @@
|
||||
package attribute_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -43,12 +42,6 @@ func TestValue(t *testing.T) {
|
||||
wantType: attribute.BOOLSLICE,
|
||||
wantValue: []bool{true, false, true},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]bool) correctly return key's internal bool values",
|
||||
value: k.Array([]bool{true, false}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]bool{true, false},
|
||||
},
|
||||
{
|
||||
name: "Key.Int64() correctly returns keys's internal int64 value",
|
||||
value: k.Int64(42).Value,
|
||||
@@ -61,12 +54,6 @@ func TestValue(t *testing.T) {
|
||||
wantType: attribute.INT64SLICE,
|
||||
wantValue: []int64{42, -3, 12},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]int64) correctly returns keys's internal int64 values",
|
||||
value: k.Array([]int64{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]int64{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.Int() correctly returns keys's internal signed integral value",
|
||||
value: k.Int(42).Value,
|
||||
@@ -79,12 +66,6 @@ func TestValue(t *testing.T) {
|
||||
wantType: attribute.INT64SLICE,
|
||||
wantValue: []int64{42, -3, 12},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]int) correctly returns keys's internal signed integral values",
|
||||
value: k.Array([]int{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]int{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.Float64() correctly returns keys's internal float64 value",
|
||||
value: k.Float64(42.1).Value,
|
||||
@@ -97,12 +78,6 @@ func TestValue(t *testing.T) {
|
||||
wantType: attribute.FLOAT64SLICE,
|
||||
wantValue: []float64{42, -3, 12},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]float64) correctly returns keys's internal float64 values",
|
||||
value: k.Array([]float64{42, 43}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]float64{42, 43},
|
||||
},
|
||||
{
|
||||
name: "Key.String() correctly returns keys's internal string value",
|
||||
value: k.String("foo").Value,
|
||||
@@ -115,18 +90,6 @@ func TestValue(t *testing.T) {
|
||||
wantType: attribute.STRINGSLICE,
|
||||
wantValue: []string{"forty-two", "negative three", "twelve"},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([]string) correctly return key's internal string values",
|
||||
value: k.Array([]string{"foo", "bar"}).Value,
|
||||
wantType: attribute.ARRAY,
|
||||
wantValue: [2]string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "Key.Array([][]int) refuses to create multi-dimensional array",
|
||||
value: k.Array([][]int{{1, 2}, {3, 4}}).Value,
|
||||
wantType: attribute.INVALID,
|
||||
wantValue: nil,
|
||||
},
|
||||
} {
|
||||
t.Logf("Running test case %s", testcase.name)
|
||||
if testcase.value.Type() != testcase.wantType {
|
||||
@@ -141,11 +104,3 @@ func TestValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsArrayValue(t *testing.T) {
|
||||
v := attribute.ArrayValue([]int{1, 2, 3}).AsArray()
|
||||
// Ensure the returned dynamic type is stable.
|
||||
if got, want := reflect.TypeOf(v).Kind(), reflect.Array; got != want {
|
||||
t.Errorf("AsArray() returned %T, want %T", got, want)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user