@@ -6,6 +6,8 @@ enum SentryScopeField: UInt, CaseIterable {
66 case user
77 case dist
88 case environment
9+ case traceContext
10+ case tags
911
1012 var name : String {
1113 switch self {
@@ -17,6 +19,10 @@ enum SentryScopeField: UInt, CaseIterable {
1719 return " dist "
1820 case . environment:
1921 return " environment "
22+ case . tags:
23+ return " tags "
24+ case . traceContext:
25+ return " trace_context "
2026 }
2127 }
2228}
@@ -121,6 +127,30 @@ enum SentryScopeField: UInt, CaseIterable {
121127 writeFieldToDisk ( field: . environment, data: encode ( string: environment) )
122128 }
123129
130+ // MARK: - Tags
131+ @objc
132+ public func readPreviousTagsFromDisk( ) -> [ String : String ] ? {
133+ readFieldFromDisk ( field: . tags) { data in
134+ decodeTags ( from: data)
135+ }
136+ }
137+
138+ func writeTagsToDisk( tags: [ String : String ] ) {
139+ writeFieldToDisk ( field: . tags, data: encode ( tags: tags) )
140+ }
141+
142+ // MARK: - Trace Context
143+ @objc
144+ public func readPreviousTraceContextFromDisk( ) -> [ String : Any ] ? {
145+ readFieldFromDisk ( field: . traceContext) { data in
146+ decodeTraceContext ( from: data)
147+ }
148+ }
149+
150+ func writeTraceContextToDisk( traceContext: [ String : Any ] ) {
151+ writeFieldToDisk ( field: . traceContext, data: encode ( traceContext: traceContext) )
152+ }
153+
124154 // MARK: - Private Functions
125155
126156 private func moveCurrentFileToPreviousFile( field: SentryScopeField ) {
@@ -261,3 +291,75 @@ extension SentryScopePersistentStore {
261291 return String ( data: data, encoding: . utf8)
262292 }
263293}
294+
295+ // MARK: - Tags
296+ extension SentryScopePersistentStore {
297+ private func encode( tags: [ String : String ] ) -> Data ? {
298+ // We need to check if the Tags is a valid JSON object before encoding it.
299+ // Otherwise it will throw an unhandled `NSInvalidArgumentException` exception.
300+ // The error handler is required due but seems not to be executed.
301+ guard let sanitizedTags = sentry_sanitize ( tags) else {
302+ SentrySDKLog . error ( " Failed to sanitize tags, reason: tags is not valid json: \( tags) " )
303+ return nil
304+ }
305+ guard let data = SentrySerialization . data ( withJSONObject: sanitizedTags) else {
306+ SentrySDKLog . error ( " Failed to serialize tags, reason: tags is not valid json: \( tags) " )
307+ return nil
308+ }
309+
310+ return data
311+ }
312+
313+ private func decodeTags( from data: Data ) -> [ String : String ] ? {
314+ guard let deserialized = SentrySerialization . deserializeDictionary ( fromJsonData: data) else {
315+ SentrySDKLog . error ( " Failed to deserialize tags, reason: data is not valid json " )
316+ return nil
317+ }
318+
319+ // `SentrySerialization` is a wrapper around `NSJSONSerialization` which returns any type of data (`id`).
320+ // It is the casted to a `NSDictionary`, which is then casted to a `[AnyHashable: Any]` in Swift.
321+ //
322+ // The responsibility of validating and casting the deserialized data from any data to a dictionary is delegated
323+ // to the `SentrySerialization` class.
324+ //
325+ // As this decode Tags method specifically returns a dictionary of strings, we need to ensure that
326+ // each value is a string.
327+ //
328+ // If the deserialized value is not a string, something clearly went wrong and we should discard the data.
329+
330+ // Iterate through the deserialized dictionary and check if the type is a dictionary.
331+ // When all values are strings, we can safely cast it to `[String: String]` without allocating
332+ // additional memory (like when mapping values).
333+ for (key, value) in deserialized {
334+ guard value is String else {
335+ SentrySDKLog . error ( " Failed to deserialize tags, reason: value for key \( key) is not a valid string " )
336+ return nil
337+ }
338+ }
339+
340+ return deserialized as? [ String : String ]
341+ }
342+ }
343+
344+ // MARK: - Trace Context
345+ extension SentryScopePersistentStore {
346+ private func encode( traceContext: [ String : Any ] ) -> Data ? {
347+ guard let sanitized = sentry_sanitize ( traceContext) else {
348+ SentrySDKLog . error ( " Failed to sanitize traceContext, reason: not valid json: \( traceContext) " )
349+ return nil
350+ }
351+ guard let data = SentrySerialization . data ( withJSONObject: sanitized) else {
352+ SentrySDKLog . error ( " Failed to serialize traceContext, reason: not valid json: \( traceContext) " )
353+ return nil
354+ }
355+ return data
356+ }
357+
358+ private func decodeTraceContext( from data: Data ) -> [ String : Any ] ? {
359+ guard let deserialized = SentrySerialization . deserializeDictionary ( fromJsonData: data) else {
360+ SentrySDKLog . error ( " Failed to deserialize traceContext, reason: data is not valid json " )
361+ return nil
362+ }
363+ return deserialized as? [ String : Any ]
364+ }
365+ }
0 commit comments