Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0d9354d
docs: Phase 1 — map existing Newtonsoft wire format
ajwillshire May 25, 2026
ed05cd6
test: Phase 2 — empirical byte-format pinning suite (103 cases)
ajwillshire May 25, 2026
ecdb0b3
feat: Phase 3 — STJ union converter prototype (FSharpUnionConverter<T>)
ajwillshire May 25, 2026
15328af
feat: Phase 4 — full STJ converter set + parallel byte-compat matrix
ajwillshire May 25, 2026
3992fbb
test: Phase 6 — verification matrix + forge spot-check adaptation
ajwillshire May 25, 2026
68a6f16
feat: Phase 4b — Server-side STJ opt-in + Giraffe HTTP integration
ajwillshire May 25, 2026
c23b1de
test: Phase 4c — explicit null-handling coverage + surfaces Newtonsof…
ajwillshire May 25, 2026
6d55876
docs: Phase 7 — UPSTREAM-ISSUE-DRAFT.md + UPSTREAM-PR-DRAFT.md
ajwillshire May 25, 2026
745e606
feat: Phase 4d — STJ opt-in across all sibling adapters + DotnetClient
ajwillshire May 25, 2026
23a9ad7
docs: refresh UPSTREAM drafts after Phase 4d (sibling adapters)
ajwillshire May 25, 2026
cf50148
feat: Phase 4e — Pojo + StringEnum DU dispatch in STJ + fix latent Ne…
ajwillshire May 25, 2026
bf4c0c3
feat: Phase 4f — generalize outer-array argument parsing (STJ path dr…
ajwillshire May 25, 2026
e451f03
feat: Phase 5 — flip default to System.Text.Json + deprecate Newtonso…
ajwillshire May 25, 2026
cbcd245
fix: Phase 8 — close all 10 INVESTIGATE-GAPS findings
ajwillshire May 26, 2026
befa111
docs: reframe UPSTREAM-PR-DRAFT for PR-first (no preceding issue)
ajwillshire May 26, 2026
d9e4809
fix: Phase 10 — restore Newtonsoft-equivalent read leniency for Fable…
ajwillshire May 26, 2026
c9a3cec
docs: Phase 10 — verified locally + record IntegrationTests recipe
ajwillshire May 26, 2026
2b99132
fix: STJ DateTime Kind preservation + value-type defaults for missing…
ajwillshire Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,019 changes: 2,019 additions & 0 deletions BYTE-COMPAT-MAP.md

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions Fable.Remoting.AspNetCore/Middleware.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ module internal Middleware =

let (>=>) = compose

let setResponseBody (response: obj) logger : HttpHandler =
let setResponseBody (backend: JsonSerializerBackend) (response: obj) logger : HttpHandler =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
use ms = new MemoryStream ()
jsonSerialize response ms
jsonSerializeWithBackend backend response ms
let responseBody = System.Text.Encoding.UTF8.GetString (ms.ToArray ())
return! writeStringAsync responseBody ctx logger
}
Expand All @@ -66,8 +66,8 @@ module internal Middleware =
}

/// Sets the body of the response to type of JSON
let setBody value logger : HttpHandler =
setResponseBody value logger
let setBody backend value logger : HttpHandler =
setResponseBody backend value logger
>=> setContentType "application/json; charset=utf-8"

/// Used to forward of the Http context
Expand All @@ -76,14 +76,15 @@ module internal Middleware =

let fail (ex: exn) (routeInfo: RouteInfo<HttpContext>) (options: RemotingOptions<HttpContext, 't>) : HttpHandler =
let logger = options.DiagnosticsLogger
let backend = options.JsonSerializer
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
match options.ErrorHandler with
| None -> return! setBody (Errors.unhandled routeInfo.methodName) logger next ctx
| None -> return! setBody backend (Errors.unhandled routeInfo.methodName) logger next ctx
| Some errorHandler ->
match errorHandler ex routeInfo with
| Ignore -> return! setBody (Errors.ignored routeInfo.methodName) logger next ctx
| Propagate error -> return! setBody (Errors.propagated error) logger next ctx
| Ignore -> return! setBody backend (Errors.ignored routeInfo.methodName) logger next ctx
| Propagate error -> return! setBody backend (Errors.propagated error) logger next ctx
}

let notFound (options: RemotingOptions<HttpContext, 'impl>) next (ctx: HttpContext) =
Expand Down
10 changes: 6 additions & 4 deletions Fable.Remoting.AwsLambda/FableLambdaAdapter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ module private FuncsUtil =
let private path (r: HttpRequestData) = r.RawPath

let setJsonBody
(backend: JsonSerializerBackend)
(res: HttpResponseData)
(response: obj)
(logger: Option<string -> unit>)
(req: HttpRequestData)
: Task<HttpResponseData option> =
task {
use ms = new MemoryStream()
jsonSerialize response ms
jsonSerializeWithBackend backend response ms
let responseBody = System.Text.Encoding.UTF8.GetString(ms.ToArray())
Diagnostics.outputPhase logger responseBody
res.SetHeaderValues("Content-Type", "application/json; charset=utf-8", false)
Expand All @@ -58,13 +59,14 @@ module private FuncsUtil =
: Task<HttpResponseData option> =
let resp = HttpResponseData(StatusCode = int HttpStatusCode.InternalServerError)
let logger = options.DiagnosticsLogger
let backend = options.JsonSerializer

match options.ErrorHandler with
| None -> setJsonBody resp (Errors.unhandled routeInfo.methodName) logger req
| None -> setJsonBody backend resp (Errors.unhandled routeInfo.methodName) logger req
| Some errorHandler ->
match errorHandler ex routeInfo with
| Ignore -> setJsonBody resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody resp (Errors.propagated error) logger req
| Ignore -> setJsonBody backend resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody backend resp (Errors.propagated error) logger req

let halt: HttpResponseData option = None

Expand Down
10 changes: 6 additions & 4 deletions Fable.Remoting.AwsLambda/FableLambdaApiGatewayAdapter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ module private FuncsUtil =
let private path (r: HttpRequestData) = r.Path

let setJsonBody
(backend: JsonSerializerBackend)
(res: HttpResponseData)
(response: obj)
(logger: Option<string -> unit>)
(req: HttpRequestData)
: Task<HttpResponseData option> =
task {
use ms = new MemoryStream()
jsonSerialize response ms
jsonSerializeWithBackend backend response ms
let responseBody = System.Text.Encoding.UTF8.GetString(ms.ToArray())
Diagnostics.outputPhase logger responseBody
res.Headers <- dict [("Content-Type", "application/json; charset=utf-8" )]
Expand All @@ -62,13 +63,14 @@ module private FuncsUtil =
: Task<HttpResponseData option> =
let resp = HttpResponseData(StatusCode = int HttpStatusCode.InternalServerError)
let logger = options.DiagnosticsLogger
let backend = options.JsonSerializer

match options.ErrorHandler with
| None -> setJsonBody resp (Errors.unhandled routeInfo.methodName) logger req
| None -> setJsonBody backend resp (Errors.unhandled routeInfo.methodName) logger req
| Some errorHandler ->
match errorHandler ex routeInfo with
| Ignore -> setJsonBody resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody resp (Errors.propagated error) logger req
| Ignore -> setJsonBody backend resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody backend resp (Errors.propagated error) logger req

let halt: HttpResponseData option = None

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
module AdapterTests

// Legacy Newtonsoft path tests for the AzureFunctions adapter. Requires a
// manually-running FunctionApp on localhost:7071. The STJ-default path is
// covered by the Phase 4b/4d HTTP integration tests in Giraffe/Suave/Falco
// (the adapter code uses the identical setBody-with-backend pattern).
#nowarn "44"

open System
open System.Net.Http
open Expecto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ module private FuncsUtil =

let private path (r:HttpRequestData) = r.Url.PathAndQuery.Split("?").[0]

let setJsonBody (res:HttpResponseData) (response: obj) (logger: Option<string -> unit>) (req:HttpRequestData) : Task<HttpResponseData option> =
let setJsonBody (backend: JsonSerializerBackend) (res:HttpResponseData) (response: obj) (logger: Option<string -> unit>) (req:HttpRequestData) : Task<HttpResponseData option> =
task {
use ms = new MemoryStream ()
jsonSerialize response ms
jsonSerializeWithBackend backend response ms
let responseBody = System.Text.Encoding.UTF8.GetString (ms.ToArray ())
Diagnostics.outputPhase logger responseBody
let res = res |> setContentType "application/json; charset=utf-8"
Expand All @@ -47,12 +47,13 @@ module private FuncsUtil =
let fail (ex: exn) (routeInfo: RouteInfo<HttpRequestData>) (options: RemotingOptions<HttpRequestData, 't>) (req:HttpRequestData) : Task<HttpResponseData option> =
let resp = req.CreateResponse(HttpStatusCode.InternalServerError)
let logger = options.DiagnosticsLogger
let backend = options.JsonSerializer
match options.ErrorHandler with
| None -> setJsonBody resp (Errors.unhandled routeInfo.methodName) logger req
| None -> setJsonBody backend resp (Errors.unhandled routeInfo.methodName) logger req
| Some errorHandler ->
match errorHandler ex routeInfo with
| Ignore -> setJsonBody resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody resp (Errors.propagated error) logger req
| Ignore -> setJsonBody backend resp (Errors.ignored routeInfo.methodName) logger req
| Propagate error -> setJsonBody backend resp (Errors.propagated error) logger req

let halt: HttpResponseData option = None

Expand Down
5 changes: 5 additions & 0 deletions Fable.Remoting.Benchmarks/Serialization.fs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
module Serialization

// Benchmarks for the legacy Newtonsoft path. STJ-default benchmarks would
// be a follow-up — left here as a reference point for any future
// retirement-PR performance comparison.
#nowarn "44"

open BenchmarkDotNet.Attributes
open Fable.Remoting.Json
open Newtonsoft.Json
Expand Down
Loading