Skip to content

Commit e181c5a

Browse files
slashmoktoso
andauthored
Hold trace-id in TraceID struct (#11)
Co-authored-by: Konrad `ktoso` Malawski <[email protected]>
1 parent 8502b4c commit e181c5a

File tree

6 files changed

+239
-22
lines changed

6 files changed

+239
-22
lines changed

Sources/W3CTraceContext/TraceID.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift W3C Trace Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift W3C Trace Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
/// A unique identifier of a distributed trace through a system.
15+
public struct TraceID {
16+
/// The high (left) part of this trace id.
17+
public let high: UInt64
18+
19+
/// The low (right) part of this trace id.
20+
public let low: UInt64
21+
22+
/// Initialize a `TraceID` with the given high and low parts.
23+
///
24+
/// - Parameters:
25+
/// - high: The high part of the trace id
26+
/// - low: The low part of the trace id
27+
public init(high: UInt64, low: UInt64) {
28+
self.high = high
29+
self.low = low
30+
}
31+
32+
/// Initialize a `TraceID` from the given hex string.
33+
///
34+
/// - Parameter hexString: The hex string representation of the trace id to be initialized.
35+
public init?<Hex: StringProtocol>(hexString: Hex) {
36+
guard hexString.count == 32, hexString != Substring(repeating: "0", count: 32) else { return nil }
37+
let highStartIndex = hexString.startIndex
38+
let lowStartIndex = hexString.index(highStartIndex, offsetBy: 16)
39+
guard let high = UInt64(hexString[highStartIndex ..< lowStartIndex], radix: 16) else { return nil }
40+
guard let low = UInt64(hexString[lowStartIndex ..< hexString.endIndex], radix: 16) else { return nil }
41+
self.init(high: high, low: low)
42+
}
43+
44+
/// Generate a random `TraceID`.
45+
///
46+
/// - Returns: A random `TraceID`.
47+
public static func random() -> TraceID {
48+
var generator = SystemRandomNumberGenerator()
49+
return self.random(using: &generator)
50+
}
51+
52+
/// Generate a random `TraceID` using the given `RandomNumberGenerator`.
53+
///
54+
/// - Parameter generator: The generator used to produce the `high` and `low` part.
55+
/// - Returns: A random `TraceID`.
56+
public static func random<G: RandomNumberGenerator>(using generator: inout G) -> TraceID {
57+
TraceID(
58+
high: .random(in: 1 ... .max, using: &generator),
59+
low: .random(in: 1 ... .max, using: &generator)
60+
)
61+
}
62+
}
63+
64+
extension TraceID: Equatable {
65+
public static func == (lhs: TraceID, rhs: TraceID) -> Bool {
66+
lhs.high == rhs.high && lhs.low == rhs.low
67+
}
68+
}
69+
70+
extension TraceID: Comparable {
71+
public static func < (lhs: TraceID, rhs: TraceID) -> Bool {
72+
(lhs.high < rhs.high) || (lhs.high == rhs.high && lhs.low < rhs.low)
73+
}
74+
}
75+
76+
extension TraceID: CustomStringConvertible {
77+
public var description: String {
78+
let highHex = self.high.paddedHexString(radix: 16)
79+
let lowHex = self.low.paddedHexString(radix: 16)
80+
return "\(highHex)\(lowHex)"
81+
}
82+
}

Sources/W3CTraceContext/TraceParent.swift

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct TraceParent {
2020
/// The ID of the whole trace forest, used to uniquely identify a distributed trace through a system.
2121
///
2222
/// - SeeAlso: [W3C TraceContext: trace-id](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#trace-id)
23-
public let traceID: String
23+
public let traceID: TraceID
2424

2525
/// The ID of the incoming request as known by the caller (in some tracing systems, this is known as the span-id, where a span is the execution of
2626
/// a client request).
@@ -33,7 +33,7 @@ public struct TraceParent {
3333
/// - SeeAlso: [W3C TraceContext: trace-flags](https://www.w3.org/TR/2020/REC-trace-context-1-20200206/#trace-flags)
3434
public internal(set) var traceFlags: TraceFlags
3535

36-
init(traceID: String, parentID: String, traceFlags: TraceFlags) {
36+
init(traceID: TraceID, parentID: String, traceFlags: TraceFlags) {
3737
self.traceID = traceID
3838
self.parentID = parentID
3939
self.traceFlags = traceFlags
@@ -71,9 +71,8 @@ extension TraceParent: RawRepresentable {
7171

7272
// trace-id
7373
let traceIDComponent = components[1]
74-
guard traceIDComponent.count == 32 else { return nil }
75-
guard traceIDComponent != String(repeating: "0", count: 32) else { return nil }
76-
self.traceID = String(traceIDComponent)
74+
guard let traceID = TraceID(hexString: traceIDComponent) else { return nil }
75+
self.traceID = traceID
7776

7877
// parent-id
7978
let parentIDComponent = components[2]
@@ -104,7 +103,7 @@ extension TraceParent {
104103
/// - Note: `traceFlags` will be set to 0.
105104
/// - Returns: A `TraceParent` with random `traceID` & `parentID`.
106105
public static func random<G: RandomNumberGenerator>(using generator: inout G) -> TraceParent {
107-
let traceID = Self.randomTraceID(using: &generator)
106+
let traceID = TraceID.random(using: &generator)
108107
let parentID = Self.randomParentID(using: &generator)
109108
return .init(traceID: traceID, parentID: parentID, traceFlags: [])
110109
}
@@ -117,16 +116,6 @@ extension TraceParent {
117116
return .random(using: &g)
118117
}
119118

120-
static func randomTraceID<G: RandomNumberGenerator>(using generator: inout G) -> String {
121-
let traceIDHigh = UInt64
122-
.random(in: 1 ... UInt64.max, using: &generator)
123-
.paddedHexString(radix: 16)
124-
let traceIDLow = UInt64
125-
.random(in: 1 ... UInt64.max, using: &generator)
126-
.paddedHexString(radix: 16)
127-
return traceIDHigh + traceIDLow
128-
}
129-
130119
static func randomParentID<G: RandomNumberGenerator>(using generator: inout G) -> String {
131120
UInt64
132121
.random(in: 1 ... UInt64.max, using: &generator)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift W3C Trace Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift W3C Trace Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import XCTest
15+
16+
struct TestRandomNumberGenerator: RandomNumberGenerator {
17+
var queue: [UInt64]
18+
private let file: StaticString
19+
private let line: UInt
20+
21+
init(queue: [UInt64], file: StaticString = #file, line: UInt = #line) {
22+
self.queue = queue
23+
self.file = file
24+
self.line = line
25+
}
26+
27+
mutating func next() -> UInt64 {
28+
guard !self.queue.isEmpty else {
29+
XCTFail("Requested more random numbers than contained in queue", file: self.file, line: self.line)
30+
preconditionFailure()
31+
}
32+
return self.queue.removeFirst()
33+
}
34+
}

Tests/W3CTraceContextTests/TraceContextTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class TraceContextTests: XCTestCase {
2828
traceContext,
2929
TraceContext(
3030
parent: TraceParent(
31-
traceID: "0af7651916cd43dd8448eb211c80319c",
31+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
3232
parentID: "b7ad6b7169203331",
3333
traceFlags: .sampled
3434
),
@@ -53,7 +53,7 @@ final class TraceContextTests: XCTestCase {
5353
traceContext,
5454
TraceContext(
5555
parent: TraceParent(
56-
traceID: "0af7651916cd43dd8448eb211c80319c",
56+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
5757
parentID: "b7ad6b7169203331",
5858
traceFlags: .sampled
5959
),
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift W3C Trace Context open source project
4+
//
5+
// Copyright (c) 2020 Moritz Lang and the Swift W3C Trace Context project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
@testable import W3CTraceContext
15+
import XCTest
16+
17+
final class TraceIDTests: XCTestCase {
18+
// MARK: - Decoding
19+
20+
func test_decodingFromHexString_succeeds() throws {
21+
let high: UInt64 = 9_532_127_138_774_266_268
22+
let low: UInt64 = 790_211_418_057_950_173
23+
let hexString = high.paddedHexString(radix: 16) + low.paddedHexString(radix: 16)
24+
25+
let traceID = try XCTUnwrap(TraceID(hexString: hexString))
26+
XCTAssertEqual(traceID.high, high)
27+
XCTAssertEqual(traceID.low, low)
28+
}
29+
30+
func test_decodingFromHexString_fails_invalidHex() {
31+
XCTAssertNil(TraceID(hexString: "THIRTY_TWO_INVALID_HEXCHARACTERS"))
32+
}
33+
34+
func test_decodingFromHexString_fails_invalidHighHex() {
35+
XCTAssertNil(TraceID(hexString: "INVALIDHIGHERHEXbd75781e8c42f2c1"))
36+
}
37+
38+
func test_decodingFromHexString_fails_invalidLowHex() {
39+
XCTAssertNil(TraceID(hexString: "bd75781e8c42f2c1_INVALIDLOWERHEX"))
40+
}
41+
42+
func test_decodingFromHexString_fails_tooShort() {
43+
XCTAssertNil(TraceID(hexString: "tooshort"))
44+
}
45+
46+
func test_decodingFromHexString_fails_allZeros() {
47+
XCTAssertNil(TraceID(hexString: String(repeating: "0", count: 16)))
48+
}
49+
50+
// MARK: - Encoding
51+
52+
func test_encodingToHexString_succeeds() {
53+
let traceID = TraceID(high: .max, low: .max)
54+
55+
XCTAssertEqual(String(describing: traceID), "ffffffffffffffffffffffffffffffff")
56+
}
57+
58+
func test_encodingToHexString_padsLeadingZeros() {
59+
let traceID = TraceID(high: 256, low: 256)
60+
61+
XCTAssertEqual(String(describing: traceID), "00000000000001000000000000000100")
62+
}
63+
64+
// MARK: - Equality
65+
66+
func test_equatingTraceIDs_succeeds() {
67+
let traceID = TraceID(high: 1, low: 1)
68+
69+
XCTAssertEqual(traceID, TraceID(high: 1, low: 1))
70+
XCTAssertNotEqual(traceID, TraceID(high: 2, low: 1))
71+
XCTAssertNotEqual(traceID, TraceID(high: 1, low: 2))
72+
XCTAssertNotEqual(traceID, TraceID(high: 2, low: 2))
73+
}
74+
75+
// MARK: - Comparison
76+
77+
func test_comparingTraceIDs_succeeds() {
78+
XCTAssertGreaterThan(TraceID(high: 3, low: 3), TraceID(high: 2, low: 2))
79+
XCTAssertGreaterThan(TraceID(high: 2, low: 3), TraceID(high: 2, low: 2))
80+
XCTAssertGreaterThan(TraceID(high: 2, low: 2), TraceID(high: 1, low: 1))
81+
XCTAssertGreaterThan(TraceID(high: 2, low: 2), TraceID(high: 2, low: 1))
82+
}
83+
84+
// MARK: - Randomness
85+
86+
func test_generatingRandomTraceID_succeeds_usingSystemNumberGenerator() {
87+
let traceID = TraceID.random()
88+
89+
// must always be greater than 0 to be valid
90+
XCTAssertGreaterThan(traceID.high, 0)
91+
XCTAssertGreaterThan(traceID.low, 0)
92+
}
93+
94+
func test_generatingRandomTraceID_succeeds_usingTestNumberGenerator() {
95+
var generator = TestRandomNumberGenerator(queue: [1, 2])
96+
97+
let traceID = TraceID.random(using: &generator)
98+
XCTAssertEqual(traceID, TraceID(high: 1, low: 2))
99+
}
100+
}

Tests/W3CTraceContextTests/TraceParentTests.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ final class TraceParentRawRepresentableTests: XCTestCase {
1919

2020
func testEncodesToValidRawValue() {
2121
let traceParent = TraceParent(
22-
traceID: "0af7651916cd43dd8448eb211c80319c",
22+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
2323
parentID: "b7ad6b7169203331",
2424
traceFlags: .sampled
2525
)
@@ -38,7 +38,11 @@ final class TraceParentRawRepresentableTests: XCTestCase {
3838

3939
XCTAssertEqual(
4040
traceParent,
41-
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: .sampled)
41+
TraceParent(
42+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
43+
parentID: "b7ad6b7169203331",
44+
traceFlags: .sampled
45+
)
4246
)
4347
}
4448

@@ -51,7 +55,11 @@ final class TraceParentRawRepresentableTests: XCTestCase {
5155

5256
XCTAssertEqual(
5357
traceParent,
54-
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: [])
58+
TraceParent(
59+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
60+
parentID: "b7ad6b7169203331",
61+
traceFlags: []
62+
)
5563
)
5664
}
5765

@@ -64,7 +72,11 @@ final class TraceParentRawRepresentableTests: XCTestCase {
6472

6573
XCTAssertEqual(
6674
traceParent,
67-
TraceParent(traceID: "0af7651916cd43dd8448eb211c80319c", parentID: "b7ad6b7169203331", traceFlags: [])
75+
TraceParent(
76+
traceID: TraceID(hexString: "0af7651916cd43dd8448eb211c80319c")!, // !-safe, we know this is a valid traceID
77+
parentID: "b7ad6b7169203331",
78+
traceFlags: []
79+
)
6880
)
6981

7082
XCTAssertFalse(traceParent.traceFlags.contains(.sampled))

0 commit comments

Comments
 (0)