Skip to content

feat: add Initializer and Synchronizer source contracts #259

Draft
kinyoklion wants to merge 5 commits intomainfrom
rlamb/sdk-2183/fdv2-requestor-polling-base
Draft

feat: add Initializer and Synchronizer source contracts #259
kinyoklion wants to merge 5 commits intomainfrom
rlamb/sdk-2183/fdv2-requestor-polling-base

Conversation

@kinyoklion
Copy link
Copy Markdown
Member

@kinyoklion kinyoklion commented Apr 27, 2026

BEGIN_COMMIT_OVERRIDE
feat: add Initializer and Synchronizer source contracts
feat: add FDv2 requestor and polling base
END_COMMIT_OVERRIDE

Adds the HTTP layer for FDv2 polling, plus the Initializer and Synchronizer contracts that future polling and streaming sources will implement.

The requestor is a thin HTTP wrapper: builds the URL, sends GET (context in path) or POST (context in body), tracks ETag for if-none-match, and returns a
response record. It knows nothing about the FDv2 protocol.

The polling base wraps the requestor with FDv2 semantics: classifies HTTP errors as interrupted vs terminal, treats 304 as a no-op change set, detects the
x-ld-fd-fallback header, and runs the response body through a fresh protocol handler to produce an FDv2SourceResult.

Adds source.dart with the Initializer/Synchronizer interfaces and the
SelectorGetter/PingHandler typedefs. These are the contracts for FDv2
data sources: Initializer for one-shot results, Synchronizer for
streaming results, SelectorGetter for lazy selector reads, and
PingHandler for legacy ping-driven polls. Concrete implementations
follow in subsequent commits.
Adds the HTTP layer for FDv2 polling:

- endpoints.dart: FDv2 polling and streaming path constants, uniform
  across mobile and browser.
- requestor.dart: FDv2Requestor — pure HTTP. Builds GET URLs with the
  encoded context in the path or POST URLs with the context JSON in
  the body. Adds basis (when the selector is non-empty) and
  withReasons query params. Tracks ETag across requests via
  if-none-match. Returns a RequestorResponse record.
- polling_base.dart: FDv2PollingBase — wraps the requestor with FDv2
  protocol semantics. Network errors produce interrupted; the
  x-ld-fd-fallback header produces terminalError with fdv1Fallback
  set; 304 produces a none-type ChangeSetResult; recoverable 4xx/5xx
  produce interrupted; non-recoverable 4xx produce terminalError; 200
  bodies are parsed as FDv2EventsCollection and run through a fresh
  FDv2ProtocolHandler.

No data source integration yet — that follows in SDK-2184.
Fixes from review of PR #259:

- pollOnce now honors its 'never throws' contract: widen the
  try/catch in _parseBody to cover the FDv2EventsCollection.fromJson
  cast and per-event fromJson casts. Malformed event shapes
  (non-Map elements in events/payloads, wrong-type object fields)
  return interrupted instead of propagating a TypeError.
- ETag is now persisted only on 200. A 5xx with an ETag could
  otherwise poison the next request and cause a follow-up 304 to be
  misclassified as 'cache is current' even when the SDK has no
  successful payload. A 304 leaves the existing ETag alone (it
  confirms the stored value).
- URL building uses Uri parsing instead of string concatenation, so
  custom polling URLs with embedded query parameters (e.g. a relay
  proxy with a token) are preserved correctly. Our query params
  merge with the base URL's.
- Network exception messages no longer echo into the public
  StatusResult.message. They are categorized into fixed strings
  (Network/TLS/Timeout); the full err.toString() stays in the warn
  log only. SocketException, TlsException, and HandshakeException
  details (remote IP, cert CN, OS error codes) no longer surface on
  dataSourceStatus.lastError.message.
- x-ld-fd-fallback header is matched case-insensitively. Doc notes
  that the header takes precedence over body and status code.
- Debug log line no longer interpolates the full URL (which embeds
  the base64url-encoded context in GET mode).
- HTTP error paths (4xx / 5xx) now log warn/error like the network
  and JSON parse paths do.
- FDv2Endpoints.streaming doc now reflects that the constant serves
  both POST and GET (via streamingGet).
- FDv2Requestor doc states the serial-only contract for request().
- Defensive guard in the requestor: basis is omitted when the
  selector's state string is empty, even if isEmpty=false (covers
  Selector(state: '', version: N)).

New tests cover malformed event shapes, ETag handling on 4xx/5xx
and 304, custom polling URL with query parameters, basis/withReasons
on POST mode, fallback header precedence over 200/304, case-
insensitive header matching, intent-none on 200, heartbeat-only
response, and exception-message sanitization.
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.

1 participant