Skip to content

Commit 78d1d60

Browse files
committed
feat(network-details): Add data holder classes for network detail capture
Based on design from sentry-java - SentryNetworkRequestData Top-level encapsulation of an entire network request - SentryReplayNetworkRequestOrResponse Full details for both requests and responses. - SentryNetworkBody: Body (incl. parsing logic) and warnings for both requests and responses. - SentryNetworkBodyWarning: Enum for warning codes
1 parent af36dde commit 78d1d60

File tree

10 files changed

+316
-7
lines changed

10 files changed

+316
-7
lines changed

Sources/Sentry/SentryNetworkBody.m

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#import "SentryNetworkBody.h"
2+
3+
@implementation SentryNetworkBody
4+
5+
- (instancetype)initWithBody:(nullable id)body
6+
{
7+
return [self initWithBody:body warnings:nil];
8+
}
9+
10+
- (instancetype)initWithBody:(nullable id)body warnings:(nullable NSArray<NSNumber *> *)warnings
11+
{
12+
if (self = [super init]) {
13+
_body = body;
14+
_warnings = warnings;
15+
}
16+
return self;
17+
}
18+
19+
- (NSDictionary *)serialize
20+
{
21+
NSMutableDictionary *result = [NSMutableDictionary dictionary];
22+
23+
if (self.body) {
24+
result[@"body"] = self.body;
25+
}
26+
27+
if (self.warnings && self.warnings.count > 0) {
28+
NSMutableArray *warningStrings = [NSMutableArray arrayWithCapacity:self.warnings.count];
29+
for (NSNumber *warningNumber in self.warnings) {
30+
SentryNetworkBodyWarning warning
31+
= (SentryNetworkBodyWarning)[warningNumber integerValue];
32+
[warningStrings addObject:SentryNetworkBodyWarningToString(warning)];
33+
}
34+
result[@"warnings"] = warningStrings;
35+
}
36+
37+
return result;
38+
}
39+
40+
@end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import "SentryNetworkBodyWarning.h"
2+
3+
NSString *
4+
SentryNetworkBodyWarningToString(SentryNetworkBodyWarning warning)
5+
{
6+
switch (warning) {
7+
case SentryNetworkBodyWarningJsonTruncated:
8+
return @"JSON_TRUNCATED";
9+
case SentryNetworkBodyWarningTextTruncated:
10+
return @"TEXT_TRUNCATED";
11+
case SentryNetworkBodyWarningInvalidJson:
12+
return @"INVALID_JSON";
13+
case SentryNetworkBodyWarningBodyParseError:
14+
return @"BODY_PARSE_ERROR";
15+
}
16+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#import "SentryNetworkRequestData.h"
2+
3+
NSString *const SentryReplayNetworkDetailsKey = @"_networkDetails";
4+
5+
@interface SentryNetworkRequestData ()
6+
@property (nonatomic, copy, readwrite, nullable) NSString *method;
7+
@property (nonatomic, strong, readwrite, nullable) NSNumber *statusCode;
8+
@property (nonatomic, strong, readwrite, nullable) NSNumber *requestBodySize;
9+
@property (nonatomic, strong, readwrite, nullable) NSNumber *responseBodySize;
10+
@property (nonatomic, strong, readwrite, nullable) SentryReplayNetworkRequestOrResponse *request;
11+
@property (nonatomic, strong, readwrite, nullable) SentryReplayNetworkRequestOrResponse *response;
12+
@end
13+
14+
@implementation SentryNetworkRequestData
15+
16+
- (instancetype)initWithMethod:(nullable NSString *)method
17+
{
18+
if (self = [super init]) {
19+
_method = [method copy];
20+
}
21+
return self;
22+
}
23+
24+
- (void)setRequestDetails:(SentryReplayNetworkRequestOrResponse *)requestData
25+
{
26+
self.request = requestData;
27+
self.requestBodySize = requestData.size;
28+
}
29+
30+
- (void)setResponseDetails:(NSInteger)statusCode
31+
responseData:(SentryReplayNetworkRequestOrResponse *)responseData
32+
{
33+
self.statusCode = @(statusCode);
34+
self.response = responseData;
35+
self.responseBodySize = responseData.size;
36+
}
37+
38+
- (NSDictionary *)serialize
39+
{
40+
NSMutableDictionary *result = [NSMutableDictionary dictionary];
41+
42+
if (self.method) {
43+
result[@"method"] = self.method;
44+
}
45+
if (self.statusCode) {
46+
result[@"statusCode"] = self.statusCode;
47+
}
48+
if (self.requestBodySize) {
49+
result[@"requestBodySize"] = self.requestBodySize;
50+
}
51+
if (self.responseBodySize) {
52+
result[@"responseBodySize"] = self.responseBodySize;
53+
}
54+
if (self.request) {
55+
result[@"request"] = [self.request serialize];
56+
}
57+
if (self.response) {
58+
result[@"response"] = [self.response serialize];
59+
}
60+
61+
return result;
62+
}
63+
64+
- (NSString *)description
65+
{
66+
NSDictionary *serialized = [self serialize];
67+
return [NSString stringWithFormat:@"SentryNetworkRequestData: %@", serialized];
68+
}
69+
70+
@end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#import "SentryReplayNetworkRequestOrResponse.h"
2+
3+
@implementation SentryReplayNetworkRequestOrResponse
4+
5+
- (instancetype)initWithSize:(nullable NSNumber *)size
6+
body:(nullable SentryNetworkBody *)body
7+
headers:(NSDictionary<NSString *, NSString *> *)headers
8+
{
9+
if (self = [super init]) {
10+
_size = size;
11+
_body = body;
12+
_headers = [headers copy] ?: @{};
13+
}
14+
return self;
15+
}
16+
17+
- (NSDictionary *)serialize
18+
{
19+
NSMutableDictionary *result = [NSMutableDictionary dictionary];
20+
21+
if (self.size) {
22+
result[@"size"] = self.size;
23+
}
24+
25+
if (self.body) {
26+
result[@"body"] = [self.body serialize];
27+
}
28+
29+
result[@"headers"] = self.headers;
30+
31+
return result;
32+
}
33+
34+
@end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#import "SentryNetworkBodyWarning.h"
2+
#import <Foundation/Foundation.h>
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
/**
7+
* Represents a captured network request or response body with optional warnings.
8+
* Mirrors sentry-java NetworkBody class.
9+
*/
10+
@interface SentryNetworkBody : NSObject
11+
12+
/**
13+
* The captured body content. May be nil if body could not be captured.
14+
* For JSON: NSDictionary or NSArray (parsed JSON)
15+
* For text: NSString
16+
* For form-urlencoded: NSDictionary
17+
*/
18+
@property (nonatomic, strong, readonly, nullable)
19+
id body; // OK: Can hold multiple body types (NSString, NSDictionary, NSData)
20+
21+
/** List of warnings encountered during body capture/parsing. May be nil. */
22+
@property (nonatomic, strong, readonly, nullable) NSArray<NSNumber *> *warnings;
23+
24+
- (instancetype)initWithBody:(nullable id)body;
25+
- (instancetype)initWithBody:(nullable id)body
26+
warnings:(nullable NSArray<NSNumber *> *)warnings NS_DESIGNATED_INITIALIZER;
27+
28+
- (instancetype)init NS_UNAVAILABLE;
29+
30+
/** Serializes to dictionary for inclusion in breadcrumb data. */
31+
- (NSDictionary *)serialize;
32+
33+
@end
34+
35+
NS_ASSUME_NONNULL_END
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#import <Foundation/Foundation.h>
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
/**
6+
* Warning codes for network body capture issues.
7+
* Mirrors sentry-java NetworkBody.NetworkBodyWarning enum.
8+
*/
9+
typedef NS_ENUM(NSInteger, SentryNetworkBodyWarning) {
10+
/** JSON body was truncated mid-parse */
11+
SentryNetworkBodyWarningJsonTruncated,
12+
13+
/** Text body was truncated */
14+
SentryNetworkBodyWarningTextTruncated,
15+
16+
/** JSON parsing failed - body is malformed */
17+
SentryNetworkBodyWarningInvalidJson,
18+
19+
/** Generic body parse error */
20+
SentryNetworkBodyWarningBodyParseError
21+
};
22+
23+
/**
24+
* Converts a warning enum value to its string representation for serialization.
25+
*/
26+
FOUNDATION_EXPORT NSString *_Nonnull SentryNetworkBodyWarningToString(
27+
SentryNetworkBodyWarning warning);
28+
29+
NS_ASSUME_NONNULL_END
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#import "SentryDefines.h"
2+
#import "SentryReplayNetworkRequestOrResponse.h"
3+
#import <Foundation/Foundation.h>
4+
5+
NS_ASSUME_NONNULL_BEGIN
6+
7+
/**
8+
* Key used to store network details in breadcrumb data dictionary.
9+
*/
10+
SENTRY_EXTERN NSString *const SentryReplayNetworkDetailsKey;
11+
12+
/**
13+
* Main container for network request/response tracking.
14+
* Mirrors sentry-java NetworkRequestData class.
15+
*/
16+
@interface SentryNetworkRequestData : NSObject
17+
18+
/** HTTP method (nullable) */
19+
@property (nonatomic, copy, readonly, nullable) NSString *method;
20+
21+
/** HTTP status code (nullable) */
22+
@property (nonatomic, strong, readonly, nullable) NSNumber *statusCode;
23+
24+
/** Request body size in bytes (nullable) */
25+
@property (nonatomic, strong, readonly, nullable) NSNumber *requestBodySize;
26+
27+
/** Response body size in bytes (nullable) */
28+
@property (nonatomic, strong, readonly, nullable) NSNumber *responseBodySize;
29+
30+
/** Request details (nullable) */
31+
@property (nonatomic, strong, readonly, nullable) SentryReplayNetworkRequestOrResponse *request;
32+
33+
/** Response details (nullable) */
34+
@property (nonatomic, strong, readonly, nullable) SentryReplayNetworkRequestOrResponse *response;
35+
36+
- (instancetype)initWithMethod:(nullable NSString *)method NS_DESIGNATED_INITIALIZER;
37+
38+
- (instancetype)init NS_UNAVAILABLE;
39+
40+
/** Sets request details and updates requestBodySize */
41+
- (void)setRequestDetails:(SentryReplayNetworkRequestOrResponse *)requestData;
42+
43+
/** Sets response details, statusCode, and updates responseBodySize */
44+
- (void)setResponseDetails:(NSInteger)statusCode
45+
responseData:(SentryReplayNetworkRequestOrResponse *)responseData;
46+
47+
/** Serializes to dictionary for inclusion in breadcrumb data. */
48+
- (NSDictionary *)serialize;
49+
50+
@end
51+
52+
NS_ASSUME_NONNULL_END
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#import "SentryNetworkBody.h"
2+
#import <Foundation/Foundation.h>
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
/**
7+
* Represents captured HTTP request or response details.
8+
* Mirrors sentry-java ReplayNetworkRequestOrResponse class.
9+
*/
10+
@interface SentryReplayNetworkRequestOrResponse : NSObject
11+
12+
/** Content size in bytes (nullable) */
13+
@property (nonatomic, strong, readonly, nullable) NSNumber *size;
14+
15+
/** Body content (nullable) */
16+
@property (nonatomic, strong, readonly, nullable) SentryNetworkBody *body;
17+
18+
/** HTTP headers (non-null) */
19+
@property (nonatomic, copy, readonly) NSDictionary<NSString *, NSString *> *headers;
20+
21+
- (instancetype)initWithSize:(nullable NSNumber *)size
22+
body:(nullable SentryNetworkBody *)body
23+
headers:(NSDictionary<NSString *, NSString *> *)headers
24+
NS_DESIGNATED_INITIALIZER;
25+
26+
- (instancetype)init NS_UNAVAILABLE;
27+
28+
/** Serializes to dictionary for inclusion in breadcrumb data. */
29+
- (NSDictionary *)serialize;
30+
31+
@end
32+
33+
NS_ASSUME_NONNULL_END

Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -720,26 +720,22 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions {
720720
*
721721
* - Parameter userHeaders: Headers specified by the user (can be nil)
722722
* - Parameter defaults: Default headers that must always be included
723-
* - Returns: Array containing both user headers and default headers (with duplicates removed)
723+
* - Returns: Array containing both user headers and default headers with duplicates removed.
724724
*/
725725
private static func mergeWithDefaultHeaders(_ userHeaders: [String]?, defaults: [String]) -> [String] {
726726
let providedHeaders = userHeaders ?? []
727727

728-
// Use Set to remove duplicates, then convert back to Array
729-
// Case-insensitive comparison to avoid duplicate headers with different casing
730728
var seenHeaders = Set<String>()
731729
var result: [String] = []
732-
733-
// Add default headers first
730+
734731
for header in defaults {
735732
let lowercased = header.lowercased()
736733
if !seenHeaders.contains(lowercased) {
737734
seenHeaders.insert(lowercased)
738735
result.append(header)
739736
}
740737
}
741-
742-
// Add user-provided headers
738+
743739
for header in providedHeaders {
744740
let lowercased = header.lowercased()
745741
if !seenHeaders.contains(lowercased) {

Tests/SentryTests/SentryTests-Bridging-Header.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,17 @@
106106
#import "SentryNSDictionarySanitize.h"
107107
#import "SentryNSError.h"
108108
#import "SentryNSURLSessionTaskSearch.h"
109+
#import "SentryNetworkBody.h"
110+
#import "SentryNetworkBodyWarning.h"
111+
#import "SentryNetworkRequestData.h"
109112
#import "SentryNetworkTracker.h"
110113
#import "SentryNoOpSpan.h"
111114
#import "SentryOptionsInternal.h"
112115
#import "SentryPerformanceTracker+Testing.h"
113116
#import "SentryPerformanceTracker.h"
114117
#import "SentryProfilingConditionals.h"
115118
#import "SentryQueueableRequestManager.h"
119+
#import "SentryReplayNetworkRequestOrResponse.h"
116120
#import "SentrySDK+Private.h"
117121
#import "SentrySDKInternal+Tests.h"
118122
#import "SentrySampleDecision+Private.h"

0 commit comments

Comments
 (0)