Repository F# setup
open System
open System.Net.Http
open System.Threading
open System.Threading.Tasks
open Axial
open Axial.HttpClient
open Axial.PlatformServiceHTTP Clients
This page shows the shortest path from HttpClient boilerplate to a typed, testable HTTP workflow.
A direct HttpClient call mixes four failure channels into exceptions and manual status checks:
// Untracked: exceptions for transport, manual status checks, unchecked parsing.
let! response = client.GetAsync($"https://api.example.com/users/{userId}") |> Async.AwaitTask
response.EnsureSuccessStatusCode() |> ignore
let! body = response.Content.ReadAsStringAsync() |> Async.AwaitTask
let user = parseUser body // throws on bad payloadsThe same call as an Axial workflow:
open Axial.HttpClient
open Axial.HttpClient.DSL
type User = { Id: int; Name: string }
/// A hand-written decoder for {"id":1,"name":"Ada"}; a JSON library's decoder fits the same signature.
let decodeUser (json: string) : Result<User, string> =
let found = Text.RegularExpressions.Regex.Match(json, """^\{"id":(\d+),"name":"([^"]*)"\}$""")
if found.Success then Ok { Id = int found.Groups[1].Value; Name = found.Groups[2].Value }
else Error $"not a user: {json}"
let user (token: string) (userId: int) : Flow<#IHasHttp, HttpError, User> =
GET $"https://api.example.com/users/{userId}"
|> bearer token
|> fetchJson decodeUser
AxialHttpClientAxial.HttpClient.DSLFsLiveDocsGeneratedPage4_03F7AABF2A94.UserId: intintAn abbreviation for the CLI type . Basic Types
Name: stringstringAn abbreviation for the CLI type . Basic Types
decodeUser: string -> Result<User,string>json: stringMicrosoft.FSharp.Core.FSharpResult`2Helper type for error handling without exceptions. Choices and Results
found: Text.RegularExpressions.MatchTextMatch: string * string -> Text.RegularExpressions.MatchSearches the specified input string for the first occurrence of the specified regular expression. The string to search for a match. The regular expression pattern to match. An object that contains information about the match. A regular expression parsing error occurred. or is . A time-out occurred. For more information about time-outs, see the Remarks section.
RegularExpressionsSystem.Text.RegularExpressions.RegexRepresents an immutable regular expression.
Success: boolGets a value indicating whether the match is successful. if the match is successful; otherwise, .
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
int: ^T -> intConverts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using Int32.Parse() with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type. The input value. The converted int int 'A' // evaluates to 65 int 0xff // evaluates to 255 int -10 // evaluates to -10
Groups: Text.RegularExpressions.GroupCollectionGets a collection of groups matched by the regular expression. The character groups matched by the pattern.
Item: Text.RegularExpressions.GroupEnables access to a member of the collection by integer index. The zero-based index of the collection member to be retrieved. The member of the collection specified by .
Value: stringGets the captured substring from the input string. The substring that is captured by the match.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
user: string -> int -> Flow<'a,HttpError,User>token: stringuserId: intAxial.Flow`3Represents a cold workflow that reads an environment, returns a typed result, and is executed explicitly through one of its execution members such as ToTask, ToAsync, or RunSynchronously. The type of the environment dependency. The type of the failure value. The type of the success value.
Axial.HttpClient.IHasHttpDeclares that an environment supplies the HTTP service. Implement this on the environment supplied at the host edge. A workflow that sends requests constrains its environment with 'env :> IHasHttp.
Axial.HttpClient.HttpErrorA recoverable HTTP transport, timeout, status, or decoding failure.
GET: FormattableString -> HttpRequestCreates a GET request from an interpolated URL. Every hole is URL-encoded as one value. <example><code>GET $"https://api.example.com/users/{userId}"</code></example>
(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
bearer: string -> HttpRequest -> HttpRequestAdds a redacted bearer-token Authorization header. <example><code>request |> bearer token</code></example>
fetchJson: (string -> Result<'a,string>) -> HttpRequest -> Flow<'b,HttpError,'a>Sends the request and decodes the JSON response. <example><code>GET $"{root}/users/{id}" |> fetchJson (Json.deserializeResult codec)</code></example>
user is a Flow<#IHasHttp, HttpError, User>. The URL hole is URL-encoded as one value, the bearer token is
redacted from every plan and error transcript, connection failures, timeouts, unexpected statuses, and decode
failures all arrive as one typed HttpError, and nothing is sent until a Flow runtime runs the workflow.
Two Levels
Axial.HttpClient has two deliberate levels:
- The
HttpandRequestmodules wrap the commonHttpClientoperations with explicit requests, typed errors, and service-based execution. Use them when you want full control over every request field. - The
DSLmodule adds interpolated URL builders (GET $"..."), pipe-friendly configuration, and terminal verbs (fetch,fetchText,fetchJson) for the everyday call that should read as one line.
Both levels build the same immutable HttpRequest value, so they mix freely: start a request with GET $"..."
and finish it with Request.expect [ 200; 404 ] >> Http.sendResult.
Mental Model
Http.get/GET $"..."create an immutableHttpRequest. Construction never performs I/O.Request.*and DSL combinators (query,bearer,timeout,jsonBody,expect) configure it.Http.send,fetch,fetchText, orfetchJsonconvert it toFlow<#IHasHttp, HttpError, _>.- The Flow runtime resolves
IHttpfrom the environment and performs the exchange.
Because the service boundary is one IHttp.Send method, a complete test fake is a few lines, and
Request.plan renders a redacted description of any request without sending it.
Choose A Guide
- Requests: safe interpolated URLs, query parameters, headers, bodies, and secret redaction.
- Responses and errors: response transcripts, typed JSON decoding, and the
HttpErrormodel. - Reliability: per-request timeouts, expected statuses, and transient-failure retries.
- Testing HTTP: fakes, the live
HttpClientservice, and layer composition.

