Skip to content

Coerce non-string API error bodies to string in ApiResult::getError() - #56

Open
spencerkittleson wants to merge 1 commit into
main-php82from
fix/apiresult-getError-strict-types-typeerror
Open

Coerce non-string API error bodies to string in ApiResult::getError()#56
spencerkittleson wants to merge 1 commit into
main-php82from
fix/apiresult-getError-strict-types-typeerror

Conversation

@spencerkittleson

Copy link
Copy Markdown

Issue: https://github.com/MindTouch/Deki/issues/38741

Fixes MindTouch/Deki#38741

ELI5

getError() promises callers a string (or nothing), but it was handing back whatever the API actually sent — sometimes a boolean or a number instead of text. Under PHP's strict mode, breaking that promise doesn't just give the wrong answer, it crashes immediately with a fatal error that the normal error-handling code around it can't catch. This fix makes getError() always convert what it finds into an actual string before handing it back, so it can no longer break its own promise.

Summary

ApiResult::getError() is declared : ?string but its implementation returned deserialized API response body values (body/error/message, body/exception/message, or the raw body fallback) as-is without checking their type. Under declare(strict_types=1), PHP enforces the return type at the return statement, so a non-string scalar (bool/int/float) at any of these paths throws a TypeError instead of returning a value.

This TypeError is a \Error, not an \Exception, so it is not caught by the try/catch (ApiResultException $e) / catch (Exception $e) blocks callers use around API calls — turning a normal "API returned an error" case into an uncaught fatal error for the current request. Worse, this happens while constructing ApiResultException in ApiPlug::invokeComplete(), so ApiResultException's own defensive is_string($error) ? $error : 'unknown api error' check never runs — the TypeError fires from inside getError() before control ever reaches the constructor body.

Seen in production as ~5k occurrences since May 2026 in deki-web (Rollbar item deki-web/6531).

Fix

Coerce every returned value with StringEx::stringify() (already used elsewhere in this class) so getError() always honors its declared ?string contract regardless of what type the API serialized. The array branch (is_array($error)getXml('body')) and the curl-error branch (already ?string-safe) are unchanged.

Tests

Extended getError_Test::body_expected_dataProvider() with cases covering a boolean message, an integer message, and a float body — all previously would have thrown a TypeError, now correctly coerce to their string representation.

Follow-up

Once merged, web/composer.lock in MindTouch/Deki needs to be bumped to pick up this fix (package is pinned to dev-main-php82).

getError() is declared ": ?string" but could return non-string scalars
(bool/int/float) deserialized straight out of the API response body,
which throws a TypeError under strict_types instead of returning a
value. This TypeError is not an Exception, so it isn't caught by the
try/catch blocks that construct ApiResultException, turning a handled
API error into an uncaught fatal for the current request.

Coerce every returned value with StringEx::stringify() so getError()
always honors its declared ?string contract regardless of what shape
the API serialized.

Deki issue: MindTouch/Deki#38741

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 pull request fixes a strict typing contract violation in ApiResult::getError() by ensuring any non-string scalar values returned from deserialized API error bodies are coerced to strings, preventing uncaught TypeError fatals under declare(strict_types=1).

Changes:

  • Update ApiResult::getError() to coerce returned error/body values via StringEx::stringify() to always satisfy the declared ?string return type.
  • Extend getError_Test data provider and test signature to cover boolean, integer, and float body/error message cases that previously would have thrown TypeError.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/ApiResult.php Coerces non-string API error/body values to strings before returning from getError().
tests/ApiResult/getError_Test.php Adds regression coverage for non-string scalar error/body values under strict typing.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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