Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ViewController: UIViewController {
case .alias:
aliasEvent()
case .none:
analytics?.log(message: "Failed to establish event type", kind: .error)
analytics?.log(message: "Failed to establish event type")
}

clearAll()
Expand Down
33 changes: 20 additions & 13 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/segmentio/sovran-swift.git", from: "1.1.0")
.package(url: "https://github.com/segmentio/sovran-swift.git", from: "1.1.0"),
.package(url: "https://github.com/segmentio/jsonsafeencoder-swift.git", from: "1.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Segment",
dependencies: [
.product(name: "Sovran", package: "sovran-swift")
.product(name: "Sovran", package: "sovran-swift"),
.product(name: "JSONSafeEncoder", package: "jsonsafeencoder-swift")
],
exclude: ["PrivacyInfo.xcprivacy"]),
.testTarget(
Expand Down
6 changes: 4 additions & 2 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/segmentio/sovran-swift.git", from: "1.1.0")
.package(url: "https://github.com/segmentio/sovran-swift.git", from: "1.1.0"),
.package(url: "https://github.com/segmentio/jsonsafeencoder-swift.git", from: "1.0.0")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Segment",
dependencies: [
.product(name: "Sovran", package: "sovran-swift")
.product(name: "Sovran", package: "sovran-swift"),
.product(name: "JSONSafeEncoder", package: "jsonsafeencoder-swift")
],
exclude: ["PrivacyInfo.xcprivacy"]),
.testTarget(
Expand Down
14 changes: 13 additions & 1 deletion Sources/Segment/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import JSONSafeEncoder
#if os(Linux)
import FoundationNetworking
#endif
Expand Down Expand Up @@ -37,10 +38,10 @@ public class Configuration {
var requestFactory: ((URLRequest) -> URLRequest)? = nil
var errorHandler: ((Error) -> Void)? = nil
var flushPolicies: [FlushPolicy] = [CountBasedFlushPolicy(), IntervalBasedFlushPolicy()]

var operatingMode: OperatingMode = .asynchronous
var flushQueue: DispatchQueue = OperatingMode.defaultQueue
var userAgent: String? = nil
var jsonNonConformingNumberStrategy: JSONSafeEncoder.NonConformingFloatEncodingStrategy = .zero
}

internal var values: Values
Expand All @@ -50,6 +51,7 @@ public class Configuration {
/// - Parameter writeKey: Your Segment write key value
public init(writeKey: String) {
self.values = Values(writeKey: writeKey)
JSON.jsonNonConformingNumberStrategy = self.values.jsonNonConformingNumberStrategy
// enable segment destination by default
var settings = Settings(writeKey: writeKey)
settings.integrations = try? JSON([
Expand Down Expand Up @@ -216,11 +218,21 @@ public extension Configuration {
return self
}

/// Specify a custom UserAgent string. This bypasses the OS dependent check entirely.
@discardableResult
func userAgent(_ userAgent: String) -> Configuration {
values.userAgent = userAgent
return self
}

/// This option specifies how NaN/Infinity are handled when encoding JSON.
/// The default is .zero. See JSONSafeEncoder.NonConformingFloatEncodingStrategy for more informatino.
@discardableResult
func jsonNonConformingNumberStrategy(_ strategy: JSONSafeEncoder.NonConformingFloatEncodingStrategy) -> Configuration {
values.jsonNonConformingNumberStrategy = strategy
JSON.jsonNonConformingNumberStrategy = values.jsonNonConformingNumberStrategy
return self
}
}

extension Analytics {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Segment/ObjC/ObjCAnalytics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#if !os(Linux)

import Foundation
import JSONSafeEncoder

// MARK: - ObjC Compatibility

Expand Down Expand Up @@ -164,7 +165,7 @@ extension ObjCAnalytics {
var result: [String: Any]? = nil
if let system: System = analytics.store.currentState() {
do {
let encoder = JSONEncoder.default
let encoder = JSONSafeEncoder.default
let json = try encoder.encode(system.settings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand Down
3 changes: 2 additions & 1 deletion Sources/Segment/ObjC/ObjCConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#if !os(Linux)

import Foundation
import JSONSafeEncoder

@objc(SEGConfiguration)
public class ObjCConfiguration: NSObject {
Expand Down Expand Up @@ -75,7 +76,7 @@ public class ObjCConfiguration: NSObject {
get {
var result = [String: Any]()
do {
let encoder = JSONEncoder.default
let encoder = JSONSafeEncoder.default
let json = try encoder.encode(configuration.values.defaultSettings)
if let r = try JSONSerialization.jsonObject(with: json) as? [String: Any] {
result = r
Expand Down
23 changes: 21 additions & 2 deletions Sources/Segment/Utilities/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,24 @@
//

import Foundation
import JSONSafeEncoder

extension JSONDecoder {
static var `default`: JSONDecoder {
let d = JSONDecoder()
d.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
return d
}
}

extension JSONSafeEncoder {
static var `default`: JSONSafeEncoder {
let e = JSONSafeEncoder()
e.dateEncodingStrategy = .formatted(DateFormatter.iso8601)
e.nonConformingFloatEncodingStrategy = JSON.jsonNonConformingNumberStrategy
return e
}
}

// MARK: - JSON Definition

Expand All @@ -18,6 +35,8 @@ public enum JSON: Equatable {
case array([JSON])
case object([String: JSON])

static var jsonNonConformingNumberStrategy: JSONSafeEncoder.NonConformingFloatEncodingStrategy = .zero

internal enum JSONError: Error {
case unknown
case nonJSONType(type: String)
Expand All @@ -35,7 +54,7 @@ public enum JSON: Equatable {

// For Value types
public init<T: Codable>(with value: T) throws {
let encoder = JSONEncoder.default
let encoder = JSONSafeEncoder.default
let json = try encoder.encode(value)
let output = try JSONSerialization.jsonObject(with: json)
try self.init(output)
Expand Down Expand Up @@ -136,7 +155,7 @@ extension Encodable {
public func toString(pretty: Bool) -> String {
var returnString = ""
do {
let encoder = JSONEncoder.default
let encoder = JSONSafeEncoder.default
if pretty {
encoder.outputFormatting = .prettyPrinted
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/Segment/Utilities/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ extension Optional: Flattenable {
}
}

/* for dev testing only
#if DEBUG
class TrackingDispatchGroup: CustomStringConvertible {
internal let group = DispatchGroup()

Expand Down Expand Up @@ -102,3 +104,5 @@ class TrackingDispatchGroup: CustomStringConvertible {
group.notify(qos: qos, flags: flags, queue: queue, execute: work)
}
}
#endif
*/
18 changes: 1 addition & 17 deletions Sources/Segment/Utilities/iso8601.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import JSONSafeEncoder

enum SegmentISO8601DateFormatter {
static let shared: ISO8601DateFormatter = {
Expand All @@ -16,7 +17,6 @@ enum SegmentISO8601DateFormatter {
}

internal extension Date {
// TODO: support nanoseconds
func iso8601() -> String {
return SegmentISO8601DateFormatter.shared.string(from: self)
}
Expand All @@ -38,19 +38,3 @@ extension DateFormatter {
return formatter
}()
}

extension JSONDecoder {
static var `default`: JSONDecoder {
let d = JSONDecoder()
d.dateDecodingStrategy = .formatted(DateFormatter.iso8601)
return d
}
}

extension JSONEncoder {
static var `default`: JSONEncoder {
let e = JSONEncoder()
e.dateEncodingStrategy = .formatted(DateFormatter.iso8601)
return e
}
}
35 changes: 35 additions & 0 deletions Tests/Segment-Tests/Analytics_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -823,4 +823,39 @@ final class Analytics_Tests: XCTestCase {
XCTAssertEqual(ziggysFound!.count, 3)
XCTAssertEqual(goobersFound!.count, 2)
}

func testJSONNaNDefaultHandlingZero() throws {
// notice we didn't set the nan handling option. zero is the default.
let analytics = Analytics(configuration: Configuration(writeKey: "test"))
let outputReader = OutputReaderPlugin()
analytics.add(plugin: outputReader)

waitUntilStarted(analytics: analytics)

analytics.track(name: "test track", properties: ["TestNaN": Double.nan])

let trackEvent: TrackEvent? = outputReader.lastEvent as? TrackEvent
XCTAssertTrue(trackEvent?.event == "test track")
XCTAssertTrue(trackEvent?.type == "track")
let d: Double? = trackEvent?.properties?.value(forKeyPath: "TestNaN")
XCTAssertTrue(d! == 0)
}

func testJSONNaNHandlingNull() throws {
let analytics = Analytics(configuration: Configuration(writeKey: "test")
.jsonNonConformingNumberStrategy(.null)
)
let outputReader = OutputReaderPlugin()
analytics.add(plugin: outputReader)

waitUntilStarted(analytics: analytics)

analytics.track(name: "test track", properties: ["TestNaN": Double.nan])

let trackEvent: TrackEvent? = outputReader.lastEvent as? TrackEvent
XCTAssertTrue(trackEvent?.event == "test track")
XCTAssertTrue(trackEvent?.type == "track")
let d: Double? = trackEvent?.properties?.value(forKeyPath: "TestNaN")
XCTAssertNil(d)
}
}
Loading