Skip to content

feat: Add URL extraction support to AFError#3976

Open
darkbrewx wants to merge 1 commit into
Alamofire:masterfrom
darkbrewx:feat/aferror-url-extraction
Open

feat: Add URL extraction support to AFError#3976
darkbrewx wants to merge 1 commit into
Alamofire:masterfrom
darkbrewx:feat/aferror-url-extraction

Conversation

@darkbrewx

@darkbrewx darkbrewx commented Sep 5, 2025

Copy link
Copy Markdown

Issue Link 🔗

#3965

Goals ⚽

Enhance AFError.url property to extract URL information from all applicable error types, improving debugging and error tracking capabilities. Previously only supported multipart encoding failures.

Implementation Details 🚧

Core Changes:

  • Extended AFError.url to support 17 different error types through comprehensive switch statement
  • Added extractURL(from:) helper method for consistent URL extraction from nested errors
  • Enhanced documentation with detailed URL extraction behavior per error type

Supported Error Types:

  • Nested errors: .multipartEncodingFailed, .responseValidationFailed, .responseSerializationFailed delegate to reason's URL
  • Network errors: .sessionTaskFailed, .sessionInvalidated, .requestAdaptationFailed, .requestRetryFailed extract from underlying URLError
  • Creation errors: Return nil - use response.request?.url for request URL

Usage:

AF.request("https://invalid-url.example").responseData { response in
    switch response.result {
    case let .failure(error):
        if let failingURL = error.url {
            print("Request failed for URL: \(failingURL)")
        }
        
        // For .invalidURL errors, use .urlConvertible for original input
        if case .invalidURL = error, let original = error.urlConvertible {
            print("Invalid URL input: \(original)")
        }
    case .success: break
    }
}

Backward compatibility: Existing AFError.url behavior for multipart errors unchanged.

Testing Details 🔍

New Tests: 461 lines of comprehensive test cases in AFErrorURLExtractionTests

  • ✅ URL extraction for all 17 AFError types
  • ✅ Nested error delegation (validation, serialization, multipart)
  • ✅ URLError extraction from network failures
  • ✅ Edge cases and nil handling

Documentation: Added Usage.md section with examples and architectural notes.

Results: All tests pass, existing AFError functionality unaffected.

- Add public url property to AFError for extracting URLs from error contexts
- Implement extractURL helper method for consistent URL extraction from underlying errors
- Add url properties to ResponseValidationFailureReason and ResponseSerializationFailureReason
- Support URL extraction from URLError instances via failingURL property
- Handle nested error types including multipart, validation, and serialization failures
- Delegate to reason-specific URL extraction for complex error types
- Return nil for creation/encoding/lifecycle errors as per design decision
- Add comprehensive test suite with 34 test cases covering all 17 AFError types
- Update Usage.md with documentation on extracting URLs from AFError instances
- Note architectural limitation that errors don't automatically include request URLs

@jshier jshier left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I have a few points of cleanup, and I'll review the contents of the tests once that occurs.

Comment thread Source/Core/AFError.swift

/// The `URL` associated with the error.
/// Helper method to extract URL from an error if it's URLError or AFError
private func extractURL(from error: (any Error)?) -> URL? {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems better expressed as an extension on Error, which you can added in the extension section later in this file.

extension Error {
  var url: URL? {
    ...
  }
}

Additionally, it seems like should be more cases where URLs are available from Foundation errors, please do a more thorough examination.

Comment thread Source/Core/AFError.swift
case let .dataFileReadFailed(at: url):
return url
case let .customValidationFailed(error):
if let urlError = error as? URLError {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should simply be the Error.url extension property.

Comment thread Documentation/Usage.md
}
```

#### Extracting URLs from `AFError`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This documentation doesn't seem all that useful right now, please remove it.

Comment thread Source/Core/AFError.swift
/// Extracts URL information from error contexts where applicable. Due to Alamofire's architecture,
/// errors don't automatically include request URLs.
///
/// **URL extraction by error type:**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for any bold treatment here, and please extract the error explanations into a Note section so that it renders independently in the markdown.

Comment thread Source/Core/AFError.swift
case let .requestAdaptationFailed(error):
return extractURL(from: error)
case let .requestRetryFailed(retryError, originalError):
return extractURL(from: originalError) ?? extractURL(from: retryError)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a valid fallback, the URL of a retry failure should only come from the retryError.

//
// AFErrorURLExtractionTests.swift
//
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All new source files should have the current year as copyright.

@jshier

jshier commented Dec 20, 2025

Copy link
Copy Markdown
Contributor

Thanks for your patience! I finally have time to review the pending PRs now that I've gotten the important 5.11 release out, so I should be getting these reviewed and merged over the holidays.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants