FsConfig 4.1.0
dotnet add package FsConfig --version 4.1.0
NuGet\Install-Package FsConfig -Version 4.1.0
<PackageReference Include="FsConfig" Version="4.1.0" />
paket add FsConfig --version 4.1.0
#r "nuget: FsConfig, 4.1.0"
// Install FsConfig as a Cake Addin #addin nuget:?package=FsConfig&version=4.1.0 // Install FsConfig as a Cake Tool #tool nuget:?package=FsConfig&version=4.1.0
FsConfig
FsConfig is a F# library for reading configuration data from environment variables and AppSettings with type safety
Why FsConfig?
To understand FsConfig, let's have a look at an use case from the FsTweet application.
The FsTweet application follows The Twelve-Factor App guideline for managing the configuration data. During the application bootstrap, it retrieves its ten configuration parameters from their respective environment variables.
open System
let main argv =
let fsTweetConnString =
Environment.GetEnvironmentVariable "FSTWEET_DB_CONN_STRING"
let serverToken =
Environment.GetEnvironmentVariable "FSTWEET_POSTMARK_SERVER_TOKEN"
let senderEmailAddress =
Environment.GetEnvironmentVariable "FSTWEET_SENDER_EMAIL_ADDRESS"
let env =
Environment.GetEnvironmentVariable "FSTWEET_ENVIRONMENT"
let streamConfig : GetStream.Config = {
ApiKey =
Environment.GetEnvironmentVariable "FSTWEET_STREAM_KEY"
ApiSecret =
Environment.GetEnvironmentVariable "FSTWEET_STREAM_SECRET"
AppId =
Environment.GetEnvironmentVariable "FSTWEET_STREAM_APP_ID"
}
let serverKey =
Environment.GetEnvironmentVariable "FSTWEET_SERVER_KEY"
let port =
Environment.GetEnvironmentVariable "PORT" |> uint16
// ...
Though the code snippet does the job, there are some shortcomings.
- The code is verbose.
- There is no error handling to deal with the absence of values or wrong values.
- Explicit type casting
With the help of FsConfig, we can overcome these limitations by specifying the configuration data as a F# Record type.
type StreamConfig = {
Key : string
Secret : string
AppId : string
}
[<Convention("FSTWEET")>]
type Config = {
DbConnString : string
PostmarkServerToken : string
SenderEmailAddress : string
ServerKey : string
Environment : string
[<CustomName("PORT")>]
Port : uint16
Stream : StreamConfig
}
And then read all the associated environment variables in a single function call with type safety and error handling!
let main argv =
let config =
match EnvConfig.Get<Config>() with
| Ok config -> config
| Error error ->
match error with
| NotFound envVarName ->
failwithf "Environment variable %s not found" envVarName
| BadValue (envVarName, value) ->
failwithf "Environment variable %s has invalid value %s" envVarName value
| NotSupported msg ->
failwith msg
Supported Data Types
FsConfig supports the following data types and leverages their respective TryParse
function to do the type conversion.
Int16
,Int32
,Int64
,UInt16
,UInt32
,UInt64
Byte
,SByte
Single
,Double
,Decimal
Char
,String
Bool
TimeSpan
,DateTimeOffset
,DateTime
Guid
Enum
list
of all the above typesoption
of all the above types
Option Type
FsConfig allows us to specify optional configuration parameters using the option
type. In the previous example, if the configuration parameter Port
is optional, we can define it like
type Config = {
...
- Port : uint16
+ Port : uint16 option
}
Discriminated Union Type
FsConfig supports Discriminated Union Types that has cases alone.
type Color =
| Red
| Green
| Blue
type Config = {
ConsoleColor : Color
}
With this configuration declaration, FsConfig read the environment variable
CONSOLE_COLOR
and populates theConsoleColor
field of typeColor
.
List of Discriminated Union Types also supported!
List Type
FsConfig also supports list
type, and it expects comma separated individual values.
For example, to get mulitple ports, we can define the config as
type Config = {
Port : uint16 list
}
and then pass the value 8084,8085,8080
using the environment variable PORT
.
The default separator for the list can be changed if needed using the ListSeparator
attribute.
[<Convention("MYENV")>]
type CustomListSeparatorSampleConfig = {
ProcessNames : string list
[<ListSeparator(';')>]
ProcessIds : uint16 list
[<ListSeparator('|')>]
PipedFlow : int list
}
With this configuration declaration, FSConfig would be able to read the following entries from App.settings.
<add key="MYENVProcessNames" value="conhost.exe,gitter.exe"/>
<add key="MYENVProcessIds" value="4700;15680"/>
<add key="MYENVPipedFlow" value="4700|15680|-1" />
A definition similar to the one shown below will allow parsing of standalone lists.
type IntListUsingSemiColonsConfig = {
[<ListSeparator(';')>]
IntListUp : int list
}
E.g. an environment containing
INT_LIST_UP=42;43;44
Record Type
As shown in the initial example, FsConfig allows us to group similar configuration into a record type.
type AwsConfig = {
AccessKeyId : string
DefaultRegion : string
SecretAccessKey : string
}
type Config = {
Aws : AwsConfig
}
With this configuration declaration, FsConfig read the environment variables
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, andAWS_DEFAULT_REGION
and populates theAws
field of typeAwsConfig
.
Default Value
If you'd like to use a default value in the absence of a field value, you can make use of the DefaultValue
attribute.
type Config = {
[<DefaultValue("8080")>]
HttpServerPort : int16
[<DefaultValue("Server=localhost;Port=5432;Database=FsTweet;User Id=postgres;Password=test;")>]
DbConnectionString: string
}
Environment Variable Name Convention & Customization
By default, FsConfig follows Underscores with uppercase convention, as in UPPER_CASE
, for deriving the environment variable name.
type Config = {
ServerKey : string
}
Using this configuration declaration, FsConfig read the environment variable
SERVER_KEY
and populates theServerKey
field
To specify a custom prefix in the environment variables, we can make use of the Convention
attribute.
[<Convention("FSTWEET")>]
type Config = {
ServerKey : string
}
For this configuration declaration, FsConfig read the environment variable
FSTWEET_SERVER_KEY
and populates theServerKey
field.
We can also override the separator character _
using the Convention
attribute's optional field Separator
[<Convention("FSTWEET", Separator="-")>]
type Config = {
ServerKey : string
}
In this case, FsConfig derives the environment variable name as
FSTWEET-SERVER-KEY
.
If an environment variable name is not following a convention, we can override the environment variable name at the field level using the CustomName
attribute.
type Config = {
[<CustomName("MY_SERVER_KEY")>]
ServerKey : string
}
Here, FsConfig uses the environment variable name
MY_SERVER_KEY
to get the ServerKey.
We can also merely customise (or control) the environment variable name generation by passing an higher-order function while calling the Get
function
open FsConfig
// Prefix -> string -> string
let lowerCaseConfigNameCanonicalizer (Prefix prefix) (name : string) =
let lowerCaseName = name.ToLowerInvariant()
if String.IsNullOrEmpty prefix then
name.ToLowerInvariant()
else
sprintf "%s-%s" (prefix.ToLowerInvariant()) lowerCaseName
[<Convention("FSTWEET")>]
type Config = {
ServerKey : string
}
let main argv =
let config =
match EnvConfig.Get<Config> lowerCaseConfigNameCanonicalizer with
| Ok config -> config
| Error error -> failwithf "Error : %A" error
FsConfig computes the environment variable name as
fstweet-server-key
in this scenario.
Getting Individual Environment Variables
FsConfig also supports reading value directly by explicitly specifying the environment variable name
EnvConfig.Get<decimal> "MY_APP_INITIAL_BALANCE" // Result<decimal, ConfigParseError>
App Config
FsConfig supports App Config for both DotNet Core and Non DotNet Core Applications.
DotNet Core Applications (Supported from V2.0.0 or above)
Non DotNet Core Applications (Only Supported in V0.0.6 or below)
DotNet Core Configuration (Supported from V2.0.0 or above)
FsConfig abstracts the configuration provider by depending on IConfigurationRoot.
let configurationRoot : IConfigurationRoot = // ...
let appConfig = new AppConfig(configurationRoot)
After creating an instance appConfig
(of type AppConfig
from FsConfig), you can use it to read the configuration values as below
// Reading Primitive
let result =
appConfig.Get<int> "processId" // Result<int, ConfigParseError>
// A Sample Record
type SampleConfig = {
ProcessId : int
ProcessName : string
}
// Reading a Record type
let result =
appConfig.Get<SampleConfig> () // Result<SampleConfig, ConfigParseError>
// A Sample Nested Record
type AwsConfig = {
AccessKeyId : string
DefaultRegion : string
SecretAccessKey : string
}
type Config = {
MagicNumber : int
Aws : AwsConfig
}
// Reading a Nested Record type
let result =
appConfig.Get<Config> () // Result<Config, ConfigParseError>
Refer below for creating configurationRoot
based on the file type and using FsConfig to read the values.
JSON
{
"processId" : "123",
"processName" : "FsConfig",
"magicNumber" : 42,
"aws" : {
"accessKeyId" : "Id-123",
"defaultRegion" : "us-east-1",
"secretAccessKey" : "secret123"
},
"colors" : "Red,Green"
}
This JSON file can be read using
// Requires NuGet package
// Microsoft.Extensions.Configuration.Json
let configurationRoot =
ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("settings.json").Build()
let appConfig = new AppConfig(configurationRoot)
let result =
appConfig.Get<Config> () // Result<Config, ConfigParseError>
XML
<Settings>
<ProcessId>123</ProcessId>
<ProcessName>FsConfig</ProcessName>
<MagicNumber>42</MagicNumber>
<Aws>
<AccessKeyId>Id-123</AccessKeyId>
<DefaultRegion>us-east-1</DefaultRegion>
<SecretAccessKey>secret123</SecretAccessKey>
</Aws>
<Colors>Red,Green</Colors>
</Settings>
This XML file can be read using
// Requires NuGet package
// Microsoft.Extensions.Configuration.Xml
let configurationRoot =
ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddXmlFile("settings.xml").Build()
let appConfig = new AppConfig(configurationRoot)
let result =
appConfig.Get<Config> () // Result<Config, ConfigParseError>
INI
ProcessId=123
ProcessName=FsConfig
MagicNumber=42
Colors=Red,Green
[Aws]
AccessKeyId=Id-123
DefaultRegion=us-east-1
SecretAccessKey=secret123
This INI file can be read using
// Requires NuGet package
// Microsoft.Extensions.Configuration.Ini
let configurationRoot =
ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddIniFile("settings.ini").Build()
let appConfig = new AppConfig(configurationRoot)
let result =
appConfig.Get<Config> () // Result<Config, ConfigParseError>
appSettings (Only Supported in V0.0.6 or below)
We can read the appSettings
values using the AppConfig
type instead of EnvConfig
type.
FsConfig uses the exact name of the field to derive the appSettings
key name and doesn't use any separator by default.
type AwsConfig = {
AccessKeyId : string
DefaultRegion : string
SecretAccessKey : string
}
type Config = {
Port : uint16
Aws : AwsConfig
}
let main argv =
let config =
match AppConfig.Get<Config>() with
| Ok config -> config
| Error error -> failwithf "Error : %A" error
The above code snippet looks for
appSettings
values with the namePort
,AwsAccessKeyId
,AwsDefaultRegion
,AwsSecretAccessKey
and populates the respective fields.
All the customisation that we have seen for EnvConfig
is applicable for AppConfig
as well.
How FsConfig Works
If you are curious to know how FsConfig works and its internals then you might be interested in my blog post, Generic Programming Made Easy that deep dives into the initial implementation of FsConfig.
Feedback
We all need people who will give us feedback. That's how we improve - Bill Gates.
Your suggestions/feedback are welcome!
Acknowledgements
The idea of FsConfig is inspired by Kelsey Hightower's golang library envconfig.
FsConfig uses Eirik Tsarpalis's TypeShape library for generic programming.
Maintainer(s)
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- FSharp.Core (>= 6.0.0)
- Microsoft.Extensions.Configuration (>= 6.0.0)
- TypeShape (>= 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
4.1.0 | 20,201 | 10/24/2023 |
4.0.0 | 13,214 | 6/18/2023 |
3.1.0 | 66,705 | 7/25/2021 |
3.0.0 | 24,611 | 1/30/2021 |
2.1.6 | 20,366 | 4/26/2020 |
2.1.5 | 9,554 | 11/11/2019 |
2.1.4 | 1,175 | 10/18/2019 |
2.1.3 | 3,921 | 6/9/2019 |
2.1.2 | 743 | 5/24/2019 |
2.0.2 | 2,246 | 8/12/2018 |
2.0.1 | 931 | 7/27/2018 |
2.0.0 | 1,286 | 6/26/2018 |
2.0.0-beta1 | 805 | 6/6/2018 |
1.2.1 | 1,288 | 5/29/2018 |
1.1.2 | 1,037 | 5/29/2018 |
1.1.1 | 1,057 | 5/10/2018 |
1.1.0 | 955 | 5/7/2018 |
1.0.0 | 1,796 | 4/28/2018 |
1.0.0-beta1 | 977 | 4/28/2018 |
0.0.6 | 1,213 | 4/27/2018 |
0.0.5 | 1,032 | 2/23/2018 |
0.0.4 | 1,014 | 2/5/2018 |
0.0.3 | 1,049 | 2/3/2018 |
0.0.2 | 1,003 | 2/3/2018 |
0.0.1 | 1,062 | 2/1/2018 |
## [4.1.0] - 2023-10-24
[4.1.0]: https://github.com/demystifyfp/FsConfig/compare/v4.0.0...v4.1.0
### Added
- Support for optional subsections by [queil](https://github.com/queil)