diff --git a/lib/src/api/otel_api.dart b/lib/src/api/otel_api.dart index 2e56691..558e083 100644 --- a/lib/src/api/otel_api.dart +++ b/lib/src/api/otel_api.dart @@ -604,19 +604,20 @@ class OTelAPI { return OTelFactory.otelFactory!.traceId(traceId); } - /// Creates a new [TraceId] from a hex string + /// Creates a new [TraceId] from a hex string. + /// + /// If the string is malformed or has the wrong length, the error is + /// reported to the error handler and an invalid [TraceId] is returned, + /// following the OpenTelemetry error-handling spec. static TraceId traceIdFrom(String hexString) { _getAndCacheOtelFactory(); - try { - final bytes = IdGenerator.hexToBytes(hexString); - if (bytes == null || bytes.length != TraceId.traceIdLength) { - throw const FormatException( - 'TraceId must be ${TraceId.traceIdLength} bytes'); - } - return OTelFactory.otelFactory!.traceId(bytes); - } catch (e) { - throw FormatException('Invalid TraceId hex string: $hexString, $e'); + final bytes = IdGenerator.hexToBytes(hexString); + if (bytes == null || bytes.length != TraceId.traceIdLength) { + OTelErrorHandling.report( + FormatException('Invalid TraceId hex string: $hexString')); + return traceIdInvalid(); } + return OTelFactory.otelFactory!.traceId(bytes); } /// Creates an invalid [Trace] (all zeros) @@ -639,21 +640,20 @@ class OTelAPI { return OTelFactory.otelFactory!.spanId(spanId); } - /// SpanId from 8-byte String. + /// Creates a new [SpanId] from a hex string. + /// + /// If the string is malformed or has the wrong length, the error is + /// reported to the error handler and an invalid [SpanId] is returned, + /// following the OpenTelemetry error-handling spec. static SpanId spanIdFrom(String hexString) { _getAndCacheOtelFactory(); - - /// Generate a new random SpanId - try { - final bytes = IdGenerator.hexToBytes(hexString); - if (bytes == null || bytes.length != SpanId.spanIdLength) { - throw const FormatException( - 'SpanId must be ${SpanId.spanIdLength} bytes'); - } - return OTelFactory.otelFactory!.spanId(bytes); - } catch (e) { - throw FormatException('Invalid SpanId hex string: $hexString, $e'); + final bytes = IdGenerator.hexToBytes(hexString); + if (bytes == null || bytes.length != SpanId.spanIdLength) { + OTelErrorHandling.report( + FormatException('Invalid SpanId hex string: $hexString')); + return spanIdInvalid(); } + return OTelFactory.otelFactory!.spanId(bytes); } /// Creates an invalid [SpanId] (all zeros) diff --git a/test/unit/api/trace/span_id_test.dart b/test/unit/api/trace/span_id_test.dart index db81be7..b446a32 100644 --- a/test/unit/api/trace/span_id_test.dart +++ b/test/unit/api/trace/span_id_test.dart @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'package:dartastic_opentelemetry_api/src/api/otel_api.dart'; +import 'package:dartastic_opentelemetry_api/src/util/otel_error_handler.dart'; import 'package:test/test.dart'; void main() { @@ -38,10 +39,24 @@ void main() { }); test('handles invalid hex string', () { - expect( - () => OTelAPI.spanIdFrom('invalid'), - throwsA(isA()), - ); + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + + final id = OTelAPI.spanIdFrom('invalid'); + + expect(id.isValid, isFalse); + expect(id.toString(), equals('0000000000000000')); + expect(received.single, isA()); + }); + + test('handles wrong-length hex string', () { + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + + final id = OTelAPI.spanIdFrom('a1b2c3'); + + expect(id.isValid, isFalse); + expect(received.single, isA()); }); test('rejects a correctly sized uppercase hex string', () { diff --git a/test/unit/api/trace/trace_id_test.dart b/test/unit/api/trace/trace_id_test.dart index d9c89b2..ee5374b 100644 --- a/test/unit/api/trace/trace_id_test.dart +++ b/test/unit/api/trace/trace_id_test.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:dartastic_opentelemetry_api/src/api/otel_api.dart'; +import 'package:dartastic_opentelemetry_api/src/util/otel_error_handler.dart'; import 'package:test/test.dart'; void main() { @@ -40,10 +41,25 @@ void main() { }); test('handles invalid hex string', () { - expect( - () => OTelAPI.traceIdFrom('invalid'), - throwsA(isA()), - ); + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + + final id = OTelAPI.traceIdFrom('invalid'); + + expect(id.isValid, isFalse); + expect(id.toString(), + equals('00000000000000000000000000000000')); + expect(received.single, isA()); + }); + + test('handles wrong-length hex string', () { + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + + final id = OTelAPI.traceIdFrom('a1b2c3'); + + expect(id.isValid, isFalse); + expect(received.single, isA()); }); test('rejects a correctly sized uppercase hex string', () { @@ -110,15 +126,25 @@ void main() { }); test('traceIdFrom handles invalid hex strings', () { - expect(() { - OTelAPI.traceIdFrom('invalid-hex'); - }, throwsFormatException); + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + addTearDown(OTelErrorHandling.resetToDefault); + + final id = OTelAPI.traceIdFrom('invalid-hex'); + + expect(id.isValid, isFalse); + expect(received.single, isA()); }); test('spanIdFrom handles invalid hex strings', () { - expect(() { - OTelAPI.spanIdFrom('invalid-hex'); - }, throwsFormatException); + final received = []; + OTelErrorHandling.handler = (error, _) => received.add(error); + addTearDown(OTelErrorHandling.resetToDefault); + + final id = OTelAPI.spanIdFrom('invalid-hex'); + + expect(id.isValid, isFalse); + expect(received.single, isA()); }); }); }