Skip to content

feat: support Server-Sent Events as a streaming content format#2232

Merged
glennawatson merged 6 commits into
mainfrom
feat/sse-streaming-format
Jul 15, 2026
Merged

feat: support Server-Sent Events as a streaming content format#2232
glennawatson merged 6 commits into
mainfrom
feat/sse-streaming-format

Conversation

@glennawatson

@glennawatson glennawatson commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Feature - adds text/event-stream (Server-Sent Events) as a first-class streaming content format.

What is the new behavior?

A Refit method declared as IAsyncEnumerable<T> against an SSE endpoint now yields one T per SSE data event, streamed live and unbuffered.

  • New StreamingContentFormat.ServerSentEvents enum member.
  • DetectStreamingFormat maps a text/event-stream response content type to the new format.
  • SystemTextJsonContentSerializer parses each SSE event with SseParser and deserializes the event's data payload to T (the event type/id envelope is dropped), reusing the same source-generated / reflection JsonTypeInfo<T> path the serializer already uses, so it stays trim and Native AOT clean.
  • Both the generated and reflection execution paths inherit this through the shared streaming seam -> no generator or reflection-path change.
  • References the System.Net.ServerSentEvents package on the targets that lack it in-box (net4x / netstandard2.0 / net8.0 / net9.0); net10.0+ use the framework reference. This gives uniform behavior on every target framework.
public interface IChatApi
{
    [Get("/chat/stream")]
    IAsyncEnumerable<ChatToken> StreamAsync();
}

await foreach (var token in api.StreamAsync())
{
    // one ChatToken per `data:` event, streamed live
}

What is the current behavior?

Consuming SSE already worked but only by returning Task<Stream> and feeding it to System.Net.ServerSentEvents.SseParser manually. There was no typed, per-event streaming experience for SSE endpoints.

Closes #2228

What might this PR break?

None expected. The change is additive: a new StreamingContentFormat.ServerSentEvents enum member and a System.Net.ServerSentEvents package reference on the targets that lack it in-box. Existing content formats and code are unchanged. If you need the raw event envelope (event type or id), return Task<Stream> and use System.Net.ServerSentEvents.SseParser directly.

Checklist

  • I have read the Contribute guide
  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

  • Follow-up to feature: Support for SSE #1291; closes the ergonomic seam tracked in feature: first-class IAsyncEnumerable<T> consumption of Server-Sent Events (text/event-stream) #2228. SSE is simply one more frame format alongside JSON arrays and JSON Lines, slotting into the existing streaming seam with no new subsystem.
  • Tests: SSE streaming through the generated path and the reflection (dynamic-route) path; a parity test proving both paths yield the same sequence; coverage of the default (reflection) serializer and the source-generated context serializer; the DetectStreamingFormat text/event-stream branch and the serializer SSE branch.
  • Verification: dotnet build Refit.slnx -c Release (all target frameworks) -> 0 warnings, 0 errors; full Refit.Tests (net8.0) passes; generator tests pass with no snapshot change; 100% patch coverage on the changed lines.
  • Docs: README streaming section documents SSE consumption; docs/breaking-changes.md notes the new enum member and package reference.

- Add StreamingContentFormat.ServerSentEvents and map text/event-stream to it in DetectStreamingFormat
- Stream each SSE data event into IAsyncEnumerable<T> via SseParser, deserializing payloads through the existing source-generated/reflection JSON metadata path so it stays trim and Native AOT clean
- Reference System.Net.ServerSentEvents on targets that lack it in-box (net4x/netstandard2.0/net8.0/net9.0); net10.0+ use the framework reference
- Inherit both generated and reflection execution paths through the shared streaming seam (no generator change)
- Cover generated/reflection paths and source-gen/reflection serializers; document in README and breaking-changes
…rmat

# Conflicts:
#	docs/breaking-changes.md
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (759a3c0) to head (730f8b6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #2232   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          182       182           
  Lines         9444      9453    +9     
  Branches      1772      1774    +2     
=========================================
+ Hits          9444      9453    +9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…rmat

# Conflicts:
#	docs/breaking-changes.md
…rmat

# Conflicts:
#	docs/breaking-changes.md
#	src/Refit/RequestExecutionHelpers.cs
…rmat

# Conflicts:
#	docs/breaking-changes.md
…rmat

# Conflicts:
#	docs/breaking-changes.md
@sonarqubecloud

Copy link
Copy Markdown

@glennawatson
glennawatson merged commit d9b3d5f into main Jul 15, 2026
12 checks passed
@glennawatson
glennawatson deleted the feat/sse-streaming-format branch July 15, 2026 09:23
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.

feature: first-class IAsyncEnumerable<T> consumption of Server-Sent Events (text/event-stream)

2 participants