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.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScriptConfiguration read from the environment is the classic source of a late, confusing startup failure: a missing
variable surfaces as a null, and a malformed one as a parse exception somewhere further in. Axial.PlatformService
splits this into two modules: one for raw access, and one for typed reads with a failure channel.
Shared setup
// Setup for the checked examples on this page.
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.PlatformService
open Axial.State
open Axial.Telemetry
open Axial.Telemetry.JavaScript
/// Fails the docs test when an example's result differs from the value shown.
let shouldEqual expected actual =
if actual <> expected then failwithf "Expected %A but got %A" expected actual
SystemIOThreadingTasksAxialLayersConsoleFileSystemHostingBrowserNodePlatformServiceStateTelemetryJavaScriptshouldEqual: 'a -> 'a -> unitexpected: 'aactual: 'a(<>): 'T -> 'T -> boolStructural inequality The first parameter. The second parameter. The result of the comparison. 5 <> 5 // Evaluates to false 5 <> 6 // Evaluates to true [1; 2] <> [1; 2] // Evaluates to false
failwithf: Printf.StringFormat<'T,'Result> -> 'TPrint to a string buffer and raise an exception with the given result. Helper printers must return strings. The formatter. The formatted result. See Printf.failwithf (link: ) for examples.
open System
open Axial
open Axial.PlatformService
SystemAxialPlatformServiceRaw access
EnvironmentVariables returns what is there, with no opinion about what is required:
| Function | Result |
|---|---|
EnvironmentVariables.tryGet name |
string option |
EnvironmentVariables.getAll |
IReadOnlyDictionary<string, string> |
EnvironmentVariables.set name value |
Sets a variable |
EnvironmentVariables.clear name |
Removes a variable |
EnvironmentVariables.expand text |
Expands %VAR% references |
EnvironmentVariables.fromPairs builds an in-memory set for tests:
> open Axial.PlatformService;;
> let runtime () = { BaseRuntime.liveValue with EnvironmentVariables = EnvironmentVariables.fromPairs [ "REGION", "eu" ] };;
> (EnvironmentVariables.tryGet "REGION" : Flow<BaseRuntime, Never, string option>) |> Flow.run (runtime ());;
> (EnvironmentVariables.tryGet "MISSING" : Flow<BaseRuntime, Never, string option>) |> Flow.run (runtime ());;val it: Exit<string option,Never> = Success (Some "eu")
val it: Exit<string option,Never> = Success NoneThese never fail; an absent variable is None.
Typed reads
EnvironmentVariable reads a variable, requires it, and parses it, failing with EnvironmentVariableError:
let readPort : Flow<BaseRuntime, EnvironmentVariableError, int> =
EnvironmentVariable.getInt "PORT"
readPort: Flow<BaseRuntime,EnvironmentVariableError,int>Axial.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.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Axial.PlatformService.EnvironmentVariableErrorintAn abbreviation for the CLI type . Basic Types
Axial.PlatformService.EnvironmentVariableHelpers for reading and parsing environment variables through an explicit service.
getInt: string -> Flow<'env,EnvironmentVariableError,int>Reads an integer environment variable through an explicit service.
The environment is named concretely here rather than written as #IHasEnvironmentVariables, because a flow bound
to a plain value hits F#'s value restriction while its environment is still generic: a value cannot be generalised
over a type variable.
Adding a parameter removes the restriction, because a function can be. A unit parameter is enough, and the result
stays usable in any environment supplying the service:
let readPortIn () : Flow<#IHasEnvironmentVariables, EnvironmentVariableError, int> =
EnvironmentVariable.getInt "PORT"
readPortIn: unit -> Flow<'a,EnvironmentVariableError,int>Axial.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.PlatformService.IHasEnvironmentVariablesDeclares that an environment supplies the environment-variable service.
Axial.PlatformService.EnvironmentVariableErrorintAn abbreviation for the CLI type . Basic Types
Axial.PlatformService.EnvironmentVariableHelpers for reading and parsing environment variables through an explicit service.
getInt: string -> Flow<'env,EnvironmentVariableError,int>Reads an integer environment variable through an explicit service.
Prefer naming the environment when a workflow belongs to one application, and the parameterised form when a helper is genuinely shared across environment shapes.
| Function | Result |
|---|---|
get |
string, failing when absent |
tryGet |
string option, never failing |
getInt, getInt64 |
Integers |
getDouble, getDecimal |
Numbers |
getBool |
Booleans |
getGuid |
GUIDs |
getUri |
URIs |
getTimeSpan |
Durations |
Numeric parsing uses the invariant culture, so a variable set on a machine with a comma decimal separator reads the same everywhere.
EnvironmentVariableError has two cases, and they carry enough to write a useful message:
MissingVariable name: the variable was not set.InvalidVariable (name, value, expected): it was set but did not parse, with what was expected.
EnvironmentVariableErrors.describe formats either into a sentence such as Environment variable 'PORT' had value 'eighty' but expected an integer.
Failing startup once, with everything wrong listed
Because the reads are flows with a typed error, configuration validation composes into one workflow that either produces the settings record or reports what is wrong:
type Settings = { Port: int; Endpoint: Uri; Timeout: TimeSpan }
let readSettings : Flow<BaseRuntime, EnvironmentVariableError, Settings> =
flow {
let! port = EnvironmentVariable.getInt "PORT"
let! endpoint = EnvironmentVariable.getUri "API_ENDPOINT"
let! timeout = EnvironmentVariable.getTimeSpan "API_TIMEOUT"
return { Port = port; Endpoint = endpoint; Timeout = timeout }
}
let withVariables pairs =
{ BaseRuntime.liveValue with EnvironmentVariables = EnvironmentVariables.fromPairs pairs }
FsLiveDocsGeneratedPage1_65D5665AADA9.SettingsPort: intintAn abbreviation for the CLI type . Basic Types
Endpoint: UriSystem.UriProvides an object representation of a uniform resource identifier (URI) and easy access to the parts of the URI.
Timeout: TimeSpanSystem.TimeSpanRepresents a time interval.
readSettings: Flow<BaseRuntime,EnvironmentVariableError,Settings>Axial.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.PlatformService.BaseRuntimeGroups the standard operational services commonly used by workflow hosts.
Axial.PlatformService.EnvironmentVariableErrorflow: FlowBuilderThe universal flow { } computation expression.
port: intAxial.PlatformService.EnvironmentVariableHelpers for reading and parsing environment variables through an explicit service.
getInt: string -> Flow<'env,EnvironmentVariableError,int>Reads an integer environment variable through an explicit service.
endpoint: UrigetUri: string -> Flow<'env,EnvironmentVariableError,Uri>Reads a URI environment variable through an explicit service.
timeout: TimeSpangetTimeSpan: string -> Flow<'env,EnvironmentVariableError,TimeSpan>Reads a time span environment variable through an explicit service.
withVariables: (string * string) seq -> BaseRuntimepairs: (string * string) seqAxial.PlatformService.BaseRuntimeModuleHelpers for constructing the standard explicit service bundle used by workflow hosts.
liveValue: BaseRuntimeCreates the standard live base runtime as an explicit service bundle.
EnvironmentVariables: IEnvironmentVariablesreadSettings
|> Flow.run (withVariables [ "PORT", "8080"; "API_ENDPOINT", "https://api.example.com"; "API_TIMEOUT", "00:00:30" ])
|> shouldEqual (Exit.Success { Port = 8080; Endpoint = Uri "https://api.example.com"; Timeout = TimeSpan.FromSeconds 30.0 })
readSettings
|> Flow.run (withVariables [ "PORT", "eighty" ])
|> shouldEqual (Exit.Failure(Cause.Fail(EnvironmentVariableError.InvalidVariable("PORT", "eighty", "an integer"))))
readSettings: Flow<BaseRuntime,EnvironmentVariableError,Settings>(|>): '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
Axial.Flowrun: 'env -> Flow<'env,'error,'value> -> Exit<'value,'error>Runs the workflow and blocks until the final exit is available. The environment used by the workflow. The workflow to run. The final workflow exit. let exit = workflow |> Flow.run environment
withVariables: (string * string) seq -> BaseRuntimeshouldEqual: 'a -> 'a -> unitAxial.Exit`2Represents the final outcome of a workflow execution. The type of the success value. The type of the domain-specific failure value.
SuccessThe workflow completed successfully.
Port: intEndpoint: Uri``.ctor``: string -> unitInitializes a new instance of the class with the specified URI. A string that identifies the resource to be represented by the instance. Note that an IPv6 address in string form must be enclosed within brackets. For example, "http://[2607:f8b0:400d:c06::69]". is . In the .NET for Windows Store apps or the Portable Class Library, catch the base class exception, , instead. is empty. -or- The scheme specified in is not correctly formed. See . -or- contains too many slashes. -or- The password specified in is not valid. -or- The host name specified in is not valid. -or- The file name specified in is not valid. -or- The user name specified in is not valid. -or- The host or authority name specified in cannot be terminated by backslashes. -or- The port number specified in is not valid or cannot be parsed. -or- The length of exceeds 65519 characters. -or- The length of the scheme specified in exceeds 1023 characters. -or- There is an invalid character sequence in . -or- The MS-DOS path specified in must start with c:\\.
Timeout: TimeSpanSystem.TimeSpanRepresents a time interval.
FromSeconds: float -> TimeSpanReturns a that represents a specified number of seconds, where the specification is accurate to the nearest millisecond. A number of seconds, accurate to the nearest millisecond. An object that represents . is less than or greater than . -or- is . -or- is . is equal to .
FailureThe workflow failed due to a specific cause.
Axial.Cause`1Represents the cause of a failed workflow. The type of the domain-specific failure value.
FailAn expected domain-specific failure.
Axial.PlatformService.EnvironmentVariableErrorInvalidVariableThis binds sequentially, so it stops at the first problem. Run it as a layer during startup and the application cannot reach its first request with unparsed configuration; see layers for provisioning failure.
Supplying the service
EnvironmentVariables.live reads the current process environment. EnvironmentVariables.fromPairs builds a fixed
provider, which is how tests avoid mutating global process state:
let environment =
EnvironmentVariables.fromPairs
[ "PORT", "8080"
"API_ENDPOINT", "https://api.example.com" ]
environment: IEnvironmentVariablesAxial.PlatformService.EnvironmentVariablesHelpers for the environment-variable service.
fromPairs: (string * string) seq -> IEnvironmentVariablesCreates a deterministic provider from a fixed set of name/value pairs.
Prefer fromPairs over setting real variables in a test. Process environment state is shared across a test run, so
mutating it makes tests order-dependent.

