Skip to content

Commit 09450ed

Browse files
Tracker module extracted
1 parent 4a14ea0 commit 09450ed

43 files changed

Lines changed: 1367 additions & 126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Atomic.swift
3+
// PeriodicRecorderWorker
4+
//
5+
// Created by Javier L. Avrudsky on 13/08/2020.
6+
// Copyright © 2020 Split. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Thread-safe wrapper for any value type
12+
public final class Atomic<T>: @unchecked Sendable {
13+
private var currentValue: T
14+
private var lock = NSLock()
15+
16+
public init(_ value: T) {
17+
self.currentValue = value
18+
}
19+
20+
public var value: T {
21+
lock.lock()
22+
defer { lock.unlock() }
23+
return self.currentValue
24+
}
25+
26+
public func mutate(_ transformation: (inout T) -> Void) {
27+
lock.lock()
28+
transformation(&self.currentValue)
29+
lock.unlock()
30+
}
31+
32+
public func mutate(_ transformation: (T, inout T) -> Void) {
33+
lock.lock()
34+
transformation(currentValue, &self.currentValue)
35+
lock.unlock()
36+
}
37+
38+
public func getAndSet(_ newValue: T) -> T {
39+
lock.lock()
40+
defer { lock.unlock() }
41+
let oldValue = self.currentValue
42+
self.currentValue = newValue
43+
return oldValue
44+
}
45+
46+
public func set(_ newValue: T) {
47+
lock.lock()
48+
self.currentValue = newValue
49+
lock.unlock()
50+
}
51+
}

Sources/Tracker/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Tracker
2+
3+
A module that provides event tracking functionality with validation and logging.
4+
5+
## Overview
6+
7+
This module contains:
8+
- `Tracker` - Protocol and implementation for tracking events
9+
- `TrackerEvent` - Event data model
10+
- `TrackerEventValidator` - Protocol for event validation
11+
- `TrackerPropertyValidator` - Protocol for property validation
12+
- `TrackerLogger` - Protocol for logging
13+
14+
## Usage
15+
16+
```swift
17+
import Tracker
18+
19+
// Create validator implementations
20+
class MyEventValidator: TrackerEventValidator {
21+
func validate(key: String?, trafficTypeName: String?, eventTypeId: String?,
22+
value: Double?, properties: [String: Any]?,
23+
isSdkReady: Bool) -> TrackerValidationError? {
24+
// Your validation logic
25+
return nil
26+
}
27+
}
28+
29+
class MyPropertyValidator: TrackerPropertyValidator {
30+
func validate(properties: [String: Any]?,
31+
initialSizeInBytes: Int,
32+
validationTag: String) -> TrackerPropertyResult {
33+
return TrackerPropertyResult.valid(properties: properties, sizeInBytes: 0)
34+
}
35+
}
36+
37+
class MyLogger: TrackerLogger {
38+
func log(errorInfo: TrackerValidationError, tag: String) { }
39+
func e(message: String, tag: String) { }
40+
func v(_ message: String) { }
41+
}
42+
43+
// Create the tracker with closures for event push and telemetry
44+
let tracker = DefaultTracker(
45+
defaultTrafficType: "user",
46+
initialEventSizeInBytes: 1024,
47+
eventValidator: MyEventValidator(),
48+
propertyValidator: MyPropertyValidator(),
49+
logger: MyLogger(),
50+
onEventPush: { event in
51+
// Handle the event (e.g., send to server)
52+
print("Event pushed: \(event.eventType)")
53+
},
54+
onTrackLatency: { latency in
55+
// Record telemetry
56+
print("Track latency: \(latency)ms")
57+
}
58+
)
59+
60+
// Track an event
61+
let success = tracker.track(
62+
eventType: "purchase",
63+
trafficType: nil, // Uses defaultTrafficType
64+
value: 99.99,
65+
properties: ["item": "widget"],
66+
matchingKey: "user123",
67+
isSdkReady: true
68+
)
69+
```
70+
71+
## Components
72+
73+
### Tracker
74+
75+
Protocol defining the tracking interface:
76+
- `isTrackingEnabled` - Enable/disable tracking
77+
- `track(...)` - Track an event
78+
79+
### TrackerEvent
80+
81+
Data model for events:
82+
- `trafficType` - Traffic type name
83+
- `eventType` - Event type identifier
84+
- `key` - Matching key
85+
- `value` - Optional numeric value
86+
- `timestamp` - Event timestamp
87+
- `properties` - Optional properties dictionary
88+
- `sizeInBytes` - Size of the event
89+
90+
### TrackerEventValidator
91+
92+
Protocol for validating events before tracking.
93+
94+
### TrackerPropertyValidator
95+
96+
Protocol for validating event properties.
97+
98+
### TrackerLogger
99+
100+
Protocol for logging validation errors and messages.
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// TrackerTests
2+
// Copyright © 2022 Split. All rights reserved.
3+
4+
import XCTest
5+
@testable import Tracker
6+
7+
class TrackerTest: XCTestCase {
8+
9+
var tracker: Tracker!
10+
var eventValidator: TrackerEventValidatorStub!
11+
var propertyValidator: TrackerPropertyValidatorStub!
12+
var logger: TrackerLoggerStub!
13+
var pushedEvents: [TrackerEvent] = []
14+
var recordedLatencies: [Int64] = []
15+
16+
override func setUp() {
17+
eventValidator = TrackerEventValidatorStub()
18+
propertyValidator = TrackerPropertyValidatorStub()
19+
logger = TrackerLoggerStub()
20+
pushedEvents = []
21+
recordedLatencies = []
22+
23+
tracker = DefaultTracker(
24+
defaultTrafficType: "user",
25+
initialEventSizeInBytes: 1024,
26+
eventValidator: eventValidator,
27+
propertyValidator: propertyValidator,
28+
logger: logger,
29+
onEventPush: { [weak self] event in
30+
self?.pushedEvents.append(event)
31+
},
32+
onTrackLatency: { [weak self] latency in
33+
self?.recordedLatencies.append(latency)
34+
}
35+
)
36+
}
37+
38+
func testTrackEnabled() {
39+
tracker.isTrackingEnabled = true
40+
propertyValidator.result = TrackerPropertyResult.valid(properties: nil, sizeInBytes: 100)
41+
42+
let result = tracker.track(
43+
eventType: "test_event",
44+
trafficType: "test_tt",
45+
value: nil,
46+
properties: nil,
47+
matchingKey: "key1",
48+
isSdkReady: true
49+
)
50+
51+
XCTAssertTrue(result)
52+
XCTAssertEqual(pushedEvents.count, 1)
53+
XCTAssertEqual(recordedLatencies.count, 1)
54+
}
55+
56+
func testTrackDisabled() {
57+
tracker.isTrackingEnabled = false
58+
59+
let result = tracker.track(
60+
eventType: "test_event",
61+
trafficType: "test_tt",
62+
value: nil,
63+
properties: nil,
64+
matchingKey: "key1",
65+
isSdkReady: true
66+
)
67+
68+
XCTAssertFalse(result)
69+
XCTAssertEqual(pushedEvents.count, 0)
70+
XCTAssertEqual(recordedLatencies.count, 0)
71+
}
72+
73+
func testTrackWithValidationError() {
74+
tracker.isTrackingEnabled = true
75+
eventValidator.validationError = TrackerValidationError(isError: true, message: "Invalid key")
76+
77+
let result = tracker.track(
78+
eventType: "test_event",
79+
trafficType: "test_tt",
80+
value: nil,
81+
properties: nil,
82+
matchingKey: "key1",
83+
isSdkReady: true
84+
)
85+
86+
XCTAssertFalse(result)
87+
XCTAssertEqual(pushedEvents.count, 0)
88+
}
89+
90+
func testTrackWithPropertyValidationError() {
91+
tracker.isTrackingEnabled = true
92+
propertyValidator.result = TrackerPropertyResult.invalid(message: "Properties too large", sizeInBytes: 50000)
93+
94+
let result = tracker.track(
95+
eventType: "test_event",
96+
trafficType: "test_tt",
97+
value: nil,
98+
properties: ["key": "value"],
99+
matchingKey: "key1",
100+
isSdkReady: true
101+
)
102+
103+
XCTAssertFalse(result)
104+
XCTAssertEqual(pushedEvents.count, 0)
105+
}
106+
107+
func testTrackUsesDefaultTrafficType() {
108+
tracker.isTrackingEnabled = true
109+
propertyValidator.result = TrackerPropertyResult.valid(properties: nil, sizeInBytes: 100)
110+
111+
let result = tracker.track(
112+
eventType: "test_event",
113+
trafficType: nil,
114+
value: nil,
115+
properties: nil,
116+
matchingKey: "key1",
117+
isSdkReady: true
118+
)
119+
120+
XCTAssertTrue(result)
121+
XCTAssertEqual(pushedEvents.count, 1)
122+
XCTAssertEqual(pushedEvents.first?.trafficType, "user")
123+
}
124+
125+
func testTrackWithValue() {
126+
tracker.isTrackingEnabled = true
127+
propertyValidator.result = TrackerPropertyResult.valid(properties: nil, sizeInBytes: 100)
128+
129+
let result = tracker.track(
130+
eventType: "purchase",
131+
trafficType: "user",
132+
value: 99.99,
133+
properties: nil,
134+
matchingKey: "key1",
135+
isSdkReady: true
136+
)
137+
138+
XCTAssertTrue(result)
139+
XCTAssertEqual(pushedEvents.first?.value, 99.99)
140+
}
141+
}
142+
143+
// MARK: - Test Stubs
144+
145+
final class TrackerEventValidatorStub: TrackerEventValidator, @unchecked Sendable {
146+
var validationError: TrackerValidationError?
147+
148+
func validate(key: String?, trafficTypeName: String?, eventTypeId: String?, value: Double?, properties: [String: Any]?, isSdkReady: Bool) -> TrackerValidationError? {
149+
validationError
150+
}
151+
}
152+
153+
final class TrackerPropertyValidatorStub: TrackerPropertyValidator, @unchecked Sendable {
154+
var result = TrackerPropertyResult.valid(properties: nil, sizeInBytes: 0)
155+
156+
func validate(properties: [String: Any]?, initialSizeInBytes: Int, validationTag: String) -> TrackerPropertyResult {
157+
result
158+
}
159+
}
160+
161+
final class TrackerLoggerStub: TrackerLogger, @unchecked Sendable {
162+
var loggedErrors: [TrackerValidationError] = []
163+
var errorMessages: [String] = []
164+
var verboseMessages: [String] = []
165+
166+
func log(errorInfo: TrackerValidationError, tag: String) {
167+
loggedErrors.append(errorInfo)
168+
}
169+
170+
func e(message: String, tag: String) {
171+
errorMessages.append(message)
172+
}
173+
174+
func v(_ message: String) {
175+
verboseMessages.append(message)
176+
}
177+
}

0 commit comments

Comments
 (0)