Skip to content

Commit 560098c

Browse files
Dennis40816RoryCrispin
authored andcommitted
[pkg/ottl] Add BoolGetter and IsBool converter (open-telemetry#27900)
**Description:** This PR focuses on adding a new feature to the pkg/ottl package—namely, the `IsBool` converter, along with its prerequisites, `BoolGetter` and `BoolLikeGetter`. The series of commits implement the core logic, unit tests, and documentation to ensure that the `IsBool` converter is seamlessly integrated into the OTTL framework. By using this feature, users can conveniently determine whether a given value is a boolean or not. **Link to tracking Issue:** open-telemetry#27897 **Testing:** - A comprehensive set of unit tests were added to validate the functionality of the `IsBool` converter. The tests cover native boolean values, `pcommon.ValueTypeBool` objects, and incorrect types such as integers and slices. - Tests for `BoolGetter` and `BoolLikeGetter` were also included to verify their behavior and error handling mechanisms. Detailed test implementations can be found in [pkg/ottl/expression_test.go](https://github.com/Dennis40816/opentelemetry-collector-contrib/blob/0d1c2c0216f5647404573a6cfe66ebc0081a4167/pkg/ottl/expression_test.go#L1452). **Documentation:** - Updated README.md in the `pkg/ottl/ottlfuncs` directory to include detailed descriptions, usage guidelines, and examples for the `IsBool` converter. - The README file also reflects the addition of `BoolGetter` and `BoolLikeGetter` to the list of supported types for single-value parameters in OTTL functions.
1 parent 5bed58b commit 560098c

File tree

8 files changed

+502
-0
lines changed

8 files changed

+502
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add IsBool function into OTTL
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [27897]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

pkg/ottl/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ The following types are supported for single-value parameters in OTTL functions:
7777
- `StringLikeGetter`
7878
- `IntGetter`
7979
- `IntLikeGetter`
80+
- `BoolGetter`
81+
- `BoolLikeGetter`
8082
- `Enum`
8183
- `string`
8284
- `float64`

pkg/ottl/expression.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,41 @@ func (g StandardFloatGetter[K]) Get(ctx context.Context, tCtx K) (float64, error
247247
}
248248
}
249249

250+
// BoolGetter is a Getter that must return a bool.
251+
type BoolGetter[K any] interface {
252+
// Get retrieves a bool value.
253+
Get(ctx context.Context, tCtx K) (bool, error)
254+
}
255+
256+
// StandardBoolGetter is a basic implementation of BoolGetter
257+
type StandardBoolGetter[K any] struct {
258+
Getter func(ctx context.Context, tCtx K) (any, error)
259+
}
260+
261+
// Get retrieves a bool value.
262+
// If the value is not a bool a new TypeError is returned.
263+
// If there is an error getting the value it will be returned.
264+
func (g StandardBoolGetter[K]) Get(ctx context.Context, tCtx K) (bool, error) {
265+
val, err := g.Getter(ctx, tCtx)
266+
if err != nil {
267+
return false, fmt.Errorf("error getting value in %T: %w", g, err)
268+
}
269+
if val == nil {
270+
return false, TypeError("expected bool but got nil")
271+
}
272+
switch v := val.(type) {
273+
case bool:
274+
return v, nil
275+
case pcommon.Value:
276+
if v.Type() == pcommon.ValueTypeBool {
277+
return v.Bool(), nil
278+
}
279+
return false, TypeError(fmt.Sprintf("expected bool but got %v", v.Type()))
280+
default:
281+
return false, TypeError(fmt.Sprintf("expected bool but got %T", val))
282+
}
283+
}
284+
250285
// FunctionGetter uses a function factory to return an instantiated function as an Expr.
251286
type FunctionGetter[K any] interface {
252287
Get(args Arguments) (Expr[K], error)
@@ -507,6 +542,64 @@ func (g StandardIntLikeGetter[K]) Get(ctx context.Context, tCtx K) (*int64, erro
507542
return &result, nil
508543
}
509544

545+
// BoolLikeGetter is a Getter that returns a bool by converting the underlying value to a bool if necessary.
546+
type BoolLikeGetter[K any] interface {
547+
// Get retrieves a bool value.
548+
// Unlike `BoolGetter`, the expectation is that the underlying value is converted to a bool if possible.
549+
// If the value cannot be converted to a bool, nil and an error are returned.
550+
// If the value is nil, nil is returned without an error.
551+
Get(ctx context.Context, tCtx K) (*bool, error)
552+
}
553+
554+
type StandardBoolLikeGetter[K any] struct {
555+
Getter func(ctx context.Context, tCtx K) (any, error)
556+
}
557+
558+
func (g StandardBoolLikeGetter[K]) Get(ctx context.Context, tCtx K) (*bool, error) {
559+
val, err := g.Getter(ctx, tCtx)
560+
if err != nil {
561+
return nil, fmt.Errorf("error getting value in %T: %w", g, err)
562+
}
563+
if val == nil {
564+
return nil, nil
565+
}
566+
var result bool
567+
switch v := val.(type) {
568+
case bool:
569+
result = v
570+
case int:
571+
result = v != 0
572+
case int64:
573+
result = v != 0
574+
case string:
575+
result, err = strconv.ParseBool(v)
576+
if err != nil {
577+
return nil, err
578+
}
579+
case float64:
580+
result = v != 0.0
581+
case pcommon.Value:
582+
switch v.Type() {
583+
case pcommon.ValueTypeBool:
584+
result = v.Bool()
585+
case pcommon.ValueTypeInt:
586+
result = v.Int() != 0
587+
case pcommon.ValueTypeStr:
588+
result, err = strconv.ParseBool(v.Str())
589+
if err != nil {
590+
return nil, err
591+
}
592+
case pcommon.ValueTypeDouble:
593+
result = v.Double() != 0.0
594+
default:
595+
return nil, TypeError(fmt.Sprintf("unsupported value type: %v", v.Type()))
596+
}
597+
default:
598+
return nil, TypeError(fmt.Sprintf("unsupported type: %T", val))
599+
}
600+
return &result, nil
601+
}
602+
510603
func (p *Parser[K]) newGetter(val value) (Getter[K], error) {
511604
if val.IsNil != nil && *val.IsNil {
512605
return &literal[K]{value: nil}, nil

pkg/ottl/expression_test.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,6 +1449,239 @@ func Test_StandardIntLikeGetter_WrappedError(t *testing.T) {
14491449
assert.False(t, ok)
14501450
}
14511451

1452+
func Test_StandardBoolGetter(t *testing.T) {
1453+
tests := []struct {
1454+
name string
1455+
getter StandardBoolGetter[any]
1456+
want bool
1457+
valid bool
1458+
expectedErrorMsg string
1459+
}{
1460+
{
1461+
name: "primitive bool type",
1462+
getter: StandardBoolGetter[any]{
1463+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1464+
return true, nil
1465+
},
1466+
},
1467+
want: true,
1468+
valid: true,
1469+
},
1470+
{
1471+
name: "ValueTypeBool type",
1472+
getter: StandardBoolGetter[any]{
1473+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1474+
return pcommon.NewValueBool(true), nil
1475+
},
1476+
},
1477+
want: true,
1478+
valid: true,
1479+
},
1480+
{
1481+
name: "Incorrect type",
1482+
getter: StandardBoolGetter[any]{
1483+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1484+
return 1, nil
1485+
},
1486+
},
1487+
valid: false,
1488+
expectedErrorMsg: "expected bool but got int",
1489+
},
1490+
{
1491+
name: "nil",
1492+
getter: StandardBoolGetter[any]{
1493+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1494+
return nil, nil
1495+
},
1496+
},
1497+
valid: false,
1498+
expectedErrorMsg: "expected bool but got nil",
1499+
},
1500+
}
1501+
1502+
for _, tt := range tests {
1503+
t.Run(tt.name, func(t *testing.T) {
1504+
val, err := tt.getter.Get(context.Background(), nil)
1505+
if tt.valid {
1506+
assert.NoError(t, err)
1507+
assert.Equal(t, tt.want, val)
1508+
} else {
1509+
assert.IsType(t, TypeError(""), err)
1510+
assert.EqualError(t, err, tt.expectedErrorMsg)
1511+
}
1512+
})
1513+
}
1514+
}
1515+
1516+
// nolint:errorlint
1517+
func Test_StandardBoolGetter_WrappedError(t *testing.T) {
1518+
getter := StandardBoolGetter[any]{
1519+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1520+
return nil, TypeError("")
1521+
},
1522+
}
1523+
_, err := getter.Get(context.Background(), nil)
1524+
assert.Error(t, err)
1525+
_, ok := err.(TypeError)
1526+
assert.False(t, ok)
1527+
}
1528+
1529+
func Test_StandardBoolLikeGetter(t *testing.T) {
1530+
tests := []struct {
1531+
name string
1532+
getter BoolLikeGetter[any]
1533+
want any
1534+
valid bool
1535+
expectedErrorMsg string
1536+
}{
1537+
{
1538+
name: "string type true",
1539+
getter: StandardBoolLikeGetter[any]{
1540+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1541+
return "true", nil
1542+
},
1543+
},
1544+
want: true,
1545+
valid: true,
1546+
},
1547+
{
1548+
name: "string type false",
1549+
getter: StandardBoolLikeGetter[any]{
1550+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1551+
return "false", nil
1552+
},
1553+
},
1554+
want: false,
1555+
valid: true,
1556+
},
1557+
{
1558+
name: "int type",
1559+
getter: StandardBoolLikeGetter[any]{
1560+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1561+
return 0, nil
1562+
},
1563+
},
1564+
want: false,
1565+
valid: true,
1566+
},
1567+
{
1568+
name: "float64 type",
1569+
getter: StandardBoolLikeGetter[any]{
1570+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1571+
return float64(0.0), nil
1572+
},
1573+
},
1574+
want: false,
1575+
valid: true,
1576+
},
1577+
{
1578+
name: "pcommon.value type int",
1579+
getter: StandardBoolLikeGetter[any]{
1580+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1581+
v := pcommon.NewValueInt(int64(0))
1582+
return v, nil
1583+
},
1584+
},
1585+
want: false,
1586+
valid: true,
1587+
},
1588+
{
1589+
name: "pcommon.value type string",
1590+
getter: StandardBoolLikeGetter[any]{
1591+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1592+
v := pcommon.NewValueStr("false")
1593+
return v, nil
1594+
},
1595+
},
1596+
want: false,
1597+
valid: true,
1598+
},
1599+
{
1600+
name: "pcommon.value type bool",
1601+
getter: StandardBoolLikeGetter[any]{
1602+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1603+
v := pcommon.NewValueBool(true)
1604+
return v, nil
1605+
},
1606+
},
1607+
want: true,
1608+
valid: true,
1609+
},
1610+
{
1611+
name: "pcommon.value type double",
1612+
getter: StandardBoolLikeGetter[any]{
1613+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1614+
v := pcommon.NewValueDouble(float64(0.0))
1615+
return v, nil
1616+
},
1617+
},
1618+
want: false,
1619+
valid: true,
1620+
},
1621+
{
1622+
name: "nil",
1623+
getter: StandardBoolLikeGetter[any]{
1624+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1625+
return nil, nil
1626+
},
1627+
},
1628+
want: nil,
1629+
valid: true,
1630+
},
1631+
{
1632+
name: "invalid type",
1633+
getter: StandardBoolLikeGetter[any]{
1634+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1635+
return []byte{}, nil
1636+
},
1637+
},
1638+
valid: false,
1639+
expectedErrorMsg: "unsupported type: []uint8",
1640+
},
1641+
{
1642+
name: "invalid pcommon.value type",
1643+
getter: StandardBoolLikeGetter[any]{
1644+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1645+
v := pcommon.NewValueMap()
1646+
return v, nil
1647+
},
1648+
},
1649+
valid: false,
1650+
expectedErrorMsg: "unsupported value type: Map",
1651+
},
1652+
}
1653+
1654+
for _, tt := range tests {
1655+
t.Run(tt.name, func(t *testing.T) {
1656+
val, err := tt.getter.Get(context.Background(), nil)
1657+
if tt.valid {
1658+
assert.NoError(t, err)
1659+
if tt.want == nil {
1660+
assert.Nil(t, val)
1661+
} else {
1662+
assert.Equal(t, tt.want, *val)
1663+
}
1664+
} else {
1665+
assert.IsType(t, TypeError(""), err)
1666+
assert.EqualError(t, err, tt.expectedErrorMsg)
1667+
}
1668+
})
1669+
}
1670+
}
1671+
1672+
// nolint:errorlint
1673+
func Test_StandardBoolLikeGetter_WrappedError(t *testing.T) {
1674+
getter := StandardBoolLikeGetter[any]{
1675+
Getter: func(ctx context.Context, tCtx any) (any, error) {
1676+
return nil, TypeError("")
1677+
},
1678+
}
1679+
_, err := getter.Get(context.Background(), nil)
1680+
assert.Error(t, err)
1681+
_, ok := err.(TypeError)
1682+
assert.False(t, ok)
1683+
}
1684+
14521685
func Test_StandardPMapGetter(t *testing.T) {
14531686
tests := []struct {
14541687
name string

0 commit comments

Comments
 (0)