-
-
Notifications
You must be signed in to change notification settings - Fork 372
refactor: Add attributable protocol for typed attribute values #7077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
df295cf
7aecbe3
4a4dc20
afd1f46
b57d616
6a1093d
8b7c319
67494c1
ee2b24a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,67 @@ public final class SentryAttribute: NSObject { | |
| super.init() | ||
| } | ||
|
|
||
| internal init(value: Any) { | ||
| public init(stringArray values: [String]) { | ||
| self.type = "string[]" | ||
| self.value = values | ||
| super.init() | ||
| } | ||
|
|
||
| public init(booleanArray values: [Bool]) { | ||
| self.type = "boolean[]" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
private enum AttributeType: String {
case stringType = "string",
booleanType = "boolean",
integerType = "integer",
integerArrayType = "integer[]",
doubleType = "double",
doubleArrayType = "double[]"
}SentryAttributeValue could also use it for the types. |
||
| self.value = values | ||
| super.init() | ||
| } | ||
|
|
||
| public init(integerArray values: [Int]) { | ||
| self.type = "integer[]" | ||
| self.value = values | ||
| super.init() | ||
| } | ||
|
|
||
| public init(doubleArray values: [Double]) { | ||
| self.type = "double[]" | ||
| self.value = values | ||
| super.init() | ||
| } | ||
|
|
||
| /// Creates a double attribute from a float value | ||
| public init(floatArray values: [Float]) { | ||
| self.type = "double[]" | ||
| self.value = values.map(Double.init) | ||
| super.init() | ||
| } | ||
|
|
||
| internal init(attributableValue: SentryAttributeValue) { | ||
| switch attributableValue { | ||
| case .boolean(let value): | ||
| self.type = "boolean" | ||
| self.value = value | ||
| case .string(let value): | ||
| self.type = "string" | ||
| self.value = value | ||
| case .integer(let value): | ||
| self.type = "integer" | ||
| self.value = value | ||
| case .double(let value): | ||
| self.type = "double" | ||
| self.value = value | ||
| case .booleanArray(let array): | ||
| self.type = "boolean[]" | ||
| self.value = array | ||
| case .stringArray(let array): | ||
| self.type = "string[]" | ||
| self.value = array | ||
| case .integerArray(let array): | ||
| self.type = "integer[]" | ||
| self.value = array | ||
| case .doubleArray(let array): | ||
| self.type = "double[]" | ||
| self.value = array | ||
| } | ||
| } | ||
|
|
||
| internal init(value: Any) { // swiftlint:disable:this cyclomatic_complexity | ||
| switch value { | ||
| case let stringValue as String: | ||
| self.type = "string" | ||
|
|
@@ -57,6 +117,31 @@ public final class SentryAttribute: NSObject { | |
| case let floatValue as Float: | ||
| self.type = "double" | ||
| self.value = Double(floatValue) | ||
| case let stringValues as [String]: | ||
| self.type = "string[]" | ||
| self.value = stringValues | ||
| case let boolValues as [Bool]: | ||
| self.type = "boolean[]" | ||
| self.value = boolValues | ||
| case let intValues as [Int]: | ||
| self.type = "integer[]" | ||
| self.value = intValues | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| case let doubleValues as [Double]: | ||
| self.type = "double[]" | ||
| self.value = doubleValues | ||
| case let floatValues as [Float]: | ||
| self.type = "double[]" | ||
| self.value = floatValues.map(Double.init) | ||
| case let attributable as SentryAttributeValuable: | ||
| let value = attributable.asAttributeValue | ||
| self.type = value.type | ||
| self.value = value.anyValue | ||
| case let attribute as SentryAttributeValue: | ||
| self.type = attribute.type | ||
| self.value = attribute.anyValue | ||
| case let attribute as SentryAttribute: | ||
| self.type = attribute.type | ||
| self.value = attribute.value | ||
| default: | ||
| // For any other type, convert to string representation | ||
| self.type = "string" | ||
|
|
@@ -67,40 +152,51 @@ public final class SentryAttribute: NSObject { | |
| } | ||
|
|
||
| // MARK: - Internal Encodable Support | ||
| @_spi(Private) extension SentryAttribute: Encodable { | ||
| private enum CodingKeys: String, CodingKey { | ||
| case value | ||
| case type | ||
| } | ||
|
|
||
| @_spi(Private) extension SentryAttribute: Encodable { | ||
| @_spi(Private) public func encode(to encoder: any Encoder) throws { | ||
| var container = encoder.container(keyedBy: CodingKeys.self) | ||
|
|
||
| try container.encode(type, forKey: .type) | ||
| try self.asAttributeValue.encode(to: encoder) | ||
| } | ||
| } | ||
|
|
||
| switch type { | ||
| @_spi(Private) extension SentryAttribute: SentryAttributeValuable { | ||
| @_spi(Private) public var asAttributeValue: SentryAttributeValue { | ||
| switch self.type { | ||
| case "string": | ||
| guard let stringValue = value as? String else { | ||
| throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected String but got \(Swift.type(of: value))")) | ||
| if let val = self.value as? String { | ||
| return .string(val) | ||
| } | ||
| try container.encode(stringValue, forKey: .value) | ||
| case "boolean": | ||
| guard let boolValue = value as? Bool else { | ||
| throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Bool but got \(Swift.type(of: value))")) | ||
| if let val = self.value as? Bool { | ||
| return .boolean(val) | ||
| } | ||
| try container.encode(boolValue, forKey: .value) | ||
| case "integer": | ||
| guard let intValue = value as? Int else { | ||
| throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Int but got \(Swift.type(of: value))")) | ||
| if let val = self.value as? Int { | ||
| return .integer(val) | ||
| } | ||
| try container.encode(intValue, forKey: .value) | ||
| case "double": | ||
| guard let doubleValue = value as? Double else { | ||
| throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "Expected Double but got \(Swift.type(of: value))")) | ||
| if let val = self.value as? Double { | ||
| return .double(val) | ||
| } | ||
| case "string[]": | ||
| if let val = self.value as? [String] { | ||
| return .stringArray(val) | ||
| } | ||
| case "boolean[]": | ||
| if let val = self.value as? [Bool] { | ||
| return .booleanArray(val) | ||
| } | ||
| case "integer[]": | ||
| if let val = self.value as? [Int] { | ||
| return .integerArray(val) | ||
| } | ||
| case "double[]": | ||
| if let val = self.value as? [Double] { | ||
| return .doubleArray(val) | ||
| } | ||
| try container.encode(doubleValue, forKey: .value) | ||
| default: | ||
| try container.encode(String(describing: value), forKey: .value) | ||
| break | ||
| } | ||
| return .string(String(describing: value)) | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||
| public protocol SentryAttributeValuable { | ||||||
| var asAttributeValue: SentryAttributeValue { get } | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| extension String: SentryAttributeValuable { | ||||||
| public var asAttributeValue: SentryAttributeValue { | ||||||
| return .string(self) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| extension Bool: SentryAttributeValuable { | ||||||
| public var asAttributeValue: SentryAttributeValue { | ||||||
| return .boolean(self) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| extension Int: SentryAttributeValuable { | ||||||
| public var asAttributeValue: SentryAttributeValue { | ||||||
| return .integer(self) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| extension Double: SentryAttributeValuable { | ||||||
| public var asAttributeValue: SentryAttributeValue { | ||||||
| return .double(self) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| extension Float: SentryAttributeValuable { | ||||||
| public var asAttributeValue: SentryAttributeValue { | ||||||
| return .double(Double(self)) | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| public enum SentryAttributeValue: Equatable, Hashable { | ||
| case string(String) | ||
| case boolean(Bool) | ||
| case integer(Int) | ||
| case double(Double) | ||
| case stringArray([String]) | ||
| case booleanArray([Bool]) | ||
| case integerArray([Int]) | ||
| case doubleArray([Double]) | ||
|
|
||
| var type: String { | ||
| switch self { | ||
| case .string: | ||
| return "string" | ||
| case .boolean: | ||
| return "boolean" | ||
| case .integer: | ||
| return "integer" | ||
| case .double: | ||
| return "double" | ||
| case .stringArray: | ||
| return "string[]" | ||
| case .booleanArray: | ||
| return "boolean[]" | ||
| case .integerArray: | ||
| return "integer[]" | ||
| case .doubleArray: | ||
| return "double[]" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue: Encodable { | ||
| private enum CodingKeys: String, CodingKey { | ||
| case type | ||
| case value | ||
| } | ||
|
|
||
| public func encode(to encoder: any Encoder) throws { | ||
| var container = encoder.container(keyedBy: CodingKeys.self) | ||
| try container.encode(type, forKey: .type) | ||
|
|
||
| switch self { | ||
| case .string(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .boolean(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .integer(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .double(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .stringArray(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .booleanArray(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .integerArray(let value): | ||
| try container.encode(value, forKey: .value) | ||
| case .doubleArray(let value): | ||
| try container.encode(value, forKey: .value) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue { | ||
| static func from(anyValue value: Any) -> Self { | ||
| if let val = value as? String { | ||
| return .string(val) | ||
| } | ||
| if let val = value as? Bool { | ||
| return .boolean(val) | ||
| } | ||
| if let val = value as? Int { | ||
| return .integer(val) | ||
| } | ||
| if let val = value as? Double { | ||
| return .double(val) | ||
| } | ||
| if let val = value as? Float { | ||
| return .double(Double(val)) | ||
| } | ||
| if let val = value as? SentryAttributeValue { | ||
| return val | ||
| } | ||
| if let val = value as? SentryAttributeValuable { | ||
| return val.asAttributeValue | ||
| } | ||
| return .string(String(describing: value)) | ||
| } | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| var anyValue: Any { | ||
| switch self { | ||
| case .string(let value): | ||
| return value | ||
| case .boolean(let value): | ||
| return value | ||
| case .integer(let value): | ||
| return value | ||
| case .double(let value): | ||
| return value | ||
| case .stringArray(let value): | ||
| return value | ||
| case .booleanArray(let value): | ||
| return value | ||
| case .integerArray(let value): | ||
| return value | ||
| case .doubleArray(let value): | ||
| return value | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue: ExpressibleByStringLiteral { | ||
| public init(stringLiteral value: StringLiteralType) { | ||
| self = .string(value) | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue: ExpressibleByBooleanLiteral { | ||
| public init(booleanLiteral value: BooleanLiteralType) { | ||
| self = .boolean(value) | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue: ExpressibleByFloatLiteral { | ||
| public init(floatLiteral value: FloatLiteralType) { | ||
| self = .double(value) | ||
| } | ||
| } | ||
|
|
||
| extension SentryAttributeValue: ExpressibleByIntegerLiteral { | ||
| public init(integerLiteral value: IntegerLiteralType) { | ||
| self = .integer(value) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| protocol BatcherItem: Encodable { | ||
| var attributes: [String: SentryAttribute] { get set } | ||
| var attributesMap: [String: SentryAttributeValue] { get set } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was renamed because it would otherwise collide with the type of |
||
| var traceId: SentryId { get set } | ||
| var body: String { get } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h: I would highly appreciate proper code docs for all these new init methods, as these are public API.