Skip to content

Throw a "data too large" error in ListMapper when parsing data bigger than a certain size like 100MB #15780

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

Draft
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Draft
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
31 changes: 31 additions & 0 deletions Modules/Sources/Networking/Mapper/ListMapper.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import Foundation

/// Shared error types for mappers.
///
public enum MapperError: LocalizedError {
case dataTooLarge

public var errorDescription: String? {
switch self {
case .dataTooLarge:
return NSLocalizedString(
"mapper.error.data.too.large",
value: "The response data is too large to process.",
comment: "Error message when API response data exceeds the maximum allowed size."
)
}
}
}

/// ListMapper: Maps generic WooCommerce REST API Lists
///
struct ListMapper<Output: Decodable>: Mapper {
Expand All @@ -9,9 +26,23 @@ struct ListMapper<Output: Decodable>: Mapper {
///
let siteID: Int64

let maxSizeInBytes: Int64?

/// - Parameters:
/// - siteID: The site identifier associated with the items that will be parsed.
/// - maxSizeInBytes: Optional maximum size of the response data in bytes. Defaults to 100MB.
init(siteID: Int64, maxSizeInBytes: Int64? = 100 * 1024 * 1024) {
self.siteID = siteID
self.maxSizeInBytes = maxSizeInBytes
}

/// (Attempts) to convert a dictionary into [Output].
///
func map(response: Data) throws -> [Output] {
if let maxSizeInBytes, Int64(response.count) > maxSizeInBytes {
throw MapperError.dataTooLarge
}

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
decoder.userInfo = [
Expand Down
56 changes: 56 additions & 0 deletions Networking/NetworkingTests/Mapper/ListMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,60 @@ struct ListMapperTests {
// Then we map to an empty array
#expect(posProducts == [])
}

@Test func it_throws_error_when_exceeding_default_size_limit() throws {
// Given a large response that exceeds the size limit
let largeData = Data(repeating: 0, count: 101 * 1024 * 1024) // 101MB, exceeding 100MB limit

// When we map with default size limit
// Then it throws MapperError.dataTooLarge
#expect(throws: MapperError.dataTooLarge) {
_ = try ListMapper<Order>(siteID: 123).map(response: largeData)
}
}

@Test func it_throws_error_when_exceeding_custom_size_limit() throws {
// Given a response that's larger than a custom limit but smaller than default
let mediumData = Data(repeating: 0, count: 2 * 1024 * 1024) // 2MB
let customLimit: Int64 = 1 * 1024 * 1024 // 1MB limit

// When we map with custom size limit
// Then it throws MapperError.dataTooLarge
#expect(throws: MapperError.dataTooLarge) {
_ = try ListMapper<Order>(siteID: 123, maxSizeInBytes: customLimit).map(response: mediumData)
}
}

@Test func it_skips_size_check_when_maxSizeInBytes_is_nil() throws {
// Given a large response
let largeData = Data(repeating: 0, count: 101 * 1024 * 1024) // 101MB

// When we map with nil size limit (should skip size check)
// Then it should attempt to decode (will fail due to invalid JSON, but not due to size)
do {
_ = try ListMapper<Order>(siteID: 123, maxSizeInBytes: nil).map(response: largeData)
Issue.record("Expected decoding to fail due to invalid JSON")
} catch MapperError.dataTooLarge {
Issue.record("Expected decoding error, not size error")
} catch {
// Expected decoding error due to invalid JSON data
}
}

@Test func it_succeeds_when_data_exactly_at_maxSizeInBytes() throws {
// Given data that's exactly at the size limit
let exactSizeData = Data(repeating: 0, count: 100 * 1024) // Exactly 100KB
let limit: Int64 = 100 * 1024 // 100KB limit

// When we map with exact size limit
// Then it should attempt to decode (will fail due to invalid JSON, but not due to size)
do {
_ = try ListMapper<Order>(siteID: 123, maxSizeInBytes: limit).map(response: exactSizeData)
Issue.record("Expected decoding to fail due to invalid JSON")
} catch MapperError.dataTooLarge {
Issue.record("Expected decoding error, not size error")
} catch {
// Expected decoding error due to invalid JSON data
}
}
}