Skip to content

Expose normalized transport-error detail (ErrorString/ErrorSymbol/ErrorCode) — lands #31 + review fixes#33

Merged
bkaradzic-microsoft merged 6 commits into
mainfrom
pr31-transport-error-reporting
Jun 16, 2026
Merged

Expose normalized transport-error detail (ErrorString/ErrorSymbol/ErrorCode) — lands #31 + review fixes#33
bkaradzic-microsoft merged 6 commits into
mainfrom
pr31-transport-error-reporting

Conversation

@bkaradzic-microsoft

Copy link
Copy Markdown
Member

Lands the transport-error reporting work from #31 (authored by @matthargett) together with maintainer review fixes applied on top.

This is pushed as a base-repo branch because #31's head fork is org-owned, so GitHub blocks maintainer-edit pushes onto its branch despite maintainerCanModify == true. #31 will be closed manually once this merges; all of @matthargett's original commits are preserved here.

What #31 adds (unchanged)

UrlRequest::ErrorString() / ErrorSymbol() / ErrorCode() — normalized, grep-friendly transport-error detail ("<domain>:<symbol>(<code>): <detail>") for the Apple (NSURLSession) and Linux (libcurl) backends. Purely additive: SendAsync() still completes with status 0 on transport failure, so existing consumers are unaffected. Offline-deterministic gtest coverage included.

Maintainer review fixes (commit 51bbf33)

  • Zero-code contract clarified (header + README). A missing app:/// resource legitimately reports AppResourceNotFound with code 0, so the docs now state that a transport failure is signaled by a non-empty ErrorString()/ErrorSymbol()not by ErrorCode() != 0. A test asserts the code-0 contract explicitly.
  • Apple nil-safety. Added ToStdString(NSString*) so a nil localizedDescription/domain can't construct std::string{nullptr} (UB) when building the error detail or walking the underlying-error chain.
  • Unix token de-confusion. The synthesized getinfo-failure token is renamed CURLE_GETINFO_FAILEDGetInfoFailed so it no longer masquerades as a real CURLcode.
  • DNS test hardened. The curl .invalid assertion now requires a curl: transport error with a non-zero code instead of pinning CURLE_COULDNT_RESOLVE_HOST(6), tolerating proxy / DNS-hijacking resolvers; exact symbols stay covered by the connection-refused and missing-file cases.

Validation

Built and ran UrlLibTests on Win32 (MSVC): the local-file success case passes; the transport-detail cases GTEST_SKIP (Windows backend doesn't populate detail yet — unchanged from #31). Apple/Linux backends keep #31's CI coverage.

Follow-up

CI workflows land separately in #32 (the .github/ churn in this branch's history nets to zero in the tree, matching #31's file set).

Co-authored-by: Matt Hargett plaztiksyke@gmail.com

matthargett and others added 6 commits June 10, 2026 15:21
Transport failures (DNS, connection refused, TLS, missing local files)
previously completed with StatusCode 0 and no further information: the
Apple backend discarded the NSError (an existing TODO), and the curl
backend caught and dropped its own failure exception. Consumers like
JsRuntimeHost's fetch/XMLHttpRequest polyfills could only report
'network request failed', which makes field crash reports undiagnosable.

New accessors, empty/zero when the request did not fail at the transport
layer, normalized for log-pipeline (Splunk etc.) substring filtering:

  ErrorString()  ->  "<domain>:<symbol>(<code>): <detail>"
  ErrorSymbol()  ->  e.g. CURLE_COULDNT_RESOLVE_HOST, NSURLErrorTimedOut
  ErrorCode()    ->  raw numeric platform code

- Apple: capture NSError domain/code/description (resolves the TODO),
  walking the underlying-error chain for distinct-code levels; missing
  app:/// bundle resources report urllib:AppResourceNotFound(0).
- curl: CURLOPT_ERRORBUFFER detail (host/port/path specifics) with
  curl_easy_strerror fallback and stable CURLE_* symbols.
- Purely additive: SendAsync still completes normally with status 0, so
  existing consumers are unaffected until they opt in.
- Windows/Android backends still report empty/zero (follow-up).

Adds offline-deterministic gtest coverage (7 cases: success, missing
file, connection refused, NXDOMAIN via RFC 6761 .invalid, grammar shape,
reuse-clears-error, missing app resource) and a CI workflow running them
on ubuntu-latest (curl path) and macos-latest (NSURLSession path).
…imeout

- RefusingPort: race-free held-bind refusal on Linux; on Darwin a SYN to a
  bound-but-not-listening port is dropped (times out, -1001) rather than
  refused, so close after reserving the port number there. optional<> +
  ASSERT at call sites instead of EXPECT inside the helper.
- TempFile: pid-suffixed unique names, removed on destruction.
- SendAndWait: 30s wait, Abort() on timeout (currently only effective on
  the Windows backend; the offline-deterministic scenarios are the real
  bound).
Replace the single-file matrix workflow with the family pattern used by
both sibling repos: a ci.yml orchestrator calling reusable per-platform
workflow_call files (build-linux.yml, build-macos.yml), checkout@v5,
timeout-minutes, Ninja + explicit gcc/clang jobs on Linux, pinned Xcode
selection with -G Xcode on macOS, Build/<platform> dirs, and running the
test binary directly. Verified the macOS steps locally (Xcode generator,
RelWithDebInfo, 7/7 tests).
build-win32.yml mirrors JsRuntimeHost's: windows-2022 (VS2022 generator
pin), -A platform input, RelWithDebInfo with /m, crash-dump registry
staging and artifact upload on failure.

Test fixtures now compile on MSVC: Winsock variant of RefusingPort
(WSAStartup, SOCKET/closesocket), _getpid, and file:///C:/... URL
formation. The five assertions that depend on transport-error detail
GTEST_SKIP on Windows with an explicit message, since the Windows
backend does not populate the new accessors yet -- the gap stays visible
in test output while the suite (and the local-file success case, which
runs for real) stays green.
The GitHub Actions setup now lives in its own PR so the infrastructure
can be reviewed independently; it depends on this PR's UrlLibTests
target and lands after it.
- Document that a transport failure is signaled by a non-empty
  ErrorString()/ErrorSymbol(), not ErrorCode() != 0; AppResourceNotFound
  legitimately reports code 0 (header + README).
- Apple: add ToStdString() so a nil NSString (localizedDescription/domain)
  cannot construct std::string{nullptr} (undefined behavior).
- Unix: rename the synthesized getinfo-failure token from CURLE_GETINFO_FAILED
  to GetInfoFailed so it does not masquerade as a real CURLcode.
- Tests: loosen the curl DNS assertion to a "curl:" transport error with a
  non-zero code (tolerates proxy/hijacking resolvers; exact symbols stay
  covered by the refused/missing-file cases) and assert the AppResourceNotFound
  code-0 contract explicitly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 16, 2026 19:04
@bkaradzic-microsoft bkaradzic-microsoft merged commit e86ffb3 into main Jun 16, 2026
2 checks passed
@bkaradzic-microsoft bkaradzic-microsoft deleted the pr31-transport-error-reporting branch June 16, 2026 19:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR adds normalized transport-layer error detail reporting to UrlRequest via three new additive accessors (ErrorString(), ErrorSymbol(), ErrorCode()) and wires the feature into the Apple (NSURLSession) and Unix (libcurl) backends, along with offline-deterministic gtest coverage and documentation updates explaining the “code can be 0” contract.

Changes:

  • Add shared error-state plumbing in ImplBase plus public UrlRequest accessors for normalized transport-error detail.
  • Populate error detail on Apple (NSError mapping + underlying-error chain) and Unix (libcurl perform failure + error buffer).
  • Introduce UrlLibTests (googletest) with deterministic transport-failure scenarios and document usage/semantics in README.md.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Tests/UrlRequestErrorReporting.cpp Adds offline-deterministic gtests covering transport-failure detail and reuse semantics.
Tests/CMakeLists.txt Adds UrlLibTests target and pulls in googletest via FetchContent.
Source/UrlRequest_Unix.cpp Populates curl error detail (symbol/code/message) and uses CURLOPT_ERRORBUFFER.
Source/UrlRequest_Apple.mm Populates Apple transport error detail (stable symbol mapping + underlying chain) and adds nil-safe string conversion.
Source/UrlRequest_Base.h Adds shared storage/accessors for error fields plus SetError() and reset behavior.
Source/UrlRequest_Shared.h Exposes new accessors on UrlRequest by forwarding to the impl.
Include/UrlLib/UrlLib.h Adds public API declarations + documentation comments for new error accessors.
README.md Documents transport error reporting, including the “non-empty string is the failure signal” contract.
CMakeLists.txt Adds URLLIB_TESTS option and hooks in Tests/ when building top-level (desktop).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 3 to 7
#include <curl/curl.h>
#include <unistd.h>
#include <array>
#include <cstring>
#include <filesystem>
Comment on lines +145 to +147
// surface it. NSURLErrorDomain codes get stable symbols; other domains pass
// through verbatim. One level of NSUnderlyingErrorKey is appended because
// that is where CFNetwork/POSIX specifics (e.g. "Connection refused") live.
Comment on lines +91 to +108
[[nodiscard]] bool SendAndWait(UrlLib::UrlRequest& request)
{
auto done = std::make_shared<std::promise<void>>();
auto future = done->get_future();

request.SendAsync().then(arcana::inline_scheduler, arcana::cancellation::none(),
[done](const arcana::expected<void, std::exception_ptr>&) {
done->set_value();
});

if (future.wait_for(std::chrono::seconds{30}) != std::future_status::ready)
{
request.Abort();
return false;
}

return true;
}
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.

4 participants