Repository F# setup
open System
open System.IO
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.Layers
open Axial.Console
open Axial.FileSystem
open Axial.Hosting
open Axial.Hosting.Browser
open Axial.Hosting.Node
open Axial.HttpClient
open Axial.PlatformService
open Axial.Process
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptResponses And Errors
This page shows how one response transcript and one error type replace scattered status checks and exception handling.
The Response Transcript
Every exchange produces a complete HttpResponse:
let workflow =
flow {
let! response = Http.get "https://api.example.com/users" |> Http.send
let etag = response |> Response.tryHeader "ETag" // case-insensitive
return response.StatusCode, response.Text, response.Duration, etag
}Typed JSON Decoding
Response.json and the fetchJson/Http.getJson terminals take a decoder of type
string -> Result<'value, string>. Any JSON library fits that shape:
open Axial.HttpClient.DSL
let user : Flow<#IHasHttp, HttpError, User> =
GET $"https://api.example.com/users/{userId}"
|> fetchJson (Json.deserializeResult userCodec) // Reified.Schema.Json, Thoth, or hand-writtenTo POST a value and decode the reply in one step:
let created =
Http.postJson (Json.serialize userCodec) (Json.deserializeResult userCodec)
"https://api.example.com/users" userEvery way an HTTP call can fail is one case of HttpError:
match error with
| HttpError.InvalidRequest message -> ... // malformed URL or request construction
| HttpError.ConnectionFailed(request, message) -> ...// DNS, refused, dropped connection
| HttpError.TimedOut(request, timeout) -> ... // per-request timeout elapsed
| HttpError.Canceled message -> ... // the workflow was interrupted
| HttpError.Status response -> ... // status outside the expectation, full transcript
| HttpError.DecodeFailed(message, response) -> ... // body did not decode, full transcriptStatuses Are Data, Not Exceptions
Http.send fails with HttpError.Status for anything outside the request's expectation (2xx by default).
When a "failure" status is a normal outcome, widen the expectation and branch on the code:
let findUser userId =
flow {
let! response =
GET $"https://api.example.com/users/{userId}"
|> expect [ 200; 404 ]
|> fetch
if response.StatusCode = 404 then return None
else return! response |> Response.json decodeUser |> Result.map Some
}When Not To Decode
fetchText and fetchBytes return the body directly for HTML scraping, file downloads, and pass-through
proxying. Reach for fetchJson only when the payload should become a typed value; decoding a body you will
immediately re-serialize wastes the transcript you already have.

