Skip to content

[Mittel] Harden HTTP/serializer layer: response consumption, injectable client, timeouts (#34)#41

Open
maltehuebner wants to merge 1 commit into
mainfrom
improve/http-serializer-robustness-34
Open

[Mittel] Harden HTTP/serializer layer: response consumption, injectable client, timeouts (#34)#41
maltehuebner wants to merge 1 commit into
mainfrom
improve/http-serializer-robustness-34

Conversation

@maltehuebner

Copy link
Copy Markdown
Contributor

Makes the write path fail loudly and predictably, and makes the client testable.

Changes

  • Consume responses. The write methods discarded the (lazy) response, so requests only completed in the response destructor and HTTP/transport errors were thrown from __destruct() without a status check. New AbstractApi::assertSuccessful() forces completion, mapping transport failures to LuftApiTransportException and HTTP ≥ 400 to LuftApiException (new src/Exception/). All API methods call it and carry @throws.
  • Injectable client. ApiClient now receives a HttpClientInterface instead of calling HttpClient::create() in its constructor — testable with MockHttpClient, swappable for RetryableHttpClient/profiler clients. The extension builds a private scoped client (base_uri/verify/timeout/max_duration) via the HttpClient factory and injects it into ApiClient.
  • Timeouts. New api.timeout (default 10s) and api.max_duration (default 30s) config nodes stop cron-style imports from hanging.
  • Serializer precedence bug. serialize()/deserialize() merged DEFAULT_CONTEXT after the caller context, silently overriding an explicit SKIP_NULL_VALUES => false. Now array_merge(self::DEFAULT_CONTEXT, $context) so the caller wins.
  • Contracts. $uri typed as string; serializer format centralized as AbstractApi::SERIALIZER_FORMAT (protected typed const), replacing the magic 'json' in ValueApi.

Breaking changes

  • ApiClient::__construct now takes a HttpClientInterface (was hostname, port, verify). The bundle extension performs the wiring, so bundle users are unaffected; anyone constructing ApiClient manually must pass a client.
  • Removed the undocumented public StationApiInterface::SERIALIZER_FORMAT in favour of AbstractApi::SERIALIZER_FORMAT.

Overlap / notes

Verification

Against a full vendor tree (PHP 8.5):

  • Container compiles; ApiClient arg0 is a Reference to caldera_luftapi.http_client, whose options are base_uri=https://api.luft.jetzt:443/, verify_peer=true, timeout=10, max_duration=30.
  • With MockHttpClient: HTTP 500 → LuftApiException, HTTP 200 → no throw.
  • Serializer: caller SKIP_NULL_VALUES => false now keeps null props; default call still skips them.

Closes #34

🤖 Generated with Claude Code

Robustness fixes for the write path and the serializer:

- Consume responses. Symfony's HttpClient is lazy; the previous write
  methods discarded the response, so the request only completed in the
  response destructor and any HTTP/transport error was thrown from
  __destruct() with no status check. AbstractApi::assertSuccessful()
  now forces completion, translating transport failures to
  LuftApiTransportException and HTTP >= 400 to LuftApiException. All
  read/write API methods call it and document @throws.

- Inject HttpClientInterface. ApiClient no longer calls
  HttpClient::create() in its constructor; it receives a
  HttpClientInterface, so it is testable with MockHttpClient and can be
  swapped for RetryableHttpClient/profiler-aware clients. The extension
  builds a scoped client (base_uri/verify/timeout) via the HttpClient
  factory and injects it.

- Configurable timeouts. New api.timeout (default 10s) and
  api.max_duration (default 30s) prevent cron-style provider imports
  from hanging indefinitely.

- Fix serializer context precedence. serialize()/deserialize() merged
  DEFAULT_CONTEXT *after* the caller context, silently overriding an
  explicit SKIP_NULL_VALUES => false. Merge defaults first so the caller
  wins.

- Minor contracts: type the $uri parameter as string on
  ApiClientInterface/ApiClient; centralize the serializer format as
  AbstractApi::SERIALIZER_FORMAT (protected typed const) and use it in
  ValueApi instead of the magic 'json' string.

BREAKING CHANGE: ApiClient::__construct now takes a HttpClientInterface
instead of (hostname, port, verify); wiring is handled by the bundle
extension. The undocumented public StationApiInterface::SERIALIZER_FORMAT
constant was removed in favour of AbstractApi::SERIALIZER_FORMAT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

[Mittel] Robustheit des HTTP-/Serializer-Layers: verworfene Responses, nicht injizierbarer HttpClient, fehlende Timeouts, Kontext-Override im Serializer

1 participant