You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Refactor RestError enum cases to use named parameters for clarity
Add LocalizedError conformance with localized descriptions and recovery suggestions
Implement CustomDebugStringConvertible for improved debugging output
Add Equatable conformance to enable error comparison in tests
Diagram Walkthrough
flowchart LR
A["RestError enum"] -->|"Add named parameters"| B["Improved API clarity"]
A -->|"Implement LocalizedError"| C["User-friendly messages"]
A -->|"Add CustomDebugStringConvertible"| D["Better debugging"]
A -->|"Implement Equatable"| E["Test support"]
B --> F["Enhanced RestError"]
C --> F
D --> F
E --> F
Loading
File Walkthrough
Relevant files
Enhancement
RestApiCaller.swift
Update RestError instantiations with named parameters
Sources/SwiftRestRequests/RestApiCaller.swift
Update all RestError case instantiations to use named parameters
Change unexpectedHttpStatusCode(httpResponse.statusCode) to unexpectedHttpStatusCode(statusCode: httpResponse.statusCode)
Change badResponse(response, data) to badResponse(response: response, data: data)
Objective: To create a detailed and reliable record of critical system actions for security analysis and compliance.
Status: Audit Logging: The new logging added records request method, path, status, and response size but does not clearly include a user identifier or guarantee structured logs for reconstructing critical actions.
Referred Code
// Log receipt for auditing
logger.debug("Received response: method=\(httpMethod.rawValue) path=\(relativePath ??"/") status=\(httpStatus) size=\(data.count)")
// For requests without deserialization and no error just return the status
ifshouldBypassDeserialization(responseDeserializer, status: httpStatus),
httpStatus.type ==.success {
logger.debug("Bypassing deserialization for status=\(httpStatus) at path=\(relativePath ??"/")")return(nil, httpStatus)}
// If there's no body but the status indicates an error, surface a failedRestCall so callers
// can handle API-level errors. Log the condition for audit.
if data.isEmpty {
logger.warning("Empty response body received for status=\(httpStatus) at path=\(relativePath ??"/"). Treating as failedRestCall.")throwRestError.failedRestCall(response: httpResponse, status: httpStatus, errorPayload:nil)}
// Postcondition: We have a response body that needs validation/parsing.
Objective: To prevent the leakage of sensitive system information through error messages while providing sufficient detail for internal debugging.
Status: Error Detail Exposure: Logger calls emit errorDescription and underlying decoding errors which may surface internal details if routed to user-facing logs.
Referred Code
letmimeTypeError=RestError.invalidMimeType(mimeType: contentType)
logger.error("Throw mimetype error: \(String(describing: mimeTypeError.errorDescription))")throw mimeTypeError
}
return mimeType
}privatefunc decodeSuccessfulResponse<T:Deserializer>(data:Data, response:HTTPURLResponse, deserializer:T)throws->T.ResponseType?{
if response.status ==.ok {do{returntry deserializer.deserialize(data)}catch{
// Log decoding failure with diagnostics and rethrow a RestError
letmalformedResponseError=RestError.malformedResponse(response: response, data: data, underlying: error)
logger.error("Failed to deserialize successful response throwing error: \(String(describing: malformedResponseError.errorDescription)).")throw malformedResponseError
Objective: To ensure logs are useful for debugging and auditing without exposing sensitive information like PII, PHI, or cardholder data.
Status: Potential PII Logs: Logs include path and status and may include payload-derived details via errorDescription which could inadvertently contain sensitive data depending on upstream configuration.
Referred Code
// Log receipt for auditing
logger.debug("Received response: method=\(httpMethod.rawValue) path=\(relativePath ??"/") status=\(httpStatus) size=\(data.count)")
// For requests without deserialization and no error just return the status
ifshouldBypassDeserialization(responseDeserializer, status: httpStatus),
httpStatus.type ==.success {
logger.debug("Bypassing deserialization for status=\(httpStatus) at path=\(relativePath ??"/")")return(nil, httpStatus)}
// If there's no body but the status indicates an error, surface a failedRestCall so callers
// can handle API-level errors. Log the condition for audit.
if data.isEmpty {
logger.warning("Empty response body received for status=\(httpStatus) at path=\(relativePath ??"/"). Treating as failedRestCall.")throwRestError.failedRestCall(response: httpResponse, status: httpStatus, errorPayload:nil)}
// Postcondition: We have a response body that needs validation/parsing.
_ =tryvalidatedMimeType(from: httpResponse)...(clipped 54 lines)
Description: Debug descriptions interpolate untrusted response data (URLs, payload sizes) into strings that could be logged; if logs include sensitive URLs or query parameters, this may expose sensitive information in logs. RestError.swift [101-118]
Objective: To create a detailed and reliable record of critical system actions for security analysis and compliance.
Status: Missing Logging: New error paths throw RestError but do not log critical request/response context, which may limit auditability of failed REST calls.
Referred Code
// make request and install interceptor hooks
callInvokeInterceptors(&request)let(data, response)=tryawait session.data(for: request)
// check http response has a supported type
guardlet httpResponse = response as?HTTPURLResponseelse{throwRestError.badResponse(response: response, data: data)}callReceiveInterceptors(data, httpResponse)tryvalidateResponseStatusCodes(options.expectedStatusCodes, httpResponse)return(data, httpResponse)}
/// Make a REST call and deserialize the response with the given deserializer from JSON to object. For successful calls the httpStatus is returned as well. Note:
/// Some REST services do not always return
/// - Parameters:
...(clipped 67 lines)
Generic: Security-First Input Validation and Data Handling
Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent vulnerabilities
Status: Error Detail Exposure: The LocalizedError descriptions include underlying error messages and status codes which may be user-facing depending on usage, potentially exposing internal details.
Referred Code
extensionRestError:LocalizedError{publicvarerrorDescription:String?{switchself{case.badResponse:return"Received an unsupported or invalid response from the server."case.invalidMimeType(let mime):return"Unexpected content type: \(mime ??"unknown")."case.invalidQueryParameter:return"Failed to encode query parameters."case.malformedResponse(_, _,let underlying):return"Failed to decode response: \(underlying.localizedDescription)"case.failedRestCall(_,let status, _):return"Server returned an error (status: \(status))."case.unexpectedHttpStatusCode(let code):return"Unexpected HTTP status code: \(code)."}}publicvar recoverySuggestion:String?{
switch self{case.invalidQueryParameter:...(clipped 27 lines)
Re-evaluate the incomplete Equatable implementation
The Equatable implementation for RestError is incomplete, as it only compares a subset of associated values for certain cases. This should be fixed by either implementing a more thorough check or by removing the conformance and using test helpers instead.
extensionRestError:Equatable{publicstaticfunc==(lhs:RestError, rhs:RestError)->Bool{
switch (lhs, rhs){case(.invalidQueryParameter,.invalidQueryParameter):returntruecase(.invalidMimeType(let a),.invalidMimeType(let b)):return a == b
case(.unexpectedHttpStatusCode(let a),.unexpectedHttpStatusCode(let b)):return a == b
case(.badResponse(let la,let ld),.badResponse(let ra,let rd)):...(clipped 10 lines)
Solution Walkthrough:
Before:
extensionRestError:Equatable{publicstaticfunc==(lhs:RestError, rhs:RestError)->Bool{switch(lhs, rhs){
// ...
case(.malformedResponse(let la, _, _),.malformedResponse(let ra, _, _)):
// Only status code is compared. Data and underlying error are ignored.
return la.statusCode == ra.statusCode
case(.failedRestCall(_,let aStatus, _),.failedRestCall(_,let bStatus, _)):
// Only status is compared. Response and payload are ignored.
return aStatus == bStatus
default:returnfalse}}}
Why: The suggestion correctly identifies that the new Equatable conformance for RestError is incomplete for malformedResponse and failedRestCall, which could lead to incorrect test outcomes.
Medium
General
Improve Equatable conformance for malformed responses
Improve the Equatable implementation for the .malformedResponse case by also comparing the response data, in addition to the status code.
Why: The suggestion correctly identifies that the Equatable implementation for .malformedResponse is incomplete and proposes a valid improvement by comparing the response data, which enhances the accuracy of equality checks for testing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Type
Enhancement
Description
Refactor
RestErrorenum cases to use named parameters for clarityAdd
LocalizedErrorconformance with localized descriptions and recovery suggestionsImplement
CustomDebugStringConvertiblefor improved debugging outputAdd
Equatableconformance to enable error comparison in testsDiagram Walkthrough
File Walkthrough
RestApiCaller.swift
Update RestError instantiations with named parametersSources/SwiftRestRequests/RestApiCaller.swift
RestErrorcase instantiations to use named parametersunexpectedHttpStatusCode(httpResponse.statusCode)tounexpectedHttpStatusCode(statusCode: httpResponse.statusCode)badResponse(response, data)tobadResponse(response: response,data: data)failedRestCall(httpResponse, httpStatus, error: nil)tofailedRestCall(response: httpResponse, status: httpStatus,errorPayload: nil)invalidMimeType(contentType)toinvalidMimeType(mimeType:contentType)malformedResponse(response, data, error)tomalformedResponse(response: response, data: data, underlying: error)RestError.swift
Add localization and debugging support to RestErrorSources/SwiftRestRequests/RestError.swift
RestErrorcases to use named parameters instead ofpositional arguments
clarity and detail
LocalizedErrorextension witherrorDescriptionandrecoverySuggestionproperties for user-friendly error messagesCustomDebugStringConvertibleextension providing detailed debugoutput for each error case
Equatableextension enabling error comparison for testing purposes