[
  {
    "path": ".editorconfig",
    "content": "root=true\r\n\r\n[*]\r\ncharset=utf-8\r\nend_of_line=lf\r\ntrim_trailing_whitespace=true\r\ninsert_final_newline=true\r\nindent_style=space\r\nindent_size=4\r\nmax_line_length=120\r\ntab_width=4"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\n\non:\n  push:\n    branches: [master]\n    paths-ignore:\n      - '**/*.md'\n      - 'docs/**'\n  pull_request:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup .NET Core SDK\n        uses: actions/setup-dotnet@v4\n        with:\n          dotnet-version: |\n            8.0.x\n            9.0.x\n            10.0.x\n\n      - name: Restore dependencies\n        run: dotnet restore\n\n      - name: Build\n        run: dotnet build -c Release --no-restore\n\n      - name: Unit Tests\n        run: dotnet test test/Falco.Tests -c Release --no-build\n\n      - name: Integration Tests\n        run: dotnet test test/Falco.IntegrationTests -c Release --no-build\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n[Ee]xamples/[Ss]andbox/\n[Ss]andbox/\n*.sqlite\n[Mm]emory\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n.DS_Store\n\n# Rider (JetBrain's cross-platform .NET IDE) working folder\n.idea/\n.vscode/launch.json\n.vscode/launch.json\n"
  },
  {
    "path": "Build.ps1",
    "content": "[CmdletBinding()]\nparam (\n    [Parameter(HelpMessage=\"The action to execute.\")]\n    [ValidateSet(\"Build\", \"Test\", \"IntegrationTest\", \"Pack\", \"BuildSite\", \"DevelopSite\")]\n    [string] $Action = \"Build\",\n\n    [Parameter(HelpMessage=\"The msbuild configuration to use.\")]\n    [ValidateSet(\"Debug\", \"Release\")]\n    [string] $Configuration = \"Debug\",\n\n    [switch] $NoRestore,\n\n    [switch] $Clean\n)\n\nfunction RunCommand {\n    param ([string] $CommandExpr)\n    Write-Verbose \"  $CommandExpr\"\n    Invoke-Expression $CommandExpr\n}\n\n$rootDir = $PSScriptRoot\n$srcDir = Join-Path $rootDir 'src'\n$testDir = Join-Path $rootDir 'test'\n$docsOutputDir = Join-Path $rootDir 'docs'\n\nswitch ($Action) {\n    \"Test\"            { $projectdir = Join-Path $testDir 'Falco.Tests' }\n    \"IntegrationTest\" { $projectdir = Join-Path $testDir 'Falco.IntegrationTests' }\n    \"Pack\"            { $projectDir = Join-Path $srcDir 'Falco' }\n    \"BuildSite\"       { $projectDir = Join-Path $rootDir 'site' }\n    \"DevelopSite\"     { $projectDir = Join-Path $rootDir 'site' }\n    Default           { $projectDir = Join-Path $srcDir 'Falco' }\n}\n\nif(!$NoRestore.IsPresent) {\n    RunCommand \"dotnet restore $projectDir --force --force-evaluate --nologo --verbosity quiet\"\n}\n\nif ($Clean) {\n    RunCommand \"dotnet clean $projectDir -c $Configuration --nologo --verbosity quiet\"\n}\n\nswitch ($Action) {\n    \"Test\"            { RunCommand \"dotnet test `\"$projectDir`\"\" }\n    \"IntegrationTest\" { RunCommand \"dotnet test `\"$projectDir`\"\" }\n    \"Pack\"            { RunCommand \"dotnet pack `\"$projectDir`\" -c $Configuration --include-symbols --include-source\" }\n    \"BuildSite\"       { RunCommand \"dotnet run --project `\"$projectDir`\" -- `\"$docsOutputDir`\"\" }\n    \"DevelopSite\"     { RunCommand \"dotnet watch --project `\"$projectDir`\" -- run `\"$docsOutputDir`\"\" }\n    Default           { RunCommand \"dotnet build `\"$projectDir`\" -c $Configuration\" }\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [6.0.0] - _unreleased_\n\n### Added\n\n- `Request.getBodyStringOptions` to allow configuration of the maximum request body size, with a default of `Multipart.DefaultMaxSize` (32MB).\n- `Request.getFormOptions` to allow configuration of the maximum form size, with a default of `Multipart.DefaultMaxSize` (32MB).\n- Guards to `RequestValue.parse` to prevent oversized numerics or leading-zero numeric strings (non-decimal) from being parsed as floats.\n- Max size constraint to multipart form streaming, with a default of `Multipart.DefaultMaxSize` (32MB).\n- `FormData.IsValid` property to indicate whether CSRF validation succeeded for form requests.\n\n### Changed\n\n- `Request.getForm` now automatically performs CSRF validation if antiforgery services are registered and the request method is POST, PUT, PATCH, or DELETE. Returns `FormData` with `IsValid = false` when validation fails.\n- Increased multipart form streaming buffer size from 1024 to 8192 bytes to improve performance of large file uploads.\n- `Request.ifAuthenticatedInRole` accepts roles as `seq` instead of `list` for more flexible input options.\n- `Request.getJsonOptions` explicitly checks for application/json content type and empty body, returning default form of `T`. Returns 415 Unsupported Media Type response when content type is missing.\n- `Response.signInOptions` and `Response.signOutOptions` now conditionally set status code 301 and Location header only when `AuthenticationProperties.RedirectUri` is present.\n- `Response.challengeOptions` and `Response.challengeAndRedirect` now set status code 401 and WWW-Authenticate header to properly indicate authentication challenge.\n\n### Fixed\n\n- Unnecessary URL decode in `RequestValue.parseString`.\n- Default `JsonSerializerOptions` used for JSON deserialization, optimized by making read-only and static to prevent unnecessary allocations.\n- `Response.ofAttachment` now properly escapes provided filename to ensure correct handling of special characters and spaces across different browsers.\n- Added missing Location header to `Response.signInOptions` and `Response.signOutOptions` when redirect URI is specified.\n\n### Removed\n\n- `Request.getFormSecure` and `Request.mapFormSecure`, which were explicit enablements of the now default CSRF validation for form requests. `Request.getForm` and `Request.mapForm` now include automatic CSRF validation when antiforgery services are registered and request method is POST, PUT, PATCH, or DELETE.\n\n## [5.2.0] - 2025-12-21\n\n### Added\n\n- `net10.0` support.\n\n## [5.1.0] - 2025-09-09\n\n### Added\n\n- `Response.ofFragment` and `Response.ofFragmentCsrf` to return HTML fragments by element ID, with optional CSRF token support.\n- `Falco.Markup` [version 1.4.0](https://www.nuget.org/packages/Falco.Markup/1.4.0), which reverted API to 1.2.0 and provided correct support for template fragments.\n\n## [5.0.3] - 2025-09-09\n\n### Added\n\n- `Falco.Markup` [version 1.3.0](https://www.nuget.org/packages/Falco.Markup/1.3.0), which enables support for template fragment responses.\n\n## [5.0.2] - 2025-06-26\n\n### Fixed\n\n- Request module error for JSON transfer encoding chunked requests.\n\n## [5.0.1] - 2025-06-26\n\n### Added\n\n- `Falco.Markup` [version 1.2.0](https://www.nuget.org/packages/Falco.Markup/1.2.0) support, which includes a unified DSL module introduces consistent prefix-based naming conventions for elements, text shortcuts, and attributes, making code cleaner and more readable.\n\n### Fixed\n\n- `Request.mapCookies` and `Request.mapHeaders` re-added, which were accidentally removed in the 5.0.0 release.\n\n## [5.0.0] - 2025-01-28\n\n### Added\n\n- Declarative OpenAPI support.\n- `RequestData` (and `RequestValue`) to support complex form & query submissions,\n  - Provided by an HTTP key/value pair (i.e., `name=falco&classification=toolkit`) parser.\n  - A derivative `FormData` contains parsed `RequestValue` and access to `IFormFileCollection`.\n- `HttpContext.Plug<T>` for generic injection support of dependencies within `HttpHandler`'s (service locator pattern).\n- `Request.getJson<T>` for generic JSON request deserialization, using default settings (property name case-insensitive, trailing commas allowed).\n- `Request.getCookies`, replacing `Request.getCookie`.\n- `Response.signInOptions` to sign in claim principal for provided scheme and options then responds with a 301 redirect to provided URL.\n- `Response.challengeAndRedirect`, replacing `Response.challengeWithRedirect`.\n- `Routing.map[Get|Head|Post|Put|Patch|Delete|Options|Trace|Any]` which produces `HttpEndpoint` by associating a route pattern to an `HttpHandler` after mapping route.\n- `Routing.setDisplayName` to set the display name of the endpoint.\n- `Routing.setOrder` to set the order number of the endpoint.\n- `WebApplication.run`, registers the provided `HttpHandler` as the terminal middleware and runs the application.\n\n### Changed\n\n- `Xss` module renamed to `Xsrf`. Functions: `Xsrf.antiforgeryInput`, `Xsrf.getToken` & `Xsrf.validateToken`.\n\n### Fixed\n\n- Missing cancellation token pass-through during form reading, `multipart/form-data` streaming and JSON serialization/deserialization.\n- Empty request body support for JSON request methods.\n- `WebApplication.UseFalcoNotFound` & `IApplicationBuilder.UseFalcoNotFound` to correctly terminate by returning `unit` akin to the native method.\n\n### Removed\n\n- `net6.0` support dropped (end of life 2024-11-12).\n- `webHost [||] {}` builder removed.\n- `config {}` builder removed.\n- `HttpContext.GetLogger<T>()` extension removed.\n- `IApplicationBuilder.IsDevelopment()`, `IApplicationBuilder.UseWhen()` extensions removed.\n- `Services.inject<T>` (and overloads) removed.\n- `Response.withContentLength` removed (unsupported).\n- `StringCollectionReader` and derivatives removed (`FormCollectionReader`, `QueryCollectionReader`, `RouteCollectionReader`, `HeaderCollectionReader`, and `CookieCollectionReader`).\n    - All replaced by homogenous `RequestData` type.\n- `Request.streamForm`, `Request.streamFormSecure`, `Request.mapFormStream` and `Request.mapFormStreamSecure` removed.\n- `Falco.Security.Crypto` and `Falco.Security.Auth` modules removed.\n- Removed `Request.getCookie`, renamed `Request.getCookies`.\n- Removed `Response.challengeWithRedirect`, renamed `Response.challengeAndRedirect`.\n- Removed `Response.debugRequest`.\n\n## [4.0.6] - 2023-12-12\n\n- `net7.0` and `net8.0` support added.\n- Added ability to configure `IWebHostBuilder` to host builder (i.e., `webHost [||] { web_host (fun webHost -> ...) }`).\n\n## [4.0.5] - 2023-11-16\n\n- Execution order of configuration builder (i.e., `configuration { add_env }`) set to match [default](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration#alternative-hosting-approach) configuration behaviour.\n\n## [4.0.4] - 2023-03-13\n\n### Added\n\n- `Request.getFormSecure` and `Request.streamFormSecure`.\n- `use_cors` to host builder (i.e., `webHost [||] { use_cors }`).\n\n### Removed\n\n- Unused types `HttpContextAccessor` and `AsyncHttpContextAccessor`.\n\n## [4.0.3] - 2023-01-01\n\n### Added\n\n- Working tutorial sample.\n- Documentation website generator `/site`, and output `/docs`.\n\n### Removed\n\n- Internal utility functions `httpPipe` and `httpPipeTask`. See issue #94, #95.\n\n## [4.0.2] - 2022-11-30\n\n### Fixed\n\n- NuGet package metadata, invalid readme.\n\n### Changed\n\n- Hello world sample to use ASP.NET static file middleware.\n- Spelling and grammar of comments. See #96.\n\n### Removed\n\n- Unused internal function `String.parseInt`.\n\n## [4.0.1] - 2022-11-23\n\n### Added\n\n- `Response.debugRequest`, which pretty prints the content of the current request to the screen.\n- Related community projects and libraries to README.md.\n\n### Fixed\n\n- NuGet package metadata, invalid icon path.\n\n## [4.0.0] - 2022-11-07\n\nThe project no longer intends to support anything prior to net6.0, which enables the built-in `task {}` computation expression.\n\n### Added\n\n- `StringCollectionReader.GetChildren`, safely retrieves a collection of readers. Intended to be used with the \"dot notation\" collection wire format (i.e., Person.First=John&Person.Last=Doe&Person.First=Jane&Person.Last=Doe).\n- `MultipartReader.StreamSectionsAsync` for async streaming of multipart/form-data, following MSFT [spec](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads).\n- `Services.inject` helpers, for CPS-style dependency injection, supporting up to five generic input types.\n- `in_memory`, `required_ini`, `optional_ini`, `required_xml`, `optional_xml` custom operations added to the configuration builder.\n\n### Changed\n\n- `StringCollectionReader` abstract attribute removed, to support nested readers.\n- `StringCollectionReader.Get{String|StringNonEmpty|Int16|Int32|Int|Int64|Boolean|Float|Decimal|DateTime|DateTimeOffset|Guid|TimeSpan}` default value made optional.\n- Upgraded host builder expression from `IWebHostBuilde` to `WebApplication`.\n\n### Removed\n\n- `Falco.Markup`, module has been extracted into it's own [project](https://github.com/FalcoFramework/Falco.Markup).\n- Additional `StringCollectionReader` constructors, per-collection type.\n- `StringCollectionReader.TryArrayString`, use `StringCollectionReader.GetStringArray`\n- `StringCollectionReader.TryArrayInt16`, use `StringCollectionReader.GetInt16Array`\n- `StringCollectionReader.TryArrayInt32`, use `StringCollectionReader.GetInt32Array`\n- `StringCollectionReader.TryArrayInt`, use `StringCollectionReader.GetIntArray`\n- `StringCollectionReader.TryArrayInt64`, use `StringCollectionReader.GetInt64Array`\n- `StringCollectionReader.TryArrayBoolean`, use `StringCollectionReader.GetBooleanArray`\n- `StringCollectionReader.TryArrayFloat`, use `StringCollectionReader.GetFloatArray`\n- `StringCollectionReader.TryArrayDecimal`, use `StringCollectionReader.GetDecimalArray`\n- `StringCollectionReader.TryArrayDateTime`, use `StringCollectionReader.GetDateTimeArray`\n- `StringCollectionReader.TryArrayDateTimeOffset`, use `StringCollectionReader.GetDateTimeOffsetArray`\n- `StringCollectionReader.TryArrayGuid`, use `StringCollectionReader.GetGuidArray`\n- `StringCollectionReader.TryArrayTimeSpan`, use `StringCollectionReader.GetTimeSpanArray`\n- `HttpRequest.IsMultipart`, `HttpRequest.TryStreamFormAsync`, use `HttpRequest.StreamFormAsync()`\n- `Request.tryBindRoute`, use `Request.getRoute`.\n- `Request.tryBindQuery`, use `Request.getQuery`.\n- `Request.tryBindForm`, use `Request.getForm`.\n- `Request.tryBindFormStream`, use `Request.tryStreamForm`.\n- `Request.tryBindCookie`, use `Request.getCookie`.\n- `Request.getJson`, use `Request.getJsonOptions Constants.defaultJsonOptions`.\n- `Request.tryBindJsonOptions`, use `Request.getJsonOptions`.\n- `Request.tryBindJson`, use `Request.getJsonOptions Constants.defaultJsonOptions`.\n- `Request.bindJson`, use `Request.mapJson`.\n- `Request.bindRoute`, use `Request.mapRoute`.\n- `Request.bindQuery`, use `Request.mapQuery`.\n- `Request.bindCookie`, use `Request.mapCookie`.\n- `Request.bindForm`, use `Request.mapForm`.\n- `Request.bindFormStream`, use `Request.mapFormStream`.\n- `Request.bindFormSecure`, use `Request.mapFormSecure`.\n- `Request.bindFormStreamSecure`, use `Request.mapFormStreamSecure`.\n- `Response.withHeader`, use `Response.withHeaders [ x ]`.\n- `Response.redirect`, use `Response.redirectTemporarily` or `Response.redirectPermanently`\n\n## [3.1.14] - 2022-08-29\n\n### Added\n\n- Embedded readme and project icon into NuGet package.\n- Additional obsolete attributes to functions in preparation for v4.x.\n\n### Fixed\n\n- `Request.mapJson` failing to invoke next handler, caused by a bad merge which left the valid function body commented out.\n\n## [3.1.13] - 2022-08-11\n\n### Added\n\n- Obsolete attributes to `Request.bind{Json|Route|Query|Cookie|Form|FormStream|FormSecure|FormStreamSecure} functions in preparation for v4.x.\n\n### Fixed\n\n- Typo in `Markup.Attr.httpEquiv`.\n\n## [3.1.12] - 2022-05-20\n\n### Added\n\n- `Auth.signInOptions` to establish an authenticated context for the provide scheme, options and principal\n- `Markup.Attr.open'`.\n- Missing .gitignore items for JetBrains.\n\n## [3.1.11] - 2/8/2022\n\n### Added\n\n- `Auth.challenge` to challenge the specified authentication scheme.\n- `Response.challengeWithRedirect` to challenge the specified authentication scheme and redirect URI.\n\n### Fixed\n\n- Website to address certain accessibility issues.\n\n## [3.1.10] - 12/14/2021\n\n### Changed\n\n- Incorporated built-in `System.Task` expression, with compiler directives to continue supporting Ply usage.\n\n## [3.1.9] - 12/6/2021\n\n### Changed\n\n- `StringCollectionReader` lookups made case-insensitive.\n\n## [3.1.8] - 12/3/2021\n\n### Added\n\n- `net6.0` support.\n\n### Changed\n\n- Embedded PDBs to faciliate sourcelink.\n\n## [3.1.7] - 9/24/2021\n\n### Added\n\n- `HttpVerb.toHttpMethodMetadata` to properly capture the `HttpVerb.ANY` to produce an empty `HttpMethodData` (not `HttpMethodData [| \"ANY\" |]`).\n\n## [3.1.6] - 9/24/2021\n\n### Removed\n\n- Mistakenly added, experimental `Request.signOut` function.\n\n## [3.1.5] - 9/24/2021\n\n### Added\n\n- Route name metadata to support ASP.NET link generator.\n- Null check to internal `Response.writeString`.\n- Explicit starting size for the internal `StringBuilder` within `XmlNodeSerializer`\n\n## [3.1.4] - 8/24/2021\n\n### Added\n\n- Comparative view engine benchmarks.\n- `XmlNodeSerializer` type.\n- Source link support.\n\n## [3.1.3] - 8/4/2021\n\n### Added\n\n- Dependency on [Ply](https://github.com/crowded/ply).\n- `Request.authenticate` to authenticate the current request using the provided scheme.\n\n### Removed\n\n- TPL helpers.\n\n## [3.1.2] - 7/30/2021\n\n### Changed\n\n- CSRF validation switched to occur _after_ form is streamed, which includes enabling buffering for the request body.\n\n\n## [3.1.1] - 7/27/2021\n\n### Added\n\n- `stringf` function.\n\n### Removed\n\n- Dependency on [Taskbuilder.fs](https://github.com/rspeele/Taskbuilder.fs), replaced with TPL helpers.\n\n## [3.1.0] - 7/27/2021\n\n### Added\n\n- `FalcoEndpointDataSource` to properly integrate with ASP.NET endpoint routing.\n- Binary response handlers `Response.ofBinary`, `Response.ofAttachment`.\n- `IConfiguration` builder expression with JSON input hooks.\n- `Auth.getClaimValue`\n- `IServiceCollection` operations to the HostBuilder expression: `add_service`, `add_antiforgery`, `add_cookie`, `add_conf_cookies`, `add_authorization`, `add_data_protection`, `add_http_client`.\n- `IApplicationBuilder` operations to the HostBuilder expression:\n`use_middleware`, `use_if`, `use_ifnot`, `use_authentication`, `use_authorization`, `use_caching`, `use_compression`, `use_hsts`, `use_https`, `use_static_files`.\n- `not_found` operation added to HostBuilder expression to serve default document when no endpoints are matched.\n\n### Changed\n\n- Internal `Response.writeBytes` to use `BodyWriter`.\n\n### Fixed\n\n- Optional JSON config file fix, misassigned.\n\n### Removed\n\n- MVC and REST templates\n\n## [3.0.5] - 6/14/2021\n\n### Added\n\n- PowerShell website build script.\n\n### Fixed\n\n- Null reference exception when consuming `IFormCollection.Files`.\n\n## [3.0.4] - 5/5/2021\n\n### Added\n\n- `Response.signInAndRedirect`.\n- `IEndpointRouteBuilder` extension method `UserFalcoEndpoints`.\n\n\n## [3.0.3] - 4/10/2021\n\n### Added\n\n- `Auth.hasScope`, `Auth.tryFindClaim`, `Auth.getClaim`.\n- `Request.ifAuthenticatedWithScope`.\n- `CookieCollectionReader`, accessible get `Request.getCookie`, `Request.tryBindCookie`, `Request.bindCookie`, `Request.mapCookie`.\n- `StringUtils.strSplit`.\n\n## [3.0.2] - 12/8/2020\n\n### Added\n\n- `Markup.Elem.form`, `Markup.Elem.button`, `Markup.Elem.script`\n\n## [3.0.1] - 12/1/2020\n\n### Fixed\n\n- `Markup.Templates.html5` not using provided language code.\n\n## [3.0.0] - 11/27/2020\n\n### Added\n\n- `net5.0` support.\n- `IHost` builder expression, `webHost [||] {}`.\n- `IServiceCollection.AddFalco`.\n- `IServiceCollection.AddFalco (routeOptions : RouteOptions -> unit)`.\n- `IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)`.\n- `IApplicationBuilder.UseFalcoExceptionHandler (exceptionHandler : HttpHandler)`.\n- `QueryCollectionReader`.\n- `HeaderCollectionReader`.\n- `RouteCollectionReader`.\n\n### Removed\n\n- Extensions, `HttpRequest.GetHeader`, `HttpRequest.GetRouteValues`, `HttpRequest.GetRouteReader`.\n- Exceptions. `ExceptionHandler`, `ExceptionHandlingMiddleware`.\n- Host module, `Host.defaultExceptionHandler`, `Host.defaultNotFoundHandler`, `Host.startWebHostDefault`, `Host.startWebHost`.\n- `IApplicationBuilder.UseHttpEndpoints (endpoints : HttpEndpoint list)` replaced by `IApplicationBuilder.UseFalco (endpoints : HttpEndpoint list)`.\n- `Request.getHeader`, `Request.getRouteValues` replaced by `Request.getRoute`, `Request.tryGetRouteValue`.\n- `StringCollectionReader` ? dynamic operator\n\n## [2.1.0] - 11/11/2020\n\n### Added\n\n- Multimethod `HttpEndpoint` support.\n- `StringCollectionReader.TryGetStringNonEmpty` which returns `None` for empty, whitespace and null value strings.\n\n## [2.0.4] - 11/9/2020\n\n### Added\n\n- `Request.tryBindRoute`, `Request.mapRoute` and `Request.bindRoute`.\n- `Request.bindQuery`.\n- `Request.bindJson` which uses `System.Text.Json`.\n\n## [2.0.3] - 10/31/2020\n\n### Added\n\n- Dependency on [Taskbuilder.fs](https://github.com/rspeele/Taskbuilder.fs), with internal extesion for `Task<unit> -> Task` conversion.\n\n## [2.0.2] - 7/31/2020\n\n### Added\n\n- `Request.validateCsrfToken` which uses `Microsoft.AspNetCore.Antiforgery`.\n- `Response.ofJson` which uses `System.Text.Json` and references `Constants.defaultJsonOptions`.\n- `Response.ofEmpty`.\n\n## [2.0.1] - 7/20/2020\n\n### Changed\n\n- Parameter ordering for `Response.withCookieOptions`, `Response.ofJsonOptions` to ensure configuration uniformly occured first.\n\n## [2.0.0] - 7/12/2020\n\n### Added\n\n- `HttpResponseModifier` defined as `HttpContext -> HttpContext` used to make non-IO modifications to the `HttpResponse`.\n- `Response` and `Request` modules, which provide functional access to the `HttpResponse` and `HttpRequest` respectively.\n\n    - `Response.redirect`\n    - `Response.withHeader`\n    - `Response.withContentLength`\n    - `Response.withContentType`\n    - `Response.withStatusCode`\n    - `Response.withCookie`\n    - `Response.withCookieOptions`\n    - `Response.ofString`\n    - `Response.ofPlainText`\n    - `Response.ofHtml`\n    - `Response.ofJson`\n    - `Response.ofJsonOptions    `\n    - `Request.getVerb`\n    - `Request.getRouteValues`\n    - `Request.tryGetRouteValue`\n    - `Request.getQuery`\n    - `Request.tryBindQuery`\n    - `Request.getForm`\n    - `Request.tryBindForm`\n    - `Request.tryStreamForm`\n    - `Request.tryBindJson`\n    - `Request.tryBindJsonOptions`\n\n### Changed\n\n- `HttpHandler` definition changed to `HttpContext -> Task`.\n- `Falco.ViewEngine` becomes `Falco.Markup`\n- Markup functions are now fully qualified (i.e., `Elem.h1` instead of `h1`).\n- `webApp\n\n## [1.2.3] - 7/2/2020\n## [1.2.2] - 6/29/2020\n## [1.2.1] - 6/28/2020\n## [1.2.0] - 6/23/2020\n## [1.1.0] - 6/6/2020\n\nStill kicking myself over this brainfart. Starting version `1.1` for the win 🙄.\n"
  },
  {
    "path": "CNAME",
    "content": "www.falcoframework.com"
  },
  {
    "path": "Falco.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31903.59\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"src\", \"src\", \"{827E0CD3-B72D-47B6-A68D-7590B98EB39B}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Falco\", \"src\\Falco\\Falco.fsproj\", \"{D6613F0B-7571-432E-964B-B9629B3B06CB}\"\nEndProject\nProject(\"{2150E333-8FDC-42A3-9474-1A3956D46DE8}\") = \"test\", \"test\", \"{0C88DD14-F956-CE84-757C-A364CCF449FC}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Falco.Tests\", \"test\\Falco.Tests\\Falco.Tests.fsproj\", \"{9600E734-196E-4922-929E-8B5906A1FBAA}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Falco.IntegrationTests.App\", \"test\\Falco.IntegrationTests.App\\Falco.IntegrationTests.App.fsproj\", \"{7A9A92D3-EADC-40D0-8E90-82AE651195F1}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Falco.IntegrationTests\", \"test\\Falco.IntegrationTests\\Falco.IntegrationTests.fsproj\", \"{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|x64.Build.0 = Release|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB}.Release|x86.Build.0 = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|x64.Build.0 = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA}.Release|x86.Build.0 = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|x64.Build.0 = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1}.Release|x86.Build.0 = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|x64.Build.0 = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA}.Release|x86.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(NestedProjects) = preSolution\n\t\t{D6613F0B-7571-432E-964B-B9629B3B06CB} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}\n\t\t{9600E734-196E-4922-929E-8B5906A1FBAA} = {0C88DD14-F956-CE84-757C-A364CCF449FC}\n\t\t{7A9A92D3-EADC-40D0-8E90-82AE651195F1} = {0C88DD14-F956-CE84-757C-A364CCF449FC}\n\t\t{5985B9A8-267A-46FF-8E5C-3E016B36E3EA} = {0C88DD14-F956-CE84-757C-A364CCF449FC}\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Falco\n\n[![NuGet Version](https://img.shields.io/nuget/v/Falco.svg)](https://www.nuget.org/packages/Falco)\n[![build](https://github.com/FalcoFramework/Falco/actions/workflows/build.yml/badge.svg)](https://github.com/FalcoFramework/Falco/actions/workflows/build.yml)\n\n```fsharp\nopen Falco\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nwapp.Run(Response.ofPlainText \"Hello world\")\n```\n\n[Falco](https://github.com/FalcoFramework/Falco) is a toolkit for building functional-first, full-stack web applications using F#.\n\n- Built on the high-performance components of ASP.NET Core.\n- Seamlessly integrates with existing .NET Core middleware and libraries.\n- Designed to be simple, lightweight and easy to learn.\n\n## Key Features\n\n- Simple and powerful [routing](documentation/routing.md) API.\n- Uniform API for [accessing _any_ request data](documentation/request.md).\n- Native F# [view engine](documentation/markup.md).\n- Asynchronous [request handling](documentation/response.md).\n- [Authentication](documentation/authentication.md) and [security](documentation/cross-site-request-forgery.md) utilities.\n- Built-in support for [large uploads](documentation/request.md#multipartform-data-binding) and [binary responses](documentation/response.md#content-disposition).\n\n\n## Design Goals\n\n- Provide a toolset to build full-stack web application in F#.\n- Should be simple, extensible and integrate with existing .NET libraries.\n- Can be easily learned.\n\n## Learn\n\nThe best way to get started is by visiting the [documentation](https://falcoframework.com/docs). For questions and support please use [discussions](https://github.com/FalcoFramework/Falco/discussions). For chronological updates refer to the [changelog](CHANGELOG.md) is the best place to find chronological updates.\n\n### Related Libraries\n\n- [Falco.Markup](https://github.com/FalcoFramework/Falco.Markup) - an XML markup module primary used as the syntax for [authoring HTML with Falco](https://www.falcoframework.com/docs/markup.html).\n- [Falco.Htmx](https://github.com/dpraimeyuu/Falco.Htmx) - a full featured integration with [htmx JS package](https://htmx.org/).\n- [Falco.OpenApi](https://github.com/FalcoFramework/Falco.OpenApi) - a library for generating OpenAPI documentation from Falco applications.\n- [Falco.Template](https://github.com/FalcoFramework/Falco.Template) - a .NET SDK [project template](https://learn.microsoft.com/en-us/dotnet/core/tools/custom-templates) to help get started with Falco quickly.\n- [Falco.UnionRoutes](https://github.com/michaelglass/Falco.UnionRoutes/) - a library for expressing routes as a descriminated union, inspired by Haskell's [Servant](https://docs.servant.dev/).\n- [CloudSeed](https://cloudseed.xyz/) - a simple, scalable project boilerplate for F# / .NET.\n\n### Community Projects\n\n- [Falco GraphQL Sample](https://github.com/adelarsq/falco_graphql_sample) - A sample showing how to use GraphQL on Falco using .NET 6.\n- [Falco API with Tests Sample](https://github.com/jasiozet/falco-api-with-tests-template) - A sample project using Falco and unit testing.\n- [Falco + SQLite + Donald](https://github.com/galassie/FalcoSample) - A demo project using Falco, [Donald](https://github.com/pimbrouwers/Donald), and SQLite\n- [FShopOnWeb](https://github.com/NitroDevs/FShopOnWeb) - An adaptation of the classic [ASP.NET Core sample application](https://github.com/dotnet-architecture/eShopOnWeb) using Falco and an F# architecture.\n\n### Articles\n\n- Hamilton Greene - [Spin up a Fullstack F# WebApp in 10 minutes with the CloudSeed Project Template](https://hamy.xyz/blog/2025-01_fsharp-webapp-10-minutes)\n- Hamilton Greene - [Why I'm Ditching F# + Giraffe For Falco For Building WebApps](https://hamy.xyz/blog/2025-01_ditching-giraffe-for-falco)\n- Istvan - [Running ASP.Net web application with Falco on AWS Lambda](https://dev.l1x.be/posts/2020/12/18/running-asp.net-web-application-with-falco-on-aws-lambda/)\n\n### Videos\n\n- Hamilton Greene - [Build a Fullstack Webapp with F# + Falco](https://www.youtube.com/watch?v=ELPdHdtEIY8)\n- Hamilton Greene - [Build a Single-File Web API with F# + Falco](https://www.youtube.com/watch?v=SJCHBqrc3sE)\n- Hamilton Greene - [Why I'm Ditching F# + Giraffe For Falco For Building WebApps](https://www.youtube.com/watch?v=tonPeWfu_WM)\n- Ben Gobeil - [Why I'm Using Falco Instead Of Saturn | How To Switch Your Backend In SAFE Stack | StonkWatch Ep.13](https://youtu.be/DTy5gIUWvpo)\n\n\n## Contribute\n\nWe kindly ask that before submitting a pull request, you first submit an [issue](https://github.com/FalcoFramework/Falco/issues) or open a [discussion](https://github.com/FalcoFramework/Falco/discussions).\n\nIf functionality is added to the API, or changed, please kindly update the relevant [document](/docs). Unit tests must also be added and/or updated before a pull request can be successfully merged.\n\nOnly pull requests which pass all build checks and comply with the general coding standard can be approved.\n\nIf you have any further questions, submit an [issue](https://github.com/FalcoFramework/Falco/issues) or open a [discussion](https://github.com/FalcoFramework/Falco/discussions) or reach out on [Twitter](https://twitter.com/falco_framework).\n\n\n## Why \"Falco\"?\n\n[Kestrel](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) has been a game changer for the .NET web stack. In the animal kingdom, \"Kestrel\" is a name given to several members of the falcon genus. Also known as \"Falco\".\n\n\n## Find a bug?\n\nThere's an [issue](https://github.com/FalcoFramework/Falco/issues) for that.\n\n\n## License\n\nLicensed under [Apache License 2.0](https://github.com/FalcoFramework/Falco/blob/master/LICENSE).\n"
  },
  {
    "path": "docs/CNAME",
    "content": "www.falcoframework.com"
  },
  {
    "path": "docs/docs/authentication.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Authentication & Authorization. - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"authentication-authorization\">Authentication &amp; Authorization.</h1>\n<p>ASP.NET Core has built-in support for authentication and authorization. Falco includes some prebuilt, configurable handlers for common scenarios.</p>\n<blockquote>\n<p>Review the <a href=\"https://docs.microsoft.com/en-us/aspnet/core/security/authentication\">docs</a> for specific implementation details.</p>\n</blockquote>\n<h2 id=\"secure-resources\">Secure Resources</h2>\n<h3 id=\"allow-only-authenticated-access\">Allow only authenticated access</h3>\n<pre><code class=\"language-fsharp\">open Falco\n\nlet authScheme = &quot;some.secure.scheme&quot;\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuth : HttpHandler =\n        Response.ofPlainText &quot;hello authenticated user&quot;\n\n    Request.ifAuthenticated authScheme handleAuth\n</code></pre>\n<h3 id=\"allow-only-non-authenticated-access\">Allow only non-authenticated access</h3>\n<pre><code class=\"language-fsharp\">open Falco\n\nlet anonResourceOnlyHandler : HttpHandler =\n    let handleAnon : HttpHandler =\n        Response.ofPlainText &quot;hello anonymous&quot;\n\n    Request.ifNotAuthenticated authScheme handleAnon\n</code></pre>\n<h3 id=\"allow-only-authenticated-access-when-in-certain-roles\">Allow only authenticated access when in certain role(s)</h3>\n<pre><code class=\"language-fsharp\">open Falco\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuthInRole : HttpHandler =\n        Response.ofPlainText &quot;hello admin&quot;\n\n    let rolesAllowed = [ &quot;Admin&quot; ]\n\n    Request.ifAuthenticatedInRole authScheme rolesAllowed handleAuthInRole\n</code></pre>\n<h3 id=\"allow-only-authenticated-acces-with-a-certain-scope\">Allow only authenticated acces with a certain scope</h3>\n<pre><code class=\"language-fsharp\">open Falco\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuthHasScope : HttpHandler =\n        Response.ofPlainText &quot;user1, user2, user3&quot;\n\n    let issuer = &quot;https://oauth2issuer.com&quot;\n    let scope = &quot;read:users&quot;\n\n    Request.ifAuthenticatedWithScope authScheme issuer scope handleAuthHasScope\n</code></pre>\n<h3 id=\"terminate-authenticated-session\">Terminate authenticated session</h3>\n<pre><code class=\"language-fsharp\">open Falco\n\nlet logOut : HttpHandler =\n    let authScheme = &quot;...&quot;\n    let redirectTo = &quot;/login&quot;\n\n    Response.signOutAndRedirect authScheme redirectTo\n</code></pre>\n<p><a href=\"host-configuration.html\">Next: Host Configuration</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/cross-site-request-forgery.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Cross-site Scripting (XSS) Attacks - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"cross-site-scripting-xss-attacks\">Cross-site Scripting (XSS) Attacks</h1>\n<p>Cross-site scripting attacks are extremely common since they are quite simple to carry out. Fortunately, protecting against them is as easy as performing them.</p>\n<p>The <a href=\"https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery\">Microsoft.AspNetCore.Antiforgery</a> package provides the required utilities to easily protect yourself against such attacks.</p>\n<h2 id=\"activating-antiforgery-protection\">Activating Antiforgery Protection</h2>\n<p>To use the Falco Xsrf helpers, ensure that the <code>Antiforgery</code> service has been <a href=\"https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery\">registered</a>.</p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\n// ^-- this import enables antiforgery activation\n\nlet endpoints =\n    [\n        // endpoints...\n    ]\n\nlet bldr = WebApplication.CreateBuilder()\n\nbldr.Services\n    .AddAntiforgery()\n\nlet wapp = WebApplication.Create()\n\nwapp.UseAntiforgery()\n    // ^-- activate Antiforgery before routing\n    .UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n\n</code></pre>\n<h2 id=\"falco-xsrf-support\">Falco XSRF Support</h2>\n<p>Falco provides a few handlers via <code>Falco.Security.Xsrf</code>:</p>\n<pre><code class=\"language-fsharp\">open Falco.Markup\nopen Falco.Security\n\nlet formView token =\n    _html [] [\n        _body [] [\n            _form [ _methodPost_ ] [\n                // using the CSRF HTML helper, recommended to include as first\n                // form element\n                Xsrf.antiforgeryInput token\n                _control &quot;first_name&quot; [] [ _text &quot;First Name&quot; ]\n                _control &quot;first_name&quot; [] [ _text &quot;First Name&quot; ]\n                _input [ _typeSubmit_ ]\n            ]\n        ]\n    ]\n\n// A handler that demonstrates obtaining a\n// CSRF token and applying it to a view\nlet csrfViewHandler : HttpHandler =\n    Response.ofHtmlCsrf formView\n\n// A handler that demonstrates validating\n// the request's CSRF token\nlet mapFormSecureHandler : HttpHandler =\n    let mapPerson (form : FormData) =\n        { FirstName = form?first_name.AsString()\n          LastName = form?last_name.AsString }\n\n    let handleInvalid : HttpHandler =\n        Response.withStatusCode 400\n        &gt;&gt; Response.ofEmpty\n\n    Request.mapFormSecure mapPerson Response.ofJson handleInvalid\n</code></pre>\n<p><a href=\"authentication.html\">Next: Authentication</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/deployment.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Deployment - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"deployment\">Deployment</h1>\n<p>One of the key features of Falco is that it contains little to no &quot;magic&quot; (i.e., no hidden reflection or dynamic code). This means that Falco is both <a href=\"https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained\">trimmable</a> and <a href=\"https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot\">AOT</a> compatible out of the box.</p>\n<p>This means that you can deploy your Falco application as a self-contained executable, or as a native AOT executable, with no additional configuration. A huge benefit of this is that you can deploy your Falco application to any environment, without having to worry about the underlying runtime or dependencies.</p>\n<blockquote>\n<p>Important! If you're in a <strong>scale-to-zero</strong> hosting environment consider using a <a href=\"https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run\">ReadyToRun</a> deployment. This will ensure that your application will experience faster cold start times.</p>\n</blockquote>\n<h2 id=\"self-contained-deployments\">Self-contained deployments</h2>\n<p>It is highly recommended to deploy your Falco application as a self-contained executable. This means that the .NET runtime and all dependencies are included in the deployment package, so you don't have to worry about the target environment having the correct version of .NET installed. This will result in a slightly larger deployment package, but it will ensure that your application runs correctly in any environment. The larger binary size can also be offset by using trim.</p>\n<p>Below is an example [Directory.Build.props] that will help enable the non-AOT features. These properties can also be added to you fsproj file.</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n    &lt;PropertyGroup&gt;\n        &lt;SelfContained&gt;true&lt;/SelfContained&gt;\n        &lt;PublishSingleFile&gt;true&lt;/PublishSingleFile&gt;\n        &lt;PublishTrimmed&gt;true&lt;/PublishTrimmed&gt;\n        &lt;TrimMode&gt;Link&lt;/TrimMode&gt;\n        &lt;IncludeNativeLibrariesForSelfExtract&gt;true&lt;/IncludeNativeLibrariesForSelfExtract&gt;\n        &lt;EnableCompressionInSingleFile&gt;true&lt;/EnableCompressionInSingleFile&gt;\n        &lt;!-- Optional: enable if in scale-to-zero hosting environment --&gt;\n        &lt;!-- &lt;PublishReadyToRun&gt;true&lt;/PublishReadyToRun&gt; --&gt;\n    &lt;/PropertyGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<h2 id=\"native-aot-deployments\">Native AOT deployments</h2>\n<p>Publishing your app as Native AOT produces an app that's self-contained and that has been ahead-of-time (AOT) compiled to native code. Native AOT apps have faster startup time and smaller memory footprints. These apps can run on machines that don't have the .NET runtime installed.</p>\n<p>Since AOT deployments require trimming, and are single file by nature the only required msbuild property is:</p>\n<pre><code class=\"language-xml\">&lt;Project&gt;\n    &lt;PropertyGroup&gt;\n        &lt;PublishAot&gt;true&lt;/PublishAot&gt;\n    &lt;/PropertyGroup&gt;\n&lt;/Project&gt;\n</code></pre>\n<p><a href=\"example-hello-world.html\">Next: Example - Hello World</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-basic-rest-api.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - Basic REST API - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-basic-rest-api\">Example - Basic REST API</h1>\n<p>This example demonstrates how to create a basic REST API using Falco. The API will allow users to perform CRUD (Create, Read, Update, Delete) operations on a simple resource, users in this case.</p>\n<p>The API will be built using the following components, in addition to the Falco framework:</p>\n<ul>\n<li><a href=\"https://www.nuget.org/packages/System.Data.SQLite/\">System.Data.SQLite</a>, which provides SQLite support, built and maintained by the SQLite developers.</li>\n<li><a href=\"https://www.nuget.org/packages/Donald/\">Donald</a> which simplifies database access, built and maintained by the Falco developers.</li>\n</ul>\n<blockquote>\n<p>For simplicity, we'll stick to sychronous database access in this example. However, you can easily adapt the code to use asynchronous database access if needed. Specific to SQLite, in many cases it is better to use synchronous access, and let SQLite handle serialization for you.</p>\n</blockquote>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/BasicRestApi\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o BasicRestApiApp\n&gt; cd BasicRestApiApp\n&gt; dotnet add package System.Data.SQLite\n&gt; dotnet add package Donald\n</code></pre>\n<h2 id=\"overview\">Overview</h2>\n<p>The API will consist of four endpoints:</p>\n<ul>\n<li><code>GET /users</code>: Retrieve all users.</li>\n<li><code>GET /users/{username}</code>: Retrieve a user by username.</li>\n<li><code>POST /users</code>: Create a new user.</li>\n<li><code>DELETE /users/{username}</code>: Delete a user by username.</li>\n</ul>\n<p>Users will be stored in a SQLite database, and the API will use Donald to interact with the database. Our user model will be a simple record type with two properties: <code>Username</code> and <code>Full Name</code>.</p>\n<pre><code class=\"language-fsharp\">type User =\n    { Username : string\n      FullName : string }\n</code></pre>\n<p>It's also valueable to have a concrete type to represent API errors. This will be used to return error messages in a consistent format.</p>\n<pre><code class=\"language-fsharp\">type Error =\n    { Code : string\n      Message : string }\n</code></pre>\n<h2 id=\"data-access\">Data Access</h2>\n<p>To interact with the SQLite database, we'll create some abstractions for establishing new connections and performing database operations.</p>\n<p>A connection factory is a useful concept to avoid passing around connection strings. It allows us to create new connections without needing to know the details of how they are created.</p>\n<pre><code class=\"language-fsharp\">type IDbConnectionFactory =\n    abstract member Create : unit -&gt; IDbConnection\n</code></pre>\n<p>We'll also define an interface for performing list, create, read and delete operations against a set of entities.</p>\n<pre><code class=\"language-fsharp\">type IStore&lt;'TKey, 'TItem&gt; =\n    abstract member List : unit   -&gt; 'TItem list\n    abstract member Create : 'TItem -&gt; Result&lt;unit, Error&gt;\n    abstract member Read : 'TKey -&gt; 'TItem option\n    abstract member Delete : 'TKey -&gt; Result&lt;unit, Error&gt;\n</code></pre>\n<p>The <code>IStore</code> interface is generic, allowing us to use it with any type of entity. In our case, we'll create a concrete implementation for the <code>User</code> entity.</p>\n<h2 id=\"implementing-the-store\">Implementing the Store</h2>\n<h2 id=\"error-responses\">Error Responses</h2>\n<p>The API will return error responses in a consistent format. To do this, we'll create three functions for the common error cases: <code>notFound</code>, <code>badRequest</code>, and <code>serverException</code>.</p>\n<pre><code class=\"language-fsharp\">module ErrorResponse =\n    let badRequest error : HttpHandler =\n        Response.withStatusCode 400\n        &gt;&gt; Response.ofJson error\n\n    let notFound : HttpHandler =\n        Response.withStatusCode 404 &gt;&gt;\n        Response.ofJson { Code = &quot;404&quot;; Message = &quot;Not Found&quot; }\n\n    let serverException : HttpHandler =\n        Response.withStatusCode 500 &gt;&gt;\n        Response.ofJson { Code = &quot;500&quot;; Message = &quot;Server Error&quot; }\n</code></pre>\n<p>Here you can see our error type in action, which is used to return a JSON response with the error code and message. The signature of the <code>badRequest</code> function is a bit different, as it takes an error object as input and returns a <code>HttpHandler</code>. The reason for this is that we intend to invoke this function from within our handlers, and we want to be able to pass the error object directly to it.</p>\n<h2 id=\"defining-the-endpoints\">Defining the Endpoints</h2>\n<p>It can be very useful to define values for the endpoints we want to expose. This allows us to easily change the endpoint paths in one place if needed, and also provides intellisense support when using the endpoints in our code.</p>\n<pre><code class=\"language-fsharp\">module Route =\n    let userIndex = &quot;/users&quot;\n    let userAdd = &quot;/users&quot;\n    let userView = &quot;/users/{username}&quot;\n    let userRemove = &quot;/users/{username}&quot;\n</code></pre>\n<p>Next, let's implement the handlers for each of the endpoints. First, we'll implement the <code>GET /users</code> endpoint, which retrieves all users from the database.</p>\n<pre><code class=\"language-fsharp\">module UserEndpoint =\n    let index : HttpHandler = fun ctx -&gt;\n        let userStore = ctx.Plug&lt;IStore&lt;string, User&gt;&gt;()\n        let allUsers = userStore.List()\n        Response.ofJson allUsers ctx\n</code></pre>\n<p>The <code>index</code> function retrieves the <code>IStore</code> instance from the dependency container and calls the <code>List</code> method to get all users. The result is then returned as a JSON response.</p>\n<p>Next, we'll implement the <code>POST /users</code> endpoint, which creates a new user.</p>\n<pre><code class=\"language-fsharp\">module UserEndpoint =\n    // ... index handler ...\n    let add : HttpHandler = fun ctx -&gt; task {\n        let userStore = ctx.Plug&lt;IStore&lt;string, User&gt;&gt;()\n        let! userJson = Request.getJson&lt;User&gt; ctx\n        let userAddResponse =\n            match userStore.Create(userJson) with\n            | Ok result -&gt; Response.ofJson result ctx\n            | Error error -&gt; ErrorResponse.badRequest error ctx\n        return! userAddResponse }\n</code></pre>\n<p>The <code>add</code> function retrieves the <code>IStore</code> instance from the dependency container and calls the <code>Create</code> method to add a new user. The result is then returned as a JSON response. If the user creation fails, we return a bad request error.</p>\n<p>Next, we'll implement the <code>GET /users/{username}</code> endpoint, which retrieves a user by username.</p>\n<pre><code class=\"language-fsharp\">module UserEndpoint =\n    // ... index and add handlers ...\n    let view : HttpHandler = fun ctx -&gt;\n        let userStore = ctx.Plug&lt;IStore&lt;string, User&gt;&gt;()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Read(username) with\n        | Some user -&gt; Response.ofJson user ctx\n        | None -&gt; ErrorResponse.notFound ctx\n</code></pre>\n<p>The <code>view</code> function retrieves the <code>IStore</code> instance from the dependency container and calls the <code>Read</code> method to get a user by username. If the user is found, it is returned as a JSON response. If not, we return a not found error.</p>\n<p>Finally, we'll implement the <code>DELETE /users/{username}</code> endpoint, which deletes a user by username.</p>\n<pre><code class=\"language-fsharp\">module UserEndpoint =\n    // ... index, add and view handlers ...\n    let remove : HttpHandler = fun ctx -&gt;\n        let userStore = ctx.Plug&lt;IStore&lt;string, User&gt;&gt;()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Delete(username) with\n        | Ok result -&gt; Response.ofJson result ctx\n        | Error error -&gt; ErrorResponse.badRequest error ctx\n</code></pre>\n<p>The <code>remove</code> function retrieves the <code>IStore</code> instance from the dependency container and calls the <code>Delete</code> method to remove a user by username. The result is then returned as a JSON response. If the user deletion fails, we return a bad request error.</p>\n<h2 id=\"configuring-the-application\">Configuring the Application</h2>\n<p>Conventionally, you'll configure your database outside of your application scope. For the purpose of this example, we'll define and initialize the database during startup.</p>\n<pre><code class=\"language-fsharp\">module Program =\n    [&lt;EntryPoint&gt;]\n    let main args =\n        let dbConnectionFactory =\n            { new IDbConnectionFactory with\n                member _.Create() = new SQLiteConnection(&quot;Data Source=BasicRestApi.sqlite3&quot;) }\n\n        let initializeDatabase (dbConnection : IDbConnectionFactory) =\n            use conn = dbConnection.Create()\n            conn\n            |&gt; Db.newCommand &quot;CREATE TABLE IF NOT EXISTS user (username, full_name)&quot;\n            |&gt; Db.exec\n\n        initializeDatabase dbConnectionFactory\n\n        // ... rest of the application setup\n</code></pre>\n<p>First we implement the <code>IDbConnectionFactory</code> interface, which creates a new SQLite connection. Then we define a <code>initializeDatabase</code> function, which creates the database and the user table if it doesn't exist. We encapsulate the database initialization in a function, so we can quickly dispose of the connection after use.</p>\n<p>Next, we need to register our database connection factory and the <code>IStore</code> implementation in the dependency container.</p>\n<pre><code class=\"language-fsharp\">module Program =\n    [&lt;EntryPoint&gt;]\n    let main args =\n        // ... database initialization ...\n        let bldr = WebApplication.CreateBuilder(args)\n\n        bldr.Services\n            .AddAntiforgery()\n            .AddScoped&lt;IDbConnectionFactory&gt;(dbConnectionFactory)\n            .AddScoped&lt;IStore&lt;string, User&gt;, UserStore&gt;()\n            |&gt; ignore\n</code></pre>\n<p>Finally, we need to configure the application to use the defined endpoints.</p>\n<pre><code class=\"language-fsharp\">module Program =\n    [&lt;EntryPoint&gt;]\n    let main args =\n        // ... database initialization &amp; dependency registration ...\n        let wapp = bldr.Build()\n\n        let isDevelopment = wapp.Environment.EnvironmentName = &quot;Development&quot;\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorResponse.serverException)\n            .UseRouting()\n            .UseFalco(App.endpoints)\n            .Run(ErrorResponse.notFound)\n\n        0 // Exit code\n</code></pre>\n<p>The <code>UseFalco</code> method is used to register the endpoints, and the <code>Run</code> method is used to handle requests that don't match any of the defined endpoints.</p>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>And there you have it! A simple REST API built with Falco, SQLite and Donald. This example demonstrates how to create a basic CRUD API, but you can easily extend it to include more complex functionality, such as authentication, validation, and more.</p>\n<p><a href=\"example-open-api.html\">Next: Example - Open API</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-dependency-injection.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - Dependency Injection - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-dependency-injection\">Example - Dependency Injection</h1>\n<p>An important and nuanced subject to discuss is dependency injection. There's a myriad of beliefs and approaches, all of which have their merit. In the case of Falco, you are living in the world of ASP.NET which has <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection\">built-in support</a> for this. It works very well and you should use it. But make sure you follow through their <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0\">docs</a> on how it works and integrates with ASP.NET.</p>\n<p>Going back to our basic <a href=\"example-hello-world.html\">Hello World</a> app, let's add in an external dependency to demonstrate some of the basics of dependency injection in Falco.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/DependencyInjection\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o DependencyInjectionApp\n</code></pre>\n<h2 id=\"creating-abstraction\">Creating Abstraction</h2>\n<p>The benefit of abstracting functionality is that it removes the coupling between your implementation and the calling code. You instead rely on an accepted definition of what something does.</p>\n<p>F# has excellent support for <a href=\"https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes\">object programming</a>. There might be an urge to avoid this type of approach because &quot;ugh classes are gross&quot;. But suck it up buttercup, they are wickedly useful in many cases and a reminder that F# code doesn't have to adhere to some functional purism.</p>\n<p>In the case of our application, we're going to define an abstraction for greeting patrons. Then write a simple implementation.</p>\n<blockquote>\n<p>This is a completely contrived example, created purely to demonstrate how to register and consume dependencies.</p>\n</blockquote>\n<pre><code class=\"language-fsharp\">type IGreeter =\n    abstract member Greet : name : string -&gt; string\n\ntype FriendlyGreeter() =\n    interface IGreeter with\n        member _.Greet(name : string) =\n            $&quot;Hello {name} 😀&quot;\n</code></pre>\n<p>Simple enough, we describe an <code>IGreeter</code> as having the ability to <code>Greet</code> in the form of receiving a name string and return a string message. Next we define an implementation that fulfills this interface in a friendly way.</p>\n<h2 id=\"registering-the-dependency\">Registering the Dependency</h2>\n<p>To provide runtime access to our greeter, we have to register the dependency in the container. The abstraction from ASP.NET for this is called <code>IServiceCollection</code>. You can register dependencies in a number of ways, but fundamental to all is the concept of <a href=\"https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes\">service lifetime</a>. It distills down to:</p>\n<ul>\n<li>Transient = new for every container access</li>\n<li>Scoped = new for every client request</li>\n<li>Singleton = created at startup, or first container access</li>\n</ul>\n<p>Our greeter is both stateless and cheap to construct. So any of the lifetimes will suffice. But let's register it as a singleton. This time however, we'll create our web server in two stages, to gain access to the dependency container.</p>\n<pre><code class=\"language-fsharp\">let bldr = WebApplication.CreateBuilder() // &lt;-- create a configurable web application builder\n\nbldr.Services\n    .AddSingleton&lt;IGreeter, FriendlyGreeter&gt;() // &lt;-- register the greeter as singleton in the container\n    |&gt; ignore\n\nlet wapp = bldr.Build() // &lt;-- manifest our WebApplication\n\nlet endpoints =\n    [\n        mapGet &quot;/{name?}&quot;\n            (fun r -&gt; r?name.AsString(&quot;world&quot;))\n            (fun name ctx -&gt;\n                let greeter = ctx.Plug&lt;IGreeter&gt;() // &lt;-- access our dependency from the container\n                let greeting = greeter.Greet(name) // &lt;-- invoke our greeter.Greet(name) method\n                Response.ofPlainText greeting ctx)\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<p>Following through you can see the web server being created in two phases. The first to establish the context (i.e., logging, server configuration and dependencies). Second, freezing the final state and creating a configurable web application.</p>\n<p>Within the handler you can see the interaction with the dependency container using <code>ctx.Plug&lt;IGreeter&gt;()</code>. This code tells the container to return the implementation it has registered for that abstraction. In our case <code>FriendlyGreeter</code>.</p>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>Now that we're finished introducing dependency injection, let's move on to a real world example by integrating with an external view engine.</p>\n<p><a href=\"example-external-view-engine.html\">Next: Example - External View Engine</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-external-view-engine.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - External View Engine - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-external-view-engine\">Example - External View Engine</h1>\n<p>Falco comes packaged with a <a href=\"markup.html\">built-in view engine</a>. But if you'd prefer to write your own templates, or use an external template engine, that is entirely possible as well.</p>\n<p>In this example we'll do some basic page rendering by integrating with <a href=\"https://github.com/scriban/scriban\">scriban</a>. An amazing template engine by <a href=\"https://github.com/xoofx\">xoofx</a>.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/ExternalViewEngine\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o ExternalViewEngineApp\n&gt; cd ExternalViewEngineApp\n&gt; dotnet add package Scriban\n</code></pre>\n<h2 id=\"implementing-a-template-engine\">Implementing a Template Engine</h2>\n<p>There are a number of ways we could achieve this functionality. But in sticking with our previous examples, we'll create an interface. To keep things simple we'll use inline string literals for templates and perform rendering synchronously.</p>\n<pre><code class=\"language-fsharp\">open Scriban\n\ntype ITemplate =\n    abstract member Render : template: string * model: obj -&gt; string\n\ntype ScribanTemplate() =\n    interface ITemplate with\n        member _.Render(template, model) =\n            let tmpl = Template.Parse template\n            tmpl.Render(model)\n</code></pre>\n<p>We define an interface <code>ITemplate</code> which describes template rendering as a function that receives a template string literal and a model, producing a string literal. Then we implement this interface definition using Scriban.</p>\n<h2 id=\"rendering-pages\">Rendering Pages</h2>\n<p>To use our Scriban template engine we'll need to request it from the dependency container, then pass it our template literal and model.</p>\n<blockquote>\n<p>See <a href=\"example-dependency-injection.html\">dependency injection</a> for further explanation.</p>\n</blockquote>\n<p>Since rendering more than one page is the goal, we'll create a shared <code>renderPage</code> function to do the dirty work for us.</p>\n<pre><code class=\"language-fsharp\">open Falco\n\nmodule Pages =\n    let private renderPage pageTitle template viewModel : HttpHandler = fun ctx -&gt;\n        let templateService = ctx.Plug&lt;ITemplate&gt;() // &lt;-- obtain our template service from the dependency container\n        let pageContent = templateService.Render(template, viewModel) // &lt;-- render our template with the provided view model as string literal\n        let htmlTemplate = &quot;&quot;&quot;\n            &lt;!DOCTYPE html&gt;\n            &lt;html&gt;\n            &lt;head&gt;\n                &lt;meta charset=&quot;utf-8&quot;&gt;\n                &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;\n                &lt;title&gt;{{title}}&lt;/title&gt;\n            &lt;/head&gt;\n            &lt;body&gt;\n                {{content}}\n            &lt;/body&gt;\n            &lt;/html&gt;\n        &quot;&quot;&quot;\n        // ^ these triple quoted strings auto-escape characters like double quotes for us\n        //   very practical for things like HTML\n\n        let html = templateService.Render(htmlTemplate, {| Title = pageTitle; Content = pageContent |})\n\n        Response.ofHtmlString html ctx // &lt;-- return template literal as &quot;text/html; charset=utf-8&quot; response\n</code></pre>\n<p>In this function we obtain the instance of our template engine, and immediately render the user-provided template and model. Next, we define a local template literal to serve as our layout. Assigning two simple inputs, <code>{{title}}</code> and <code>{{content}}</code>. Then we render the layout template using our template engine and an anonymous object literal <code>{| Title = pageTitle; Content = pageContent |}</code>, responding with the result of this as <code>text/html</code>.</p>\n<p>To render pages, we simply need to create a localized template literal, and feed it into our <code>renderPage</code> function. Below we define a home and 404 page.</p>\n<pre><code class=\"language-fsharp\">    let homepage : HttpHandler = fun ctx -&gt;\n        let query = Request.getQuery ctx // &lt;-- obtain access to strongly-typed representation of the query string\n        let viewModel = {| Name = query?name.AsStringNonEmpty(&quot;World&quot;) |} // &lt;-- access 'name' from query, or default to 'World'\n        let template = &quot;&quot;&quot;\n            &lt;h1&gt;Hello {{ name }}!&lt;/h1&gt;\n        &quot;&quot;&quot;\n        renderPage $&quot;Hello {viewModel.Name}&quot; template viewModel ctx\n\n    let notFound : HttpHandler =\n        let template = &quot;&quot;&quot;\n            &lt;h1&gt;Page not found&lt;/h1&gt;\n        &quot;&quot;&quot;\n        renderPage &quot;Page Not Found&quot; template {||}\n</code></pre>\n<h2 id=\"registering-the-template-engine\">Registering the Template Engine</h2>\n<p>Since our Scriban template engine is stateless and dependency-free, we can use the generic extension method to register it as a singleton.</p>\n<blockquote>\n<p>Note: <code>Transient</code> and <code>Scoped</code> lifetimes would also work here.</p>\n</blockquote>\n<pre><code>open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\n\n[&lt;EntryPoint&gt;]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddSingleton&lt;ITemplate, ScribanTemplate&gt;() // &lt;-- register ITemplates implementation as a dependency\n        |&gt; ignore\n\n    let endpoints =\n        [ get &quot;/&quot; Pages.homepage ]\n\n    let wapp = bldr.Build()\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .UseFalcoNotFound(Pages.notFound)\n        .Run()\n\n    0 // Exit code\n</code></pre>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>This example demonstrates how to effectively integrate an external view engine into your Falco application. By defining a simple interface, implementing it with Scriban and adding it to the dependency container, we can render HTML pages dynamically based on user input.</p>\n<p><a href=\"example-basic-rest-api.html\">Next: Example - Basic REST API</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-hello-world-mvc.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - Hello World MVC - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-hello-world-mvc\">Example - Hello World MVC</h1>\n<p>Let's take our basic <a href=\"example-hello-world.html\">Hello World</a> to the next level. This means we're going to dial up the complexity a little bit. But we'll do this using the well recognized MVC pattern. We'll contain the app to a single file to make &quot;landscaping&quot; the pattern more straight-forward.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorldMvc\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o HelloWorldMvcApp\n</code></pre>\n<h2 id=\"model\">Model</h2>\n<p>Since this app has no persistence, the model is somewhat boring. But included here to demonstrate the concept.</p>\n<p>We define two simple record types. One to contain the patron name, the other to contain a <code>string</code> message.</p>\n<pre><code class=\"language-fsharp\">module Model =\n    type NameGreeting =\n        { Name : string }\n\n    type Greeting =\n        { Message : string }\n</code></pre>\n<h2 id=\"routing\">Routing</h2>\n<p>As the project scales, it is generally helpful to have static references to your URLs and/or URL generating functions for dynamic resources.</p>\n<p><a href=\"routing.html\">Routing</a> begins with a route template, so it's only natural to define those first.</p>\n<pre><code class=\"language-fsharp\">module Route =\n    let index = &quot;/&quot;\n    let greetPlainText = &quot;/greet/text/{name}&quot;\n    let greetJson = &quot;/greet/json/{name}&quot;\n    let greetHtml = &quot;/greet/html/{name}&quot;\n</code></pre>\n<p>Here you can see we define one static route, and 3 dynamic route templates. We can provide URL generation from these dynamic route templates quite easily with some simple functions.</p>\n<pre><code class=\"language-fsharp\">module Url =\n    let greetPlainText name = Route.greetPlainText.Replace(&quot;{name}&quot;, name)\n    let greetJson name = Route.greetJson.Replace(&quot;{name}&quot;, name)\n    let greetHtml name = Route.greetHtml.Replace(&quot;{name}&quot;, name)\n</code></pre>\n<p>These 3 functions take a string input called <code>name</code> and plug it into the <code>{name}</code> placeholder in the route template. This gives us a nice little typed API for creating our application URLs.</p>\n<h2 id=\"view\">View</h2>\n<p>Falco comes packaged with a <a href=\"https://github.com/FalcoFramework/Falco.Markup/\">lovely little HTML DSL</a>. It can produce any form of angle-markup, and does so very <a href=\"https://github.com/FalcoFramework/Falco.Markup/?tab=readme-ov-file#performance\">efficiently</a>. The main benefit is that our views are <em>pure</em> F#, compile-time checked and live alongside the rest of our code.</p>\n<p>First we define a shared HTML5 <code>layout</code> function, that references our project <code>style.css</code>. Next, we define a module to contain the views for our greetings.</p>\n<blockquote>\n<p>You'll notice the <code>style.css</code> file resides in a folder called <code>wwwroot</code>. This is an <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files\">ASP.NET convention</a> which we'll enable later when we <a href=\"#web-server\">build the web server</a>.</p>\n</blockquote>\n<pre><code class=\"language-fsharp\">module View =\n    open Model\n\n    let layout content =\n        Templates.html5 &quot;en&quot;\n            [ _link [ _href_ &quot;/style.css&quot;; _rel_ &quot;stylesheet&quot; ] ]\n            content\n\n    module GreetingView =\n        /// HTML view for /greet/html\n        let detail greeting =\n            layout [\n                _h1' $&quot;Hello {greeting.Name} from /html&quot;\n                _hr []\n                _p' &quot;Greet other ways:&quot;\n                _nav [] [\n                    _a\n                        [ _href_ (Url.greetPlainText greeting.Name) ]\n                        [ _text &quot;Greet in text&quot;]\n                    _text &quot; | &quot;\n                    _a\n                        [ _href_ (Url.greetJson greeting.Name) ]\n                        [ _text &quot;Greet in JSON &quot; ]\n                ]\n            ]\n</code></pre>\n<p>The markup code is fairly self-explanatory. But essentially:</p>\n<ul>\n<li><code>Elem</code> produces HTML elements.</li>\n<li><code>Attr</code> produces HTML element attributes.</li>\n<li><code>Text</code> produces HTML text nodes.</li>\n</ul>\n<p>Each of these modules matches (or tries to) the full HTML spec. You'll also notice two of our URL generators at work.</p>\n<h2 id=\"errors\">Errors</h2>\n<p>We'll define a couple static error pages to help prettify our error output.</p>\n<pre><code class=\"language-fsharp\">module Controller =\n    open Model\n    open View\n\n    module ErrorController =\n        let notFound : HttpHandler =\n            Response.withStatusCode 404 &gt;&gt;\n            Response.ofHtml (View.layout [ _h1' &quot;Not Found&quot; ])\n\n        let serverException : HttpHandler =\n            Response.withStatusCode 500 &gt;&gt;\n            Response.ofHtml (View.layout [ _h1' &quot;Server Error&quot; ])\n</code></pre>\n<p>Here we see the <a href=\"repsonse.html#response-modifiers\"><code>HttpResponseModifier</code></a> at play, which set the status code before buffering out the HTML response. We'll reference these pages later when be <a href=\"#web-server\">build the web server</a>.</p>\n<h2 id=\"controller\">Controller</h2>\n<p>Our controller will be responsible for four actions, as defined in our <a href=\"#routing\">route</a> module. We define four handlers, one parameterless greeting and three others which output the user provided &quot;name&quot; in different ways: plain text, JSON and HTML.</p>\n<pre><code class=\"language-fsharp\">module Controller =\n    open Model\n    open View\n\n    module ErrorController =\n        // ...\n\n    module GreetingController =\n        let index =\n            Response.ofPlainText &quot;Hello world&quot;\n\n        let plainTextDetail name =\n            Response.ofPlainText $&quot;Hello {name}&quot;\n\n        let jsonDetail name =\n            let message = { Message = $&quot;Hello {name} from /json&quot; }\n            Response.ofJson message\n\n        let htmlDetail name =\n            { Name = name }\n            |&gt; GreetingView.detail\n            |&gt; Response.ofHtml\n\n        let endpoints =\n            let mapRoute (r : RequestData) =\n                r?name.AsString()\n\n            [ get Route.index index\n              mapGet Route.greetPlainText mapRoute plainTextDetail\n              mapGet Route.greetJson mapRoute jsonDetail\n              mapGet Route.greetHtml mapRoute htmlDetail ]\n</code></pre>\n<p>You'll notice that the controller defines its own <code>endpoints</code>. This associates a route to a handler when passed into Falco (we'll do this later). Defining this within the controller is personal preference. But considering controller actions usually operate against a common URL pattern, it allows a private, reusable route mapping to exist (see <code>mapRoute</code>).</p>\n<h2 id=\"web-server\">Web Server</h2>\n<p>This is a great opportunity to demonstrate further how to configure a more complex web server than we saw in the basic hello world example.</p>\n<p>To do that, we'll define an explicit entry point function which gives us access to the command line argument. By then forwarding these into the web application, we gain further <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration#command-line\">configurability</a>. You'll notice the application contains a file called <code>appsettings.json</code>, this is another <a href=\"https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration#default-application-configuration-sources\">ASP.NET convention</a> that provides fully-featured and extensible configuration functionality.</p>\n<p>Next we define an explicit collection of endpoints, which gets passed into the <code>.UseFalco(endpoints)</code> extension method.</p>\n<p>In this example, we examine the environment name to create an &quot;is development&quot; toggle. We use this to determine the extensiveness of our error output. You'll notice we use our exception page from above when an exception occurs when not in development mode. Otherwise, we show a developer-friendly error page. Next we activate static file support, via the default web root of <code>wwwroot</code>.</p>\n<p>We end off by registering a terminal handler, which functions as our &quot;not found&quot; response.</p>\n<pre><code class=\"language-fsharp\">module Program =\n    open Controller\n\n    let endpoints =\n        [ get Route.index GreetingController.index\n          get Route.greetPlainText GreetingController.plainTextDetail\n          get Route.greetJson GreetingController.jsonDetail\n          get Route.greetHtml GreetingController.htmlDetail ]\n\n\n    /// By defining an explicit entry point, we gain access to the command line\n    /// arguments which when passed into Falco are used as the creation arguments\n    /// for the internal WebApplicationBuilder.\n    [&lt;EntryPoint&gt;]\n    let main args =\n        let wapp = WebApplication.Create(args)\n\n        let isDevelopment = wapp.Environment.EnvironmentName = &quot;Development&quot;\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorPage.serverException)\n            .Use(StaticFileExtensions.UseStaticFiles)\n            .UseFalco(endpoints)\n            .UseFalcoNotFound(ErrorPage.notFound)\n            .Run()\n\n        0\n</code></pre>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>This example was a leap ahead from our basic hello world. But having followed this, you know understand many of the patterns you'll need to know to build end-to-end server applications with Falco. Unsurprisingly, the entire program fits inside 118 LOC. One of the magnificent benefits of writing code in F#.</p>\n<p><a href=\"example-dependency-injection.html\">Next: Example - Dependency Injection</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-hello-world.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - Hello World - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-hello-world\">Example - Hello World</h1>\n<p>The goal of this program is to demonstrate the absolute bare bones hello world application, so that we can focus on the key elements when initiating a new web application.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorld\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o HelloWorldApp\n</code></pre>\n<h2 id=\"code-overview\">Code Overview</h2>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco([\n    // ^-- activate Falco endpoint source\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n        // ^-- associate GET / to plain text HttpHandler\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n    // ^-- activate Falco endpoint source\n</code></pre>\n<p>First, we open the required namespaces. <code>Falco</code> bring into scope the ability to activate the library and some other extension methods to make the fluent API more user-friendly.</p>\n<p><code>Microsoft.AspNetCore.Builder</code> enables us to create web applications in a number of ways, we're using <code>WebApplication.Create()</code> above. It also adds many other useful extension methods, that you'll see later.</p>\n<p>After creating the web application, we:</p>\n<ul>\n<li>Activate Falco using <code>wapp.UseFalco()</code>. This enables us to create endpoints.</li>\n<li>Register <code>GET /</code> endpoint to a handler which responds with &quot;hello world&quot;.</li>\n<li>Run the app.</li>\n</ul>\n<p><a href=\"example-hello-world-mvc.html\">Next: Example - Hello World MVC</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-htmx.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - HTMX - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-htmx\">Example - HTMX</h1>\n<p><a href=\"https://github.com/FalcoFramework/Falco.Htmx\">Falco.Htmx</a> brings type-safe <a href=\"https://htmx.org/\">htmx</a> support to <a href=\"https://github.com/FalcoFramework/Falco\">Falco</a>. It provides a complete mapping of all attributes, typed request data and ready-made response modifiers.</p>\n<p>In this example, we'll demonstrate some of the more common htmx attributes and how to use them with Falco.</p>\n<p>At this point, we'll assume you have reviewed the docs, other examples and understand the basics of Falco. We don't be covering any of the basics in the code review.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/Htmx\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o HtmxApp\n&gt; cd HtmxApp\n&gt; dotnet add package Falco.Htmx\n</code></pre>\n<h2 id=\"layout\">Layout</h2>\n<p>First we'll define a simple layout and enable htmx by including the script. Notice the strongly typed reference, <code>HtmxScript.cdnSrc</code>, which is provided by Falco.Htmx and resolves to the official CDN URL.</p>\n<pre><code class=\"language-fsharp\">module View =\n    let template content =\n        _html [ _lang_ &quot;en&quot; ] [\n            _head [] [\n                _script [ _src_ HtmxScript.cdnSrc ] [] ]\n            _body [] content ]\n</code></pre>\n<p>With our layout defined, we can create a view to represent our starting state.</p>\n<pre><code class=\"language-fsharp\">module View =\n    // Layout ...\n\n    let clickAndSwap =\n        template [\n            _h1' &quot;Example: Click &amp; Swap&quot;\n            _div [ _id_ &quot;content&quot; ] [\n                _button [\n                    _id_ &quot;clicker&quot;\n                    Hx.get &quot;/click&quot;\n                    Hx.swapOuterHtml ]\n                    [ _text &quot;Click Me&quot; ] ] ]\n</code></pre>\n<p>This view contains a button that, when clicked, will send a GET request to the <code>/click</code> endpoint. The response from that request will replace the button with the response from the server.</p>\n<h2 id=\"components\">Components</h2>\n<p>A nice convention when working with Falco.Markup is to create a <code>Components</code> module within your <code>View</code> module. We'll define one component here.</p>\n<p>All of the htmx attributes and properties are mapped within the <code>Hx</code> module. Wherever a limited scope of options exist, strongly typed references are provided. For example, <code>Hx.swapInnerHtml</code> is a strongly typed reference to the <code>hx-swap</code> attribute with the value <code>innerHTML</code>. This is a great way to avoid typos and ensure that your code is type-safe.</p>\n<pre><code class=\"language-fsharp\">module View =\n    // Layout &amp; view ...\n\n    module Components =\n        let resetter =\n            _div [ _id_ &quot;resetter&quot; ] [\n                _h2' &quot;Way to go! You clicked it!&quot;\n                _br []\n                _button [\n                    Hx.get &quot;/reset&quot;\n                    Hx.swapOuterHtml\n                    Hx.targetCss &quot;#resetter&quot; ]\n                    [ _text &quot;Reset&quot; ] ]\n</code></pre>\n<p>The <code>resetter</code> component is a simple button that will send a GET request to the server when clicked. The response will replace the entire <code>div</code> with the ID of <code>resetter</code> with the response from the server.</p>\n<h2 id=\"handlers\">Handlers</h2>\n<p>Next we define a couple basic handlers to handle the requests for the original document and ajax requests.</p>\n<pre><code class=\"language-fsharp\">module App =\n    let handleIndex : HttpHandler =\n        Response.ofHtml View.clickAndSwap\n\n    let handleClick : HttpHandler =\n        Response.ofHtml View.Components.resetter\n\n    let handleReset : HttpHandler =\n        Response.ofFragment &quot;clicker&quot; View.clickAndSwap\n</code></pre>\n<p>The <code>handleIndex</code> handler is returning our full click-and-swap view, containing the clicker button. Clicking it triggers a request to the <code>handleClick</code> handler, which returns the resetter component. Clicking the reset button triggers a request to the <code>handleReset</code> handler, which returns the original clicker button as a [template fragment], extracted from the same view as the original state.</p>\n<h2 id=\"web-server\">Web Server</h2>\n<p>To finish things off, we'll map our handlers to the expected routes and initialize the web server.</p>\n<pre><code class=\"language-fsharp\">let wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get &quot;/&quot; App.handleIndex\n        get &quot;/click&quot; App.handleClick\n        get &quot;/reset&quot; App.handleReset\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>That's it! You now have a simple web application that uses htmx to swap out components on the page without a full page reload. This is just the beginning of what you can do with htmx and Falco. You can use the same principles to create more complex interactions and components.</p>\n<p>For more information about the htmx integration, check out the <a href=\"https://github.com/FalcoFramework/Falco.Htmx\">Falco.Htmx</a> repository. It contains a full list of all the attributes and properties that are available, as well as examples of how to use them.</p>\n<p><a href=\"/docs\">Go back to docs home</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/example-open-api.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Example - Open API - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"example-open-api\">Example - Open API</h1>\n<p>Open API is a specification for defining APIs in a machine-readable format. It allows developers to describe the structure of their APIs, including endpoints, request/response formats, and authentication methods.</p>\n<p><a href=\"https://github.com/FalcoFramework/Falco.OpenAPI\">Falco.OpenAPI</a> is a library for generating OpenAPI documentation for Falco applications. It provides a set of combinators for annotating Falco routes with OpenAPI metadata, which can be used to generate OpenAPI documentation.</p>\n<p>We'll dial back the complexity a bit from the <a href=\"example-basic-rest-api.html\">Basic REST API</a> example and create a simple &quot;fortune teller&quot; Falco application that serves OpenAPI documentation.</p>\n<p>The code for this example can be found <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/OpenApi\">here</a>.</p>\n<h2 id=\"creating-the-application-manually\">Creating the Application Manually</h2>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o OpenApiApi\n&gt; cd OpenApiApp\n&gt; dotnet add package Falco.OpenApi\n</code></pre>\n<h2 id=\"fortunes\">Fortunes</h2>\n<p>Our fortune teller will return fortune for the name of the person specified. To model this, we'll create two simple record types.</p>\n<pre><code class=\"language-fsharp\">type FortuneInput =\n    { Name : string }\n\ntype Fortune =\n    { Description : string }\n</code></pre>\n<p>For simplicity, we'll use a static member to return a fortune. In a real application, you would likely retrieve this from a database or an external service.</p>\n<pre><code class=\"language-fsharp\">module Fortune =\n    let create age input =\n        match age with\n        | Some age when age &gt; 0 -&gt;\n            { Description = $&quot;{input.Name}, you will experience great success when you are {age + 3}.&quot; }\n        | _ -&gt;\n            { Description = $&quot;{input.Name}, your future is unclear.&quot; }\n</code></pre>\n<h2 id=\"openapi-annotations\">OpenAPI Annotations</h2>\n<p>Next, we'll annotate our route with OpenAPI metadata. This is done using the <code>OpenApi</code> module from the <code>Falco.OpenAPI</code> package. Below is the startup code for our fortune teller application. We'll dissect it after the code block, and then add the OpenAPI annotations.</p>\n<pre><code class=\"language-fsharp\">[&lt;EntryPoint&gt;]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddFalcoOpenApi()\n        // ^-- add OpenAPI services\n        .AddSwaggerGen()\n        // ^-- add Swagger services\n        |&gt; ignore\n\n    let wapp = bldr.Build()\n\n    wapp.UseHttpsRedirection()\n        .UseSwagger()\n        .UseSwaggerUI()\n    |&gt; ignore\n\n    let endpoints =\n        [\n            mapPost &quot;/fortune&quot;\n                (fun r -&gt; r?age.AsIntOption())\n                (fun ageOpt -&gt;\n                    Request.mapJson&lt;FortuneInput&gt; (Fortune.create ageOpt &gt;&gt; Response.ofJson))\n                // we'll add OpenAPI annotations here\n        ]\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .Run()\n\n    0\n</code></pre>\n<p>We've created a simple Falco application that listens for POST requests to the <code>/fortune</code> endpoint. The request body is expected to be a JSON object with a <code>name</code> property. The response will be a JSON object with a <code>description</code> property.</p>\n<p>Now, let's add the OpenAPI annotations to our route.</p>\n<pre><code class=\"language-fsharp\">[&lt;EntryPoint&gt;]\nlet main args =\n    // ... application setup code ...\n    let endpoints =\n        [\n            mapPost &quot;/fortune&quot;\n                (fun r -&gt; r?age.AsIntOption())\n                (fun ageOpt -&gt;\n                    Request.mapJson&lt;FortuneInput&gt; (Fortune.create ageOpt &gt;&gt; Response.ofJson))\n                |&gt; OpenApi.name &quot;Fortune&quot;\n                |&gt; OpenApi.summary &quot;A mystic fortune teller&quot;\n                |&gt; OpenApi.description &quot;Get a glimpse into your future, if you dare.&quot;\n                |&gt; OpenApi.query [\n                    { Type = typeof&lt;int&gt;; Name = &quot;Age&quot;; Required = false } ]\n                |&gt; OpenApi.acceptsType typeof&lt;FortuneInput&gt;\n                |&gt; OpenApi.returnType typeof&lt;Fortune&gt;\n        ]\n\n    // ... application startup code ...\n\n    0 // Exit code\n</code></pre>\n<p>In the code above, we use the <code>OpenApi</code> module to annotate our route with metadata.</p>\n<p>Here's a breakdown of the annotations:</p>\n<ul>\n<li><code>OpenApi.name</code>: Sets the name of the operation.</li>\n<li><code>OpenApi.summary</code>: Provides a short summary of the operation.</li>\n<li><code>OpenApi.description</code>: Provides a detailed description of the operation.</li>\n<li><code>OpenApi.query</code>: Specifies the query parameters for the operation. In this case, we have an optional <code>age</code> parameter.</li>\n<li><code>OpenApi.acceptsType</code>: Specifies the expected request body type. In this case, we expect a JSON object that can be deserialized into a <code>FortuneInput</code> record.</li>\n<li><code>OpenApi.returnType</code>: Specifies the response type. In this case, we return a JSON object that can be serialized into a <code>Fortune</code> record.</li>\n</ul>\n<h2 id=\"wrapping-up\">Wrapping Up</h2>\n<p>That's it! You've successfully created a simple Falco application with OpenAPI documentation. You can now use the generated OpenAPI specification to generate client code, create API documentation, or integrate with other tools that support OpenAPI.</p>\n<p><a href=\"example-htmx.html\">Next: Example - htmx</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/get-started.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Getting Started - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"getting-started\">Getting Started</h1>\n<h2 id=\"using-dotnet-new\">Using <code>dotnet new</code></h2>\n<p>The easiest way to get started with Falco is by installing the <code>Falco.Template</code> package, which adds a new template to your <code>dotnet new</code> command line tool:</p>\n<pre><code class=\"language-shell\">&gt; dotnet new install &quot;Falco.Template::*&quot;\n</code></pre>\n<p>Afterwards you can create a new Falco application by running:</p>\n<pre><code class=\"language-shell\">&gt; dotnet new falco -o HelloWorldApp\n&gt; cd HelloWorldApp\n&gt; dotnet run\n</code></pre>\n<h2 id=\"manually-installing\">Manually installing</h2>\n<p>Create a new F# web project:</p>\n<pre><code class=\"language-shell\">&gt; dotnet new web -lang F# -o HelloWorldApp\n&gt; cd HelloWorldApp\n</code></pre>\n<p>Install the nuget package:</p>\n<pre><code class=\"language-shell\">&gt; dotnet add package Falco\n</code></pre>\n<p>Remove any <code>*.fs</code> files created automatically, create a new file named <code>Program.fs</code> and set the contents to the following:</p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet endpoints =\n    [\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n        // ^-- associate GET / to plain text HttpHandler\n    ]\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    // ^-- activate Falco endpoint source\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n    // ^-- run app and register terminal (i.e., not found) middleware\n</code></pre>\n<p>Run the application:</p>\n<pre><code class=\"language-shell\">&gt; dotnet run\n</code></pre>\n<p>And there you have it, an industrial-strength <a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorld\">Hello World</a> web app. Pretty sweet!</p>\n<p><a href=\"routing.html\">Next: Routing</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/host-configuration.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Host Configuration - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"host-configuration\">Host Configuration</h1>\n<p>As your app becomes more complex, you'll inevitably need to reach for some additional host configuration. This is where the <code>Microsoft.AspNetCore.Builder</code> import comes in. This assembly contains many useful extensions for configuring the server (ex: static files, authentication, authorization etc.).</p>\n<p>Most of the extension methods have existed since the early days of ASP.NET Core and operate against <code>IApplicationBuilder</code>. But more recent version of ASP.NET Core have introduced a new <code>WebApplication</code> type that implements <code>IApplicationBuilder</code> and provides some additional functionality,  notably endpoint configuration. This dichotomy makes pipelining next to impossible. In C# you don't feel the sting of this as much because of <code>void</code> returns. But in F# this results in an excess amount of <code>|&gt; ignore</code> calls.</p>\n<p>Let's take the hero code from the <a href=\"get-started.html\">Getting Started</a> page and add the static file middleware to it:</p>\n<pre><code class=\"language-fsharp\">module Program\n\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseDefaultFiles() // you might innocently think this is fine\n    .UseStaticFiles()  // and so is this\n                       // but uknowingly, the underlying type has changed\n    .UseFalco([\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n    // ^-- this is no longer starts up our application\n\n// one way to fix this:\nwapp.UseRouting() |&gt; ignore\nwapp.UseDefaultFiles().UseStaticFiles() |&gt; ignore\n\nwapp.UseFalco([\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n\n// but we can do better\n</code></pre>\n<p>To salve this, Falco comes with a several shims. The most important of these are <code>WebApplication.Use</code> and <code>WebApplication.UseIf</code> which allow you to compose a pipeline entirely driven by <code>WebApplication</code> while at the same time taking advantage of the existing ASP.NET Core extensions.</p>\n<pre><code class=\"language-fsharp\">wapp.UseRouting()\n    .Use(fun (appl : IApplicationBuilder) -&gt;\n        appl.UseDefaultFiles()\n            .UseStaticFiles())\n    .UseFalco([\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n</code></pre>\n<p>The optional, but recommended way to take advantage of these is to utilize the static methods that server as the underpinning to the various extension methods available. The code below will attempt to highlight this more clearly:</p>\n<pre><code class=\"language-fsharp\">// better yet\nwapp.UseRouting()\n    .Use(DefaultFilesExtensions.UseDefaultFiles)\n    .Use(StaticFileExtensions.UseStaticFiles)\n      // ^-- most IApplicationBuilder extensions are available as static methods similar to this\n    .UseFalco([\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n</code></pre>\n<p>Next, we can use the <code>UseIf</code> extension method to conditionally add middleware to the pipeline. This is useful for things like development exception pages, or other middleware that you only want in certain environments.</p>\n<pre><code class=\"language-fsharp\">let isDevelopment = wapp.Environment.EnvironmentName = &quot;Development&quot;\nwapp.UseRouting()\n    .UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n    .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorPage.serverException)\n    .Use(DefaultFilesExtensions.UseDefaultFiles)\n    .Use(StaticFileExtensions.UseStaticFiles)\n    .UseFalco([\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n    ])\n    .Run(Response.ofPlainText &quot;Not found&quot;)\n</code></pre>\n<p>This is a great way to keep your code clean and readable, while still taking advantage of the powerful middleware pipeline that ASP.NET Core provides.</p>\n<p><a href=\"deployment.html\">Next: Deployment</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/index.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Welcome to Falco's Documentation - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Project Links</h3><a href=\"/\">Project Homepage</a><a class=\"db\" href=\"https://github.com/FalcoFramework/Falco\" target=\"_blank\">Source Code</a><a class=\"db\" href=\"https://github.com/FalcoFramework/Falco/issues\" target=\"_blank\">Issue Tracker</a><a class=\"db\" href=\"https://github.com/FalcoFramework/Falco/discussions\" target=\"_blank\">Discussion</a><a class=\"db\" href=\"https://twitter.com/falco_framework\" target=\"_blank\">Twitter</a></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"welcome-to-falcos-documentation\">Welcome to Falco's Documentation</h1>\n<p>Visit the <a href=\"get-started.html\">getting started</a> page for installation and a brief overview. There are also more detailed <a href=\"example-hello-world.html\">examples</a> that shows how to create a small but complete application with Falco. The rest of the docs describe each component of Falco in detail.</p>\n<h2 id=\"guides\">Guides</h2>\n<p>Falco depends only on the high-performance base components of .NET and ASP.NET Core, and provides a toolset to build a working full-stack web application. This section of the documentation explains the different parts of Falco and how they can be used, customized, and extended.</p>\n<ul>\n<li><a href=\"get-started.html\">Getting Started</a></li>\n<li><a href=\"routing.html\">Routing</a></li>\n<li><a href=\"response.html\">Writing responses</a></li>\n<li><a href=\"request.html\">Accessing request data</a></li>\n<li><a href=\"markup.html\">View engine</a></li>\n<li>Security\n<ul>\n<li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li>\n<li><a href=\"authentication.html\">Authentication &amp; Authorization</a></li>\n</ul>\n</li>\n<li><a href=\"host-configuration.html\">Host Configuration</a></li>\n<li><a href=\"deployment.html\">Deployment</a></li>\n<li>Examples\n<ul>\n<li><a href=\"example-hello-world.html\">Hello World</a></li>\n<li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li>\n<li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li>\n<li><a href=\"example-external-view-engine.html\">External View Engine</a></li>\n<li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li>\n<li><a href=\"example-open-api.html\">Open API</a></li>\n<li><a href=\"example-htmx.html\">HTMX</a></li>\n</ul>\n</li>\n</ul>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/markup.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Markup - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"markup\">Markup</h1>\n<p>Falco.Markup is broken down into three primary modules, <code>Elem</code>, <code>Attr</code> and <code>Text</code>, which are used to generate elements, attributes and text nodes respectively. Each module contain a suite of functions mapping to the various element/attribute/node names. But can also be extended to create custom elements and attributes.</p>\n<p>Primary elements are broken down into two types, <code>ParentNode</code> or <code>SelfClosingNode</code>.</p>\n<p><code>ParentNode</code> elements are those that can contain other elements. Represented as functions that receive two inputs: attributes and optionally elements.</p>\n<p>Each of the primary modules can be access using the name directly, or using the &quot;underscore syntax&quot; seen below.</p>\n<table>\n<thead>\n<tr>\n<th>Module</th>\n<th>Syntax</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Elem</code></td>\n<td><code>_h1 [] []</code></td>\n</tr>\n<tr>\n<td><code>Attr</code></td>\n<td><code>_class_ &quot;my-class&quot;</code></td>\n</tr>\n<tr>\n<td><code>Text</code></td>\n<td><code>_text &quot;Hello world!&quot;</code></td>\n</tr>\n<tr>\n<td><code>Text</code> shortcuts</td>\n<td><code>_h1' &quot;Hello world&quot;</code> (note the trailing apostrophe)</td>\n</tr>\n</tbody>\n</table>\n<pre><code class=\"language-fsharp\">let markup =\n    _div [ _class_ &quot;heading&quot; ] [\n        _h1' &quot;Hello world!&quot; ]\n</code></pre>\n<p><code>SelfClosingNode</code> elements are self-closing tags. Represented as functions that receive one input: attributes.</p>\n<pre><code class=\"language-fsharp\">let markup =\n    _div [ _class_ &quot;divider&quot; ] [\n        _hr [] ]\n</code></pre>\n<p>Text is represented using the <code>TextNode</code> and created using one of the functions in the <code>Text</code> module.</p>\n<pre><code class=\"language-fsharp\">let markup =\n    _div [] [\n        _p' &quot;A paragraph&quot;\n        _p [] [ _textf &quot;Hello %s&quot; &quot;Jim&quot; ]\n        _code [] [ _textEnc &quot;&lt;div&gt;Hello&lt;/div&gt;&quot; ] // HTML encodes text before rendering\n    ]\n</code></pre>\n<p>Attributes contain two subtypes as well, <code>KeyValueAttr</code> which represent key/value attributes or <code>NonValueAttr</code> which represent boolean attributes.</p>\n<pre><code class=\"language-fsharp\">let markup =\n    _input [ _type_ &quot;text&quot;; _required_ ]\n</code></pre>\n<p>Most <a href=\"https://developer.mozilla.org/en-US/docs/Web/Events\">JavaScript Events</a> have also been mapped in the <code>Attr</code> module. All of these events are prefixed with the word &quot;on&quot; (i.e., <code>_onclick_</code>, <code>_onfocus_</code> etc.)</p>\n<pre><code class=\"language-fsharp\">let markup =\n    _button [ _onclick_ &quot;console.log(\\&quot;hello world\\&quot;)&quot; ] [ _text &quot;Click me&quot; ]\n</code></pre>\n<h2 id=\"html\">HTML</h2>\n<p>Though Falco.Markup can be used to produce any markup. It is first and foremost an HTML library.</p>\n<h3 id=\"combining-views-to-create-complex-output\">Combining views to create complex output</h3>\n<pre><code class=\"language-fsharp\">open Falco.Markup\n\n// Components\nlet divider =\n    _hr [ _class_ &quot;divider&quot; ]\n\n// Template\nlet master (title : string) (content : XmlNode list) =\n    _html [ _lang_ &quot;en&quot; ] [\n        _head [] [\n            _title [] [ _text title ]\n        ]\n        _body [] content\n    ]\n\n// Views\nlet homeView =\n    master &quot;Homepage&quot; [\n        _h1' &quot;Homepage&quot;\n        divider\n        _p' &quot;Lorem ipsum dolor sit amet, consectetur adipiscing.&quot;\n    ]\n\nlet aboutView =\n    master &quot;About Us&quot; [\n        _h1' &quot;About&quot;\n        divider\n        _p' &quot;Lorem ipsum dolor sit amet, consectetur adipiscing.&quot;\n    ]\n</code></pre>\n<h3 id=\"strongly-typed-views\">Strongly-typed views</h3>\n<pre><code class=\"language-fsharp\">open Falco.Markup\n\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet doc (person : Person) =\n    _html [ _lang_ &quot;en&quot; ] [\n        _head [] [\n            _title [] [ _text &quot;Sample App&quot; ]\n        ]\n        _body [] [\n            _main [] [\n                _h1' &quot;Sample App&quot;\n                _p' $&quot;{person.First} {person.Last}&quot;\n            ]\n        ]\n    ]\n</code></pre>\n<h3 id=\"forms\">Forms</h3>\n<p>Forms are the lifeblood of HTML applications. A basic form using the markup module would like the following:</p>\n<pre><code class=\"language-fsharp\">let dt = DateTime.Now\n\n_form [ _methodPost_; _action_ &quot;/submit&quot; ] [\n    _label [ _for_' &quot;name&quot; ] [ _text &quot;Name&quot; ]\n    _input [ _id_ &quot;name&quot;; _name_ &quot;name&quot;; _typeText_ ]\n\n    _label [ _for_' &quot;birthdate&quot; ] [ _text &quot;Birthday&quot; ]\n    _input [ _id_ &quot;birthdate&quot;; _name_ &quot;birthdate&quot;; _typeDate_; _valueDate_ dt ]\n\n    _input [ _typeSubmit_ ]\n]\n</code></pre>\n<p>Expanding on this, we can create a more complex form involving multiple inputs and input types as follows:</p>\n<pre><code class=\"language-fsharp\">_form [ _methodPost_; _action_ &quot;/submit&quot; ] [\n    _label [ _for_' &quot;name&quot; ] [ _text &quot;Name&quot; ]\n    _input [ _id_ &quot;name&quot;; _name_ &quot;name&quot; ]\n\n    _label [ _for_' &quot;bio&quot; ] [ _text &quot;Bio&quot; ]\n    _textarea [ _name_ &quot;id&quot;; _name_ &quot;bio&quot; ] []\n\n    _label [ _for_' &quot;hobbies&quot; ] [ _text &quot;Hobbies&quot; ]\n    _select [ _id_ &quot;hobbies&quot;; _name_ &quot;hobbies&quot;; _multiple_ ] [\n        _option [ _value_ &quot;programming&quot; ] [ _text &quot;Programming&quot; ]\n        _option [ _value_ &quot;diy&quot; ] [ _text &quot;DIY&quot; ]\n        _option [ _value_ &quot;basketball&quot; ] [ _text &quot;Basketball&quot; ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text &quot;Do you like chocolate?&quot; ]\n        _label [] [\n            _text &quot;Yes&quot;\n            _input [ _typeRadio_; _name_ &quot;chocolate&quot;; _value_ &quot;yes&quot; ] ]\n        _label [] [\n            _text &quot;No&quot;\n            _input [ _typeRadio_; _name_ &quot;chocolate&quot;; _value_ &quot;no&quot; ] ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text &quot;Subscribe to our newsletter&quot; ]\n        _label [] [\n            _text &quot;Receive updates about product&quot;\n            _input [ _typeCheckbox_; _name_ &quot;newsletter&quot;; _value_ &quot;product&quot; ] ]\n        _label [] [\n            _text &quot;Receive updates about company&quot;\n            _input [ _typeCheckbox_; _name_ &quot;newsletter&quot;; _value_ &quot;company&quot; ] ]\n    ]\n\n    _input [ _typeSubmit_ ]\n]\n</code></pre>\n<p>A simple but useful <em>meta</em>-element <code>_control</code> can reduce the verbosity required to create form outputs. The same form would look like:</p>\n<pre><code class=\"language-fsharp\">_form [ _methodPost_; _action_ &quot;/submit&quot; ] [\n    _control &quot;name&quot; [] [ _text &quot;Name&quot; ]\n\n    _controlTextarea &quot;bio&quot; [] [ _text &quot;Bio&quot; ] []\n\n    _controlSelect &quot;hobbies&quot; [ _multiple_ ] [ _text &quot;Hobbies&quot; ] [\n        _option [ _value_ &quot;programming&quot; ] [ _text &quot;Programming&quot; ]\n        _option [ _value_ &quot;diy&quot; ] [ _text &quot;DIY&quot; ]\n        _option [ _value_ &quot;basketball&quot; ] [ _text &quot;Basketball&quot; ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text &quot;Do you like chocolate?&quot; ]\n        _control &quot;chocolate&quot; [ _id_ &quot;chocolate_yes&quot;; _typeRadio_ ] [ _text &quot;yes&quot; ]\n        _control &quot;chocolate&quot; [ _id_ &quot;chocolate_no&quot;; _typeRadio_ ] [ _text &quot;no&quot; ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text &quot;Subscribe to our newsletter&quot; ]\n        _control &quot;newsletter&quot; [ _id_ &quot;newsletter_product&quot;; _typeCheckbox_ ] [ _text &quot;Receive updates about product&quot; ]\n        _control &quot;newsletter&quot; [ _id_ &quot;newsletter_company&quot;; _typeCheckbox_ ] [ _text &quot;Receive updates about company&quot; ]\n    ]\n\n    _input [ _typeSubmit_ ]\n]\n</code></pre>\n<h3 id=\"attribute-value\">Attribute Value</h3>\n<p>One of the more common places of sytanctic complexity is with <code>_value_</code> which expects, like all <code>Attr</code> functions, <code>string</code> input. Some helpers exist to simplify this.</p>\n<pre><code class=\"language-fsharp\">let dt = DateTime.Now\n\n_input [ _typeDate_; _valueStringf_ &quot;yyyy-MM-dd&quot; dt ]\n\n// you could also just use:\n_input [ _typeDate_; _valueDate_ dt ] // formatted to ISO-8601 yyyy-MM-dd\n\n// or,\n_input [ _typeMonth_; _valueMonth_ dt ] // formatted to ISO-8601 yyyy-MM\n\n// or,\n_input [ _typeWeek_; _valueWeek_ dt ] // formatted to Gregorian yyyy-W#\n\n// it works for TimeSpan too:\nlet ts = TimeSpan(12,12,0)\n_input [ _typeTime_; _valueTime_ ts ] // formatted to hh:mm\n\n// there is a helper for Option too:\nlet someTs = Some ts\n_input [ _typeTime_; _valueOption_ _valueTime_ someTs ]\n</code></pre>\n<h3 id=\"merging-attributes\">Merging Attributes</h3>\n<p>The markup module allows you to easily create components, an excellent way to reduce code repetition in your UI. To support runtime customization, it is advisable to ensure components (or reusable markup blocks) retain a similar function &quot;shape&quot; to standard elements. That being, <code>XmlAttribute list -&gt; XmlNode list -&gt; XmlNode</code>.</p>\n<p>This means that you will inevitably end up needing to combine your predefined <code>XmlAttribute list</code> with a list provided at runtime. To facilitate this, the <code>Attr.merge</code> function will group attributes by key, and intelligently concatenate the values in the case of additive attributes (i.e., <code>class</code>, <code>style</code> and <code>accept</code>).</p>\n<pre><code class=\"language-fsharp\">open Falco.Markup\n\n// Components\nlet heading (attrs : XmlAttribute list) (content : XmlNode list) =\n    // safely combine the default XmlAttribute list with those provided\n    // at runtime\n    let attrs' =\n        Attr.merge [ _class_ &quot;text-large&quot; ] attrs\n\n    _div [] [\n        _h1 [ attrs' ] content\n    ]\n\n// Template\nlet master (title : string) (content : XmlNode list) =\n    _html [ _lang_ &quot;en&quot; ] [\n        _head [] [\n            _title [] [ _text title ]\n        ]\n        _body [] content\n    ]\n\n// Views\nlet homepage =\n    master &quot;Homepage&quot; [\n        heading [ _class_ &quot;red&quot; ] [ _text &quot;Welcome to the homepage&quot; ]\n        _p' &quot;Lorem ipsum dolor sit amet, consectetur adipiscing.&quot;\n    ]\n\nlet homepage =\n    master &quot;About Us&quot; [\n        heading [ _class_ &quot;purple&quot; ] [ _text &quot;This is what we're all about&quot; ]\n        _p' &quot;Lorem ipsum dolor sit amet, consectetur adipiscing.&quot;\n    ]\n</code></pre>\n<h2 id=\"custom-elements-attributes\">Custom Elements &amp; Attributes</h2>\n<p>Every effort has been taken to ensure the HTML and SVG specs are mapped to functions in the module. In the event an element or attribute you need is missing, you can either file an <a href=\"https://github.com/pimbrouwers/Falco.Markup/issues\">issue</a>, or more simply extend the module in your project.</p>\n<p>An example creating custom XML elements and using them to create a structured XML document:</p>\n<pre><code class=\"language-fsharp\">open Falco.Makrup\n\nmodule XmlElem =\n    let books = Attr.create &quot;books&quot;\n    let book = Attr.create &quot;book&quot;\n    let name = Attr.create &quot;name&quot;\n\nmodule XmlAttr =\n    let soldOut = Attr.createBool &quot;soldOut&quot;\n\nlet xmlDoc =\n    XmlElem.books [] [\n        XmlElem.book [ XmlAttr.soldOut ] [\n            XmlElem.name [] [ _text &quot;To Kill A Mockingbird&quot; ]\n        ]\n    ]\n\nlet xml = renderXml xmlDoc\n</code></pre>\n<h2 id=\"template-fragments\">Template Fragments</h2>\n<p>There are circumstances where you may want to render only a portion of your view. Especially common in <a href=\"https://htmx.org/essays/hypermedia-driven-applications/\">hypermedia driven</a> applications. Supporting <a href=\"https://htmx.org/essays/template-fragments/\">template fragments</a> is helpful in maintaining locality of behaviour, because it allows you to decompose a particular view for partial updates internally without pulling fragments of the template out to separate files for rendering, creating a large number of individual templates.</p>\n<p>Falco.Markup supports this pattern by way of the <code>renderFragment</code> function, which will traverse the provided <code>XmlNode</code> tree and render only the child node matching the provided <code>id</code>. Otherwise, gracefully returning an empty string if no match is found.</p>\n<pre><code class=\"language-fsharp\">open Falco.Markup\n\nlet view =\n    _div [ _id_ &quot;my-div&quot;; _class_ &quot;my-class&quot; ] [\n        _h1 [ _id_ &quot;my-heading&quot; ] [ _text &quot;hello&quot; ] ]\n\nlet render = renderFragment doc &quot;my-heading&quot;\n// produces: &lt;h1 id=&quot;my-heading&quot;&gt;hello&lt;/h1&gt;\n</code></pre>\n<h2 id=\"svg\">SVG</h2>\n<p>Much of the SVG spec has been mapped to element and attributes functions. There is also an SVG template to help initialize a new drawing with a valid viewbox.</p>\n<pre><code class=\"language-fsharp\">open Falco.Markup\nopen Falco.Markup.Svg\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text#example\nlet svgDrawing =\n    Templates.svg (0, 0, 240, 80) [\n        _style [] [\n            _text &quot;.small { font: italic 13px sans-serif; }&quot;\n            _text &quot;.heavy { font: bold 30px sans-serif; }&quot;\n            _text &quot;.Rrrrr { font: italic 40px serif; fill: red; }&quot;\n        ]\n        _text [ _x_ &quot;20&quot;; _y_ &quot;35&quot;; _class_ &quot;small&quot; ] [ _text &quot;My&quot; ]\n        _text [ _x_ &quot;40&quot;; _y_ &quot;35&quot;; _class_ &quot;heavy&quot; ] [ _text &quot;cat&quot; ]\n        _text [ _x_ &quot;55&quot;; _y_ &quot;55&quot;; _class_ &quot;small&quot; ] [ _text &quot;is&quot; ]\n        _text [ _x_ &quot;65&quot;; _y_ &quot;55&quot;; _class_ &quot;Rrrrr&quot; ] [ _text &quot;Grumpy!&quot; ]\n    ]\n\nlet svg = renderNode svgDrawing\n</code></pre>\n<p><a href=\"cross-site-request-forgery.html\">Next: Cross-site Request Forgery (XSRF)</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/migrating-from-v4-to-v5.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Migrating from v4.x to v5.x - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"migrating-from-v4.x-to-v5.x\">Migrating from v4.x to v5.x</h1>\n<p>With Falco v5.x the main objective was to simplify the API and improve the overall devlopment experience long term. The idea being provide only what is necessary, or provides the most value in the most frequently developed areas.</p>\n<p>This document will attempt to cover the anticipated transformations necessary to upgrade from v4.x to v5.x. Pull requests are welcome for missing scenarios, thank you in advance for your help.</p>\n<h2 id=\"webhost-expression\"><code>webHost</code> expression</h2>\n<p>Perhaps the most significant change is the removal of the <code>webHost</code> expression, which attempted to make web application server construction more pleasant. Microsoft has made really nice strides in this area (i.e., <code>WebApplication</code>) and it's been difficult at times to stay sync with the breaking changes to the underlying interfaces. As such, we elected to remove it altogether.</p>\n<p>Below demonstrates how to migrate a &quot;hello world&quot; app from v4 to v5 by replacing the <code>webHost</code> expression with the Microsoft provided <code>WebApplicationBuilder</code>.</p>\n<table>\n<tr>\n<td>\n<pre><code class=\"language-fsharp\">// Falco v4.x\nopen Falco\n\nwebHost args {\n\n    use_static_files\n\n    endpoints [\n        get &quot;/&quot;\n            (Response.ofPlainText &quot;hello world&quot;)\n    ]\n}\n</code></pre>\n</td>\n<td>\n<pre><code class=\"language-fsharp\">// Falco v5.x\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet endpoints =\n    [\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello World!&quot;)\n        // ^-- associate GET / to plain text HttpHandler\n    ]\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    // ^-- activate Falco endpoint source\n    .Run()\n</code></pre>\n</td>\n</tr>\n</table>\n<h2 id=\"configuration-expression\"><code>configuration</code> expression</h2>\n<p>The configuration expression has also been removed. Again, the idea being to try and get in the way of potentially evolving APIs as much as possible. Even more so in the areas where the code was mostly decorative.</p>\n<blockquote>\n<p>Note: This example is entirely trivial since the <code>WebApplication.CreateBuilder()</code> configures a host with common, sensible defaults.</p>\n</blockquote>\n<table>\n<tr>\n<td>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.HostBuilder\n\nlet config = configuration [||] {\n    required_json &quot;appsettings.json&quot;\n    optional_json &quot;appsettings.Development.json&quot;\n}\n\nwebHost [||] {\n    endpoints []\n}\n</code></pre>\n</td>\n<td>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.Configuration\n// ^-- this import adds access to Configuration\n\nlet bldr = WebApplication.CreateBuilder()\nlet conf =\n    bldr.Configuration\n        .AddJsonFile(&quot;appsettings.json&quot;, optional = false)\n        .AddJsonFile(&quot;appsettings.Development.json&quot;)\n\nlet wapp = WebApplication.Create()\n\nlet endpoints = []\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n</td>\n</tr>\n</table>\n<h2 id=\"stringcollectionreader-replaced-by-requestdata\"><code>StringCollectionReader</code> replaced by <code>RequestData</code></h2>\n<p>For the most part, this upgrade won't require any changes for the end user. Especially if the continuation-style functions in the <code>Request</code> module were used.</p>\n<p>Explicit references to: <code>CookieCollectionReader</code>, <code>HeaderCollectionReader</code>, <code>RouteCollectionReader</code>, <code>QueryCollectionReader</code> will need to be updated to <code>RequestData</code>. <code>FormCollectionReader</code> has been replaced by <code>FormData</code>.</p>\n<h2 id=\"form-streaming\">Form Streaming</h2>\n<p>Falco now automatically detects whether the form is transmiting <code>multipart/form-data</code>, which means deprecating the <code>Request</code> module streaming functions.</p>\n<ul>\n<li><code>Request.streamForm</code> becomes -&gt; <code>Request.mapForm</code></li>\n<li><code>Request.streamFormSecure</code> becomes -&gt; <code>Request.mapFormSecure</code></li>\n<li><code>Request.mapFormStream</code>  becomes -&gt; <code>Request.mapForm</code></li>\n<li><code>Request.mapFormStreamSecure</code> becomes -&gt; <code>Request.mapFormSecure</code></li>\n</ul>\n<h2 id=\"removed-services.injectt1.t5\">Removed <code>Services.inject&lt;'T1 .. 'T5&gt;</code></h2>\n<p>This type was removed because it continued to pose problems for certain code analysis tools. To continue using the service locator pattern, you can now use the more versatile <code>HttpContext</code> extension method <code>ctx.Plug&lt;T&gt;()</code>. For example:</p>\n<pre><code class=\"language-fsharp\">let myHandler : HttpHandler =\n    Services.inject&lt;MyService&gt; (fun myService ctx -&gt;\n        let message = myService.CreateMessage()\n        Response.ofPlainText $&quot;{message}&quot; ctx)\n\n// becomes\nlet myHandler : HttpHandler = fun ctx -&gt;\n    let myService = ctx.Plug&lt;MyService&gt;()\n    let message = myService.CreateMessage()\n    Response.ofPlainText $&quot;{message}&quot; ctx\n\n</code></pre>\n<h2 id=\"xss-module-renamed-to-xsrf\"><code>Xss</code> module renamed to <code>Xsrf</code></h2>\n<p>The <code>Xss</code> module has been renamed to <code>Xsrf</code> to better describe it's intent.</p>\n<pre><code class=\"language-fsharp\">    //before: Xss.antiforgeryInput\n    Xsrf.antiforgeryInput // ..\n\n    //before: Xss.getToken\n    Xsrf.getToken // ..\n\n    //before: Xss.validateToken\n    Xsrf.validateToken // ..\n</code></pre>\n<h2 id=\"crypto-module-removed\"><code>Crypto</code> module removed</h2>\n<p>The Crypto module provided functionality for: random numbers, salt generation and key derivation. The code in this module was really a veneer on top of the cryptographic providers in the base library. Extracting this code into your project would be dead simple. The <a href=\"https://github.com/FalcoFramework/Falco/blob/25d828d832c0fde2dfff04775bea1eced9050458/src/Falco/Security.fs#L3\">source</a> is permalinked here for such purposes.</p>\n<h2 id=\"auth-module-removed\"><code>Auth</code> module removed</h2>\n<p>The <code>Auth</code> module functionality was ported one-to-one to the <code>Response</code> module.</p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/request.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Request Handling - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"request-handling\">Request Handling</h1>\n<p>Falco exposes a <strong>uniform API</strong> to obtain typed values from <code>IFormCollection</code>, <code>IQueryCollection</code>, <code>RouteValueDictionary</code>, <code>IHeaderCollection</code>, and <code>IRequestCookieCollection</code>. This is achieved by means of the <code>RequestData</code> type and it's derivative <code>FormData</code>. These abstractions are intended to make it easier to work with the url-encoded key/value collections.</p>\n<blockquote>\n<p>Take note of the similarities when interacting with the different sources of request data.</p>\n</blockquote>\n<h2 id=\"a-brief-aside-on-the-keyvalue-semantics\">A brief aside on the key/value semantics</h2>\n<p><code>RequestData</code> is supported by a recursive discriminated union called <code>RequestValue</code> which represents a parsed key/value collection.</p>\n<p>The <code>RequestValue</code> parsing process provides some simple, yet powerful, syntax to submit objects and collections over-the-wire, to facilitate complex form and query submissions.</p>\n<h3 id=\"key-syntax-object-notation\">Key Syntax: Object Notation</h3>\n<p>Keys using dot notation are interpreted as complex (i.e., nested values) objects.</p>\n<p>Consider the following POST request:</p>\n<pre><code>POST /my-form HTTP/1.1\nHost: foo.example\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 46\n\nuser.name=john%20doe&amp;user.email=abc@def123.com\n</code></pre>\n<p>This will be intepreted as the following <code>RequestValue</code>:</p>\n<pre><code class=\"language-fsharp\">RObject [\n    &quot;user&quot;, RObject [\n        &quot;name&quot;, RString &quot;john doe&quot;\n        &quot;email&quot;, RString &quot;abc@def123.com&quot;\n    ]\n]\n</code></pre>\n<p>See <a href=\"#form-binding\">form binding</a> for details on interacting with form data.</p>\n<h3 id=\"key-syntax-list-notation\">Key Syntax: List Notation</h3>\n<p>Keys using square bracket notation are interpreted as lists, which can include both primitives and <a href=\"#key-syntax-object-notation\">complex objects</a>. Both indexed and non-indexed variants are supported.</p>\n<p>Consider the following request:</p>\n<pre><code>GET /my-search?name=john&amp;season[0]=summer&amp;season[1]=winter&amp;hobbies[]=hiking HTTP/1.1\nHost: foo.example\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 68\n</code></pre>\n<p>This will be interpreted as the following <code>RequestValue</code>:</p>\n<pre><code class=\"language-fsharp\">RObject [\n    &quot;name&quot;, RString &quot;john&quot;\n    &quot;season&quot;, RList [ RString &quot;summer&quot;; RString &quot;winter&quot; ]\n    &quot;hobbies&quot;, RList [ RString &quot;hking&quot; ]\n]\n</code></pre>\n<p>See <a href=\"#query-binding\">query binding</a> for details on interacting with form data.</p>\n<h2 id=\"request-data-access\">Request Data Access</h2>\n<p><code>RequestData</code> provides the ability to safely read primitive types from flat and nested key/value collections.</p>\n<pre><code class=\"language-fsharp\">let requestData : RequestData = // From: Route | Query | Form\n\n// Retrieve primitive options\nlet str : string option = requestData.TryGetString &quot;name&quot;\nlet flt : float option = requestData.TryGetFloat &quot;temperature&quot;\n\n// Retrieve primitive, or default\nlet str : string = requestData.GetString &quot;name&quot;\nlet strOrDefault : string = requestData.GetString (&quot;name&quot;, &quot;John Doe&quot;)\nlet flt : float = requestData.GetFloat &quot;temperature&quot;\n\n// Retrieve primitive list\nlet strList : string list = requestData.GetStringList &quot;hobbies&quot;\nlet grades : int list = requestData.GetInt32List &quot;grades&quot;\n\n// Dynamic access, useful for nested/complex collections\n// Equivalent to:\n// requestData.Get(&quot;user&quot;).Get(&quot;email_address&quot;).AsString()\nlet userEmail = requestData?user?email_address.AsString()\n\n</code></pre>\n<h2 id=\"route-binding\">Route Binding</h2>\n<p>Provides access to the values found in the <code>RouteValueDictionary</code>.</p>\n<pre><code class=\"language-fsharp\">open Falco\n\n// Assuming a route pattern of /{Name}\nlet manualRouteHandler : HttpHandler = fun ctx -&gt;\n    let r = Request.getRoute ctx\n    let name = r.GetString &quot;Name&quot;\n    // Or, let name = r?Name.AsString()\n    // Or, let name = r.TryGetString &quot;Name&quot; |&gt; Option.defaultValue &quot;&quot;\n    Response.ofPlainText name ctx\n\nlet mapRouteHandler : HttpHandler =\n    Request.mapRoute (fun r -&gt;\n        r.GetString &quot;Name&quot;)\n        Response.ofPlainText\n\n</code></pre>\n<h2 id=\"query-binding\">Query Binding</h2>\n<p>Provides access to the values found in the <code>IQueryCollection</code>, as well as the <code>RouteValueDictionary</code>. In the case of matching keys, the values in the <code>IQueryCollection</code> take precedence.</p>\n<pre><code class=\"language-fsharp\">open Falco\n\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet form : HttpHandler =\n    Response.ofHtmlCsrf view\n\nlet manualQueryHandler : HttpHandler = fun ctx -&gt;\n    let q = Request.getQuery ctx\n\n    let person =\n        { FirstName = q.GetString (&quot;FirstName&quot;, &quot;John&quot;) // Get value or return default value\n          LastName  = q.GetString (&quot;LastName&quot;, &quot;Doe&quot;) }\n\n    Response.ofJson person ctx\n\nlet mapQueryHandler : HttpHandler =\n    Request.mapQuery (fun q -&gt;\n        let first = q.GetString (&quot;FirstName&quot;, &quot;John&quot;) // Get value or return default value\n        let last = q.GetString (&quot;LastName&quot;, &quot;Doe&quot;)\n        { FirstName = first; LastName = last })\n        Response.ofJson\n</code></pre>\n<h2 id=\"form-binding\">Form Binding</h2>\n<p>Provides access to the values found in he <code>IFormCollection</code>, as well as the <code>RouteValueDictionary</code>. In the case of matching keys, the values in the <code>IFormCollection</code> take precedence.</p>\n<p>The <code>FormData</code> inherits from <code>RequestData</code> type also exposes the <code>IFormFilesCollection</code> via the <code>_.Files</code> member and <code>_.TryGetFile(name : string)</code> method.</p>\n<pre><code class=\"language-fsharp\">type Person =\n    { FirstName : string\n      LastName : string }\n\nlet manualFormHandler : HttpHandler = fun ctx -&gt;\n    task {\n        let! f : FormData = Request.getForm ctx\n\n        let person =\n            { FirstName = f.GetString (&quot;FirstName&quot;, &quot;John&quot;) // Get value or return default value\n              LastName = f.GetString (&quot;LastName&quot;, &quot;Doe&quot;) }\n\n        return! Response.ofJson person ctx\n    }\n\nlet mapFormHandler : HttpHandler =\n    Request.mapForm (fun f -&gt;\n        let first = f.GetString (&quot;FirstName&quot;, &quot;John&quot;) // Get value or return default value\n        let last = f.GetString (&quot;LastName&quot;, &quot;Doe&quot;)\n        { FirstName = first; LastName = last })\n        Response.ofJson\n\nlet mapFormSecureHandler : HttpHandler =\n    Request.mapFormSecure (fun f -&gt; // `Request.mapFormSecure` will automatically validate CSRF token for you.\n        let first = f.GetString (&quot;FirstName&quot;, &quot;John&quot;) // Get value or return default value\n        let last = f.GetString (&quot;LastName&quot;, &quot;Doe&quot;)\n        { FirstName = first; LastName = last })\n        Response.ofJson\n        (Response.withStatusCode 400 &gt;&gt; Response.ofEmpty)\n\n</code></pre>\n<h3 id=\"multipartform-data-binding\"><code>multipart/form-data</code> Binding</h3>\n<p>Microsoft defines <a href=\"https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#upload-large-files-with-streaming\">large upload</a> as anything <strong>&gt; 64KB</strong>, which well... is most uploads. Anything beyond this size and they recommend streaming the multipart data to avoid excess memory consumption.</p>\n<p>To make this process <strong>a lot</strong> easier Falco's form handlers will attempt to stream multipart form-data, or return an error message indicating the likely problem.</p>\n<pre><code class=\"language-fsharp\">let imageUploadHandler : HttpHandler =\n    let formBinder (f : FormData) : IFormFile option =\n        f.TryGetFormFile &quot;profile_image&quot;\n\n    let uploadImage (profileImage : IFormFile option) : HttpHandler =\n        // Process the uploaded file ...\n\n    // Safely buffer the multipart form submission\n    Request.mapForm formBinder uploadImage\n\nlet secureImageUploadHandler : HttpHandler =\n    let formBinder (f : FormData) : IFormFile option =\n        f.TryGetFormFile &quot;profile_image&quot;\n\n    let uploadImage (profileImage : IFormFile option) : HttpHandler =\n        // Process the uploaded file ...\n\n    let handleInvalidCsrf : HttpHandler =\n        Response.withStatusCode 400 &gt;&gt; Response.ofEmpty\n\n    // Safely buffer the multipart form submission\n    Request.mapFormSecure formBinder uploadImage handleInvalidCsrf\n</code></pre>\n<h2 id=\"json\">JSON</h2>\n<p>These handlers use the .NET built-in <code>System.Text.Json.JsonSerializer</code>.</p>\n<pre><code class=\"language-fsharp\">type Person =\n    { FirstName : string\n      LastName : string }\n\nlet jsonHandler : HttpHandler =\n    Response.ofJson {\n        FirstName = &quot;John&quot;\n        LastName = &quot;Doe&quot; }\n\nlet mapJsonHandler : HttpHandler =\n    let handleOk person : HttpHandler =\n        let message = sprintf &quot;hello %s %s&quot; person.First person.Last\n        Response.ofPlainText message\n\n    Request.mapJson handleOk\n\nlet mapJsonOptionsHandler : HttpHandler =\n    let options = JsonSerializerOptions()\n    options.DefaultIgnoreCondition &lt;- JsonIgnoreCondition.WhenWritingNull\n\n    let handleOk person : HttpHandler =\n        let message = sprintf &quot;hello %s %s&quot; person.First person.Last\n        Response.ofPlainText message\n\n    Request.mapJsonOption options handleOk\n</code></pre>\n<p><a href=\"markup.html\">Next: View engine</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/response.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Response Writing - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"response-writing\">Response Writing</h1>\n<p>The <code>HttpHandler</code> type is used to represent the processing of a request. It can be thought of as the eventual (i.e., asynchronous) completion and processing of an HTTP request, defined in F# as: <code>HttpContext -&gt; Task</code>. Handlers will typically involve some combination of: <a href=\"request.html#route-binding\">route inspection</a>, <a href=\"request.html#form-binding\">form</a>/<a href=\"request.html#query-binding\">query</a> binding, business logic and finally response writing. With access to the <code>HttpContext</code> you are able to inspect all components of the request, and manipulate the response in any way you choose.</p>\n<h2 id=\"plain-text-responses\">Plain Text responses</h2>\n<pre><code class=\"language-fsharp\">let textHandler : HttpHandler =\n    Response.ofPlainText &quot;hello world&quot;\n</code></pre>\n<h2 id=\"html-responses\">HTML responses</h2>\n<p>Write your views in plain F#, directly in your assembly, using the <a href=\"markup.html\">Markup</a> module. A performant F# DSL capable of generating any angle-bracket markup. Also available directly as a standalone <a href=\"https://www.nuget.org/packages/Falco.Markup\">NuGet</a> package.</p>\n<pre><code class=\"language-fsharp\">let htmlHandler : HttpHandler =\n    let html =\n        _html [ _lang_ &quot;en&quot; ] [\n            _head [] []\n            _body [] [\n                _h1' &quot;Sample App&quot; // shorthand for: `_h1 [] [ Text.raw &quot;Sample App&quot; ]`\n            ]\n        ]\n\n    Response.ofHtml html\n\n// Automatically protect against XSS attacks\nlet secureHtmlHandler : HttpHandler =\n    let html token =\n        _html [] [\n            _body [] [\n                _form [ _method_ &quot;post&quot; ] [\n                    _input [ _name_ &quot;first_name&quot; ]\n                    _input [ _name_ &quot;last_name&quot; ]\n                    // using the CSRF HTML helper\n                    Xsrf.antiforgeryInput token\n                    _input [ _type_ &quot;submit&quot;; _value_ &quot;Submit&quot; ]\n                ]\n            ]\n        ]\n\n    Response.ofHtmlCsrf html\n</code></pre>\n<p>Alternatively, if you're using an external view engine and want to return an HTML response from a string literal, then you can use <code>Response.ofHtmlString</code>.</p>\n<pre><code class=\"language-fsharp\">let htmlHandler : HttpHandler =\n    Response.ofHtmlString &quot;&lt;html&gt;...&lt;/html&gt;&quot;\n</code></pre>\n<h2 id=\"template-fragments\">Template Fragments</h2>\n<p>If you want to return a <a href=\"https://htmx.org/essays/template-fragments/\">fragment of HTML</a>, for example when working with <a href=\"https://htmx.org/\">htmx</a>, you can use <code>Response.ofFragment</code> (or <code>Response.ofFragmentCsrf</code>). This function takes an element ID as its first argument, and a <code>XmlNode</code> as its second argument. The server will return only the contents of the node with the specified ID.</p>\n<pre><code class=\"language-fsharp\">let fragmentHandler : HttpHandler =\n    let html =\n        _div [ _id_ &quot;greeting&quot; ] [\n            _h1 [ _id_ &quot;heading&quot; ] [ _text &quot;Hello, World!&quot; ]\n        ]\n\n    Response.ofFragment &quot;heading&quot; html\n</code></pre>\n<p>This will return only the contents of the <code>h1</code> element, i.e. <code>&lt;h1 id=&quot;heading&quot;&gt;Hello, World!&lt;/h1&gt;</code>. In the case of multiple elements with the same ID, the first one found will be returned. If no element with the specified ID is found, an empty response will be returned.</p>\n<h2 id=\"json-responses\">JSON responses</h2>\n<p>These handlers use the .NET built-in <code>System.Text.Json.JsonSerializer</code>.</p>\n<pre><code class=\"language-fsharp\">type Person =\n    { First : string\n      Last  : string }\n\nlet jsonHandler : HttpHandler =\n    let name = { First = &quot;John&quot;; Last = &quot;Doe&quot; }\n    Response.ofJson name\n\nlet jsonOptionsHandler : HttpHandler =\n    let options = JsonSerializerOptions()\n    options.DefaultIgnoreCondition &lt;- JsonIgnoreCondition.WhenWritingNull\n    let name = { First = &quot;John&quot;; Last = &quot;Doe&quot; }\n    Response.ofJsonOptions options name\n</code></pre>\n<h2 id=\"redirect-301302-response\">Redirect (301/302) Response</h2>\n<pre><code class=\"language-fsharp\">let oldUrlHandler : HttpHandler =\n    Response.redirectPermanently &quot;/new-url&quot; // HTTP 301\n\nlet redirectUrlHandler : HttpHandler =\n    Response.redirectTemporarily &quot;/new-url&quot; // HTTP 302\n</code></pre>\n<h2 id=\"content-disposition\">Content Disposition</h2>\n<pre><code class=\"language-fsharp\">let inlineBinaryHandler : HttpHandler =\n    let contentType = &quot;image/jpeg&quot;\n    let headers = [ HeaderNames.CacheControl,  &quot;no-store, max-age=0&quot; ]\n    let bytes = // ... binary data\n    Response.ofBinary contentType headers bytes\n\nlet attachmentHandler : HttpHandler =\n    let filename = &quot;profile.jpg&quot;\n    let contentType = &quot;image/jpeg&quot;\n    let headers = [ HeaderNames.CacheControl,  &quot;no-store, max-age=0&quot; ]\n    let bytes = // ... binary data\n    Response.ofAttachment filename contentType headers bytes\n</code></pre>\n<h2 id=\"response-modifiers\">Response Modifiers</h2>\n<p>Response modifiers can be thought of as the in-and-out modification of the <code>HttpResponse</code>. A preamble to writing and returning. Since these functions receive the <code>Httpcontext</code> as input and return it as the only output, they can take advantage of function compoistion.</p>\n<h3 id=\"set-the-status-code-of-the-response\">Set the status code of the response</h3>\n<pre><code class=\"language-fsharp\">let notFoundHandler : HttpHandler =\n    Response.withStatusCode 404\n    &gt;&gt; Response.ofPlainText &quot;Not found&quot;\n</code></pre>\n<h3 id=\"add-a-headers-to-the-response\">Add a header(s) to the response</h3>\n<pre><code class=\"language-fsharp\">let handlerWithHeaders : HttpHandler =\n    Response.withHeaders [ &quot;Content-Language&quot;, &quot;en-us&quot; ]\n    &gt;&gt; Response.ofPlainText &quot;Hello world&quot;\n</code></pre>\n<h3 id=\"add-a-cookie-to-the-response\">Add a cookie to the response</h3>\n<blockquote>\n<p>IMPORTANT: <em>Do not</em> use this for authentication. Instead use the <code>Response.signInAndRedirect</code> and <code>Response.signOutAndRedirect</code> functions found in the <a href=\"authenication.html\">Authentication</a> module.</p>\n</blockquote>\n<pre><code class=\"language-fsharp\">let handlerWithCookie : HttpHandler =\n    Response.withCookie &quot;greeted&quot; &quot;1&quot;\n    &gt;&gt; Response.ofPlainText &quot;Hello world&quot;\n\nlet handlerWithCookieOptions : HttpHandler =\n    let options = CookieOptions()\n    options.Expires &lt;- DateTime.Now.Minutes(15)\n    Response.withCookie options &quot;greeted&quot; &quot;1&quot;\n    &gt;&gt; Response.ofPlainText &quot;Hello world&quot;\n</code></pre>\n<p><a href=\"request.html\">Next: Request Handling</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/docs/routing.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Routing - Falco Documentation</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto lh-copy\"><div class=\"min-vh-100 mw9 center pa3 overflow-hidden\"><nav class=\"sidebar w-20-l fl-l mb3 mb0-l\"><div class=\"flex items-center\"><a href=\"/docs\" class=\"db w3 w4-l\"><img src=\"/brand.svg\" class=\"br3\" /></a><h2 class=\"dn-l mt3 ml3 fw4 gray\">Falco Documentation</h2></div><div class=\"dn db-l\"><h3>Contents</h3><ul class=\"nl3 f6\"><li><a href=\"get-started.html\">Getting Started</a></li><li><a href=\"routing.html\">Routing</a></li><li><a href=\"response.html\">Response Writing</a></li><li><a href=\"request.html\">Request Handling</a></li><li><a href=\"markup.html\">Markup</a></li><li>Security<ul><li><a href=\"cross-site-request-forgery.html\">Cross Site Request Forgery (XSRF)</a></li><li><a href=\"authentication.html\">Authentication & Authorization</a></li></ul></li><li><a href=\"host-configuration.html\">Host Configuration</a></li><li><a href=\"deployment.html\">Deployment</a></li><li>Examples<ul><li><a href=\"example-hello-world.html\">Hello World</a></li><li><a href=\"example-hello-world-mvc.html\">Hello World MVC</a></li><li><a href=\"example-dependency-injection.html\">Dependency Injection</a></li><li><a href=\"example-external-view-engine.html\">External View Engine</a></li><li><a href=\"example-basic-rest-api.html\">Basic REST API</a></li><li><a href=\"example-open-api.html\">Open API</a></li><li><a href=\"example-htmx.html\">htmx</a></li></ul></li><li><a href=\"migrating-from-v4-to-v5.html\">V5 Migration Guide</a></li></ul></div></nav><main class=\"w-80-l fl-l pl3-l\"><h1 id=\"routing\">Routing</h1>\n<p>Routing is responsible for matching incoming HTTP requests and dispatching those requests to the app's <code>HttpHandler</code>s. The breakdown of <a href=\"https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#configuring-endpoint-metadata\">Endpoint Routing</a> is simple. Associate a specific <a href=\"https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-template-reference\">route pattern</a> and an HTTP verb to an <a href=\"request.html\"><code>HttpHandler</code></a> which represents the ongoing processing (and eventual return) of a request.</p>\n<p>Bearing this in mind, routing can practically be represented by a list of these &quot;mappings&quot; known in Falco as an <code>HttpEndpoint</code> which bind together: a route, verb and handler.</p>\n<blockquote>\n<p>Note: All of the following examples are <em>fully functioning</em> web apps.</p>\n</blockquote>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [ get &quot;/&quot; (Response.ofPlainText &quot;hello world&quot;) ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<p>The preceding example includes a single <code>HttpEndpoint</code>:</p>\n<ul>\n<li>When an HTTP <code>GET</code> request is sent to the root URL <code>/</code>:\n<ul>\n<li>The <code>HttpHandler</code> shown executes.</li>\n<li><code>Hello World!</code> is written to the HTTP response using the <a href=\"response.html\">Response</a> module.</li>\n</ul>\n</li>\n<li>If the request method is not <code>GET</code> or the URL is not <code>/</code>, no route matches and an HTTP 404 is returned.</li>\n</ul>\n<p>The following example shows a more sophisticated <code>HttpEndpoint</code>:</p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get &quot;/hello/{name:alpha}&quot; (fun ctx -&gt;\n            let route = Request.getRoute ctx\n            let name = route.GetString &quot;name&quot;\n            let message = sprintf &quot;Hello %s&quot; name\n            Response.ofPlainText message ctx)\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<p>The string <code>/hello/{name:alpha}</code> is a <strong>route template</strong>. It is used to configure how the endpoint is matched. In this case, the template matches:</p>\n<ul>\n<li>A URL like <code>/hello/Ryan</code></li>\n<li>Any URL path that begins with <code>/hello/</code> followed by a sequence of alphabetic characters. <code>:alpha</code> applies a route constraint that matches only alphabetic characters.\n<ul>\n<li>Full route constraint reference: <a href=\"https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-constraint-reference\">https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-constraint-reference</a>.</li>\n</ul>\n</li>\n</ul>\n<p>The second segment of the URL path, <code>{name:alpha}</code>:</p>\n<ul>\n<li>Is bound to the <code>name</code> parameter.</li>\n<li>Is captured and stored in <code>HttpRequest.RouteValues</code>, which Falco exposes through a <a href=\"request.html\">uniform API</a> to obtain primitive typed values.</li>\n</ul>\n<p>An alternative way to express the <code>HttEndpoint</code> above is seen below.</p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet greetingHandler name : HttpHandler =\n    let message = sprintf &quot;Hello %s&quot; name\n    Response.ofPlainText message\n\nlet endpoints =\n    [ mapGet &quot;/hello/{name:alpha}&quot; (fun route -&gt; route.GetString &quot;name&quot;) greetingHandler ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<h2 id=\"multi-method-endpoints\">Multi-method Endpoints</h2>\n<p>There are scenarios where you may want to accept multiple HTTP verbs to single a URL. For example, a <code>GET</code>/<code>POST</code> form submission.</p>\n<p>To create a &quot;multi-method&quot; endpoint, the <code>all</code> function accepts a list of HTTP Verb and HttpHandler pairs.</p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Falco.Markup\nopen Microsoft.AspNetCore.Builder\n\nlet form =\n    Templates.html5 &quot;en&quot; [] [\n        _form [ _method_ &quot;post&quot; ] [\n            _input [ _name_ &quot;name&quot; ]\n            _input [ _type_ &quot;submit&quot; ] ] ]\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get &quot;/&quot; (Response.ofPlainText &quot;Hello from /&quot;)\n        all &quot;/form&quot; [\n            GET, Response.ofHtml form\n            POST, Response.ofEmpty ]\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n</code></pre>\n<p><a href=\"response.html\">Next: Response Writing</a></p>\n</main></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\" /><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge, chrome=1\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /><title>Falco - F# web toolkit for ASP.NET Core</title><meta name=\"description\" content=\"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" /><link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\" /><link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" /><link href=\"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\" rel=\"stylesheet\" /><link href=\"/prism.css\" rel=\"stylesheet\" /><link href=\"/tachyons.css\" rel=\"stylesheet\" /><link href=\"/style.css\" rel=\"stylesheet\" /><script async src=\"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\"></script><script>window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');</script></head><body class=\"noto bg-merlot bg-dots bg-parallax\"><header class=\"pv3\"><div class=\"mw8 center pa3\"><div><nav class=\"flex flex-column flex-row-l items-center\"><a href=\"/\"><img src=\"/icon.svg\" class=\"w3 pb3 pb0-l o-80 hover-o-100\" /></a><div class=\"flex-grow-1-l tc tr-l\"><a href=\"/docs\" title=\"Overview of Falco's key features\" class=\"dib mh2 mh3-l no-underline white-90 hover-white\">docs</a><a href=\"https://github.com/FalcoFramework/Falco\" title=\"Fork Falco on GitHub\" alt=\"Falco GitHub Link\" target=\"_blank\" class=\"dib mh2 ml3-l no-underline white-90 hover-white\">code</a><a href=\"https://github.com/FalcoFramework/Falco/tree/master/examples\" title=\"Falco code samples\" alt=\"Faclo code samples link\" class=\"dib ml2 mh3-l no-underline white-90 hover-white\">samples</a><a href=\"https://github.com/FalcoFramework/Falco/discussions\" title=\"Need help?\" alt=\"Faclo GitHub discussions link\" class=\"dib ml2 mh3-l no-underline white-90 hover-white\">help</a></div></nav></div><div class=\"mw6 center pb5 noto tc fw4 lh-copy white\"><h1 class=\"mt4 mb3 fw4 f2\">Meet Falco.</h1><h2 class=\"mt0 mb4 fw4 f4 f3-l\">Falco is a toolkit for building fast and functional-first web applications using F#.</h2><div class=\"tc\"><a href=\"/docs/get-started.html\" title=\"Learn how to get started using Falco\" class=\"dib mh2 mb2 ph3 pv2 merlot bg-white ba b--white br2 no-underline\">Get Started</a><a href=\"#falco\" class=\"dib mh2 ph3 pv2 white ba b--white br2 no-underline\">Learn More</a></div></div><div class=\"mb4 bt b--white-20 tc lh-solid\"><a href=\"https://www.nuget.org/packages/Falco\" class=\"dib center ph1 ph4-l pv3 bg-merlot white no-underline ty--50\" target=\"_blank\">Latest release: 5.2.0 (December, 21, 2025)</a></div><div class=\"cf tc lh-copy\"><div class=\"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\"><img src=\"/icons/fast.svg\" class=\"w4 o-90\" /><h3 class=\"mv2 white\">Fast & Lightweight</h3><div class=\"mb3 white-90\">Optimized for speed and low memory usage.</div><a href=\"https://web-frameworks-benchmark.netlify.app/result?l=fsharp\" target=\"_blank\" class=\"dib mh2 pa2 f6 white ba b--white br2 no-underline\">Learn More</a></div><div class=\"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\"><img src=\"/icons/easy.svg\" class=\"w4 o-90\" /><h3 class=\"mv2 white\">Easy to Learn</h3><div class=\"mb3 white-90\">Simple, predictable, and easy to pick up.</div><a href=\"/docs/get-started.html\" title=\"Learn how to get started using Falco\" class=\"dib mh2 pa2 f6 white ba b--white br2 no-underline\">Get Started</a></div><div class=\"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\"><img src=\"/icons/view.svg\" class=\"w4 o-90\" /><h3 class=\"mv2 white\">Native View Engine</h3><div class=\"mb3 white-90\">Markup is written in F# and compiled.</div><a href=\"/docs/markup.html\" title=\"View examples of Falco markup module\" class=\"dib mh2 pa2 f6 white ba b--white br2 no-underline\">See Examples</a></div><div class=\"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l\"><img src=\"/icons/integrate.svg\" class=\"w4 o-90\" /><h3 class=\"mv2 white\">Customizable</h3><div class=\"mb3 white-90\">Seamlessly integrates with ASP.NET.</div><a href=\"https://github.com/FalcoFramework/Falco/tree/master/samples/ScribanExample\" target=\"_blank\" title=\"Example of incorporating a third-party view engine\" class=\"dib mh2 pa2 f6 white ba b--white br2 no-underline\">Explore How</a></div></div></div></header><div class=\"h100vh bg-white\"><div class=\"cf mw8 center pv4 ph3\"><main><p><a href=\"https://www.nuget.org/packages/Falco\"><img src=\"https://img.shields.io/nuget/v/Falco.svg\" alt=\"NuGet Version\" /></a>\n<a href=\"https://github.com/FalcoFramework/Falco/actions/workflows/build.yml\"><img src=\"https://github.com/FalcoFramework/Falco/actions/workflows/build.yml/badge.svg\" alt=\"build\" /></a></p>\n<pre><code class=\"language-fsharp\">open Falco\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nwapp.Run(Response.ofPlainText &quot;Hello world&quot;)\n</code></pre>\n<p><a href=\"https://github.com/FalcoFramework/Falco\">Falco</a> is a toolkit for building functional-first, full-stack web applications using F#.</p>\n<ul>\n<li>Built on the high-performance components of ASP.NET Core.</li>\n<li>Seamlessly integrates with existing .NET Core middleware and libraries.</li>\n<li>Designed to be simple, lightweight and easy to learn.</li>\n</ul>\n<h2 id=\"key-features\">Key Features</h2>\n<ul>\n<li>Simple and powerful <a href=\"docs/routing.html\">routing</a> API.</li>\n<li>Uniform API for <a href=\"docs/request.html\">accessing <em>any</em> request data</a>.</li>\n<li>Native F# <a href=\"docs/markup.html\">view engine</a>.</li>\n<li>Asynchronous <a href=\"docs/response.html\">request handling</a>.</li>\n<li><a href=\"docs/authentication.html\">Authentication</a> and <a href=\"docs/cross-site-request-forgery.html\">security</a> utilities.</li>\n<li>Built-in support for <a href=\"docs/request.html#multipartform-data-binding\">large uploads</a> and <a href=\"docs/response.html#content-disposition\">binary responses</a>.</li>\n</ul>\n<h2 id=\"design-goals\">Design Goals</h2>\n<ul>\n<li>Provide a toolset to build full-stack web application in F#.</li>\n<li>Should be simple, extensible and integrate with existing .NET libraries.</li>\n<li>Can be easily learned.</li>\n</ul>\n<h2 id=\"learn\">Learn</h2>\n<p>The best way to get started is by visiting the <a href=\"https://falcoframework.com/docs\">documentation</a>. For questions and support please use <a href=\"https://github.com/FalcoFramework/Falco/discussions\">discussions</a>. For chronological updates refer to the <a href=\"CHANGELOG.html\">changelog</a> is the best place to find chronological updates.</p>\n<h3 id=\"related-libraries\">Related Libraries</h3>\n<ul>\n<li><a href=\"https://github.com/FalcoFramework/Falco.Markup\">Falco.Markup</a> - an XML markup module primary used as the syntax for <a href=\"https://www.falcoframework.com/docs/markup.html\">authoring HTML with Falco</a>.</li>\n<li><a href=\"https://github.com/dpraimeyuu/Falco.Htmx\">Falco.Htmx</a> - a full featured integration with <a href=\"https://htmx.org/\">htmx JS package</a>.</li>\n<li><a href=\"https://github.com/FalcoFramework/Falco.OpenApi\">Falco.OpenApi</a> - a library for generating OpenAPI documentation from Falco applications.</li>\n<li><a href=\"https://github.com/FalcoFramework/Falco.Template\">Falco.Template</a> - a .NET SDK <a href=\"https://learn.microsoft.com/en-us/dotnet/core/tools/custom-templates\">project template</a> to help get started with Falco quickly.</li>\n<li><a href=\"https://cloudseed.xyz/\">CloudSeed</a> - a simple, scalable project boilerplate for F# / .NET.</li>\n</ul>\n<h3 id=\"community-projects\">Community Projects</h3>\n<ul>\n<li><a href=\"https://github.com/adelarsq/falco_graphql_sample\">Falco GraphQL Sample</a> - A sample showing how to use GraphQL on Falco using .NET 6.</li>\n<li><a href=\"https://github.com/jasiozet/falco-api-with-tests-template\">Falco API with Tests Sample</a> - A sample project using Falco and unit testing.</li>\n<li><a href=\"https://github.com/galassie/FalcoSample\">Falco + SQLite + Donald</a> - A demo project using Falco, <a href=\"https://github.com/pimbrouwers/Donald\">Donald</a>, and SQLite</li>\n<li><a href=\"https://github.com/NitroDevs/FShopOnWeb\">FShopOnWeb</a> - An adaptation of the classic <a href=\"https://github.com/dotnet-architecture/eShopOnWeb\">ASP.NET Core sample application</a> using Falco and an F# architecture.</li>\n</ul>\n<h3 id=\"articles\">Articles</h3>\n<ul>\n<li>Hamilton Greene - <a href=\"https://hamy.xyz/blog/2025-01_fsharp-webapp-10-minutes\">Spin up a Fullstack F# WebApp in 10 minutes with the CloudSeed Project Template</a></li>\n<li>Hamilton Greene - <a href=\"https://hamy.xyz/blog/2025-01_ditching-giraffe-for-falco\">Why I'm Ditching F# + Giraffe For Falco For Building WebApps</a></li>\n<li>Istvan - <a href=\"https://dev.l1x.be/posts/2020/12/18/running-asp.net-web-application-with-falco-on-aws-lambda/\">Running ASP.Net web application with Falco on AWS Lambda</a></li>\n</ul>\n<h3 id=\"videos\">Videos</h3>\n<ul>\n<li>Hamilton Greene - <a href=\"https://www.youtube.com/watch?v=ELPdHdtEIY8\">Build a Fullstack Webapp with F# + Falco</a></li>\n<li>Hamilton Greene - <a href=\"https://www.youtube.com/watch?v=SJCHBqrc3sE\">Build a Single-File Web API with F# + Falco</a></li>\n<li>Hamilton Greene - <a href=\"https://www.youtube.com/watch?v=tonPeWfu_WM\">Why I'm Ditching F# + Giraffe For Falco For Building WebApps</a></li>\n<li>Ben Gobeil - <a href=\"https://youtu.be/DTy5gIUWvpo\">Why I'm Using Falco Instead Of Saturn | How To Switch Your Backend In SAFE Stack | StonkWatch Ep.13</a></li>\n</ul>\n<h2 id=\"contribute\">Contribute</h2>\n<p>We kindly ask that before submitting a pull request, you first submit an <a href=\"https://github.com/FalcoFramework/Falco/issues\">issue</a> or open a <a href=\"https://github.com/FalcoFramework/Falco/discussions\">discussion</a>.</p>\n<p>If functionality is added to the API, or changed, please kindly update the relevant <a href=\"/docs\">document</a>. Unit tests must also be added and/or updated before a pull request can be successfully merged.</p>\n<p>Only pull requests which pass all build checks and comply with the general coding standard can be approved.</p>\n<p>If you have any further questions, submit an <a href=\"https://github.com/FalcoFramework/Falco/issues\">issue</a> or open a <a href=\"https://github.com/FalcoFramework/Falco/discussions\">discussion</a> or reach out on <a href=\"https://twitter.com/falco_framework\">Twitter</a>.</p>\n<h2 id=\"why-falco\">Why &quot;Falco&quot;?</h2>\n<p><a href=\"https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel\">Kestrel</a> has been a game changer for the .NET web stack. In the animal kingdom, &quot;Kestrel&quot; is a name given to several members of the falcon genus. Also known as &quot;Falco&quot;.</p>\n<h2 id=\"find-a-bug\">Find a bug?</h2>\n<p>There's an <a href=\"https://github.com/FalcoFramework/Falco/issues\">issue</a> for that.</p>\n<h2 id=\"license\">License</h2>\n<p>Licensed under <a href=\"https://github.com/FalcoFramework/Falco/blob/master/LICENSE\">Apache License 2.0</a>.</p></main></div></div><footer class=\"cl pa3 bg-merlot\"><div class=\"f7 tc white-70\">&copy; 2020-2025 Pim Brouwers & contributors.</div></footer><script src=\"/prism.js\"></script></body></html>"
  },
  {
    "path": "docs/prism.css",
    "content": "code[class*=\"language-\"],pre[class*=\"language-\"]{color:#ccc;background:none;font-family:Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=\"language-\"]{padding:1em;margin:0.5em 0;overflow:auto}:not(pre) > code[class*=\"language-\"],pre[class*=\"language-\"]{background:#2d2d2d}:not(pre) > code[class*=\"language-\"]{padding:0.1em;border-radius:0.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}div.code-toolbar{position:relative}div.code-toolbar > .toolbar{position:absolute;top:0.3em;right:0.2em;transition:opacity 0.3s ease-in-out;opacity:0}div.code-toolbar:hover > .toolbar{opacity:1}div.code-toolbar:focus-within > .toolbar{opacity:1}div.code-toolbar > .toolbar .toolbar-item{display:inline-block}div.code-toolbar > .toolbar a{cursor:pointer}div.code-toolbar > .toolbar button{background:none;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar > .toolbar a,div.code-toolbar > .toolbar button,div.code-toolbar > .toolbar span{color:#bbb;font-size:0.8em;padding:0 0.5em;background:#f5f2f0;background:rgba(224, 224, 224, 0.2);box-shadow:0 2px 0 0 rgba(0,0,0,0.2);border-radius:0.5em}div.code-toolbar > .toolbar a:focus,div.code-toolbar > .toolbar a:hover,div.code-toolbar > .toolbar button:focus,div.code-toolbar > .toolbar button:hover,div.code-toolbar > .toolbar span:focus,div.code-toolbar > .toolbar span:hover{color:inherit;text-decoration:none}"
  },
  {
    "path": "docs/prism.js",
    "content": "/* PrismJS 1.22.0\nhttps://prismjs.com/download.html#themes=prism-tomorrow&languages=css+clike+csharp+fsharp+powershell+sql&plugins=toolbar */\nvar _self=\"undefined\"!=typeof window?window:\"undefined\"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\\blang(?:uage)?-([\\w-]+)\\b/i,n=0,_={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof M?new M(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/\\u00a0/g,\" \")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,\"__id\",{value:++n}),e.__id},clone:function t(e,r){var a,n;switch(r=r||{},_.util.type(e)){case\"Object\":if(n=_.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case\"Array\":return n=_.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,\"none\"])[1].toLowerCase():\"none\"},currentScript:function(){if(\"undefined\"==typeof document)return null;if(\"currentScript\"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\\r\\n]*\\((.*):.+:.+\\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName(\"script\");for(var r in t)if(t[r].src==n)return t[r]}return null}},isActive:function(e,n,t){for(var r=\"no-\"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{extend:function(e,n){var t=_.util.clone(_.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||_.languages)[t],i={};for(var l in a)if(a.hasOwnProperty(l)){if(l==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(l)||(i[l]=a[l])}var s=r[t];return r[t]=i,_.languages.DFS(_.languages,function(e,n){n===s&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,r,a){a=a||{};var i=_.util.objId;for(var l in n)if(n.hasOwnProperty(l)){t.call(n,l,n[l],r||l);var o=n[l],s=_.util.type(o);\"Object\"!==s||a[i(o)]?\"Array\"!==s||a[i(o)]||(a[i(o)]=!0,e(o,t,l,a)):(a[i(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){_.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*=\"language-\"], [class*=\"language-\"] code, code[class*=\"lang-\"], [class*=\"lang-\"] code'};_.hooks.run(\"before-highlightall\",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),_.hooks.run(\"before-all-elements-highlight\",r);for(var a,i=0;a=r.elements[i++];)_.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=_.util.getLanguage(e),a=_.languages[r];e.className=e.className.replace(c,\"\").replace(/\\s+/g,\" \")+\" language-\"+r;var i=e.parentElement;i&&\"pre\"===i.nodeName.toLowerCase()&&(i.className=i.className.replace(c,\"\").replace(/\\s+/g,\" \")+\" language-\"+r);var l={element:e,language:r,grammar:a,code:e.textContent};function o(e){l.highlightedCode=e,_.hooks.run(\"before-insert\",l),l.element.innerHTML=l.highlightedCode,_.hooks.run(\"after-highlight\",l),_.hooks.run(\"complete\",l),t&&t.call(l.element)}if(_.hooks.run(\"before-sanity-check\",l),!l.code)return _.hooks.run(\"complete\",l),void(t&&t.call(l.element));if(_.hooks.run(\"before-highlight\",l),l.grammar)if(n&&u.Worker){var s=new Worker(_.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:l.language,code:l.code,immediateClose:!0}))}else o(_.highlight(l.code,l.grammar,l.language));else o(_.util.encode(l.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return _.hooks.run(\"before-tokenize\",r),r.tokens=_.tokenize(r.code,r.grammar),_.hooks.run(\"after-tokenize\",r),M.stringify(_.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new i;return z(a,a.head,e),function e(n,t,r,a,i,l){for(var o in r)if(r.hasOwnProperty(o)&&r[o]){var s=r[o];s=Array.isArray(s)?s:[s];for(var u=0;u<s.length;++u){if(l&&l.cause==o+\",\"+u)return;var c=s[u],g=c.inside,f=!!c.lookbehind,h=!!c.greedy,d=c.alias;if(h&&!c.pattern.global){var v=c.pattern.toString().match(/[imsuy]*$/)[0];c.pattern=RegExp(c.pattern.source,v+\"g\")}for(var p=c.pattern||c,m=a.next,y=i;m!==t.tail&&!(l&&y>=l.reach);y+=m.value.length,m=m.next){var k=m.value;if(t.length>n.length)return;if(!(k instanceof M)){var b,x=1;if(h){if(!(b=W(p,y,n,f)))break;var w=b.index,A=b.index+b[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof M)continue;for(var S=m;S!==t.tail&&(P<A||\"string\"==typeof S.value);S=S.next)x++,P+=S.value.length;x--,k=n.slice(y,P),b.index-=y}else if(!(b=W(p,0,k,f)))continue;var w=b.index,E=b[0],O=k.slice(0,w),L=k.slice(w+E.length),N=y+k.length;l&&N>l.reach&&(l.reach=N);var j=m.prev;O&&(j=z(t,j,O),y+=O.length),I(t,j,x);var C=new M(o,g?_.tokenize(E,g):E,d,E);m=z(t,j,C),L&&z(t,m,L),1<x&&e(n,t,r,m.prev,y,{cause:o+\",\"+u,reach:N})}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=_.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=_.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:M};function M(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||\"\").length}function W(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function z(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function I(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;(n.next=r).prev=n,e.length-=a}if(u.Prism=_,M.stringify=function n(e,t){if(\"string\"==typeof e)return e;if(Array.isArray(e)){var r=\"\";return e.forEach(function(e){r+=n(e,t)}),r}var a={type:e.type,content:n(e.content,t),tag:\"span\",classes:[\"token\",e.type],attributes:{},language:t},i=e.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(a.classes,i):a.classes.push(i)),_.hooks.run(\"wrap\",a);var l=\"\";for(var o in a.attributes)l+=\" \"+o+'=\"'+(a.attributes[o]||\"\").replace(/\"/g,\"&quot;\")+'\"';return\"<\"+a.tag+' class=\"'+a.classes.join(\" \")+'\"'+l+\">\"+a.content+\"</\"+a.tag+\">\"},!u.document)return u.addEventListener&&(_.disableWorkerMessageHandler||u.addEventListener(\"message\",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(_.highlight(r,_.languages[t],t)),a&&u.close()},!1)),_;var e=_.util.currentScript();function t(){_.manual||_.highlightAll()}if(e&&(_.filename=e.src,e.hasAttribute(\"data-manual\")&&(_.manual=!0)),!_.manual){var r=document.readyState;\"loading\"===r||\"interactive\"===r&&e&&e.defer?document.addEventListener(\"DOMContentLoaded\",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return _}(_self);\"undefined\"!=typeof module&&module.exports&&(module.exports=Prism),\"undefined\"!=typeof global&&(global.Prism=Prism);\n!function(e){var t=/(\"|')(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/;e.languages.css={comment:/\\/\\*[\\s\\S]*?\\*\\//,atrule:{pattern:/@[\\w-]+[\\s\\S]*?(?:;|(?=\\s*\\{))/,inside:{rule:/^@[\\w-]+/,\"selector-function-argument\":{pattern:/(\\bselector\\s*\\((?!\\s*\\))\\s*)(?:[^()]|\\((?:[^()]|\\([^()]*\\))*\\))+?(?=\\s*\\))/,lookbehind:!0,alias:\"selector\"},keyword:{pattern:/(^|[^\\w-])(?:and|not|only|or)(?![\\w-])/,lookbehind:!0}}},url:{pattern:RegExp(\"\\\\burl\\\\((?:\"+t.source+\"|(?:[^\\\\\\\\\\r\\n()\\\"']|\\\\\\\\[^])*)\\\\)\",\"i\"),greedy:!0,inside:{function:/^url/i,punctuation:/^\\(|\\)$/,string:{pattern:RegExp(\"^\"+t.source+\"$\"),alias:\"url\"}}},selector:RegExp(\"[^{}\\\\s](?:[^{};\\\"']|\"+t.source+\")*?(?=\\\\s*\\\\{)\"),string:{pattern:t,greedy:!0},property:/[-_a-z\\xA0-\\uFFFF][-\\w\\xA0-\\uFFFF]*(?=\\s*:)/i,important:/!important\\b/i,function:/[-a-z0-9]+(?=\\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var s=e.languages.markup;s&&(s.tag.addInlined(\"style\",\"css\"),e.languages.insertBefore(\"inside\",\"attr-value\",{\"style-attr\":{pattern:/(^|[\"'\\s])style\\s*=\\s*(?:\"[^\"]*\"|'[^']*')/i,lookbehind:!0,inside:{\"attr-value\":{pattern:/=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+)/,inside:{style:{pattern:/([\"'])[\\s\\S]+(?=[\"']$)/,lookbehind:!0,alias:\"language-css\",inside:e.languages.css},punctuation:[{pattern:/^=/,alias:\"attr-equals\"},/\"|'/]}},\"attr-name\":/^style/i}}},s.tag))}(Prism);\nPrism.languages.clike={comment:[{pattern:/(^|[^\\\\])\\/\\*[\\s\\S]*?(?:\\*\\/|$)/,lookbehind:!0},{pattern:/(^|[^\\\\:])\\/\\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/([\"'])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,greedy:!0},\"class-name\":{pattern:/(\\b(?:class|interface|extends|implements|trait|instanceof|new)\\s+|\\bcatch\\s+\\()[\\w.\\\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\\\]/}},keyword:/\\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\\b/,boolean:/\\b(?:true|false)\\b/,function:/\\w+(?=\\()/,number:/\\b0x[\\da-f]+\\b|(?:\\b\\d+\\.?\\d*|\\B\\.\\d+)(?:e[+-]?\\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\\+\\+?|&&?|\\|\\|?|[?*/~^%]/,punctuation:/[{}[\\];(),.:]/};\n!function(s){function a(e,s){return e.replace(/<<(\\d+)>>/g,function(e,n){return\"(?:\"+s[+n]+\")\"})}function t(e,n,s){return RegExp(a(e,n),s||\"\")}function e(e,n){for(var s=0;s<n;s++)e=e.replace(/<<self>>/g,function(){return\"(?:\"+e+\")\"});return e.replace(/<<self>>/g,\"[^\\\\s\\\\S]\")}var n=\"bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void\",i=\"class enum interface struct\",r=\"add alias and ascending async await by descending from get global group into join let nameof not notnull on or orderby partial remove select set unmanaged value when where\",o=\"abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield\";function l(e){return\"\\\\b(?:\"+e.trim().replace(/ /g,\"|\")+\")\\\\b\"}var d=l(i),p=RegExp(l(n+\" \"+i+\" \"+r+\" \"+o)),c=l(i+\" \"+r+\" \"+o),u=l(n+\" \"+i+\" \"+o),g=e(\"<(?:[^<>;=+\\\\-*/%&|^]|<<self>>)*>\",2),b=e(\"\\\\((?:[^()]|<<self>>)*\\\\)\",2),h=\"@?\\\\b[A-Za-z_]\\\\w*\\\\b\",f=a(\"<<0>>(?:\\\\s*<<1>>)?\",[h,g]),m=a(\"(?!<<0>>)<<1>>(?:\\\\s*\\\\.\\\\s*<<1>>)*\",[c,f]),k=\"\\\\[\\\\s*(?:,\\\\s*)*\\\\]\",y=a(\"<<0>>(?:\\\\s*(?:\\\\?\\\\s*)?<<1>>)*(?:\\\\s*\\\\?)?\",[m,k]),w=a(\"(?:<<0>>|<<1>>)(?:\\\\s*(?:\\\\?\\\\s*)?<<2>>)*(?:\\\\s*\\\\?)?\",[a(\"\\\\(<<0>>+(?:,<<0>>+)+\\\\)\",[a(\"[^,()<>[\\\\];=+\\\\-*/%&|^]|<<0>>|<<1>>|<<2>>\",[g,b,k])]),m,k]),v={keyword:p,punctuation:/[<>()?,.:[\\]]/},x=\"'(?:[^\\r\\n'\\\\\\\\]|\\\\\\\\.|\\\\\\\\[Uux][\\\\da-fA-F]{1,8})'\",$='\"(?:\\\\\\\\.|[^\\\\\\\\\"\\r\\n])*\"';s.languages.csharp=s.languages.extend(\"clike\",{string:[{pattern:t(\"(^|[^$\\\\\\\\])<<0>>\",['@\"(?:\"\"|\\\\\\\\[^]|[^\\\\\\\\\"])*\"(?!\")']),lookbehind:!0,greedy:!0},{pattern:t(\"(^|[^@$\\\\\\\\])<<0>>\",[$]),lookbehind:!0,greedy:!0},{pattern:RegExp(x),greedy:!0,alias:\"character\"}],\"class-name\":[{pattern:t(\"(\\\\busing\\\\s+static\\\\s+)<<0>>(?=\\\\s*;)\",[m]),lookbehind:!0,inside:v},{pattern:t(\"(\\\\busing\\\\s+<<0>>\\\\s*=\\\\s*)<<1>>(?=\\\\s*;)\",[h,w]),lookbehind:!0,inside:v},{pattern:t(\"(\\\\busing\\\\s+)<<0>>(?=\\\\s*=)\",[h]),lookbehind:!0},{pattern:t(\"(\\\\b<<0>>\\\\s+)<<1>>\",[d,f]),lookbehind:!0,inside:v},{pattern:t(\"(\\\\bcatch\\\\s*\\\\(\\\\s*)<<0>>\",[m]),lookbehind:!0,inside:v},{pattern:t(\"(\\\\bwhere\\\\s+)<<0>>\",[h]),lookbehind:!0},{pattern:t(\"(\\\\b(?:is(?:\\\\s+not)?|as)\\\\s+)<<0>>\",[y]),lookbehind:!0,inside:v},{pattern:t(\"\\\\b<<0>>(?=\\\\s+(?!<<1>>)<<2>>(?:\\\\s*[=,;:{)\\\\]]|\\\\s+(?:in|when)\\\\b))\",[w,u,h]),inside:v}],keyword:p,number:/(?:\\b0(?:x[\\da-f_]*[\\da-f]|b[01_]*[01])|(?:\\B\\.\\d+(?:_+\\d+)*|\\b\\d+(?:_+\\d+)*(?:\\.\\d+(?:_+\\d+)*)?)(?:e[-+]?\\d+(?:_+\\d+)*)?)(?:ul|lu|[dflmu])?\\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\\1|~|\\?\\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\\?\\.?|::|[{}[\\];(),.:]/}),s.languages.insertBefore(\"csharp\",\"number\",{range:{pattern:/\\.\\./,alias:\"operator\"}}),s.languages.insertBefore(\"csharp\",\"punctuation\",{\"named-parameter\":{pattern:t(\"([(,]\\\\s*)<<0>>(?=\\\\s*:)\",[h]),lookbehind:!0,alias:\"punctuation\"}}),s.languages.insertBefore(\"csharp\",\"class-name\",{namespace:{pattern:t(\"(\\\\b(?:namespace|using)\\\\s+)<<0>>(?:\\\\s*\\\\.\\\\s*<<0>>)*(?=\\\\s*[;{])\",[h]),lookbehind:!0,inside:{punctuation:/\\./}},\"type-expression\":{pattern:t(\"(\\\\b(?:default|typeof|sizeof)\\\\s*\\\\(\\\\s*)(?:[^()\\\\s]|\\\\s(?!\\\\s*\\\\))|<<0>>)*(?=\\\\s*\\\\))\",[b]),lookbehind:!0,alias:\"class-name\",inside:v},\"return-type\":{pattern:t(\"<<0>>(?=\\\\s+(?:<<1>>\\\\s*(?:=>|[({]|\\\\.\\\\s*this\\\\s*\\\\[)|this\\\\s*\\\\[))\",[w,m]),inside:v,alias:\"class-name\"},\"constructor-invocation\":{pattern:t(\"(\\\\bnew\\\\s+)<<0>>(?=\\\\s*[[({])\",[w]),lookbehind:!0,inside:v,alias:\"class-name\"},\"generic-method\":{pattern:t(\"<<0>>\\\\s*<<1>>(?=\\\\s*\\\\()\",[h,g]),inside:{function:t(\"^<<0>>\",[h]),generic:{pattern:RegExp(g),alias:\"class-name\",inside:v}}},\"type-list\":{pattern:t(\"\\\\b((?:<<0>>\\\\s+<<1>>|where\\\\s+<<2>>)\\\\s*:\\\\s*)(?:<<3>>|<<4>>)(?:\\\\s*,\\\\s*(?:<<3>>|<<4>>))*(?=\\\\s*(?:where|[{;]|=>|$))\",[d,f,h,w,p.source]),lookbehind:!0,inside:{keyword:p,\"class-name\":{pattern:RegExp(w),greedy:!0,inside:v},punctuation:/,/}},preprocessor:{pattern:/(^\\s*)#.*/m,lookbehind:!0,alias:\"property\",inside:{directive:{pattern:/(\\s*#)\\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\\b/,lookbehind:!0,alias:\"keyword\"}}}});var _=$+\"|\"+x,B=a(\"/(?![*/])|//[^\\r\\n]*[\\r\\n]|/\\\\*(?:[^*]|\\\\*(?!/))*\\\\*/|<<0>>\",[_]),E=e(a(\"[^\\\"'/()]|<<0>>|\\\\(<<self>>*\\\\)\",[B]),2),R=\"\\\\b(?:assembly|event|field|method|module|param|property|return|type)\\\\b\",P=a(\"<<0>>(?:\\\\s*\\\\(<<1>>*\\\\))?\",[m,E]);s.languages.insertBefore(\"csharp\",\"class-name\",{attribute:{pattern:t(\"((?:^|[^\\\\s\\\\w>)?])\\\\s*\\\\[\\\\s*)(?:<<0>>\\\\s*:\\\\s*)?<<1>>(?:\\\\s*,\\\\s*<<1>>)*(?=\\\\s*\\\\])\",[R,P]),lookbehind:!0,greedy:!0,inside:{target:{pattern:t(\"^<<0>>(?=\\\\s*:)\",[R]),alias:\"keyword\"},\"attribute-arguments\":{pattern:t(\"\\\\(<<0>>*\\\\)\",[E]),inside:s.languages.csharp},\"class-name\":{pattern:RegExp(m),inside:{punctuation:/\\./}},punctuation:/[:,]/}}});var z=\":[^}\\r\\n]+\",S=e(a(\"[^\\\"'/()]|<<0>>|\\\\(<<self>>*\\\\)\",[B]),2),j=a(\"\\\\{(?!\\\\{)(?:(?![}:])<<0>>)*<<1>>?\\\\}\",[S,z]),A=e(a(\"[^\\\"'/()]|/(?!\\\\*)|/\\\\*(?:[^*]|\\\\*(?!/))*\\\\*/|<<0>>|\\\\(<<self>>*\\\\)\",[_]),2),F=a(\"\\\\{(?!\\\\{)(?:(?![}:])<<0>>)*<<1>>?\\\\}\",[A,z]);function U(e,n){return{interpolation:{pattern:t(\"((?:^|[^{])(?:\\\\{\\\\{)*)<<0>>\",[e]),lookbehind:!0,inside:{\"format-string\":{pattern:t(\"(^\\\\{(?:(?![}:])<<0>>)*)<<1>>(?=\\\\}$)\",[n,z]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\\{|\\}$/,expression:{pattern:/[\\s\\S]+/,alias:\"language-csharp\",inside:s.languages.csharp}}},string:/[\\s\\S]+/}}s.languages.insertBefore(\"csharp\",\"string\",{\"interpolation-string\":[{pattern:t('(^|[^\\\\\\\\])(?:\\\\$@|@\\\\$)\"(?:\"\"|\\\\\\\\[^]|\\\\{\\\\{|<<0>>|[^\\\\\\\\{\"])*\"',[j]),lookbehind:!0,greedy:!0,inside:U(j,S)},{pattern:t('(^|[^@\\\\\\\\])\\\\$\"(?:\\\\\\\\.|\\\\{\\\\{|<<0>>|[^\\\\\\\\\"{])*\"',[F]),lookbehind:!0,greedy:!0,inside:U(F,A)}]})}(Prism),Prism.languages.dotnet=Prism.languages.cs=Prism.languages.csharp;\nPrism.languages.fsharp=Prism.languages.extend(\"clike\",{comment:[{pattern:/(^|[^\\\\])\\(\\*[\\s\\S]*?\\*\\)/,lookbehind:!0},{pattern:/(^|[^\\\\:])\\/\\/.*/,lookbehind:!0}],string:{pattern:/(?:\"\"\"[\\s\\S]*?\"\"\"|@\"(?:\"\"|[^\"])*\"|\"(?:\\\\[\\s\\S]|[^\\\\\"])*\")B?|'(?:[^\\\\']|\\\\(?:.|\\d{3}|x[a-fA-F\\d]{2}|u[a-fA-F\\d]{4}|U[a-fA-F\\d]{8}))'B?/,greedy:!0},\"class-name\":{pattern:/(\\b(?:exception|inherit|interface|new|of|type)\\s+|\\w\\s*:\\s*|\\s:\\??>\\s*)[.\\w]+\\b(?:\\s*(?:->|\\*)\\s*[.\\w]+\\b)*(?!\\s*[:.])/,lookbehind:!0,inside:{operator:/->|\\*/,punctuation:/\\./}},keyword:/\\b(?:let|return|use|yield)(?:!\\B|\\b)|\\b(?:abstract|and|as|assert|base|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|global|if|in|inherit|inline|interface|internal|lazy|match|member|module|mutable|namespace|new|not|null|of|open|or|override|private|public|rec|select|static|struct|then|to|true|try|type|upcast|val|void|when|while|with|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|include|method|mixin|object|parallel|process|protected|pure|sealed|tailcall|trait|virtual|volatile)\\b/,number:[/\\b0x[\\da-fA-F]+(?:un|lf|LF)?\\b/,/\\b0b[01]+(?:y|uy)?\\b/,/(?:\\b\\d+\\.?\\d*|\\B\\.\\d+)(?:[fm]|e[+-]?\\d+)?\\b/i,/\\b\\d+(?:[IlLsy]|u[lsy]?|UL)?\\b/],operator:/([<>~&^])\\1\\1|([*.:<>&])\\2|<-|->|[!=:]=|<?\\|{1,3}>?|\\??(?:<=|>=|<>|[-+*/%=<>])\\??|[!?^&]|~[+~-]|:>|:\\?>?/}),Prism.languages.insertBefore(\"fsharp\",\"keyword\",{preprocessor:{pattern:/^[^\\r\\n\\S]*#.*/m,alias:\"property\",inside:{directive:{pattern:/(\\s*#)\\b(?:else|endif|if|light|line|nowarn)\\b/,lookbehind:!0,alias:\"keyword\"}}}}),Prism.languages.insertBefore(\"fsharp\",\"punctuation\",{\"computation-expression\":{pattern:/[_a-z]\\w*(?=\\s*\\{)/i,alias:\"keyword\"}}),Prism.languages.insertBefore(\"fsharp\",\"string\",{annotation:{pattern:/\\[<.+?>\\]/,inside:{punctuation:/^\\[<|>\\]$/,\"class-name\":{pattern:/^\\w+$|(^|;\\s*)[A-Z]\\w*(?=\\()/,lookbehind:!0},\"annotation-content\":{pattern:/[\\s\\S]+/,inside:Prism.languages.fsharp}}}});\n!function(e){var i=Prism.languages.powershell={comment:[{pattern:/(^|[^`])<#[\\s\\S]*?#>/,lookbehind:!0},{pattern:/(^|[^`])#.*/,lookbehind:!0}],string:[{pattern:/\"(?:`[\\s\\S]|[^`\"])*\"/,greedy:!0,inside:{function:{pattern:/(^|[^`])\\$\\((?:\\$\\([^\\r\\n()]*\\)|(?!\\$\\()[^\\r\\n)])*\\)/,lookbehind:!0,inside:{}}}},{pattern:/'(?:[^']|'')*'/,greedy:!0}],namespace:/\\[[a-z](?:\\[(?:\\[[^\\]]*]|[^\\[\\]])*]|[^\\[\\]])*]/i,boolean:/\\$(?:true|false)\\b/i,variable:/\\$\\w+\\b/,function:[/\\b(?:Add|Approve|Assert|Backup|Block|Checkpoint|Clear|Close|Compare|Complete|Compress|Confirm|Connect|Convert|ConvertFrom|ConvertTo|Copy|Debug|Deny|Disable|Disconnect|Dismount|Edit|Enable|Enter|Exit|Expand|Export|Find|ForEach|Format|Get|Grant|Group|Hide|Import|Initialize|Install|Invoke|Join|Limit|Lock|Measure|Merge|Move|New|Open|Optimize|Out|Ping|Pop|Protect|Publish|Push|Read|Receive|Redo|Register|Remove|Rename|Repair|Request|Reset|Resize|Resolve|Restart|Restore|Resume|Revoke|Save|Search|Select|Send|Set|Show|Skip|Sort|Split|Start|Step|Stop|Submit|Suspend|Switch|Sync|Tee|Test|Trace|Unblock|Undo|Uninstall|Unlock|Unprotect|Unpublish|Unregister|Update|Use|Wait|Watch|Where|Write)-[a-z]+\\b/i,/\\b(?:ac|cat|chdir|clc|cli|clp|clv|compare|copy|cp|cpi|cpp|cvpa|dbp|del|diff|dir|ebp|echo|epal|epcsv|epsn|erase|fc|fl|ft|fw|gal|gbp|gc|gci|gcs|gdr|gi|gl|gm|gp|gps|group|gsv|gu|gv|gwmi|iex|ii|ipal|ipcsv|ipsn|irm|iwmi|iwr|kill|lp|ls|measure|mi|mount|move|mp|mv|nal|ndr|ni|nv|ogv|popd|ps|pushd|pwd|rbp|rd|rdr|ren|ri|rm|rmdir|rni|rnp|rp|rv|rvpa|rwmi|sal|saps|sasv|sbp|sc|select|set|shcm|si|sl|sleep|sls|sort|sp|spps|spsv|start|sv|swmi|tee|trcm|type|write)\\b/i],keyword:/\\b(?:Begin|Break|Catch|Class|Continue|Data|Define|Do|DynamicParam|Else|ElseIf|End|Exit|Filter|Finally|For|ForEach|From|Function|If|InlineScript|Parallel|Param|Process|Return|Sequence|Switch|Throw|Trap|Try|Until|Using|Var|While|Workflow)\\b/i,operator:{pattern:/(\\W?)(?:!|-(?:eq|ne|gt|ge|lt|le|sh[lr]|not|b?(?:and|x?or)|(?:Not)?(?:Like|Match|Contains|In)|Replace|Join|is(?:Not)?|as)\\b|-[-=]?|\\+[+=]?|[*\\/%]=?)/i,lookbehind:!0},punctuation:/[|{}[\\];(),.]/},r=i.string[0].inside;r.boolean=i.boolean,r.variable=i.variable,r.function.inside=i}();\nPrism.languages.sql={comment:{pattern:/(^|[^\\\\])(?:\\/\\*[\\s\\S]*?\\*\\/|(?:--|\\/\\/|#).*)/,lookbehind:!0},variable:[{pattern:/@([\"'`])(?:\\\\[\\s\\S]|(?!\\1)[^\\\\])+\\1/,greedy:!0},/@[\\w.$]+/],string:{pattern:/(^|[^@\\\\])(\"|')(?:\\\\[\\s\\S]|(?!\\2)[^\\\\]|\\2\\2)*\\2/,greedy:!0,lookbehind:!0},function:/\\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\\s*\\()/i,keyword:/\\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:S|ING)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\\b/i,boolean:/\\b(?:TRUE|FALSE|NULL)\\b/i,number:/\\b0x[\\da-f]+\\b|\\b\\d+\\.?\\d*|\\B\\.\\d+\\b/i,operator:/[-+*\\/=%^~]|&&?|\\|\\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\\b/i,punctuation:/[;[\\]()`,.]/};\n!function(){if(\"undefined\"!=typeof self&&self.Prism&&self.document){var i=[],l={},c=function(){};Prism.plugins.toolbar={};var e=Prism.plugins.toolbar.registerButton=function(e,n){var t;t=\"function\"==typeof n?n:function(e){var t;return\"function\"==typeof n.onClick?((t=document.createElement(\"button\")).type=\"button\",t.addEventListener(\"click\",function(){n.onClick.call(this,e)})):\"string\"==typeof n.url?(t=document.createElement(\"a\")).href=n.url:t=document.createElement(\"span\"),n.className&&t.classList.add(n.className),t.textContent=n.text,t},e in l?console.warn('There is a button with the key \"'+e+'\" registered already.'):i.push(l[e]=t)},t=Prism.plugins.toolbar.hook=function(a){var e=a.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&!e.parentNode.classList.contains(\"code-toolbar\")){var t=document.createElement(\"div\");t.classList.add(\"code-toolbar\"),e.parentNode.insertBefore(t,e),t.appendChild(e);var r=document.createElement(\"div\");r.classList.add(\"toolbar\");var n=i,o=function(e){for(;e;){var t=e.getAttribute(\"data-toolbar-order\");if(null!=t)return(t=t.trim()).length?t.split(/\\s*,\\s*/g):[];e=e.parentElement}}(a.element);o&&(n=o.map(function(e){return l[e]||c})),n.forEach(function(e){var t=e(a);if(t){var n=document.createElement(\"div\");n.classList.add(\"toolbar-item\"),n.appendChild(t),r.appendChild(n)}}),t.appendChild(r)}};e(\"label\",function(e){var t=e.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&t.hasAttribute(\"data-label\")){var n,a,r=t.getAttribute(\"data-label\");try{a=document.querySelector(\"template#\"+r)}catch(e){}return a?n=a.content:(t.hasAttribute(\"data-url\")?(n=document.createElement(\"a\")).href=t.getAttribute(\"data-url\"):n=document.createElement(\"span\"),n.textContent=r),n}}),Prism.hooks.add(\"complete\",t)}}();\n"
  },
  {
    "path": "docs/style.css",
    "content": ":root {\n  --gray: rgba(51,51,51,.6);\n  --silver: rgb(204, 204, 204);\n  --slate: rgb(51, 51, 51);\n  --merlot: rgb(59, 3, 109);\n  --purple: rgb(120, 60, 180);\n  --washed-purple: rgba(120, 60, 180, 0.17);\n}\n\n/* sizing */\n.h100vh { min-height: 100vh }\n.mw9 { max-width: 80rem }\n\n/* font */\n.noto { font-family: 'Noto Sans JP', sans-serif }\n.noto-serif { font-family: 'Noto Serif JP', Georgia, serif }\n\n/* bg */\n.bg-merlot { background-color: var(--merlot) }\n.bg-dots { background-image: radial-gradient(rgba(255,255,255,.3) 1px, transparent 1px), radial-gradient(rgba(255,255,255,.3) 1px, transparent 1px); background-size: 75px 75px; background-position: 0px 0px,40px 25px }\n.bg-parallax {  background-attachment: fixed }\n\n/* color */\n.merlot { color: var(--merlot) }\n\n/* opacity */\n.hover-o-100:hover { opacity: 1 }\n\n/* transform */\n.ty--50 { transform: translateY(-50%);}\n\n/* text */\nmain h1, main h2, main h3, main h4, main h5 { margin-bottom: 0; font-weight: 400 }\nmain h1 { margin-top: 0; font-size: 2.5rem }\nmain h2 { margin-top: 3rem; font-size: 2em; color: var(--gray) }\nmain h3 { margin-top: 2rem; font-size: 1.5em }\nmain h2 + h3 { margin-top: .5rem }\nmain p { line-height: 1.4 }\n\na { color: inherit; transition: color .125s ease-in }\nmain a:visited, .sidebar a:visited { color: inherit }\nmain a:hover, .sidebar a:hover { color: var(--merlot) }\n\nblockquote { margin-left: 0; margin-right: 0; padding: 1rem; border-left: 4px solid var(--purple); background: var(--washed-purple) }\nblockquote p { margin: 0 }\n\n/* tables */\ntable { text-align: left; border-collapse: collapse }\ntd, th { padding: .5rem; border-bottom: 1px solid var(--silver) }\n\n/* code */\ncode[class*=\"language-\"], pre[class*=\"language-\"] { font-size: .75rem }\npre[class*=\"language-\"] { padding: 1rem }\npre { overflow-x: auto; overflow-wrap: normal; white-space: pre; font-size: .875rem; margin: 1rem 0; padding: 1rem; color: #eff0f9; background: #333 }\ncode { vertical-align: middle; font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; background-color: var(--washed-purple) }\npre > code { background-color: inherit }\nh2 > code, h3 > code, p > code { padding: 0 .25rem }\n\n@media screen and (min-width: 60em) {\n    code[class*=\"language-\"], pre[class*=\"language-\"] { font-size: .875rem }\n}\n"
  },
  {
    "path": "docs/tachyons.css",
    "content": "html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}.border-box,a,article,aside,blockquote,body,code,dd,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,html,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],legend,li,main,nav,ol,p,pre,section,table,td,textarea,th,tr,ul{box-sizing:border-box}.aspect-ratio{height:0;position:relative}.aspect-ratio--16x9{padding-bottom:56.25%}.aspect-ratio--9x16{padding-bottom:177.77%}.aspect-ratio--4x3{padding-bottom:75%}.aspect-ratio--3x4{padding-bottom:133.33%}.aspect-ratio--6x4{padding-bottom:66.6%}.aspect-ratio--4x6{padding-bottom:150%}.aspect-ratio--8x5{padding-bottom:62.5%}.aspect-ratio--5x8{padding-bottom:160%}.aspect-ratio--7x5{padding-bottom:71.42%}.aspect-ratio--5x7{padding-bottom:140%}.aspect-ratio--1x1{padding-bottom:100%}.aspect-ratio--object{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}img{max-width:100%}.cover{background-size:cover!important}.contain{background-size:contain!important}.bg-center{background-position:50%}.bg-center,.bg-top{background-repeat:no-repeat}.bg-top{background-position:top}.bg-right{background-position:100%}.bg-bottom,.bg-right{background-repeat:no-repeat}.bg-bottom{background-position:bottom}.bg-left{background-repeat:no-repeat;background-position:0}.outline{outline:1px solid}.outline-transparent{outline:1px solid transparent}.outline-0{outline:0}.ba{border-style:solid;border-width:1px}.bt{border-top-style:solid;border-top-width:1px}.br{border-right-style:solid;border-right-width:1px}.bb{border-bottom-style:solid;border-bottom-width:1px}.bl{border-left-style:solid;border-left-width:1px}.bn{border-style:none;border-width:0}.b--black{border-color:#000}.b--near-black{border-color:#111}.b--dark-gray{border-color:#333}.b--mid-gray{border-color:#555}.b--gray{border-color:#777}.b--silver{border-color:#999}.b--light-silver{border-color:#aaa}.b--moon-gray{border-color:#ccc}.b--light-gray{border-color:#eee}.b--near-white{border-color:#f4f4f4}.b--white{border-color:#fff}.b--white-90{border-color:hsla(0,0%,100%,.9)}.b--white-80{border-color:hsla(0,0%,100%,.8)}.b--white-70{border-color:hsla(0,0%,100%,.7)}.b--white-60{border-color:hsla(0,0%,100%,.6)}.b--white-50{border-color:hsla(0,0%,100%,.5)}.b--white-40{border-color:hsla(0,0%,100%,.4)}.b--white-30{border-color:hsla(0,0%,100%,.3)}.b--white-20{border-color:hsla(0,0%,100%,.2)}.b--white-10{border-color:hsla(0,0%,100%,.1)}.b--white-05{border-color:hsla(0,0%,100%,.05)}.b--white-025{border-color:hsla(0,0%,100%,.025)}.b--white-0125{border-color:hsla(0,0%,100%,.0125)}.b--black-90{border-color:rgba(0,0,0,.9)}.b--black-80{border-color:rgba(0,0,0,.8)}.b--black-70{border-color:rgba(0,0,0,.7)}.b--black-60{border-color:rgba(0,0,0,.6)}.b--black-50{border-color:rgba(0,0,0,.5)}.b--black-40{border-color:rgba(0,0,0,.4)}.b--black-30{border-color:rgba(0,0,0,.3)}.b--black-20{border-color:rgba(0,0,0,.2)}.b--black-10{border-color:rgba(0,0,0,.1)}.b--black-05{border-color:rgba(0,0,0,.05)}.b--black-025{border-color:rgba(0,0,0,.025)}.b--black-0125{border-color:rgba(0,0,0,.0125)}.b--dark-red{border-color:#e7040f}.b--red{border-color:#ff4136}.b--light-red{border-color:#ff725c}.b--orange{border-color:#ff6300}.b--gold{border-color:#ffb700}.b--yellow{border-color:gold}.b--light-yellow{border-color:#fbf1a9}.b--purple{border-color:#5e2ca5}.b--light-purple{border-color:#a463f2}.b--dark-pink{border-color:#d5008f}.b--hot-pink{border-color:#ff41b4}.b--pink{border-color:#ff80cc}.b--light-pink{border-color:#ffa3d7}.b--dark-green{border-color:#137752}.b--green{border-color:#19a974}.b--light-green{border-color:#9eebcf}.b--navy{border-color:#001b44}.b--dark-blue{border-color:#00449e}.b--blue{border-color:#357edd}.b--light-blue{border-color:#96ccff}.b--lightest-blue{border-color:#cdecff}.b--washed-blue{border-color:#f6fffe}.b--washed-green{border-color:#e8fdf5}.b--washed-yellow{border-color:#fffceb}.b--washed-red{border-color:#ffdfdf}.b--transparent{border-color:transparent}.b--inherit{border-color:inherit}.b--initial{border-color:initial}.b--unset{border-color:unset}.br0{border-radius:0}.br1{border-radius:.125rem}.br2{border-radius:.25rem}.br3{border-radius:.5rem}.br4{border-radius:1rem}.br-100{border-radius:100%}.br-pill{border-radius:9999px}.br--bottom{border-top-left-radius:0;border-top-right-radius:0}.br--top{border-bottom-right-radius:0}.br--right,.br--top{border-bottom-left-radius:0}.br--right{border-top-left-radius:0}.br--left{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit{border-radius:inherit}.br-initial{border-radius:initial}.br-unset{border-radius:unset}.b--dotted{border-style:dotted}.b--dashed{border-style:dashed}.b--solid{border-style:solid}.b--none{border-style:none}.bw0{border-width:0}.bw1{border-width:.125rem}.bw2{border-width:.25rem}.bw3{border-width:.5rem}.bw4{border-width:1rem}.bw5{border-width:2rem}.bt-0{border-top-width:0}.br-0{border-right-width:0}.bb-0{border-bottom-width:0}.bl-0{border-left-width:0}.shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.top-1{top:1rem}.right-1{right:1rem}.bottom-1{bottom:1rem}.left-1{left:1rem}.top-2{top:2rem}.right-2{right:2rem}.bottom-2{bottom:2rem}.left-2{left:2rem}.top--1{top:-1rem}.right--1{right:-1rem}.bottom--1{bottom:-1rem}.left--1{left:-1rem}.top--2{top:-2rem}.right--2{right:-2rem}.bottom--2{bottom:-2rem}.left--2{left:-2rem}.absolute--fill{top:0;right:0;bottom:0;left:0}.cf:after,.cf:before{content:\" \";display:table}.cf:after{clear:both}.cf{*zoom:1}.cl{clear:left}.cr{clear:right}.cb{clear:both}.cn{clear:none}.dn{display:none}.di{display:inline}.db{display:block}.dib{display:inline-block}.dit{display:inline-table}.dt{display:table}.dtc{display:table-cell}.dt-row{display:table-row}.dt-row-group{display:table-row-group}.dt-column{display:table-column}.dt-column-group{display:table-column-group}.dt--fixed{table-layout:fixed;width:100%}.flex{display:flex}.inline-flex{display:inline-flex}.flex-auto{flex:1 1 auto;min-width:0;min-height:0}.flex-none{flex:none}.flex-column{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.flex-wrap-reverse{flex-wrap:wrap-reverse}.flex-column-reverse{flex-direction:column-reverse}.flex-row-reverse{flex-direction:row-reverse}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.items-stretch{align-items:stretch}.self-start{align-self:flex-start}.self-end{align-self:flex-end}.self-center{align-self:center}.self-baseline{align-self:baseline}.self-stretch{align-self:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.content-start{align-content:flex-start}.content-end{align-content:flex-end}.content-center{align-content:center}.content-between{align-content:space-between}.content-around{align-content:space-around}.content-stretch{align-content:stretch}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-last{order:99999}.flex-grow-0{flex-grow:0}.flex-grow-1{flex-grow:1}.flex-shrink-0{flex-shrink:0}.flex-shrink-1{flex-shrink:1}.fl{float:left}.fl,.fr{_display:inline}.fr{float:right}.fn{float:none}.sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.serif{font-family:georgia,times,serif}.system-sans-serif{font-family:sans-serif}.system-serif{font-family:serif}.code,code{font-family:Consolas,monaco,monospace}.courier{font-family:Courier Next,courier,monospace}.helvetica{font-family:helvetica neue,helvetica,sans-serif}.avenir{font-family:avenir next,avenir,sans-serif}.athelas{font-family:athelas,georgia,serif}.georgia{font-family:georgia,serif}.times{font-family:times,serif}.bodoni{font-family:Bodoni MT,serif}.calisto{font-family:Calisto MT,serif}.garamond{font-family:garamond,serif}.baskerville{font-family:baskerville,serif}.i{font-style:italic}.fs-normal{font-style:normal}.normal{font-weight:400}.b{font-weight:700}.fw1{font-weight:100}.fw2{font-weight:200}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.fw7{font-weight:700}.fw8{font-weight:800}.fw9{font-weight:900}.input-reset{-webkit-appearance:none;-moz-appearance:none}.button-reset::-moz-focus-inner,.input-reset::-moz-focus-inner{border:0;padding:0}.h1{height:1rem}.h2{height:2rem}.h3{height:4rem}.h4{height:8rem}.h5{height:16rem}.h-25{height:25%}.h-50{height:50%}.h-75{height:75%}.h-100{height:100%}.min-h-100{min-height:100%}.vh-25{height:25vh}.vh-50{height:50vh}.vh-75{height:75vh}.vh-100{height:100vh}.min-vh-100{min-height:100vh}.h-auto{height:auto}.h-inherit{height:inherit}.tracked{letter-spacing:.1em}.tracked-tight{letter-spacing:-.05em}.tracked-mega{letter-spacing:.25em}.lh-solid{line-height:1}.lh-title{line-height:1.25}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.list{list-style-type:none}.mw-100{max-width:100%}.mw1{max-width:1rem}.mw2{max-width:2rem}.mw3{max-width:4rem}.mw4{max-width:8rem}.mw5{max-width:16rem}.mw6{max-width:32rem}.mw7{max-width:48rem}.mw8{max-width:64rem}.mw9{max-width:96rem}.mw-none{max-width:none}.w1{width:1rem}.w2{width:2rem}.w3{width:4rem}.w4{width:8rem}.w5{width:16rem}.w-10{width:10%}.w-20{width:20%}.w-25{width:25%}.w-30{width:30%}.w-33{width:33%}.w-34{width:34%}.w-40{width:40%}.w-50{width:50%}.w-60{width:60%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-90{width:90%}.w-100{width:100%}.w-third{width:33.33333%}.w-two-thirds{width:66.66667%}.w-auto{width:auto}.overflow-visible{overflow:visible}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-auto{overflow:auto}.overflow-x-visible{overflow-x:visible}.overflow-x-hidden{overflow-x:hidden}.overflow-x-scroll{overflow-x:scroll}.overflow-x-auto{overflow-x:auto}.overflow-y-visible{overflow-y:visible}.overflow-y-hidden{overflow-y:hidden}.overflow-y-scroll{overflow-y:scroll}.overflow-y-auto{overflow-y:auto}.static{position:static}.relative{position:relative}.absolute{position:absolute}.fixed{position:fixed}.o-100{opacity:1}.o-90{opacity:.9}.o-80{opacity:.8}.o-70{opacity:.7}.o-60{opacity:.6}.o-50{opacity:.5}.o-40{opacity:.4}.o-30{opacity:.3}.o-20{opacity:.2}.o-10{opacity:.1}.o-05{opacity:.05}.o-025{opacity:.025}.o-0{opacity:0}.rotate-45{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.black-90{color:rgba(0,0,0,.9)}.black-80{color:rgba(0,0,0,.8)}.black-70{color:rgba(0,0,0,.7)}.black-60{color:rgba(0,0,0,.6)}.black-50{color:rgba(0,0,0,.5)}.black-40{color:rgba(0,0,0,.4)}.black-30{color:rgba(0,0,0,.3)}.black-20{color:rgba(0,0,0,.2)}.black-10{color:rgba(0,0,0,.1)}.black-05{color:rgba(0,0,0,.05)}.white-90{color:hsla(0,0%,100%,.9)}.white-80{color:hsla(0,0%,100%,.8)}.white-70{color:hsla(0,0%,100%,.7)}.white-60{color:hsla(0,0%,100%,.6)}.white-50{color:hsla(0,0%,100%,.5)}.white-40{color:hsla(0,0%,100%,.4)}.white-30{color:hsla(0,0%,100%,.3)}.white-20{color:hsla(0,0%,100%,.2)}.white-10{color:hsla(0,0%,100%,.1)}.black{color:#000}.near-black{color:#111}.dark-gray{color:#333}.mid-gray{color:#555}.gray{color:#777}.silver{color:#999}.light-silver{color:#aaa}.moon-gray{color:#ccc}.light-gray{color:#eee}.near-white{color:#f4f4f4}.white{color:#fff}.dark-red{color:#e7040f}.red{color:#ff4136}.light-red{color:#ff725c}.orange{color:#ff6300}.gold{color:#ffb700}.yellow{color:gold}.light-yellow{color:#fbf1a9}.purple{color:#5e2ca5}.light-purple{color:#a463f2}.dark-pink{color:#d5008f}.hot-pink{color:#ff41b4}.pink{color:#ff80cc}.light-pink{color:#ffa3d7}.dark-green{color:#137752}.green{color:#19a974}.light-green{color:#9eebcf}.navy{color:#001b44}.dark-blue{color:#00449e}.blue{color:#357edd}.light-blue{color:#96ccff}.lightest-blue{color:#cdecff}.washed-blue{color:#f6fffe}.washed-green{color:#e8fdf5}.washed-yellow{color:#fffceb}.washed-red{color:#ffdfdf}.color-inherit{color:inherit}.bg-black-90{background-color:rgba(0,0,0,.9)}.bg-black-80{background-color:rgba(0,0,0,.8)}.bg-black-70{background-color:rgba(0,0,0,.7)}.bg-black-60{background-color:rgba(0,0,0,.6)}.bg-black-50{background-color:rgba(0,0,0,.5)}.bg-black-40{background-color:rgba(0,0,0,.4)}.bg-black-30{background-color:rgba(0,0,0,.3)}.bg-black-20{background-color:rgba(0,0,0,.2)}.bg-black-10{background-color:rgba(0,0,0,.1)}.bg-black-05{background-color:rgba(0,0,0,.05)}.bg-white-90{background-color:hsla(0,0%,100%,.9)}.bg-white-80{background-color:hsla(0,0%,100%,.8)}.bg-white-70{background-color:hsla(0,0%,100%,.7)}.bg-white-60{background-color:hsla(0,0%,100%,.6)}.bg-white-50{background-color:hsla(0,0%,100%,.5)}.bg-white-40{background-color:hsla(0,0%,100%,.4)}.bg-white-30{background-color:hsla(0,0%,100%,.3)}.bg-white-20{background-color:hsla(0,0%,100%,.2)}.bg-white-10{background-color:hsla(0,0%,100%,.1)}.bg-black{background-color:#000}.bg-near-black{background-color:#111}.bg-dark-gray{background-color:#333}.bg-mid-gray{background-color:#555}.bg-gray{background-color:#777}.bg-silver{background-color:#999}.bg-light-silver{background-color:#aaa}.bg-moon-gray{background-color:#ccc}.bg-light-gray{background-color:#eee}.bg-near-white{background-color:#f4f4f4}.bg-white{background-color:#fff}.bg-transparent{background-color:transparent}.bg-dark-red{background-color:#e7040f}.bg-red{background-color:#ff4136}.bg-light-red{background-color:#ff725c}.bg-orange{background-color:#ff6300}.bg-gold{background-color:#ffb700}.bg-yellow{background-color:gold}.bg-light-yellow{background-color:#fbf1a9}.bg-purple{background-color:#5e2ca5}.bg-light-purple{background-color:#a463f2}.bg-dark-pink{background-color:#d5008f}.bg-hot-pink{background-color:#ff41b4}.bg-pink{background-color:#ff80cc}.bg-light-pink{background-color:#ffa3d7}.bg-dark-green{background-color:#137752}.bg-green{background-color:#19a974}.bg-light-green{background-color:#9eebcf}.bg-navy{background-color:#001b44}.bg-dark-blue{background-color:#00449e}.bg-blue{background-color:#357edd}.bg-light-blue{background-color:#96ccff}.bg-lightest-blue{background-color:#cdecff}.bg-washed-blue{background-color:#f6fffe}.bg-washed-green{background-color:#e8fdf5}.bg-washed-yellow{background-color:#fffceb}.bg-washed-red{background-color:#ffdfdf}.bg-inherit{background-color:inherit}.hover-black:focus,.hover-black:hover{color:#000}.hover-near-black:focus,.hover-near-black:hover{color:#111}.hover-dark-gray:focus,.hover-dark-gray:hover{color:#333}.hover-mid-gray:focus,.hover-mid-gray:hover{color:#555}.hover-gray:focus,.hover-gray:hover{color:#777}.hover-silver:focus,.hover-silver:hover{color:#999}.hover-light-silver:focus,.hover-light-silver:hover{color:#aaa}.hover-moon-gray:focus,.hover-moon-gray:hover{color:#ccc}.hover-light-gray:focus,.hover-light-gray:hover{color:#eee}.hover-near-white:focus,.hover-near-white:hover{color:#f4f4f4}.hover-white:focus,.hover-white:hover{color:#fff}.hover-black-90:focus,.hover-black-90:hover{color:rgba(0,0,0,.9)}.hover-black-80:focus,.hover-black-80:hover{color:rgba(0,0,0,.8)}.hover-black-70:focus,.hover-black-70:hover{color:rgba(0,0,0,.7)}.hover-black-60:focus,.hover-black-60:hover{color:rgba(0,0,0,.6)}.hover-black-50:focus,.hover-black-50:hover{color:rgba(0,0,0,.5)}.hover-black-40:focus,.hover-black-40:hover{color:rgba(0,0,0,.4)}.hover-black-30:focus,.hover-black-30:hover{color:rgba(0,0,0,.3)}.hover-black-20:focus,.hover-black-20:hover{color:rgba(0,0,0,.2)}.hover-black-10:focus,.hover-black-10:hover{color:rgba(0,0,0,.1)}.hover-white-90:focus,.hover-white-90:hover{color:hsla(0,0%,100%,.9)}.hover-white-80:focus,.hover-white-80:hover{color:hsla(0,0%,100%,.8)}.hover-white-70:focus,.hover-white-70:hover{color:hsla(0,0%,100%,.7)}.hover-white-60:focus,.hover-white-60:hover{color:hsla(0,0%,100%,.6)}.hover-white-50:focus,.hover-white-50:hover{color:hsla(0,0%,100%,.5)}.hover-white-40:focus,.hover-white-40:hover{color:hsla(0,0%,100%,.4)}.hover-white-30:focus,.hover-white-30:hover{color:hsla(0,0%,100%,.3)}.hover-white-20:focus,.hover-white-20:hover{color:hsla(0,0%,100%,.2)}.hover-white-10:focus,.hover-white-10:hover{color:hsla(0,0%,100%,.1)}.hover-inherit:focus,.hover-inherit:hover{color:inherit}.hover-bg-black:focus,.hover-bg-black:hover{background-color:#000}.hover-bg-near-black:focus,.hover-bg-near-black:hover{background-color:#111}.hover-bg-dark-gray:focus,.hover-bg-dark-gray:hover{background-color:#333}.hover-bg-mid-gray:focus,.hover-bg-mid-gray:hover{background-color:#555}.hover-bg-gray:focus,.hover-bg-gray:hover{background-color:#777}.hover-bg-silver:focus,.hover-bg-silver:hover{background-color:#999}.hover-bg-light-silver:focus,.hover-bg-light-silver:hover{background-color:#aaa}.hover-bg-moon-gray:focus,.hover-bg-moon-gray:hover{background-color:#ccc}.hover-bg-light-gray:focus,.hover-bg-light-gray:hover{background-color:#eee}.hover-bg-near-white:focus,.hover-bg-near-white:hover{background-color:#f4f4f4}.hover-bg-white:focus,.hover-bg-white:hover{background-color:#fff}.hover-bg-transparent:focus,.hover-bg-transparent:hover{background-color:transparent}.hover-bg-black-90:focus,.hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.hover-bg-black-80:focus,.hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.hover-bg-black-70:focus,.hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.hover-bg-black-60:focus,.hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.hover-bg-black-50:focus,.hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.hover-bg-black-40:focus,.hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.hover-bg-black-30:focus,.hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.hover-bg-black-20:focus,.hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.hover-bg-black-10:focus,.hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.hover-bg-white-90:focus,.hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.hover-bg-white-80:focus,.hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.hover-bg-white-70:focus,.hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.hover-bg-white-60:focus,.hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.hover-bg-white-50:focus,.hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.hover-bg-white-40:focus,.hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.hover-bg-white-30:focus,.hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.hover-bg-white-20:focus,.hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.hover-bg-white-10:focus,.hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.hover-dark-red:focus,.hover-dark-red:hover{color:#e7040f}.hover-red:focus,.hover-red:hover{color:#ff4136}.hover-light-red:focus,.hover-light-red:hover{color:#ff725c}.hover-orange:focus,.hover-orange:hover{color:#ff6300}.hover-gold:focus,.hover-gold:hover{color:#ffb700}.hover-yellow:focus,.hover-yellow:hover{color:gold}.hover-light-yellow:focus,.hover-light-yellow:hover{color:#fbf1a9}.hover-purple:focus,.hover-purple:hover{color:#5e2ca5}.hover-light-purple:focus,.hover-light-purple:hover{color:#a463f2}.hover-dark-pink:focus,.hover-dark-pink:hover{color:#d5008f}.hover-hot-pink:focus,.hover-hot-pink:hover{color:#ff41b4}.hover-pink:focus,.hover-pink:hover{color:#ff80cc}.hover-light-pink:focus,.hover-light-pink:hover{color:#ffa3d7}.hover-dark-green:focus,.hover-dark-green:hover{color:#137752}.hover-green:focus,.hover-green:hover{color:#19a974}.hover-light-green:focus,.hover-light-green:hover{color:#9eebcf}.hover-navy:focus,.hover-navy:hover{color:#001b44}.hover-dark-blue:focus,.hover-dark-blue:hover{color:#00449e}.hover-blue:focus,.hover-blue:hover{color:#357edd}.hover-light-blue:focus,.hover-light-blue:hover{color:#96ccff}.hover-lightest-blue:focus,.hover-lightest-blue:hover{color:#cdecff}.hover-washed-blue:focus,.hover-washed-blue:hover{color:#f6fffe}.hover-washed-green:focus,.hover-washed-green:hover{color:#e8fdf5}.hover-washed-yellow:focus,.hover-washed-yellow:hover{color:#fffceb}.hover-washed-red:focus,.hover-washed-red:hover{color:#ffdfdf}.hover-bg-dark-red:focus,.hover-bg-dark-red:hover{background-color:#e7040f}.hover-bg-red:focus,.hover-bg-red:hover{background-color:#ff4136}.hover-bg-light-red:focus,.hover-bg-light-red:hover{background-color:#ff725c}.hover-bg-orange:focus,.hover-bg-orange:hover{background-color:#ff6300}.hover-bg-gold:focus,.hover-bg-gold:hover{background-color:#ffb700}.hover-bg-yellow:focus,.hover-bg-yellow:hover{background-color:gold}.hover-bg-light-yellow:focus,.hover-bg-light-yellow:hover{background-color:#fbf1a9}.hover-bg-purple:focus,.hover-bg-purple:hover{background-color:#5e2ca5}.hover-bg-light-purple:focus,.hover-bg-light-purple:hover{background-color:#a463f2}.hover-bg-dark-pink:focus,.hover-bg-dark-pink:hover{background-color:#d5008f}.hover-bg-hot-pink:focus,.hover-bg-hot-pink:hover{background-color:#ff41b4}.hover-bg-pink:focus,.hover-bg-pink:hover{background-color:#ff80cc}.hover-bg-light-pink:focus,.hover-bg-light-pink:hover{background-color:#ffa3d7}.hover-bg-dark-green:focus,.hover-bg-dark-green:hover{background-color:#137752}.hover-bg-green:focus,.hover-bg-green:hover{background-color:#19a974}.hover-bg-light-green:focus,.hover-bg-light-green:hover{background-color:#9eebcf}.hover-bg-navy:focus,.hover-bg-navy:hover{background-color:#001b44}.hover-bg-dark-blue:focus,.hover-bg-dark-blue:hover{background-color:#00449e}.hover-bg-blue:focus,.hover-bg-blue:hover{background-color:#357edd}.hover-bg-light-blue:focus,.hover-bg-light-blue:hover{background-color:#96ccff}.hover-bg-lightest-blue:focus,.hover-bg-lightest-blue:hover{background-color:#cdecff}.hover-bg-washed-blue:focus,.hover-bg-washed-blue:hover{background-color:#f6fffe}.hover-bg-washed-green:focus,.hover-bg-washed-green:hover{background-color:#e8fdf5}.hover-bg-washed-yellow:focus,.hover-bg-washed-yellow:hover{background-color:#fffceb}.hover-bg-washed-red:focus,.hover-bg-washed-red:hover{background-color:#ffdfdf}.hover-bg-inherit:focus,.hover-bg-inherit:hover{background-color:inherit}.pa0{padding:0}.pa1{padding:.25rem}.pa2{padding:.5rem}.pa3{padding:1rem}.pa4{padding:2rem}.pa5{padding:4rem}.pa6{padding:8rem}.pa7{padding:16rem}.pl0{padding-left:0}.pl1{padding-left:.25rem}.pl2{padding-left:.5rem}.pl3{padding-left:1rem}.pl4{padding-left:2rem}.pl5{padding-left:4rem}.pl6{padding-left:8rem}.pl7{padding-left:16rem}.pr0{padding-right:0}.pr1{padding-right:.25rem}.pr2{padding-right:.5rem}.pr3{padding-right:1rem}.pr4{padding-right:2rem}.pr5{padding-right:4rem}.pr6{padding-right:8rem}.pr7{padding-right:16rem}.pb0{padding-bottom:0}.pb1{padding-bottom:.25rem}.pb2{padding-bottom:.5rem}.pb3{padding-bottom:1rem}.pb4{padding-bottom:2rem}.pb5{padding-bottom:4rem}.pb6{padding-bottom:8rem}.pb7{padding-bottom:16rem}.pt0{padding-top:0}.pt1{padding-top:.25rem}.pt2{padding-top:.5rem}.pt3{padding-top:1rem}.pt4{padding-top:2rem}.pt5{padding-top:4rem}.pt6{padding-top:8rem}.pt7{padding-top:16rem}.pv0{padding-top:0;padding-bottom:0}.pv1{padding-top:.25rem;padding-bottom:.25rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.pv3{padding-top:1rem;padding-bottom:1rem}.pv4{padding-top:2rem;padding-bottom:2rem}.pv5{padding-top:4rem;padding-bottom:4rem}.pv6{padding-top:8rem;padding-bottom:8rem}.pv7{padding-top:16rem;padding-bottom:16rem}.ph0{padding-left:0;padding-right:0}.ph1{padding-left:.25rem;padding-right:.25rem}.ph2{padding-left:.5rem;padding-right:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ph5{padding-left:4rem;padding-right:4rem}.ph6{padding-left:8rem;padding-right:8rem}.ph7{padding-left:16rem;padding-right:16rem}.ma0{margin:0}.ma1{margin:.25rem}.ma2{margin:.5rem}.ma3{margin:1rem}.ma4{margin:2rem}.ma5{margin:4rem}.ma6{margin:8rem}.ma7{margin:16rem}.ml0{margin-left:0}.ml1{margin-left:.25rem}.ml2{margin-left:.5rem}.ml3{margin-left:1rem}.ml4{margin-left:2rem}.ml5{margin-left:4rem}.ml6{margin-left:8rem}.ml7{margin-left:16rem}.mr0{margin-right:0}.mr1{margin-right:.25rem}.mr2{margin-right:.5rem}.mr3{margin-right:1rem}.mr4{margin-right:2rem}.mr5{margin-right:4rem}.mr6{margin-right:8rem}.mr7{margin-right:16rem}.mb0{margin-bottom:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mb4{margin-bottom:2rem}.mb5{margin-bottom:4rem}.mb6{margin-bottom:8rem}.mb7{margin-bottom:16rem}.mt0{margin-top:0}.mt1{margin-top:.25rem}.mt2{margin-top:.5rem}.mt3{margin-top:1rem}.mt4{margin-top:2rem}.mt5{margin-top:4rem}.mt6{margin-top:8rem}.mt7{margin-top:16rem}.mv0{margin-top:0;margin-bottom:0}.mv1{margin-top:.25rem;margin-bottom:.25rem}.mv2{margin-top:.5rem;margin-bottom:.5rem}.mv3{margin-top:1rem;margin-bottom:1rem}.mv4{margin-top:2rem;margin-bottom:2rem}.mv5{margin-top:4rem;margin-bottom:4rem}.mv6{margin-top:8rem;margin-bottom:8rem}.mv7{margin-top:16rem;margin-bottom:16rem}.mh0{margin-left:0;margin-right:0}.mh1{margin-left:.25rem;margin-right:.25rem}.mh2{margin-left:.5rem;margin-right:.5rem}.mh3{margin-left:1rem;margin-right:1rem}.mh4{margin-left:2rem;margin-right:2rem}.mh5{margin-left:4rem;margin-right:4rem}.mh6{margin-left:8rem;margin-right:8rem}.mh7{margin-left:16rem;margin-right:16rem}.na1{margin:-.25rem}.na2{margin:-.5rem}.na3{margin:-1rem}.na4{margin:-2rem}.na5{margin:-4rem}.na6{margin:-8rem}.na7{margin:-16rem}.nl1{margin-left:-.25rem}.nl2{margin-left:-.5rem}.nl3{margin-left:-1rem}.nl4{margin-left:-2rem}.nl5{margin-left:-4rem}.nl6{margin-left:-8rem}.nl7{margin-left:-16rem}.nr1{margin-right:-.25rem}.nr2{margin-right:-.5rem}.nr3{margin-right:-1rem}.nr4{margin-right:-2rem}.nr5{margin-right:-4rem}.nr6{margin-right:-8rem}.nr7{margin-right:-16rem}.nb1{margin-bottom:-.25rem}.nb2{margin-bottom:-.5rem}.nb3{margin-bottom:-1rem}.nb4{margin-bottom:-2rem}.nb5{margin-bottom:-4rem}.nb6{margin-bottom:-8rem}.nb7{margin-bottom:-16rem}.nt1{margin-top:-.25rem}.nt2{margin-top:-.5rem}.nt3{margin-top:-1rem}.nt4{margin-top:-2rem}.nt5{margin-top:-4rem}.nt6{margin-top:-8rem}.nt7{margin-top:-16rem}.collapse{border-collapse:collapse;border-spacing:0}.striped--light-silver:nth-child(odd){background-color:#aaa}.striped--moon-gray:nth-child(odd){background-color:#ccc}.striped--light-gray:nth-child(odd){background-color:#eee}.striped--near-white:nth-child(odd){background-color:#f4f4f4}.stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.strike{text-decoration:line-through}.underline{text-decoration:underline}.no-underline{text-decoration:none}.tl{text-align:left}.tr{text-align:right}.tc{text-align:center}.tj{text-align:justify}.ttc{text-transform:capitalize}.ttl{text-transform:lowercase}.ttu{text-transform:uppercase}.ttn{text-transform:none}.f-6,.f-headline{font-size:6rem}.f-5,.f-subheadline{font-size:5rem}.f1{font-size:3rem}.f2{font-size:2.25rem}.f3{font-size:1.5rem}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.measure-wide{max-width:34em}.measure-narrow{max-width:20em}.indent{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps{font-variant:small-caps}.truncate{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.overflow-container{overflow-y:scroll}.center{margin-left:auto}.center,.mr-auto{margin-right:auto}.ml-auto{margin-left:auto}.clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal{white-space:normal}.nowrap{white-space:nowrap}.pre{white-space:pre}.v-base{vertical-align:baseline}.v-mid{vertical-align:middle}.v-top{vertical-align:top}.v-btm{vertical-align:bottom}.dim{opacity:1}.dim,.dim:focus,.dim:hover{transition:opacity .15s ease-in}.dim:focus,.dim:hover{opacity:.5}.dim:active{opacity:.8;transition:opacity .15s ease-out}.glow,.glow:focus,.glow:hover{transition:opacity .15s ease-in}.glow:focus,.glow:hover{opacity:1}.hide-child .child{opacity:0;transition:opacity .15s ease-in}.hide-child:active .child,.hide-child:focus .child,.hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.underline-hover:focus,.underline-hover:hover{text-decoration:underline}.grow{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-out;transition:transform .25s ease-out;transition:transform .25s ease-out,-webkit-transform .25s ease-out}.grow:focus,.grow:hover{-webkit-transform:scale(1.05);transform:scale(1.05)}.grow:active{-webkit-transform:scale(.9);transform:scale(.9)}.grow-large{-moz-osx-font-smoothing:grayscale;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0);transform:translateZ(0);transition:-webkit-transform .25s ease-in-out;transition:transform .25s ease-in-out;transition:transform .25s ease-in-out,-webkit-transform .25s ease-in-out}.grow-large:focus,.grow-large:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.grow-large:active{-webkit-transform:scale(.95);transform:scale(.95)}.pointer:hover,.shadow-hover{cursor:pointer}.shadow-hover{position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:after{content:\"\";box-shadow:0 0 16px 2px rgba(0,0,0,.2);border-radius:inherit;opacity:0;position:absolute;top:0;left:0;width:100%;height:100%;z-index:-1;transition:opacity .5s cubic-bezier(.165,.84,.44,1)}.shadow-hover:focus:after,.shadow-hover:hover:after{opacity:1}.bg-animate,.bg-animate:focus,.bg-animate:hover{transition:background-color .15s ease-in-out}.z-0{z-index:0}.z-1{z-index:1}.z-2{z-index:2}.z-3{z-index:3}.z-4{z-index:4}.z-5{z-index:5}.z-999{z-index:999}.z-9999{z-index:9999}.z-max{z-index:2147483647}.z-inherit{z-index:inherit}.z-initial{z-index:auto}.z-unset{z-index:unset}.nested-copy-line-height ol,.nested-copy-line-height p,.nested-copy-line-height ul{line-height:1.5}.nested-headline-line-height h1,.nested-headline-line-height h2,.nested-headline-line-height h3,.nested-headline-line-height h4,.nested-headline-line-height h5,.nested-headline-line-height h6{line-height:1.25}.nested-list-reset ol,.nested-list-reset ul{padding-left:0;margin-left:0;list-style-type:none}.nested-copy-indent p+p{text-indent:1em;margin-top:0;margin-bottom:0}.nested-copy-separator p+p{margin-top:1.5em}.nested-img img{width:100%;max-width:100%;display:block}.nested-links a{color:#357edd;transition:color .15s ease-in}.nested-links a:focus,.nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.debug *{outline:1px solid gold}.debug-white *{outline:1px solid #fff}.debug-black *{outline:1px solid #000}.debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAFElEQVR4AWPAC97/9x0eCsAEPgwAVLshdpENIxcAAAAASUVORK5CYII=) repeat 0 0}.debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMklEQVR4AWOgCLz/b0epAa6UGuBOqQHOQHLUgFEDnAbcBZ4UGwDOkiCnkIhdgNgNxAYAiYlD+8sEuo8AAAAASUVORK5CYII=) repeat 0 0}.debug-grid-8-solid{background:#fff url(data:image/gif;base64,R0lGODdhCAAIAPEAAADw/wDx/////wAAACwAAAAACAAIAAACDZQvgaeb/lxbAIKA8y0AOw==) repeat 0 0}.debug-grid-16-solid{background:#fff url(data:image/gif;base64,R0lGODdhEAAQAPEAAADw/wDx/xXy/////ywAAAAAEAAQAAACIZyPKckYDQFsb6ZqD85jZ2+BkwiRFKehhqQCQgDHcgwEBQA7) repeat 0 0}@media screen and (min-width:30em){.aspect-ratio-ns{height:0;position:relative}.aspect-ratio--16x9-ns{padding-bottom:56.25%}.aspect-ratio--9x16-ns{padding-bottom:177.77%}.aspect-ratio--4x3-ns{padding-bottom:75%}.aspect-ratio--3x4-ns{padding-bottom:133.33%}.aspect-ratio--6x4-ns{padding-bottom:66.6%}.aspect-ratio--4x6-ns{padding-bottom:150%}.aspect-ratio--8x5-ns{padding-bottom:62.5%}.aspect-ratio--5x8-ns{padding-bottom:160%}.aspect-ratio--7x5-ns{padding-bottom:71.42%}.aspect-ratio--5x7-ns{padding-bottom:140%}.aspect-ratio--1x1-ns{padding-bottom:100%}.aspect-ratio--object-ns{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-ns{background-size:cover!important}.contain-ns{background-size:contain!important}.bg-center-ns{background-position:50%}.bg-center-ns,.bg-top-ns{background-repeat:no-repeat}.bg-top-ns{background-position:top}.bg-right-ns{background-position:100%}.bg-bottom-ns,.bg-right-ns{background-repeat:no-repeat}.bg-bottom-ns{background-position:bottom}.bg-left-ns{background-repeat:no-repeat;background-position:0}.outline-ns{outline:1px solid}.outline-transparent-ns{outline:1px solid transparent}.outline-0-ns{outline:0}.ba-ns{border-style:solid;border-width:1px}.bt-ns{border-top-style:solid;border-top-width:1px}.br-ns{border-right-style:solid;border-right-width:1px}.bb-ns{border-bottom-style:solid;border-bottom-width:1px}.bl-ns{border-left-style:solid;border-left-width:1px}.bn-ns{border-style:none;border-width:0}.br0-ns{border-radius:0}.br1-ns{border-radius:.125rem}.br2-ns{border-radius:.25rem}.br3-ns{border-radius:.5rem}.br4-ns{border-radius:1rem}.br-100-ns{border-radius:100%}.br-pill-ns{border-radius:9999px}.br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.br--top-ns{border-bottom-right-radius:0}.br--right-ns,.br--top-ns{border-bottom-left-radius:0}.br--right-ns{border-top-left-radius:0}.br--left-ns{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-ns{border-radius:inherit}.br-initial-ns{border-radius:initial}.br-unset-ns{border-radius:unset}.b--dotted-ns{border-style:dotted}.b--dashed-ns{border-style:dashed}.b--solid-ns{border-style:solid}.b--none-ns{border-style:none}.bw0-ns{border-width:0}.bw1-ns{border-width:.125rem}.bw2-ns{border-width:.25rem}.bw3-ns{border-width:.5rem}.bw4-ns{border-width:1rem}.bw5-ns{border-width:2rem}.bt-0-ns{border-top-width:0}.br-0-ns{border-right-width:0}.bb-0-ns{border-bottom-width:0}.bl-0-ns{border-left-width:0}.shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-ns{top:0}.left-0-ns{left:0}.right-0-ns{right:0}.bottom-0-ns{bottom:0}.top-1-ns{top:1rem}.left-1-ns{left:1rem}.right-1-ns{right:1rem}.bottom-1-ns{bottom:1rem}.top-2-ns{top:2rem}.left-2-ns{left:2rem}.right-2-ns{right:2rem}.bottom-2-ns{bottom:2rem}.top--1-ns{top:-1rem}.right--1-ns{right:-1rem}.bottom--1-ns{bottom:-1rem}.left--1-ns{left:-1rem}.top--2-ns{top:-2rem}.right--2-ns{right:-2rem}.bottom--2-ns{bottom:-2rem}.left--2-ns{left:-2rem}.absolute--fill-ns{top:0;right:0;bottom:0;left:0}.cl-ns{clear:left}.cr-ns{clear:right}.cb-ns{clear:both}.cn-ns{clear:none}.dn-ns{display:none}.di-ns{display:inline}.db-ns{display:block}.dib-ns{display:inline-block}.dit-ns{display:inline-table}.dt-ns{display:table}.dtc-ns{display:table-cell}.dt-row-ns{display:table-row}.dt-row-group-ns{display:table-row-group}.dt-column-ns{display:table-column}.dt-column-group-ns{display:table-column-group}.dt--fixed-ns{table-layout:fixed;width:100%}.flex-ns{display:flex}.inline-flex-ns{display:inline-flex}.flex-auto-ns{flex:1 1 auto;min-width:0;min-height:0}.flex-none-ns{flex:none}.flex-column-ns{flex-direction:column}.flex-row-ns{flex-direction:row}.flex-wrap-ns{flex-wrap:wrap}.flex-nowrap-ns{flex-wrap:nowrap}.flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.flex-column-reverse-ns{flex-direction:column-reverse}.flex-row-reverse-ns{flex-direction:row-reverse}.items-start-ns{align-items:flex-start}.items-end-ns{align-items:flex-end}.items-center-ns{align-items:center}.items-baseline-ns{align-items:baseline}.items-stretch-ns{align-items:stretch}.self-start-ns{align-self:flex-start}.self-end-ns{align-self:flex-end}.self-center-ns{align-self:center}.self-baseline-ns{align-self:baseline}.self-stretch-ns{align-self:stretch}.justify-start-ns{justify-content:flex-start}.justify-end-ns{justify-content:flex-end}.justify-center-ns{justify-content:center}.justify-between-ns{justify-content:space-between}.justify-around-ns{justify-content:space-around}.content-start-ns{align-content:flex-start}.content-end-ns{align-content:flex-end}.content-center-ns{align-content:center}.content-between-ns{align-content:space-between}.content-around-ns{align-content:space-around}.content-stretch-ns{align-content:stretch}.order-0-ns{order:0}.order-1-ns{order:1}.order-2-ns{order:2}.order-3-ns{order:3}.order-4-ns{order:4}.order-5-ns{order:5}.order-6-ns{order:6}.order-7-ns{order:7}.order-8-ns{order:8}.order-last-ns{order:99999}.flex-grow-0-ns{flex-grow:0}.flex-grow-1-ns{flex-grow:1}.flex-shrink-0-ns{flex-shrink:0}.flex-shrink-1-ns{flex-shrink:1}.fl-ns{float:left}.fl-ns,.fr-ns{_display:inline}.fr-ns{float:right}.fn-ns{float:none}.i-ns{font-style:italic}.fs-normal-ns{font-style:normal}.normal-ns{font-weight:400}.b-ns{font-weight:700}.fw1-ns{font-weight:100}.fw2-ns{font-weight:200}.fw3-ns{font-weight:300}.fw4-ns{font-weight:400}.fw5-ns{font-weight:500}.fw6-ns{font-weight:600}.fw7-ns{font-weight:700}.fw8-ns{font-weight:800}.fw9-ns{font-weight:900}.h1-ns{height:1rem}.h2-ns{height:2rem}.h3-ns{height:4rem}.h4-ns{height:8rem}.h5-ns{height:16rem}.h-25-ns{height:25%}.h-50-ns{height:50%}.h-75-ns{height:75%}.h-100-ns{height:100%}.min-h-100-ns{min-height:100%}.vh-25-ns{height:25vh}.vh-50-ns{height:50vh}.vh-75-ns{height:75vh}.vh-100-ns{height:100vh}.min-vh-100-ns{min-height:100vh}.h-auto-ns{height:auto}.h-inherit-ns{height:inherit}.tracked-ns{letter-spacing:.1em}.tracked-tight-ns{letter-spacing:-.05em}.tracked-mega-ns{letter-spacing:.25em}.lh-solid-ns{line-height:1}.lh-title-ns{line-height:1.25}.lh-copy-ns{line-height:1.5}.mw-100-ns{max-width:100%}.mw1-ns{max-width:1rem}.mw2-ns{max-width:2rem}.mw3-ns{max-width:4rem}.mw4-ns{max-width:8rem}.mw5-ns{max-width:16rem}.mw6-ns{max-width:32rem}.mw7-ns{max-width:48rem}.mw8-ns{max-width:64rem}.mw9-ns{max-width:96rem}.mw-none-ns{max-width:none}.w1-ns{width:1rem}.w2-ns{width:2rem}.w3-ns{width:4rem}.w4-ns{width:8rem}.w5-ns{width:16rem}.w-10-ns{width:10%}.w-20-ns{width:20%}.w-25-ns{width:25%}.w-30-ns{width:30%}.w-33-ns{width:33%}.w-34-ns{width:34%}.w-40-ns{width:40%}.w-50-ns{width:50%}.w-60-ns{width:60%}.w-70-ns{width:70%}.w-75-ns{width:75%}.w-80-ns{width:80%}.w-90-ns{width:90%}.w-100-ns{width:100%}.w-third-ns{width:33.33333%}.w-two-thirds-ns{width:66.66667%}.w-auto-ns{width:auto}.overflow-visible-ns{overflow:visible}.overflow-hidden-ns{overflow:hidden}.overflow-scroll-ns{overflow:scroll}.overflow-auto-ns{overflow:auto}.overflow-x-visible-ns{overflow-x:visible}.overflow-x-hidden-ns{overflow-x:hidden}.overflow-x-scroll-ns{overflow-x:scroll}.overflow-x-auto-ns{overflow-x:auto}.overflow-y-visible-ns{overflow-y:visible}.overflow-y-hidden-ns{overflow-y:hidden}.overflow-y-scroll-ns{overflow-y:scroll}.overflow-y-auto-ns{overflow-y:auto}.static-ns{position:static}.relative-ns{position:relative}.absolute-ns{position:absolute}.fixed-ns{position:fixed}.rotate-45-ns{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-ns{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-ns{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-ns{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-ns{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-ns{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-ns{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-ns{padding:0}.pa1-ns{padding:.25rem}.pa2-ns{padding:.5rem}.pa3-ns{padding:1rem}.pa4-ns{padding:2rem}.pa5-ns{padding:4rem}.pa6-ns{padding:8rem}.pa7-ns{padding:16rem}.pl0-ns{padding-left:0}.pl1-ns{padding-left:.25rem}.pl2-ns{padding-left:.5rem}.pl3-ns{padding-left:1rem}.pl4-ns{padding-left:2rem}.pl5-ns{padding-left:4rem}.pl6-ns{padding-left:8rem}.pl7-ns{padding-left:16rem}.pr0-ns{padding-right:0}.pr1-ns{padding-right:.25rem}.pr2-ns{padding-right:.5rem}.pr3-ns{padding-right:1rem}.pr4-ns{padding-right:2rem}.pr5-ns{padding-right:4rem}.pr6-ns{padding-right:8rem}.pr7-ns{padding-right:16rem}.pb0-ns{padding-bottom:0}.pb1-ns{padding-bottom:.25rem}.pb2-ns{padding-bottom:.5rem}.pb3-ns{padding-bottom:1rem}.pb4-ns{padding-bottom:2rem}.pb5-ns{padding-bottom:4rem}.pb6-ns{padding-bottom:8rem}.pb7-ns{padding-bottom:16rem}.pt0-ns{padding-top:0}.pt1-ns{padding-top:.25rem}.pt2-ns{padding-top:.5rem}.pt3-ns{padding-top:1rem}.pt4-ns{padding-top:2rem}.pt5-ns{padding-top:4rem}.pt6-ns{padding-top:8rem}.pt7-ns{padding-top:16rem}.pv0-ns{padding-top:0;padding-bottom:0}.pv1-ns{padding-top:.25rem;padding-bottom:.25rem}.pv2-ns{padding-top:.5rem;padding-bottom:.5rem}.pv3-ns{padding-top:1rem;padding-bottom:1rem}.pv4-ns{padding-top:2rem;padding-bottom:2rem}.pv5-ns{padding-top:4rem;padding-bottom:4rem}.pv6-ns{padding-top:8rem;padding-bottom:8rem}.pv7-ns{padding-top:16rem;padding-bottom:16rem}.ph0-ns{padding-left:0;padding-right:0}.ph1-ns{padding-left:.25rem;padding-right:.25rem}.ph2-ns{padding-left:.5rem;padding-right:.5rem}.ph3-ns{padding-left:1rem;padding-right:1rem}.ph4-ns{padding-left:2rem;padding-right:2rem}.ph5-ns{padding-left:4rem;padding-right:4rem}.ph6-ns{padding-left:8rem;padding-right:8rem}.ph7-ns{padding-left:16rem;padding-right:16rem}.ma0-ns{margin:0}.ma1-ns{margin:.25rem}.ma2-ns{margin:.5rem}.ma3-ns{margin:1rem}.ma4-ns{margin:2rem}.ma5-ns{margin:4rem}.ma6-ns{margin:8rem}.ma7-ns{margin:16rem}.ml0-ns{margin-left:0}.ml1-ns{margin-left:.25rem}.ml2-ns{margin-left:.5rem}.ml3-ns{margin-left:1rem}.ml4-ns{margin-left:2rem}.ml5-ns{margin-left:4rem}.ml6-ns{margin-left:8rem}.ml7-ns{margin-left:16rem}.mr0-ns{margin-right:0}.mr1-ns{margin-right:.25rem}.mr2-ns{margin-right:.5rem}.mr3-ns{margin-right:1rem}.mr4-ns{margin-right:2rem}.mr5-ns{margin-right:4rem}.mr6-ns{margin-right:8rem}.mr7-ns{margin-right:16rem}.mb0-ns{margin-bottom:0}.mb1-ns{margin-bottom:.25rem}.mb2-ns{margin-bottom:.5rem}.mb3-ns{margin-bottom:1rem}.mb4-ns{margin-bottom:2rem}.mb5-ns{margin-bottom:4rem}.mb6-ns{margin-bottom:8rem}.mb7-ns{margin-bottom:16rem}.mt0-ns{margin-top:0}.mt1-ns{margin-top:.25rem}.mt2-ns{margin-top:.5rem}.mt3-ns{margin-top:1rem}.mt4-ns{margin-top:2rem}.mt5-ns{margin-top:4rem}.mt6-ns{margin-top:8rem}.mt7-ns{margin-top:16rem}.mv0-ns{margin-top:0;margin-bottom:0}.mv1-ns{margin-top:.25rem;margin-bottom:.25rem}.mv2-ns{margin-top:.5rem;margin-bottom:.5rem}.mv3-ns{margin-top:1rem;margin-bottom:1rem}.mv4-ns{margin-top:2rem;margin-bottom:2rem}.mv5-ns{margin-top:4rem;margin-bottom:4rem}.mv6-ns{margin-top:8rem;margin-bottom:8rem}.mv7-ns{margin-top:16rem;margin-bottom:16rem}.mh0-ns{margin-left:0;margin-right:0}.mh1-ns{margin-left:.25rem;margin-right:.25rem}.mh2-ns{margin-left:.5rem;margin-right:.5rem}.mh3-ns{margin-left:1rem;margin-right:1rem}.mh4-ns{margin-left:2rem;margin-right:2rem}.mh5-ns{margin-left:4rem;margin-right:4rem}.mh6-ns{margin-left:8rem;margin-right:8rem}.mh7-ns{margin-left:16rem;margin-right:16rem}.na1-ns{margin:-.25rem}.na2-ns{margin:-.5rem}.na3-ns{margin:-1rem}.na4-ns{margin:-2rem}.na5-ns{margin:-4rem}.na6-ns{margin:-8rem}.na7-ns{margin:-16rem}.nl1-ns{margin-left:-.25rem}.nl2-ns{margin-left:-.5rem}.nl3-ns{margin-left:-1rem}.nl4-ns{margin-left:-2rem}.nl5-ns{margin-left:-4rem}.nl6-ns{margin-left:-8rem}.nl7-ns{margin-left:-16rem}.nr1-ns{margin-right:-.25rem}.nr2-ns{margin-right:-.5rem}.nr3-ns{margin-right:-1rem}.nr4-ns{margin-right:-2rem}.nr5-ns{margin-right:-4rem}.nr6-ns{margin-right:-8rem}.nr7-ns{margin-right:-16rem}.nb1-ns{margin-bottom:-.25rem}.nb2-ns{margin-bottom:-.5rem}.nb3-ns{margin-bottom:-1rem}.nb4-ns{margin-bottom:-2rem}.nb5-ns{margin-bottom:-4rem}.nb6-ns{margin-bottom:-8rem}.nb7-ns{margin-bottom:-16rem}.nt1-ns{margin-top:-.25rem}.nt2-ns{margin-top:-.5rem}.nt3-ns{margin-top:-1rem}.nt4-ns{margin-top:-2rem}.nt5-ns{margin-top:-4rem}.nt6-ns{margin-top:-8rem}.nt7-ns{margin-top:-16rem}.strike-ns{text-decoration:line-through}.underline-ns{text-decoration:underline}.no-underline-ns{text-decoration:none}.tl-ns{text-align:left}.tr-ns{text-align:right}.tc-ns{text-align:center}.tj-ns{text-align:justify}.ttc-ns{text-transform:capitalize}.ttl-ns{text-transform:lowercase}.ttu-ns{text-transform:uppercase}.ttn-ns{text-transform:none}.f-6-ns,.f-headline-ns{font-size:6rem}.f-5-ns,.f-subheadline-ns{font-size:5rem}.f1-ns{font-size:3rem}.f2-ns{font-size:2.25rem}.f3-ns{font-size:1.5rem}.f4-ns{font-size:1.25rem}.f5-ns{font-size:1rem}.f6-ns{font-size:.875rem}.f7-ns{font-size:.75rem}.measure-ns{max-width:30em}.measure-wide-ns{max-width:34em}.measure-narrow-ns{max-width:20em}.indent-ns{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-ns{font-variant:small-caps}.truncate-ns{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-ns{margin-left:auto}.center-ns,.mr-auto-ns{margin-right:auto}.ml-auto-ns{margin-left:auto}.clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-ns{white-space:normal}.nowrap-ns{white-space:nowrap}.pre-ns{white-space:pre}.v-base-ns{vertical-align:baseline}.v-mid-ns{vertical-align:middle}.v-top-ns{vertical-align:top}.v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em) and (max-width:60em){.aspect-ratio-m{height:0;position:relative}.aspect-ratio--16x9-m{padding-bottom:56.25%}.aspect-ratio--9x16-m{padding-bottom:177.77%}.aspect-ratio--4x3-m{padding-bottom:75%}.aspect-ratio--3x4-m{padding-bottom:133.33%}.aspect-ratio--6x4-m{padding-bottom:66.6%}.aspect-ratio--4x6-m{padding-bottom:150%}.aspect-ratio--8x5-m{padding-bottom:62.5%}.aspect-ratio--5x8-m{padding-bottom:160%}.aspect-ratio--7x5-m{padding-bottom:71.42%}.aspect-ratio--5x7-m{padding-bottom:140%}.aspect-ratio--1x1-m{padding-bottom:100%}.aspect-ratio--object-m{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-m{background-size:cover!important}.contain-m{background-size:contain!important}.bg-center-m{background-position:50%}.bg-center-m,.bg-top-m{background-repeat:no-repeat}.bg-top-m{background-position:top}.bg-right-m{background-position:100%}.bg-bottom-m,.bg-right-m{background-repeat:no-repeat}.bg-bottom-m{background-position:bottom}.bg-left-m{background-repeat:no-repeat;background-position:0}.outline-m{outline:1px solid}.outline-transparent-m{outline:1px solid transparent}.outline-0-m{outline:0}.ba-m{border-style:solid;border-width:1px}.bt-m{border-top-style:solid;border-top-width:1px}.br-m{border-right-style:solid;border-right-width:1px}.bb-m{border-bottom-style:solid;border-bottom-width:1px}.bl-m{border-left-style:solid;border-left-width:1px}.bn-m{border-style:none;border-width:0}.br0-m{border-radius:0}.br1-m{border-radius:.125rem}.br2-m{border-radius:.25rem}.br3-m{border-radius:.5rem}.br4-m{border-radius:1rem}.br-100-m{border-radius:100%}.br-pill-m{border-radius:9999px}.br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.br--top-m{border-bottom-right-radius:0}.br--right-m,.br--top-m{border-bottom-left-radius:0}.br--right-m{border-top-left-radius:0}.br--left-m{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-m{border-radius:inherit}.br-initial-m{border-radius:initial}.br-unset-m{border-radius:unset}.b--dotted-m{border-style:dotted}.b--dashed-m{border-style:dashed}.b--solid-m{border-style:solid}.b--none-m{border-style:none}.bw0-m{border-width:0}.bw1-m{border-width:.125rem}.bw2-m{border-width:.25rem}.bw3-m{border-width:.5rem}.bw4-m{border-width:1rem}.bw5-m{border-width:2rem}.bt-0-m{border-top-width:0}.br-0-m{border-right-width:0}.bb-0-m{border-bottom-width:0}.bl-0-m{border-left-width:0}.shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-m{top:0}.left-0-m{left:0}.right-0-m{right:0}.bottom-0-m{bottom:0}.top-1-m{top:1rem}.left-1-m{left:1rem}.right-1-m{right:1rem}.bottom-1-m{bottom:1rem}.top-2-m{top:2rem}.left-2-m{left:2rem}.right-2-m{right:2rem}.bottom-2-m{bottom:2rem}.top--1-m{top:-1rem}.right--1-m{right:-1rem}.bottom--1-m{bottom:-1rem}.left--1-m{left:-1rem}.top--2-m{top:-2rem}.right--2-m{right:-2rem}.bottom--2-m{bottom:-2rem}.left--2-m{left:-2rem}.absolute--fill-m{top:0;right:0;bottom:0;left:0}.cl-m{clear:left}.cr-m{clear:right}.cb-m{clear:both}.cn-m{clear:none}.dn-m{display:none}.di-m{display:inline}.db-m{display:block}.dib-m{display:inline-block}.dit-m{display:inline-table}.dt-m{display:table}.dtc-m{display:table-cell}.dt-row-m{display:table-row}.dt-row-group-m{display:table-row-group}.dt-column-m{display:table-column}.dt-column-group-m{display:table-column-group}.dt--fixed-m{table-layout:fixed;width:100%}.flex-m{display:flex}.inline-flex-m{display:inline-flex}.flex-auto-m{flex:1 1 auto;min-width:0;min-height:0}.flex-none-m{flex:none}.flex-column-m{flex-direction:column}.flex-row-m{flex-direction:row}.flex-wrap-m{flex-wrap:wrap}.flex-nowrap-m{flex-wrap:nowrap}.flex-wrap-reverse-m{flex-wrap:wrap-reverse}.flex-column-reverse-m{flex-direction:column-reverse}.flex-row-reverse-m{flex-direction:row-reverse}.items-start-m{align-items:flex-start}.items-end-m{align-items:flex-end}.items-center-m{align-items:center}.items-baseline-m{align-items:baseline}.items-stretch-m{align-items:stretch}.self-start-m{align-self:flex-start}.self-end-m{align-self:flex-end}.self-center-m{align-self:center}.self-baseline-m{align-self:baseline}.self-stretch-m{align-self:stretch}.justify-start-m{justify-content:flex-start}.justify-end-m{justify-content:flex-end}.justify-center-m{justify-content:center}.justify-between-m{justify-content:space-between}.justify-around-m{justify-content:space-around}.content-start-m{align-content:flex-start}.content-end-m{align-content:flex-end}.content-center-m{align-content:center}.content-between-m{align-content:space-between}.content-around-m{align-content:space-around}.content-stretch-m{align-content:stretch}.order-0-m{order:0}.order-1-m{order:1}.order-2-m{order:2}.order-3-m{order:3}.order-4-m{order:4}.order-5-m{order:5}.order-6-m{order:6}.order-7-m{order:7}.order-8-m{order:8}.order-last-m{order:99999}.flex-grow-0-m{flex-grow:0}.flex-grow-1-m{flex-grow:1}.flex-shrink-0-m{flex-shrink:0}.flex-shrink-1-m{flex-shrink:1}.fl-m{float:left}.fl-m,.fr-m{_display:inline}.fr-m{float:right}.fn-m{float:none}.i-m{font-style:italic}.fs-normal-m{font-style:normal}.normal-m{font-weight:400}.b-m{font-weight:700}.fw1-m{font-weight:100}.fw2-m{font-weight:200}.fw3-m{font-weight:300}.fw4-m{font-weight:400}.fw5-m{font-weight:500}.fw6-m{font-weight:600}.fw7-m{font-weight:700}.fw8-m{font-weight:800}.fw9-m{font-weight:900}.h1-m{height:1rem}.h2-m{height:2rem}.h3-m{height:4rem}.h4-m{height:8rem}.h5-m{height:16rem}.h-25-m{height:25%}.h-50-m{height:50%}.h-75-m{height:75%}.h-100-m{height:100%}.min-h-100-m{min-height:100%}.vh-25-m{height:25vh}.vh-50-m{height:50vh}.vh-75-m{height:75vh}.vh-100-m{height:100vh}.min-vh-100-m{min-height:100vh}.h-auto-m{height:auto}.h-inherit-m{height:inherit}.tracked-m{letter-spacing:.1em}.tracked-tight-m{letter-spacing:-.05em}.tracked-mega-m{letter-spacing:.25em}.lh-solid-m{line-height:1}.lh-title-m{line-height:1.25}.lh-copy-m{line-height:1.5}.mw-100-m{max-width:100%}.mw1-m{max-width:1rem}.mw2-m{max-width:2rem}.mw3-m{max-width:4rem}.mw4-m{max-width:8rem}.mw5-m{max-width:16rem}.mw6-m{max-width:32rem}.mw7-m{max-width:48rem}.mw8-m{max-width:64rem}.mw9-m{max-width:96rem}.mw-none-m{max-width:none}.w1-m{width:1rem}.w2-m{width:2rem}.w3-m{width:4rem}.w4-m{width:8rem}.w5-m{width:16rem}.w-10-m{width:10%}.w-20-m{width:20%}.w-25-m{width:25%}.w-30-m{width:30%}.w-33-m{width:33%}.w-34-m{width:34%}.w-40-m{width:40%}.w-50-m{width:50%}.w-60-m{width:60%}.w-70-m{width:70%}.w-75-m{width:75%}.w-80-m{width:80%}.w-90-m{width:90%}.w-100-m{width:100%}.w-third-m{width:33.33333%}.w-two-thirds-m{width:66.66667%}.w-auto-m{width:auto}.overflow-visible-m{overflow:visible}.overflow-hidden-m{overflow:hidden}.overflow-scroll-m{overflow:scroll}.overflow-auto-m{overflow:auto}.overflow-x-visible-m{overflow-x:visible}.overflow-x-hidden-m{overflow-x:hidden}.overflow-x-scroll-m{overflow-x:scroll}.overflow-x-auto-m{overflow-x:auto}.overflow-y-visible-m{overflow-y:visible}.overflow-y-hidden-m{overflow-y:hidden}.overflow-y-scroll-m{overflow-y:scroll}.overflow-y-auto-m{overflow-y:auto}.static-m{position:static}.relative-m{position:relative}.absolute-m{position:absolute}.fixed-m{position:fixed}.rotate-45-m{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-m{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-m{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-m{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-m{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-m{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-m{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-m{padding:0}.pa1-m{padding:.25rem}.pa2-m{padding:.5rem}.pa3-m{padding:1rem}.pa4-m{padding:2rem}.pa5-m{padding:4rem}.pa6-m{padding:8rem}.pa7-m{padding:16rem}.pl0-m{padding-left:0}.pl1-m{padding-left:.25rem}.pl2-m{padding-left:.5rem}.pl3-m{padding-left:1rem}.pl4-m{padding-left:2rem}.pl5-m{padding-left:4rem}.pl6-m{padding-left:8rem}.pl7-m{padding-left:16rem}.pr0-m{padding-right:0}.pr1-m{padding-right:.25rem}.pr2-m{padding-right:.5rem}.pr3-m{padding-right:1rem}.pr4-m{padding-right:2rem}.pr5-m{padding-right:4rem}.pr6-m{padding-right:8rem}.pr7-m{padding-right:16rem}.pb0-m{padding-bottom:0}.pb1-m{padding-bottom:.25rem}.pb2-m{padding-bottom:.5rem}.pb3-m{padding-bottom:1rem}.pb4-m{padding-bottom:2rem}.pb5-m{padding-bottom:4rem}.pb6-m{padding-bottom:8rem}.pb7-m{padding-bottom:16rem}.pt0-m{padding-top:0}.pt1-m{padding-top:.25rem}.pt2-m{padding-top:.5rem}.pt3-m{padding-top:1rem}.pt4-m{padding-top:2rem}.pt5-m{padding-top:4rem}.pt6-m{padding-top:8rem}.pt7-m{padding-top:16rem}.pv0-m{padding-top:0;padding-bottom:0}.pv1-m{padding-top:.25rem;padding-bottom:.25rem}.pv2-m{padding-top:.5rem;padding-bottom:.5rem}.pv3-m{padding-top:1rem;padding-bottom:1rem}.pv4-m{padding-top:2rem;padding-bottom:2rem}.pv5-m{padding-top:4rem;padding-bottom:4rem}.pv6-m{padding-top:8rem;padding-bottom:8rem}.pv7-m{padding-top:16rem;padding-bottom:16rem}.ph0-m{padding-left:0;padding-right:0}.ph1-m{padding-left:.25rem;padding-right:.25rem}.ph2-m{padding-left:.5rem;padding-right:.5rem}.ph3-m{padding-left:1rem;padding-right:1rem}.ph4-m{padding-left:2rem;padding-right:2rem}.ph5-m{padding-left:4rem;padding-right:4rem}.ph6-m{padding-left:8rem;padding-right:8rem}.ph7-m{padding-left:16rem;padding-right:16rem}.ma0-m{margin:0}.ma1-m{margin:.25rem}.ma2-m{margin:.5rem}.ma3-m{margin:1rem}.ma4-m{margin:2rem}.ma5-m{margin:4rem}.ma6-m{margin:8rem}.ma7-m{margin:16rem}.ml0-m{margin-left:0}.ml1-m{margin-left:.25rem}.ml2-m{margin-left:.5rem}.ml3-m{margin-left:1rem}.ml4-m{margin-left:2rem}.ml5-m{margin-left:4rem}.ml6-m{margin-left:8rem}.ml7-m{margin-left:16rem}.mr0-m{margin-right:0}.mr1-m{margin-right:.25rem}.mr2-m{margin-right:.5rem}.mr3-m{margin-right:1rem}.mr4-m{margin-right:2rem}.mr5-m{margin-right:4rem}.mr6-m{margin-right:8rem}.mr7-m{margin-right:16rem}.mb0-m{margin-bottom:0}.mb1-m{margin-bottom:.25rem}.mb2-m{margin-bottom:.5rem}.mb3-m{margin-bottom:1rem}.mb4-m{margin-bottom:2rem}.mb5-m{margin-bottom:4rem}.mb6-m{margin-bottom:8rem}.mb7-m{margin-bottom:16rem}.mt0-m{margin-top:0}.mt1-m{margin-top:.25rem}.mt2-m{margin-top:.5rem}.mt3-m{margin-top:1rem}.mt4-m{margin-top:2rem}.mt5-m{margin-top:4rem}.mt6-m{margin-top:8rem}.mt7-m{margin-top:16rem}.mv0-m{margin-top:0;margin-bottom:0}.mv1-m{margin-top:.25rem;margin-bottom:.25rem}.mv2-m{margin-top:.5rem;margin-bottom:.5rem}.mv3-m{margin-top:1rem;margin-bottom:1rem}.mv4-m{margin-top:2rem;margin-bottom:2rem}.mv5-m{margin-top:4rem;margin-bottom:4rem}.mv6-m{margin-top:8rem;margin-bottom:8rem}.mv7-m{margin-top:16rem;margin-bottom:16rem}.mh0-m{margin-left:0;margin-right:0}.mh1-m{margin-left:.25rem;margin-right:.25rem}.mh2-m{margin-left:.5rem;margin-right:.5rem}.mh3-m{margin-left:1rem;margin-right:1rem}.mh4-m{margin-left:2rem;margin-right:2rem}.mh5-m{margin-left:4rem;margin-right:4rem}.mh6-m{margin-left:8rem;margin-right:8rem}.mh7-m{margin-left:16rem;margin-right:16rem}.na1-m{margin:-.25rem}.na2-m{margin:-.5rem}.na3-m{margin:-1rem}.na4-m{margin:-2rem}.na5-m{margin:-4rem}.na6-m{margin:-8rem}.na7-m{margin:-16rem}.nl1-m{margin-left:-.25rem}.nl2-m{margin-left:-.5rem}.nl3-m{margin-left:-1rem}.nl4-m{margin-left:-2rem}.nl5-m{margin-left:-4rem}.nl6-m{margin-left:-8rem}.nl7-m{margin-left:-16rem}.nr1-m{margin-right:-.25rem}.nr2-m{margin-right:-.5rem}.nr3-m{margin-right:-1rem}.nr4-m{margin-right:-2rem}.nr5-m{margin-right:-4rem}.nr6-m{margin-right:-8rem}.nr7-m{margin-right:-16rem}.nb1-m{margin-bottom:-.25rem}.nb2-m{margin-bottom:-.5rem}.nb3-m{margin-bottom:-1rem}.nb4-m{margin-bottom:-2rem}.nb5-m{margin-bottom:-4rem}.nb6-m{margin-bottom:-8rem}.nb7-m{margin-bottom:-16rem}.nt1-m{margin-top:-.25rem}.nt2-m{margin-top:-.5rem}.nt3-m{margin-top:-1rem}.nt4-m{margin-top:-2rem}.nt5-m{margin-top:-4rem}.nt6-m{margin-top:-8rem}.nt7-m{margin-top:-16rem}.strike-m{text-decoration:line-through}.underline-m{text-decoration:underline}.no-underline-m{text-decoration:none}.tl-m{text-align:left}.tr-m{text-align:right}.tc-m{text-align:center}.tj-m{text-align:justify}.ttc-m{text-transform:capitalize}.ttl-m{text-transform:lowercase}.ttu-m{text-transform:uppercase}.ttn-m{text-transform:none}.f-6-m,.f-headline-m{font-size:6rem}.f-5-m,.f-subheadline-m{font-size:5rem}.f1-m{font-size:3rem}.f2-m{font-size:2.25rem}.f3-m{font-size:1.5rem}.f4-m{font-size:1.25rem}.f5-m{font-size:1rem}.f6-m{font-size:.875rem}.f7-m{font-size:.75rem}.measure-m{max-width:30em}.measure-wide-m{max-width:34em}.measure-narrow-m{max-width:20em}.indent-m{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-m{font-variant:small-caps}.truncate-m{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-m{margin-left:auto}.center-m,.mr-auto-m{margin-right:auto}.ml-auto-m{margin-left:auto}.clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-m{white-space:normal}.nowrap-m{white-space:nowrap}.pre-m{white-space:pre}.v-base-m{vertical-align:baseline}.v-mid-m{vertical-align:middle}.v-top-m{vertical-align:top}.v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.aspect-ratio-l{height:0;position:relative}.aspect-ratio--16x9-l{padding-bottom:56.25%}.aspect-ratio--9x16-l{padding-bottom:177.77%}.aspect-ratio--4x3-l{padding-bottom:75%}.aspect-ratio--3x4-l{padding-bottom:133.33%}.aspect-ratio--6x4-l{padding-bottom:66.6%}.aspect-ratio--4x6-l{padding-bottom:150%}.aspect-ratio--8x5-l{padding-bottom:62.5%}.aspect-ratio--5x8-l{padding-bottom:160%}.aspect-ratio--7x5-l{padding-bottom:71.42%}.aspect-ratio--5x7-l{padding-bottom:140%}.aspect-ratio--1x1-l{padding-bottom:100%}.aspect-ratio--object-l{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:100}.cover-l{background-size:cover!important}.contain-l{background-size:contain!important}.bg-center-l{background-position:50%}.bg-center-l,.bg-top-l{background-repeat:no-repeat}.bg-top-l{background-position:top}.bg-right-l{background-position:100%}.bg-bottom-l,.bg-right-l{background-repeat:no-repeat}.bg-bottom-l{background-position:bottom}.bg-left-l{background-repeat:no-repeat;background-position:0}.outline-l{outline:1px solid}.outline-transparent-l{outline:1px solid transparent}.outline-0-l{outline:0}.ba-l{border-style:solid;border-width:1px}.bt-l{border-top-style:solid;border-top-width:1px}.br-l{border-right-style:solid;border-right-width:1px}.bb-l{border-bottom-style:solid;border-bottom-width:1px}.bl-l{border-left-style:solid;border-left-width:1px}.bn-l{border-style:none;border-width:0}.br0-l{border-radius:0}.br1-l{border-radius:.125rem}.br2-l{border-radius:.25rem}.br3-l{border-radius:.5rem}.br4-l{border-radius:1rem}.br-100-l{border-radius:100%}.br-pill-l{border-radius:9999px}.br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.br--top-l{border-bottom-right-radius:0}.br--right-l,.br--top-l{border-bottom-left-radius:0}.br--right-l{border-top-left-radius:0}.br--left-l{border-top-right-radius:0;border-bottom-right-radius:0}.br-inherit-l{border-radius:inherit}.br-initial-l{border-radius:initial}.br-unset-l{border-radius:unset}.b--dotted-l{border-style:dotted}.b--dashed-l{border-style:dashed}.b--solid-l{border-style:solid}.b--none-l{border-style:none}.bw0-l{border-width:0}.bw1-l{border-width:.125rem}.bw2-l{border-width:.25rem}.bw3-l{border-width:.5rem}.bw4-l{border-width:1rem}.bw5-l{border-width:2rem}.bt-0-l{border-top-width:0}.br-0-l{border-right-width:0}.bb-0-l{border-bottom-width:0}.bl-0-l{border-left-width:0}.shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}.top-0-l{top:0}.left-0-l{left:0}.right-0-l{right:0}.bottom-0-l{bottom:0}.top-1-l{top:1rem}.left-1-l{left:1rem}.right-1-l{right:1rem}.bottom-1-l{bottom:1rem}.top-2-l{top:2rem}.left-2-l{left:2rem}.right-2-l{right:2rem}.bottom-2-l{bottom:2rem}.top--1-l{top:-1rem}.right--1-l{right:-1rem}.bottom--1-l{bottom:-1rem}.left--1-l{left:-1rem}.top--2-l{top:-2rem}.right--2-l{right:-2rem}.bottom--2-l{bottom:-2rem}.left--2-l{left:-2rem}.absolute--fill-l{top:0;right:0;bottom:0;left:0}.cl-l{clear:left}.cr-l{clear:right}.cb-l{clear:both}.cn-l{clear:none}.dn-l{display:none}.di-l{display:inline}.db-l{display:block}.dib-l{display:inline-block}.dit-l{display:inline-table}.dt-l{display:table}.dtc-l{display:table-cell}.dt-row-l{display:table-row}.dt-row-group-l{display:table-row-group}.dt-column-l{display:table-column}.dt-column-group-l{display:table-column-group}.dt--fixed-l{table-layout:fixed;width:100%}.flex-l{display:flex}.inline-flex-l{display:inline-flex}.flex-auto-l{flex:1 1 auto;min-width:0;min-height:0}.flex-none-l{flex:none}.flex-column-l{flex-direction:column}.flex-row-l{flex-direction:row}.flex-wrap-l{flex-wrap:wrap}.flex-nowrap-l{flex-wrap:nowrap}.flex-wrap-reverse-l{flex-wrap:wrap-reverse}.flex-column-reverse-l{flex-direction:column-reverse}.flex-row-reverse-l{flex-direction:row-reverse}.items-start-l{align-items:flex-start}.items-end-l{align-items:flex-end}.items-center-l{align-items:center}.items-baseline-l{align-items:baseline}.items-stretch-l{align-items:stretch}.self-start-l{align-self:flex-start}.self-end-l{align-self:flex-end}.self-center-l{align-self:center}.self-baseline-l{align-self:baseline}.self-stretch-l{align-self:stretch}.justify-start-l{justify-content:flex-start}.justify-end-l{justify-content:flex-end}.justify-center-l{justify-content:center}.justify-between-l{justify-content:space-between}.justify-around-l{justify-content:space-around}.content-start-l{align-content:flex-start}.content-end-l{align-content:flex-end}.content-center-l{align-content:center}.content-between-l{align-content:space-between}.content-around-l{align-content:space-around}.content-stretch-l{align-content:stretch}.order-0-l{order:0}.order-1-l{order:1}.order-2-l{order:2}.order-3-l{order:3}.order-4-l{order:4}.order-5-l{order:5}.order-6-l{order:6}.order-7-l{order:7}.order-8-l{order:8}.order-last-l{order:99999}.flex-grow-0-l{flex-grow:0}.flex-grow-1-l{flex-grow:1}.flex-shrink-0-l{flex-shrink:0}.flex-shrink-1-l{flex-shrink:1}.fl-l{float:left}.fl-l,.fr-l{_display:inline}.fr-l{float:right}.fn-l{float:none}.i-l{font-style:italic}.fs-normal-l{font-style:normal}.normal-l{font-weight:400}.b-l{font-weight:700}.fw1-l{font-weight:100}.fw2-l{font-weight:200}.fw3-l{font-weight:300}.fw4-l{font-weight:400}.fw5-l{font-weight:500}.fw6-l{font-weight:600}.fw7-l{font-weight:700}.fw8-l{font-weight:800}.fw9-l{font-weight:900}.h1-l{height:1rem}.h2-l{height:2rem}.h3-l{height:4rem}.h4-l{height:8rem}.h5-l{height:16rem}.h-25-l{height:25%}.h-50-l{height:50%}.h-75-l{height:75%}.h-100-l{height:100%}.min-h-100-l{min-height:100%}.vh-25-l{height:25vh}.vh-50-l{height:50vh}.vh-75-l{height:75vh}.vh-100-l{height:100vh}.min-vh-100-l{min-height:100vh}.h-auto-l{height:auto}.h-inherit-l{height:inherit}.tracked-l{letter-spacing:.1em}.tracked-tight-l{letter-spacing:-.05em}.tracked-mega-l{letter-spacing:.25em}.lh-solid-l{line-height:1}.lh-title-l{line-height:1.25}.lh-copy-l{line-height:1.5}.mw-100-l{max-width:100%}.mw1-l{max-width:1rem}.mw2-l{max-width:2rem}.mw3-l{max-width:4rem}.mw4-l{max-width:8rem}.mw5-l{max-width:16rem}.mw6-l{max-width:32rem}.mw7-l{max-width:48rem}.mw8-l{max-width:64rem}.mw9-l{max-width:96rem}.mw-none-l{max-width:none}.w1-l{width:1rem}.w2-l{width:2rem}.w3-l{width:4rem}.w4-l{width:8rem}.w5-l{width:16rem}.w-10-l{width:10%}.w-20-l{width:20%}.w-25-l{width:25%}.w-30-l{width:30%}.w-33-l{width:33%}.w-34-l{width:34%}.w-40-l{width:40%}.w-50-l{width:50%}.w-60-l{width:60%}.w-70-l{width:70%}.w-75-l{width:75%}.w-80-l{width:80%}.w-90-l{width:90%}.w-100-l{width:100%}.w-third-l{width:33.33333%}.w-two-thirds-l{width:66.66667%}.w-auto-l{width:auto}.overflow-visible-l{overflow:visible}.overflow-hidden-l{overflow:hidden}.overflow-scroll-l{overflow:scroll}.overflow-auto-l{overflow:auto}.overflow-x-visible-l{overflow-x:visible}.overflow-x-hidden-l{overflow-x:hidden}.overflow-x-scroll-l{overflow-x:scroll}.overflow-x-auto-l{overflow-x:auto}.overflow-y-visible-l{overflow-y:visible}.overflow-y-hidden-l{overflow-y:hidden}.overflow-y-scroll-l{overflow-y:scroll}.overflow-y-auto-l{overflow-y:auto}.static-l{position:static}.relative-l{position:relative}.absolute-l{position:absolute}.fixed-l{position:fixed}.rotate-45-l{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.rotate-90-l{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-135-l{-webkit-transform:rotate(135deg);transform:rotate(135deg)}.rotate-180-l{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-225-l{-webkit-transform:rotate(225deg);transform:rotate(225deg)}.rotate-270-l{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.rotate-315-l{-webkit-transform:rotate(315deg);transform:rotate(315deg)}.pa0-l{padding:0}.pa1-l{padding:.25rem}.pa2-l{padding:.5rem}.pa3-l{padding:1rem}.pa4-l{padding:2rem}.pa5-l{padding:4rem}.pa6-l{padding:8rem}.pa7-l{padding:16rem}.pl0-l{padding-left:0}.pl1-l{padding-left:.25rem}.pl2-l{padding-left:.5rem}.pl3-l{padding-left:1rem}.pl4-l{padding-left:2rem}.pl5-l{padding-left:4rem}.pl6-l{padding-left:8rem}.pl7-l{padding-left:16rem}.pr0-l{padding-right:0}.pr1-l{padding-right:.25rem}.pr2-l{padding-right:.5rem}.pr3-l{padding-right:1rem}.pr4-l{padding-right:2rem}.pr5-l{padding-right:4rem}.pr6-l{padding-right:8rem}.pr7-l{padding-right:16rem}.pb0-l{padding-bottom:0}.pb1-l{padding-bottom:.25rem}.pb2-l{padding-bottom:.5rem}.pb3-l{padding-bottom:1rem}.pb4-l{padding-bottom:2rem}.pb5-l{padding-bottom:4rem}.pb6-l{padding-bottom:8rem}.pb7-l{padding-bottom:16rem}.pt0-l{padding-top:0}.pt1-l{padding-top:.25rem}.pt2-l{padding-top:.5rem}.pt3-l{padding-top:1rem}.pt4-l{padding-top:2rem}.pt5-l{padding-top:4rem}.pt6-l{padding-top:8rem}.pt7-l{padding-top:16rem}.pv0-l{padding-top:0;padding-bottom:0}.pv1-l{padding-top:.25rem;padding-bottom:.25rem}.pv2-l{padding-top:.5rem;padding-bottom:.5rem}.pv3-l{padding-top:1rem;padding-bottom:1rem}.pv4-l{padding-top:2rem;padding-bottom:2rem}.pv5-l{padding-top:4rem;padding-bottom:4rem}.pv6-l{padding-top:8rem;padding-bottom:8rem}.pv7-l{padding-top:16rem;padding-bottom:16rem}.ph0-l{padding-left:0;padding-right:0}.ph1-l{padding-left:.25rem;padding-right:.25rem}.ph2-l{padding-left:.5rem;padding-right:.5rem}.ph3-l{padding-left:1rem;padding-right:1rem}.ph4-l{padding-left:2rem;padding-right:2rem}.ph5-l{padding-left:4rem;padding-right:4rem}.ph6-l{padding-left:8rem;padding-right:8rem}.ph7-l{padding-left:16rem;padding-right:16rem}.ma0-l{margin:0}.ma1-l{margin:.25rem}.ma2-l{margin:.5rem}.ma3-l{margin:1rem}.ma4-l{margin:2rem}.ma5-l{margin:4rem}.ma6-l{margin:8rem}.ma7-l{margin:16rem}.ml0-l{margin-left:0}.ml1-l{margin-left:.25rem}.ml2-l{margin-left:.5rem}.ml3-l{margin-left:1rem}.ml4-l{margin-left:2rem}.ml5-l{margin-left:4rem}.ml6-l{margin-left:8rem}.ml7-l{margin-left:16rem}.mr0-l{margin-right:0}.mr1-l{margin-right:.25rem}.mr2-l{margin-right:.5rem}.mr3-l{margin-right:1rem}.mr4-l{margin-right:2rem}.mr5-l{margin-right:4rem}.mr6-l{margin-right:8rem}.mr7-l{margin-right:16rem}.mb0-l{margin-bottom:0}.mb1-l{margin-bottom:.25rem}.mb2-l{margin-bottom:.5rem}.mb3-l{margin-bottom:1rem}.mb4-l{margin-bottom:2rem}.mb5-l{margin-bottom:4rem}.mb6-l{margin-bottom:8rem}.mb7-l{margin-bottom:16rem}.mt0-l{margin-top:0}.mt1-l{margin-top:.25rem}.mt2-l{margin-top:.5rem}.mt3-l{margin-top:1rem}.mt4-l{margin-top:2rem}.mt5-l{margin-top:4rem}.mt6-l{margin-top:8rem}.mt7-l{margin-top:16rem}.mv0-l{margin-top:0;margin-bottom:0}.mv1-l{margin-top:.25rem;margin-bottom:.25rem}.mv2-l{margin-top:.5rem;margin-bottom:.5rem}.mv3-l{margin-top:1rem;margin-bottom:1rem}.mv4-l{margin-top:2rem;margin-bottom:2rem}.mv5-l{margin-top:4rem;margin-bottom:4rem}.mv6-l{margin-top:8rem;margin-bottom:8rem}.mv7-l{margin-top:16rem;margin-bottom:16rem}.mh0-l{margin-left:0;margin-right:0}.mh1-l{margin-left:.25rem;margin-right:.25rem}.mh2-l{margin-left:.5rem;margin-right:.5rem}.mh3-l{margin-left:1rem;margin-right:1rem}.mh4-l{margin-left:2rem;margin-right:2rem}.mh5-l{margin-left:4rem;margin-right:4rem}.mh6-l{margin-left:8rem;margin-right:8rem}.mh7-l{margin-left:16rem;margin-right:16rem}.na1-l{margin:-.25rem}.na2-l{margin:-.5rem}.na3-l{margin:-1rem}.na4-l{margin:-2rem}.na5-l{margin:-4rem}.na6-l{margin:-8rem}.na7-l{margin:-16rem}.nl1-l{margin-left:-.25rem}.nl2-l{margin-left:-.5rem}.nl3-l{margin-left:-1rem}.nl4-l{margin-left:-2rem}.nl5-l{margin-left:-4rem}.nl6-l{margin-left:-8rem}.nl7-l{margin-left:-16rem}.nr1-l{margin-right:-.25rem}.nr2-l{margin-right:-.5rem}.nr3-l{margin-right:-1rem}.nr4-l{margin-right:-2rem}.nr5-l{margin-right:-4rem}.nr6-l{margin-right:-8rem}.nr7-l{margin-right:-16rem}.nb1-l{margin-bottom:-.25rem}.nb2-l{margin-bottom:-.5rem}.nb3-l{margin-bottom:-1rem}.nb4-l{margin-bottom:-2rem}.nb5-l{margin-bottom:-4rem}.nb6-l{margin-bottom:-8rem}.nb7-l{margin-bottom:-16rem}.nt1-l{margin-top:-.25rem}.nt2-l{margin-top:-.5rem}.nt3-l{margin-top:-1rem}.nt4-l{margin-top:-2rem}.nt5-l{margin-top:-4rem}.nt6-l{margin-top:-8rem}.nt7-l{margin-top:-16rem}.strike-l{text-decoration:line-through}.underline-l{text-decoration:underline}.no-underline-l{text-decoration:none}.tl-l{text-align:left}.tr-l{text-align:right}.tc-l{text-align:center}.tj-l{text-align:justify}.ttc-l{text-transform:capitalize}.ttl-l{text-transform:lowercase}.ttu-l{text-transform:uppercase}.ttn-l{text-transform:none}.f-6-l,.f-headline-l{font-size:6rem}.f-5-l,.f-subheadline-l{font-size:5rem}.f1-l{font-size:3rem}.f2-l{font-size:2.25rem}.f3-l{font-size:1.5rem}.f4-l{font-size:1.25rem}.f5-l{font-size:1rem}.f6-l{font-size:.875rem}.f7-l{font-size:.75rem}.measure-l{max-width:30em}.measure-wide-l{max-width:34em}.measure-narrow-l{max-width:20em}.indent-l{text-indent:1em;margin-top:0;margin-bottom:0}.small-caps-l{font-variant:small-caps}.truncate-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.center-l{margin-left:auto}.center-l,.mr-auto-l{margin-right:auto}.ml-auto-l{margin-left:auto}.clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.ws-normal-l{white-space:normal}.nowrap-l{white-space:nowrap}.pre-l{white-space:pre}.v-base-l{vertical-align:baseline}.v-mid-l{vertical-align:middle}.v-top-l{vertical-align:top}.v-btm-l{vertical-align:bottom}}"
  },
  {
    "path": "documentation/authentication.md",
    "content": "# Authentication & Authorization.\n\nASP.NET Core has built-in support for authentication and authorization. Falco includes some prebuilt, configurable handlers for common scenarios.\n\n> Review the [docs](https://docs.microsoft.com/en-us/aspnet/core/security/authentication) for specific implementation details.\n\n## Secure Resources\n\n### Allow only authenticated access\n\n```fsharp\nopen Falco\n\nlet authScheme = \"some.secure.scheme\"\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuth : HttpHandler =\n        Response.ofPlainText \"hello authenticated user\"\n\n    Request.ifAuthenticated authScheme handleAuth\n```\n\n\n### Allow only non-authenticated access\n\n```fsharp\nopen Falco\n\nlet anonResourceOnlyHandler : HttpHandler =\n    let handleAnon : HttpHandler =\n        Response.ofPlainText \"hello anonymous\"\n\n    Request.ifNotAuthenticated authScheme handleAnon\n```\n\n\n### Allow only authenticated access when in certain role(s)\n\n```fsharp\nopen Falco\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuthInRole : HttpHandler =\n        Response.ofPlainText \"hello admin\"\n\n    let rolesAllowed = [ \"Admin\" ]\n\n    Request.ifAuthenticatedInRole authScheme rolesAllowed handleAuthInRole\n```\n\n\n### Allow only authenticated acces with a certain scope\n\n```fsharp\nopen Falco\n\nlet secureResourceHandler : HttpHandler =\n    let handleAuthHasScope : HttpHandler =\n        Response.ofPlainText \"user1, user2, user3\"\n\n    let issuer = \"https://oauth2issuer.com\"\n    let scope = \"read:users\"\n\n    Request.ifAuthenticatedWithScope authScheme issuer scope handleAuthHasScope\n```\n\n### Terminate authenticated session\n\n```fsharp\nopen Falco\n\nlet logOut : HttpHandler =\n    let authScheme = \"...\"\n    let redirectTo = \"/login\"\n\n    Response.signOutAndRedirect authScheme redirectTo\n```\n\n[Next: Host Configuration](host-configuration.md)\n"
  },
  {
    "path": "documentation/cross-site-request-forgery.md",
    "content": "# Cross-site Scripting (XSS) Attacks\n\nCross-site scripting attacks are extremely common since they are quite simple to carry out. Fortunately, protecting against them is as easy as performing them.\n\nThe [Microsoft.AspNetCore.Antiforgery](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery) package provides the required utilities to easily protect yourself against such attacks.\n\n## Activating Antiforgery Protection\n\nTo use the Falco Xsrf helpers, ensure that the `Antiforgery` service has been [registered](https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery).\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\n// ^-- this import enables antiforgery activation\n\nlet endpoints =\n    [\n        // endpoints...\n    ]\n\nlet bldr = WebApplication.CreateBuilder()\n\nbldr.Services\n    .AddAntiforgery()\n\nlet wapp = WebApplication.Create()\n\nwapp.UseAntiforgery()\n    // ^-- activate Antiforgery before routing\n    .UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n\n```\n\n## Falco XSRF Support\n\nFalco provides a few handlers via `Falco.Security.Xsrf`:\n\n```fsharp\nopen Falco.Markup\nopen Falco.Security\n\nlet formView token =\n    _html [] [\n        _body [] [\n            _form [ _methodPost_ ] [\n                // using the CSRF HTML helper, recommended to include as first\n                // form element\n                Xsrf.antiforgeryInput token\n                _control \"first_name\" [] [ _text \"First Name\" ]\n                _control \"first_name\" [] [ _text \"First Name\" ]\n                _input [ _typeSubmit_ ]\n            ]\n        ]\n    ]\n\n// A handler that demonstrates obtaining a\n// CSRF token and applying it to a view\nlet csrfViewHandler : HttpHandler =\n    Response.ofHtmlCsrf formView\n\n// A handler that demonstrates validating\n// the request's CSRF token\nlet mapFormSecureHandler : HttpHandler =\n    let mapPerson (form : FormData) =\n        { FirstName = form?first_name.AsString()\n          LastName = form?last_name.AsString }\n\n    let handleInvalid : HttpHandler =\n        Response.withStatusCode 400\n        >> Response.ofEmpty\n\n    Request.mapFormSecure mapPerson Response.ofJson handleInvalid\n```\n\n[Next: Authentication](authentication.md)\n"
  },
  {
    "path": "documentation/deployment.md",
    "content": "# Deployment\n\nOne of the key features of Falco is that it contains little to no \"magic\" (i.e., no hidden reflection or dynamic code). This means that Falco is both [trimmable](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained) and [AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot) compatible out of the box.\n\nThis means that you can deploy your Falco application as a self-contained executable, or as a native AOT executable, with no additional configuration. A huge benefit of this is that you can deploy your Falco application to any environment, without having to worry about the underlying runtime or dependencies.\n\n> Important! If you're in a __scale-to-zero__ hosting environment consider using a [ReadyToRun](https://learn.microsoft.com/en-us/dotnet/core/deploying/ready-to-run) deployment. This will ensure that your application will experience faster cold start times.\n\n## Self-contained deployments\n\nIt is highly recommended to deploy your Falco application as a self-contained executable. This means that the .NET runtime and all dependencies are included in the deployment package, so you don't have to worry about the target environment having the correct version of .NET installed. This will result in a slightly larger deployment package, but it will ensure that your application runs correctly in any environment. The larger binary size can also be offset by using trim.\n\nBelow is an example [Directory.Build.props] that will help enable the non-AOT features. These properties can also be added to you fsproj file.\n\n```xml\n<Project>\n    <PropertyGroup>\n        <SelfContained>true</SelfContained>\n        <PublishSingleFile>true</PublishSingleFile>\n        <PublishTrimmed>true</PublishTrimmed>\n        <TrimMode>Link</TrimMode>\n        <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>\n        <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>\n        <!-- Optional: enable if in scale-to-zero hosting environment -->\n        <!-- <PublishReadyToRun>true</PublishReadyToRun> -->\n    </PropertyGroup>\n</Project>\n```\n\n## Native AOT deployments\n\nPublishing your app as Native AOT produces an app that's self-contained and that has been ahead-of-time (AOT) compiled to native code. Native AOT apps have faster startup time and smaller memory footprints. These apps can run on machines that don't have the .NET runtime installed.\n\nSince AOT deployments require trimming, and are single file by nature the only required msbuild property is:\n\n```xml\n<Project>\n    <PropertyGroup>\n        <PublishAot>true</PublishAot>\n    </PropertyGroup>\n</Project>\n```\n\n[Next: Example - Hello World](example-hello-world.md)\n"
  },
  {
    "path": "documentation/example-basic-rest-api.md",
    "content": "# Example - Basic REST API\n\nThis example demonstrates how to create a basic REST API using Falco. The API will allow users to perform CRUD (Create, Read, Update, Delete) operations on a simple resource, users in this case.\n\nThe API will be built using the following components, in addition to the Falco framework:\n- [System.Data.SQLite](https://www.nuget.org/packages/System.Data.SQLite/), which provides SQLite support, built and maintained by the SQLite developers.\n- [Donald](https://www.nuget.org/packages/Donald/) which simplifies database access, built and maintained by the Falco developers.\n\n> For simplicity, we'll stick to sychronous database access in this example. However, you can easily adapt the code to use asynchronous database access if needed. Specific to SQLite, in many cases it is better to use synchronous access, and let SQLite handle serialization for you.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/BasicRestApi).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o BasicRestApiApp\n> cd BasicRestApiApp\n> dotnet add package System.Data.SQLite\n> dotnet add package Donald\n```\n\n## Overview\n\nThe API will consist of four endpoints:\n\n- `GET /users`: Retrieve all users.\n- `GET /users/{username}`: Retrieve a user by username.\n- `POST /users`: Create a new user.\n- `DELETE /users/{username}`: Delete a user by username.\n\nUsers will be stored in a SQLite database, and the API will use Donald to interact with the database. Our user model will be a simple record type with two properties: `Username` and `Full Name`.\n\n```fsharp\ntype User =\n    { Username : string\n      FullName : string }\n```\n\nIt's also valueable to have a concrete type to represent API errors. This will be used to return error messages in a consistent format.\n\n```fsharp\ntype Error =\n    { Code : string\n      Message : string }\n```\n\n## Data Access\n\nTo interact with the SQLite database, we'll create some abstractions for establishing new connections and performing database operations.\n\nA connection factory is a useful concept to avoid passing around connection strings. It allows us to create new connections without needing to know the details of how they are created.\n\n```fsharp\ntype IDbConnectionFactory =\n    abstract member Create : unit -> IDbConnection\n```\n\nWe'll also define an interface for performing list, create, read and delete operations against a set of entities.\n\n```fsharp\ntype IStore<'TKey, 'TItem> =\n    abstract member List : unit   -> 'TItem list\n    abstract member Create : 'TItem -> Result<unit, Error>\n    abstract member Read : 'TKey -> 'TItem option\n    abstract member Delete : 'TKey -> Result<unit, Error>\n```\nThe `IStore` interface is generic, allowing us to use it with any type of entity. In our case, we'll create a concrete implementation for the `User` entity.\n\n## Implementing the Store\n\n## Error Responses\n\nThe API will return error responses in a consistent format. To do this, we'll create three functions for the common error cases: `notFound`, `badRequest`, and `serverException`.\n\n```fsharp\nmodule ErrorResponse =\n    let badRequest error : HttpHandler =\n        Response.withStatusCode 400\n        >> Response.ofJson error\n\n    let notFound : HttpHandler =\n        Response.withStatusCode 404 >>\n        Response.ofJson { Code = \"404\"; Message = \"Not Found\" }\n\n    let serverException : HttpHandler =\n        Response.withStatusCode 500 >>\n        Response.ofJson { Code = \"500\"; Message = \"Server Error\" }\n```\n\nHere you can see our error type in action, which is used to return a JSON response with the error code and message. The signature of the `badRequest` function is a bit different, as it takes an error object as input and returns a `HttpHandler`. The reason for this is that we intend to invoke this function from within our handlers, and we want to be able to pass the error object directly to it.\n\n## Defining the Endpoints\n\nIt can be very useful to define values for the endpoints we want to expose. This allows us to easily change the endpoint paths in one place if needed, and also provides intellisense support when using the endpoints in our code.\n\n```fsharp\nmodule Route =\n    let userIndex = \"/users\"\n    let userAdd = \"/users\"\n    let userView = \"/users/{username}\"\n    let userRemove = \"/users/{username}\"\n```\n\nNext, let's implement the handlers for each of the endpoints. First, we'll implement the `GET /users` endpoint, which retrieves all users from the database.\n\n```fsharp\nmodule UserEndpoint =\n    let index : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let allUsers = userStore.List()\n        Response.ofJson allUsers ctx\n```\nThe `index` function retrieves the `IStore` instance from the dependency container and calls the `List` method to get all users. The result is then returned as a JSON response.\n\nNext, we'll implement the `POST /users` endpoint, which creates a new user.\n\n```fsharp\nmodule UserEndpoint =\n    // ... index handler ...\n    let add : HttpHandler = fun ctx -> task {\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let! userJson = Request.getJson<User> ctx\n        let userAddResponse =\n            match userStore.Create(userJson) with\n            | Ok result -> Response.ofJson result ctx\n            | Error error -> ErrorResponse.badRequest error ctx\n        return! userAddResponse }\n```\n\nThe `add` function retrieves the `IStore` instance from the dependency container and calls the `Create` method to add a new user. The result is then returned as a JSON response. If the user creation fails, we return a bad request error.\n\nNext, we'll implement the `GET /users/{username}` endpoint, which retrieves a user by username.\n\n```fsharp\nmodule UserEndpoint =\n    // ... index and add handlers ...\n    let view : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Read(username) with\n        | Some user -> Response.ofJson user ctx\n        | None -> ErrorResponse.notFound ctx\n```\n\nThe `view` function retrieves the `IStore` instance from the dependency container and calls the `Read` method to get a user by username. If the user is found, it is returned as a JSON response. If not, we return a not found error.\n\nFinally, we'll implement the `DELETE /users/{username}` endpoint, which deletes a user by username.\n\n```fsharp\nmodule UserEndpoint =\n    // ... index, add and view handlers ...\n    let remove : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Delete(username) with\n        | Ok result -> Response.ofJson result ctx\n        | Error error -> ErrorResponse.badRequest error ctx\n```\n\nThe `remove` function retrieves the `IStore` instance from the dependency container and calls the `Delete` method to remove a user by username. The result is then returned as a JSON response. If the user deletion fails, we return a bad request error.\n\n## Configuring the Application\n\nConventionally, you'll configure your database outside of your application scope. For the purpose of this example, we'll define and initialize the database during startup.\n\n```fsharp\nmodule Program =\n    [<EntryPoint>]\n    let main args =\n        let dbConnectionFactory =\n            { new IDbConnectionFactory with\n                member _.Create() = new SQLiteConnection(\"Data Source=BasicRestApi.sqlite3\") }\n\n        let initializeDatabase (dbConnection : IDbConnectionFactory) =\n            use conn = dbConnection.Create()\n            conn\n            |> Db.newCommand \"CREATE TABLE IF NOT EXISTS user (username, full_name)\"\n            |> Db.exec\n\n        initializeDatabase dbConnectionFactory\n\n        // ... rest of the application setup\n```\n\nFirst we implement the `IDbConnectionFactory` interface, which creates a new SQLite connection. Then we define a `initializeDatabase` function, which creates the database and the user table if it doesn't exist. We encapsulate the database initialization in a function, so we can quickly dispose of the connection after use.\n\nNext, we need to register our database connection factory and the `IStore` implementation in the dependency container.\n\n```fsharp\nmodule Program =\n    [<EntryPoint>]\n    let main args =\n        // ... database initialization ...\n        let bldr = WebApplication.CreateBuilder(args)\n\n        bldr.Services\n            .AddAntiforgery()\n            .AddScoped<IDbConnectionFactory>(dbConnectionFactory)\n            .AddScoped<IStore<string, User>, UserStore>()\n            |> ignore\n```\n\nFinally, we need to configure the application to use the defined endpoints.\n\n```fsharp\nmodule Program =\n    [<EntryPoint>]\n    let main args =\n        // ... database initialization & dependency registration ...\n        let wapp = bldr.Build()\n\n        let isDevelopment = wapp.Environment.EnvironmentName = \"Development\"\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorResponse.serverException)\n            .UseRouting()\n            .UseFalco(App.endpoints)\n            .Run(ErrorResponse.notFound)\n\n        0 // Exit code\n```\n\nThe `UseFalco` method is used to register the endpoints, and the `Run` method is used to handle requests that don't match any of the defined endpoints.\n\n## Wrapping Up\n\nAnd there you have it! A simple REST API built with Falco, SQLite and Donald. This example demonstrates how to create a basic CRUD API, but you can easily extend it to include more complex functionality, such as authentication, validation, and more.\n\n[Next: Example - Open API](example-open-api.md)\n"
  },
  {
    "path": "documentation/example-dependency-injection.md",
    "content": "# Example - Dependency Injection\n\nAn important and nuanced subject to discuss is dependency injection. There's a myriad of beliefs and approaches, all of which have their merit. In the case of Falco, you are living in the world of ASP.NET which has [built-in support](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) for this. It works very well and you should use it. But make sure you follow through their [docs](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0) on how it works and integrates with ASP.NET.\n\nGoing back to our basic [Hello World](example-hello-world.md) app, let's add in an external dependency to demonstrate some of the basics of dependency injection in Falco.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/DependencyInjection).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o DependencyInjectionApp\n```\n\n## Creating Abstraction\n\nThe benefit of abstracting functionality is that it removes the coupling between your implementation and the calling code. You instead rely on an accepted definition of what something does.\n\nF# has excellent support for [object programming](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/classes). There might be an urge to avoid this type of approach because \"ugh classes are gross\". But suck it up buttercup, they are wickedly useful in many cases and a reminder that F# code doesn't have to adhere to some functional purism.\n\nIn the case of our application, we're going to define an abstraction for greeting patrons. Then write a simple implementation.\n\n> This is a completely contrived example, created purely to demonstrate how to register and consume dependencies.\n\n```fsharp\ntype IGreeter =\n    abstract member Greet : name : string -> string\n\ntype FriendlyGreeter() =\n    interface IGreeter with\n        member _.Greet(name : string) =\n            $\"Hello {name} 😀\"\n```\n\nSimple enough, we describe an `IGreeter` as having the ability to `Greet` in the form of receiving a name string and return a string message. Next we define an implementation that fulfills this interface in a friendly way.\n\n## Registering the Dependency\n\nTo provide runtime access to our greeter, we have to register the dependency in the container. The abstraction from ASP.NET for this is called `IServiceCollection`. You can register dependencies in a number of ways, but fundamental to all is the concept of [service lifetime](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes). It distills down to:\n\n- Transient = new for every container access\n- Scoped = new for every client request\n- Singleton = created at startup, or first container access\n\nOur greeter is both stateless and cheap to construct. So any of the lifetimes will suffice. But let's register it as a singleton. This time however, we'll create our web server in two stages, to gain access to the dependency container.\n\n```fsharp\nlet bldr = WebApplication.CreateBuilder() // <-- create a configurable web application builder\n\nbldr.Services\n    .AddSingleton<IGreeter, FriendlyGreeter>() // <-- register the greeter as singleton in the container\n    |> ignore\n\nlet wapp = bldr.Build() // <-- manifest our WebApplication\n\nlet endpoints =\n    [\n        mapGet \"/{name?}\"\n            (fun r -> r?name.AsString(\"world\"))\n            (fun name ctx ->\n                let greeter = ctx.Plug<IGreeter>() // <-- access our dependency from the container\n                let greeting = greeter.Greet(name) // <-- invoke our greeter.Greet(name) method\n                Response.ofPlainText greeting ctx)\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\nFollowing through you can see the web server being created in two phases. The first to establish the context (i.e., logging, server configuration and dependencies). Second, freezing the final state and creating a configurable web application.\n\nWithin the handler you can see the interaction with the dependency container using `ctx.Plug<IGreeter>()`. This code tells the container to return the implementation it has registered for that abstraction. In our case `FriendlyGreeter`.\n\n## Wrapping Up\n\nNow that we're finished introducing dependency injection, let's move on to a real world example by integrating with an external view engine.\n\n[Next: Example - External View Engine](example-external-view-engine.md)\n"
  },
  {
    "path": "documentation/example-external-view-engine.md",
    "content": "# Example - External View Engine\n\nFalco comes packaged with a [built-in view engine](markup.md). But if you'd prefer to write your own templates, or use an external template engine, that is entirely possible as well.\n\nIn this example we'll do some basic page rendering by integrating with [scriban](https://github.com/scriban/scriban). An amazing template engine by [xoofx](https://github.com/xoofx).\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/ExternalViewEngine).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o ExternalViewEngineApp\n> cd ExternalViewEngineApp\n> dotnet add package Scriban\n```\n\n## Implementing a Template Engine\n\nThere are a number of ways we could achieve this functionality. But in sticking with our previous examples, we'll create an interface. To keep things simple we'll use inline string literals for templates and perform rendering synchronously.\n\n```fsharp\nopen Scriban\n\ntype ITemplate =\n    abstract member Render : template: string * model: obj -> string\n\ntype ScribanTemplate() =\n    interface ITemplate with\n        member _.Render(template, model) =\n            let tmpl = Template.Parse template\n            tmpl.Render(model)\n```\n\nWe define an interface `ITemplate` which describes template rendering as a function that receives a template string literal and a model, producing a string literal. Then we implement this interface definition using Scriban.\n\n## Rendering Pages\n\nTo use our Scriban template engine we'll need to request it from the dependency container, then pass it our template literal and model.\n\n> See [dependency injection](example-dependency-injection.md) for further explanation.\n\nSince rendering more than one page is the goal, we'll create a shared `renderPage` function to do the dirty work for us.\n\n```fsharp\nopen Falco\n\nmodule Pages =\n    let private renderPage pageTitle template viewModel : HttpHandler = fun ctx ->\n        let templateService = ctx.Plug<ITemplate>() // <-- obtain our template service from the dependency container\n        let pageContent = templateService.Render(template, viewModel) // <-- render our template with the provided view model as string literal\n        let htmlTemplate = \"\"\"\n            <!DOCTYPE html>\n            <html>\n            <head>\n                <meta charset=\"utf-8\">\n                <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n                <title>{{title}}</title>\n            </head>\n            <body>\n                {{content}}\n            </body>\n            </html>\n        \"\"\"\n        // ^ these triple quoted strings auto-escape characters like double quotes for us\n        //   very practical for things like HTML\n\n        let html = templateService.Render(htmlTemplate, {| Title = pageTitle; Content = pageContent |})\n\n        Response.ofHtmlString html ctx // <-- return template literal as \"text/html; charset=utf-8\" response\n```\n\nIn this function we obtain the instance of our template engine, and immediately render the user-provided template and model. Next, we define a local template literal to serve as our layout. Assigning two simple inputs, `{{title}}` and `{{content}}`. Then we render the layout template using our template engine and an anonymous object literal `{| Title = pageTitle; Content = pageContent |}`, responding with the result of this as `text/html`.\n\nTo render pages, we simply need to create a localized template literal, and feed it into our `renderPage` function. Below we define a home and 404 page.\n\n```fsharp\n    let homepage : HttpHandler = fun ctx ->\n        let query = Request.getQuery ctx // <-- obtain access to strongly-typed representation of the query string\n        let viewModel = {| Name = query?name.AsStringNonEmpty(\"World\") |} // <-- access 'name' from query, or default to 'World'\n        let template = \"\"\"\n            <h1>Hello {{ name }}!</h1>\n        \"\"\"\n        renderPage $\"Hello {viewModel.Name}\" template viewModel ctx\n\n    let notFound : HttpHandler =\n        let template = \"\"\"\n            <h1>Page not found</h1>\n        \"\"\"\n        renderPage \"Page Not Found\" template {||}\n```\n\n## Registering the Template Engine\n\nSince our Scriban template engine is stateless and dependency-free, we can use the generic extension method to register it as a singleton.\n\n> Note: `Transient` and `Scoped` lifetimes would also work here.\n\n```\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\n\n[<EntryPoint>]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddSingleton<ITemplate, ScribanTemplate>() // <-- register ITemplates implementation as a dependency\n        |> ignore\n\n    let endpoints =\n        [ get \"/\" Pages.homepage ]\n\n    let wapp = bldr.Build()\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .UseFalcoNotFound(Pages.notFound)\n        .Run()\n\n    0 // Exit code\n```\n\n## Wrapping Up\n\nThis example demonstrates how to effectively integrate an external view engine into your Falco application. By defining a simple interface, implementing it with Scriban and adding it to the dependency container, we can render HTML pages dynamically based on user input.\n\n[Next: Example - Basic REST API](example-basic-rest-api.md)\n"
  },
  {
    "path": "documentation/example-hello-world-mvc.md",
    "content": "# Example - Hello World MVC\n\nLet's take our basic [Hello World](example-hello-world.md) to the next level. This means we're going to dial up the complexity a little bit. But we'll do this using the well recognized MVC pattern. We'll contain the app to a single file to make \"landscaping\" the pattern more straight-forward.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorldMvc).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o HelloWorldMvcApp\n```\n\n## Model\n\nSince this app has no persistence, the model is somewhat boring. But included here to demonstrate the concept.\n\nWe define two simple record types. One to contain the patron name, the other to contain a `string` message.\n\n```fsharp\nmodule Model =\n    type NameGreeting =\n        { Name : string }\n\n    type Greeting =\n        { Message : string }\n```\n\n## Routing\n\nAs the project scales, it is generally helpful to have static references to your URLs and/or URL generating functions for dynamic resources.\n\n[Routing](routing.md) begins with a route template, so it's only natural to define those first.\n\n```fsharp\nmodule Route =\n    let index = \"/\"\n    let greetPlainText = \"/greet/text/{name}\"\n    let greetJson = \"/greet/json/{name}\"\n    let greetHtml = \"/greet/html/{name}\"\n```\n\nHere you can see we define one static route, and 3 dynamic route templates. We can provide URL generation from these dynamic route templates quite easily with some simple functions.\n\n```fsharp\nmodule Url =\n    let greetPlainText name = Route.greetPlainText.Replace(\"{name}\", name)\n    let greetJson name = Route.greetJson.Replace(\"{name}\", name)\n    let greetHtml name = Route.greetHtml.Replace(\"{name}\", name)\n```\n\nThese 3 functions take a string input called `name` and plug it into the `{name}` placeholder in the route template. This gives us a nice little typed API for creating our application URLs.\n\n## View\n\nFalco comes packaged with a [lovely little HTML DSL](https://github.com/FalcoFramework/Falco.Markup/). It can produce any form of angle-markup, and does so very [efficiently](https://github.com/FalcoFramework/Falco.Markup/?tab=readme-ov-file#performance). The main benefit is that our views are _pure_ F#, compile-time checked and live alongside the rest of our code.\n\nFirst we define a shared HTML5 `layout` function, that references our project `style.css`. Next, we define a module to contain the views for our greetings.\n\n> You'll notice the `style.css` file resides in a folder called `wwwroot`. This is an [ASP.NET convention](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/static-files) which we'll enable later when we [build the web server](#web-server).\n\n```fsharp\nmodule View =\n    open Model\n\n    let layout content =\n        Templates.html5 \"en\"\n            [ _link [ _href_ \"/style.css\"; _rel_ \"stylesheet\" ] ]\n            content\n\n    module GreetingView =\n        /// HTML view for /greet/html\n        let detail greeting =\n            layout [\n                _h1' $\"Hello {greeting.Name} from /html\"\n                _hr []\n                _p' \"Greet other ways:\"\n                _nav [] [\n                    _a\n                        [ _href_ (Url.greetPlainText greeting.Name) ]\n                        [ _text \"Greet in text\"]\n                    _text \" | \"\n                    _a\n                        [ _href_ (Url.greetJson greeting.Name) ]\n                        [ _text \"Greet in JSON \" ]\n                ]\n            ]\n```\n\nThe markup code is fairly self-explanatory. But essentially:\n\n- `Elem` produces HTML elements.\n- `Attr` produces HTML element attributes.\n- `Text` produces HTML text nodes.\n\nEach of these modules matches (or tries to) the full HTML spec. You'll also notice two of our URL generators at work.\n\n## Errors\n\nWe'll define a couple static error pages to help prettify our error output.\n\n```fsharp\nmodule Controller =\n    open Model\n    open View\n\n    module ErrorController =\n        let notFound : HttpHandler =\n            Response.withStatusCode 404 >>\n            Response.ofHtml (View.layout [ _h1' \"Not Found\" ])\n\n        let serverException : HttpHandler =\n            Response.withStatusCode 500 >>\n            Response.ofHtml (View.layout [ _h1' \"Server Error\" ])\n\n        let endpoints =\n            [ get \"/error/not-found\" notFound\n              get \"/error/server-exception\" serverException ]\n```\n\nHere we see the [`HttpResponseModifier`](repsonse.md#response-modifiers) at play, which set the status code before buffering out the HTML response. We'll reference these pages later when be [build the web server](#web-server). We'll also add explicit endpoints for these, so they can be accessed directly or redirected to.\n\n## Controller\n\nOur controller will be responsible for four actions, as defined in our [route](#routing) module. We define four handlers, one parameterless greeting and three others which output the user provided \"name\" in different ways: plain text, JSON and HTML.\n\n```fsharp\nmodule Controller =\n    open Model\n    open View\n\n    module ErrorController =\n        // ...\n\n    module GreetingController =\n        let index =\n            Response.ofPlainText \"Hello world\"\n\n        let plainTextDetail name =\n            Response.ofPlainText $\"Hello {name}\"\n\n        let jsonDetail name =\n            let message = { Message = $\"Hello {name} from /json\" }\n            Response.ofJson message\n\n        let htmlDetail name =\n            { Name = name }\n            |> GreetingView.detail\n            |> Response.ofHtml\n\n        let endpoints =\n            let mapRoute (r : RequestData) =\n                r?name.AsString()\n\n            [ mapGet Route.index mapRoute index\n              mapGet Route.greetPlainText mapRoute plainTextDetail\n              mapGet Route.greetJson mapRoute jsonDetail\n              mapGet Route.greetHtml mapRoute index ]\n```\n\nYou'll notice that this controller defines its own `endpoints` too. This associates a route to a handler when passed into Falco (we'll do this later). Defining this within the controller is personal preference. But considering controller actions usually operate against a common URL pattern, it allows a private, reusable route mapping to exist (see `mapRoute`).\n\n## Web Server\n\nThis is a great opportunity to demonstrate further how to configure a more complex web server than we saw in the basic hello world example.\n\nTo do that, we'll define an explicit entry point function which gives us access to the command line argument. By then forwarding these into the web application, we gain further [configurability](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration#command-line). You'll notice the application contains a file called `appsettings.json`, this is another [ASP.NET convention](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration#default-application-configuration-sources) that provides fully-featured and extensible configuration functionality.\n\nNext we define an explicit collection of endpoints, which gets passed into the `.UseFalco(endpoints)` extension method.\n\nIn this example, we examine the environment name to create an \"is development\" toggle. We use this to determine the extensiveness of our error output. You'll notice we use our exception page from above when an exception occurs when not in development mode. Otherwise, we show a developer-friendly error page. Next we activate static file support, via the default web root of `wwwroot`.\n\nWe end off by registering a terminal handler, which functions as our \"not found\" response.\n\n```fsharp\nmodule Program =\n    open Controller\n\n    let endpoints =\n        ErrorController.endpoints\n        @ GreetingController.endpoints\n\n    /// By defining an explicit entry point, we gain access to the command line\n    /// arguments which when passed into Falco are used as the creation arguments\n    /// for the internal WebApplicationBuilder.\n    [<EntryPoint>]\n    let main args =\n        let wapp = WebApplication.Create(args)\n\n        let isDevelopment = wapp.Environment.EnvironmentName = \"Development\"\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorPage.serverException)\n            .Use(StaticFileExtensions.UseStaticFiles)\n            .UseFalco(endpoints)\n            .UseFalcoNotFound(ErrorPage.notFound)\n            .Run()\n\n        0\n```\n\n## Wrapping Up\n\nThis example was a leap ahead from our basic hello world. But having followed this, you know understand many of the patterns you'll need to know to build end-to-end server applications with Falco. Unsurprisingly, the entire program fits inside 118 LOC. One of the magnificent benefits of writing code in F#.\n\n[Next: Example - Dependency Injection](example-dependency-injection.md)\n"
  },
  {
    "path": "documentation/example-hello-world.md",
    "content": "# Example - Hello World\n\nThe goal of this program is to demonstrate the absolute bare bones hello world application, so that we can focus on the key elements when initiating a new web application.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorld).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o HelloWorldApp\n```\n\n## Code Overview\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco([\n    // ^-- activate Falco endpoint source\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n        // ^-- associate GET / to plain text HttpHandler\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n    // ^-- activate Falco endpoint source\n```\n\nFirst, we open the required namespaces. `Falco` bring into scope the ability to activate the library and some other extension methods to make the fluent API more user-friendly.\n\n`Microsoft.AspNetCore.Builder` enables us to create web applications in a number of ways, we're using `WebApplication.Create()` above. It also adds many other useful extension methods, that you'll see later.\n\nAfter creating the web application, we:\n\n- Activate Falco using `wapp.UseFalco()`. This enables us to create endpoints.\n- Register `GET /` endpoint to a handler which responds with \"hello world\".\n- Run the app.\n\n[Next: Example - Hello World MVC](example-hello-world-mvc.md)\n"
  },
  {
    "path": "documentation/example-htmx.md",
    "content": "# Example - HTMX\n\n[Falco.Htmx](https://github.com/FalcoFramework/Falco.Htmx) brings type-safe [htmx](https://htmx.org/) support to [Falco](https://github.com/FalcoFramework/Falco). It provides a complete mapping of all attributes, typed request data and ready-made response modifiers.\n\nIn this example, we'll demonstrate some of the more common htmx attributes and how to use them with Falco.\n\nAt this point, we'll assume you have reviewed the docs, other examples and understand the basics of Falco. We don't be covering any of the basics in the code review.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/Htmx).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o HtmxApp\n> cd HtmxApp\n> dotnet add package Falco.Htmx\n```\n\n## Layout\n\nFirst we'll define a simple layout and enable htmx by including the script. Notice the strongly typed reference, `HtmxScript.cdnSrc`, which is provided by Falco.Htmx and resolves to the official CDN URL.\n\n```fsharp\nmodule View =\n    let template content =\n        _html [ _lang_ \"en\" ] [\n            _head [] [\n                _script [ _src_ HtmxScript.cdnSrc ] [] ]\n            _body [] content ]\n```\n\nWith our layout defined, we can create a view to represent our starting state.\n\n```fsharp\nmodule View =\n    // Layout ...\n\n    let clickAndSwap =\n        template [\n            _h1' \"Example: Click & Swap\"\n            _div [ _id_ \"content\" ] [\n                _button [\n                    _id_ \"clicker\"\n                    Hx.get \"/click\"\n                    Hx.swapOuterHtml ]\n                    [ _text \"Click Me\" ] ] ]\n```\n\nThis view contains a button that, when clicked, will send a GET request to the `/click` endpoint. The response from that request will replace the button with the response from the server.\n\n## Components\n\nA nice convention when working with Falco.Markup is to create a `Components` module within your `View` module. We'll define one component here.\n\nAll of the htmx attributes and properties are mapped within the `Hx` module. Wherever a limited scope of options exist, strongly typed references are provided. For example, `Hx.swapInnerHtml` is a strongly typed reference to the `hx-swap` attribute with the value `innerHTML`. This is a great way to avoid typos and ensure that your code is type-safe.\n\n```fsharp\nmodule View =\n    // Layout & view ...\n\n    module Components =\n        let resetter =\n            _div [ _id_ \"resetter\" ] [\n                _h2' \"Way to go! You clicked it!\"\n                _br []\n                _button [\n                    Hx.get \"/reset\"\n                    Hx.swapOuterHtml\n                    Hx.targetCss \"#resetter\" ]\n                    [ _text \"Reset\" ] ]\n```\n\nThe `resetter` component is a simple button that will send a GET request to the server when clicked. The response will replace the entire `div` with the ID of `resetter` with the response from the server.\n\n## Handlers\n\nNext we define a couple basic handlers to handle the requests for the original document and ajax requests.\n\n```fsharp\nmodule App =\n    let handleIndex : HttpHandler =\n        Response.ofHtml View.clickAndSwap\n\n    let handleClick : HttpHandler =\n        Response.ofHtml View.Components.resetter\n\n    let handleReset : HttpHandler =\n        Response.ofFragment \"clicker\" View.clickAndSwap\n```\n\nThe `handleIndex` handler is returning our full click-and-swap view, containing the clicker button. Clicking it triggers a request to the `handleClick` handler, which returns the resetter component. Clicking the reset button triggers a request to the `handleReset` handler, which returns the original clicker button as a [template fragment], extracted from the same view as the original state.\n\n## Web Server\n\nTo finish things off, we'll map our handlers to the expected routes and initialize the web server.\n\n```fsharp\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get \"/\" App.handleIndex\n        get \"/click\" App.handleClick\n        get \"/reset\" App.handleReset\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\n## Wrapping Up\n\nThat's it! You now have a simple web application that uses htmx to swap out components on the page without a full page reload. This is just the beginning of what you can do with htmx and Falco. You can use the same principles to create more complex interactions and components.\n\nFor more information about the htmx integration, check out the [Falco.Htmx](https://github.com/FalcoFramework/Falco.Htmx) repository. It contains a full list of all the attributes and properties that are available, as well as examples of how to use them.\n\n[Go back to docs home](/docs)\n"
  },
  {
    "path": "documentation/example-open-api.md",
    "content": "# Example - Open API\n\nOpen API is a specification for defining APIs in a machine-readable format. It allows developers to describe the structure of their APIs, including endpoints, request/response formats, and authentication methods.\n\n[Falco.OpenAPI](https://github.com/FalcoFramework/Falco.OpenAPI) is a library for generating OpenAPI documentation for Falco applications. It provides a set of combinators for annotating Falco routes with OpenAPI metadata, which can be used to generate OpenAPI documentation.\n\nWe'll dial back the complexity a bit from the [Basic REST API](example-basic-rest-api.md) example and create a simple \"fortune teller\" Falco application that serves OpenAPI documentation.\n\nThe code for this example can be found [here](https://github.com/FalcoFramework/Falco/tree/master/examples/OpenApi).\n\n## Creating the Application Manually\n\n```shell\n> dotnet new falco -o OpenApiApi\n> cd OpenApiApp\n> dotnet add package Falco.OpenApi\n```\n\n## Fortunes\n\nOur fortune teller will return fortune for the name of the person specified. To model this, we'll create two simple record types.\n\n```fsharp\ntype FortuneInput =\n    { Name : string }\n\ntype Fortune =\n    { Description : string }\n```\n\nFor simplicity, we'll use a static member to return a fortune. In a real application, you would likely retrieve this from a database or an external service.\n\n```fsharp\nmodule Fortune =\n    let create age input =\n        match age with\n        | Some age when age > 0 ->\n            { Description = $\"{input.Name}, you will experience great success when you are {age + 3}.\" }\n        | _ ->\n            { Description = $\"{input.Name}, your future is unclear.\" }\n```\n\n## OpenAPI Annotations\n\nNext, we'll annotate our route with OpenAPI metadata. This is done using the `OpenApi` module from the `Falco.OpenAPI` package. Below is the startup code for our fortune teller application. We'll dissect it after the code block, and then add the OpenAPI annotations.\n\n```fsharp\n[<EntryPoint>]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddFalcoOpenApi()\n        // ^-- add OpenAPI services\n        .AddSwaggerGen()\n        // ^-- add Swagger services\n        |> ignore\n\n    let wapp = bldr.Build()\n\n    wapp.UseHttpsRedirection()\n        .UseSwagger()\n        .UseSwaggerUI()\n    |> ignore\n\n    let endpoints =\n        [\n            mapPost \"/fortune\"\n                (fun r -> r?age.AsIntOption())\n                (fun ageOpt ->\n                    Request.mapJson<FortuneInput> (Fortune.create ageOpt >> Response.ofJson))\n                // we'll add OpenAPI annotations here\n        ]\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .Run()\n\n    0\n```\n\nWe've created a simple Falco application that listens for POST requests to the `/fortune` endpoint. The request body is expected to be a JSON object with a `name` property. The response will be a JSON object with a `description` property.\n\nNow, let's add the OpenAPI annotations to our route.\n\n```fsharp\n[<EntryPoint>]\nlet main args =\n    // ... application setup code ...\n    let endpoints =\n        [\n            mapPost \"/fortune\"\n                (fun r -> r?age.AsIntOption())\n                (fun ageOpt ->\n                    Request.mapJson<FortuneInput> (Fortune.create ageOpt >> Response.ofJson))\n                |> OpenApi.name \"Fortune\"\n                |> OpenApi.summary \"A mystic fortune teller\"\n                |> OpenApi.description \"Get a glimpse into your future, if you dare.\"\n                |> OpenApi.query [\n                    { Type = typeof<int>; Name = \"Age\"; Required = false } ]\n                |> OpenApi.acceptsType typeof<FortuneInput>\n                |> OpenApi.returnType typeof<Fortune>\n        ]\n\n    // ... application startup code ...\n\n    0 // Exit code\n```\n\nIn the code above, we use the `OpenApi` module to annotate our route with metadata.\n\nHere's a breakdown of the annotations:\n- `OpenApi.name`: Sets the name of the operation.\n- `OpenApi.summary`: Provides a short summary of the operation.\n- `OpenApi.description`: Provides a detailed description of the operation.\n- `OpenApi.query`: Specifies the query parameters for the operation. In this case, we have an optional `age` parameter.\n- `OpenApi.acceptsType`: Specifies the expected request body type. In this case, we expect a JSON object that can be deserialized into a `FortuneInput` record.\n- `OpenApi.returnType`: Specifies the response type. In this case, we return a JSON object that can be serialized into a `Fortune` record.\n\n## Wrapping Up\n\nThat's it! You've successfully created a simple Falco application with OpenAPI documentation. You can now use the generated OpenAPI specification to generate client code, create API documentation, or integrate with other tools that support OpenAPI.\n\n[Next: Example - htmx](example-htmx.md)\n"
  },
  {
    "path": "documentation/get-started.md",
    "content": "# Getting Started\n\n## Using `dotnet new`\n\nThe easiest way to get started with Falco is by installing the `Falco.Template` package, which adds a new template to your `dotnet new` command line tool:\n\n```shell\n> dotnet new install \"Falco.Template::*\"\n```\n\nAfterwards you can create a new Falco application by running:\n\n```shell\n> dotnet new falco -o HelloWorldApp\n> cd HelloWorldApp\n> dotnet run\n```\n\n## Manually installing\n\nCreate a new F# web project:\n\n```shell\n> dotnet new web -lang F# -o HelloWorldApp\n> cd HelloWorldApp\n```\n\nInstall the nuget package:\n\n```shell\n> dotnet add package Falco\n```\n\nRemove any `*.fs` files created automatically, create a new file named `Program.fs` and set the contents to the following:\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet endpoints =\n    [\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n        // ^-- associate GET / to plain text HttpHandler\n    ]\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    // ^-- activate Falco endpoint source\n    .Run(Response.ofPlainText \"Not found\")\n    // ^-- run app and register terminal (i.e., not found) middleware\n```\n\nRun the application:\n\n```shell\n> dotnet run\n```\n\nAnd there you have it, an industrial-strength [Hello World](https://github.com/FalcoFramework/Falco/tree/master/examples/HelloWorld) web app. Pretty sweet!\n\n[Next: Routing](routing.md)\n"
  },
  {
    "path": "documentation/host-configuration.md",
    "content": "# Host Configuration\n\nAs your app becomes more complex, you'll inevitably need to reach for some additional host configuration. This is where the `Microsoft.AspNetCore.Builder` import comes in. This assembly contains many useful extensions for configuring the server (ex: static files, authentication, authorization etc.).\n\nMost of the extension methods have existed since the early days of ASP.NET Core and operate against `IApplicationBuilder`. But more recent version of ASP.NET Core have introduced a new `WebApplication` type that implements `IApplicationBuilder` and provides some additional functionality,  notably endpoint configuration. This dichotomy makes pipelining next to impossible. In C# you don't feel the sting of this as much because of `void` returns. But in F# this results in an excess amount of `|> ignore` calls.\n\nLet's take the hero code from the [Getting Started](get-started.md) page and add the static file middleware to it:\n\n```fsharp\nmodule Program\n\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseDefaultFiles() // you might innocently think this is fine\n    .UseStaticFiles()  // and so is this\n                       // but uknowingly, the underlying type has changed\n    .UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n    // ^-- this is no longer starts up our application\n\n// one way to fix this:\nwapp.UseRouting() |> ignore\nwapp.UseDefaultFiles().UseStaticFiles() |> ignore\n\nwapp.UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n\n// but we can do better\n```\n\nTo salve this, Falco comes with a several shims. The most important of these are `WebApplication.Use` and `WebApplication.UseIf` which allow you to compose a pipeline entirely driven by `WebApplication` while at the same time taking advantage of the existing ASP.NET Core extensions.\n\n```fsharp\nwapp.UseRouting()\n    .Use(fun (appl : IApplicationBuilder) ->\n        appl.UseDefaultFiles()\n            .UseStaticFiles())\n    .UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n```\n\nThe optional, but recommended way to take advantage of these is to utilize the static methods that server as the underpinning to the various extension methods available. The code below will attempt to highlight this more clearly:\n\n```fsharp\n// better yet\nwapp.UseRouting()\n    .Use(DefaultFilesExtensions.UseDefaultFiles)\n    .Use(StaticFileExtensions.UseStaticFiles)\n      // ^-- most IApplicationBuilder extensions are available as static methods similar to this\n    .UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n```\n\nNext, we can use the `UseIf` extension method to conditionally add middleware to the pipeline. This is useful for things like development exception pages, or other middleware that you only want in certain environments.\n\n```fsharp\nlet isDevelopment = wapp.Environment.EnvironmentName = \"Development\"\nwapp.UseRouting()\n    .UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n    .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorPage.serverException)\n    .Use(DefaultFilesExtensions.UseDefaultFiles)\n    .Use(StaticFileExtensions.UseStaticFiles)\n    .UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n```\n\nThis is a great way to keep your code clean and readable, while still taking advantage of the powerful middleware pipeline that ASP.NET Core provides.\n\n[Next: Deployment](deployment.md)\n"
  },
  {
    "path": "documentation/markup.md",
    "content": "# Markup\n\nFalco.Markup is broken down into three primary modules, `Elem`, `Attr` and `Text`, which are used to generate elements, attributes and text nodes respectively. Each module contain a suite of functions mapping to the various element/attribute/node names. But can also be extended to create custom elements and attributes.\n\nPrimary elements are broken down into two types, `ParentNode` or `SelfClosingNode`.\n\n`ParentNode` elements are those that can contain other elements. Represented as functions that receive two inputs: attributes and optionally elements.\n\nEach of the primary modules can be access using the name directly, or using the \"underscore syntax\" seen below.\n\n| Module | Syntax |\n|--------|--------|\n| `Elem` | `_h1 [] []` |\n| `Attr` | `_class_ \"my-class\"` |\n| `Text` | `_text \"Hello world!\"` |\n| `Text` shortcuts | `_h1' \"Hello world\"` (note the trailing apostrophe) |\n\n\n```fsharp\nlet markup =\n    _div [ _class_ \"heading\" ] [\n        _h1' \"Hello world!\" ]\n```\n\n`SelfClosingNode` elements are self-closing tags. Represented as functions that receive one input: attributes.\n\n```fsharp\nlet markup =\n    _div [ _class_ \"divider\" ] [\n        _hr [] ]\n```\n\nText is represented using the `TextNode` and created using one of the functions in the `Text` module.\n\n```fsharp\nlet markup =\n    _div [] [\n        _p' \"A paragraph\"\n        _p [] [ _textf \"Hello %s\" \"Jim\" ]\n        _code [] [ _textEnc \"<div>Hello</div>\" ] // HTML encodes text before rendering\n    ]\n```\n\nAttributes contain two subtypes as well, `KeyValueAttr` which represent key/value attributes or `NonValueAttr` which represent boolean attributes.\n\n```fsharp\nlet markup =\n    _input [ _type_ \"text\"; _required_ ]\n```\n\nMost [JavaScript Events](https://developer.mozilla.org/en-US/docs/Web/Events) have also been mapped in the `Attr` module. All of these events are prefixed with the word \"on\" (i.e., `_onclick_`, `_onfocus_` etc.)\n\n```fsharp\nlet markup =\n    _button [ _onclick_ \"console.log(\\\"hello world\\\")\" ] [ _text \"Click me\" ]\n```\n\n## HTML\n\nThough Falco.Markup can be used to produce any markup. It is first and foremost an HTML library.\n\n### Combining views to create complex output\n\n```fsharp\nopen Falco.Markup\n\n// Components\nlet divider =\n    _hr [ _class_ \"divider\" ]\n\n// Template\nlet master (title : string) (content : XmlNode list) =\n    _html [ _lang_ \"en\" ] [\n        _head [] [\n            _title [] [ _text title ]\n        ]\n        _body [] content\n    ]\n\n// Views\nlet homeView =\n    master \"Homepage\" [\n        _h1' \"Homepage\"\n        divider\n        _p' \"Lorem ipsum dolor sit amet, consectetur adipiscing.\"\n    ]\n\nlet aboutView =\n    master \"About Us\" [\n        _h1' \"About\"\n        divider\n        _p' \"Lorem ipsum dolor sit amet, consectetur adipiscing.\"\n    ]\n```\n\n### Strongly-typed views\n\n```fsharp\nopen Falco.Markup\n\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet doc (person : Person) =\n    _html [ _lang_ \"en\" ] [\n        _head [] [\n            _title [] [ _text \"Sample App\" ]\n        ]\n        _body [] [\n            _main [] [\n                _h1' \"Sample App\"\n                _p' $\"{person.First} {person.Last}\"\n            ]\n        ]\n    ]\n```\n\n### Forms\n\nForms are the lifeblood of HTML applications. A basic form using the markup module would like the following:\n\n```fsharp\nlet dt = DateTime.Now\n\n_form [ _methodPost_; _action_ \"/submit\" ] [\n    _label [ _for_' \"name\" ] [ _text \"Name\" ]\n    _input [ _id_ \"name\"; _name_ \"name\"; _typeText_ ]\n\n    _label [ _for_' \"birthdate\" ] [ _text \"Birthday\" ]\n    _input [ _id_ \"birthdate\"; _name_ \"birthdate\"; _typeDate_; _valueDate_ dt ]\n\n    _input [ _typeSubmit_ ]\n]\n```\n\nExpanding on this, we can create a more complex form involving multiple inputs and input types as follows:\n\n```fsharp\n_form [ _methodPost_; _action_ \"/submit\" ] [\n    _label [ _for_' \"name\" ] [ _text \"Name\" ]\n    _input [ _id_ \"name\"; _name_ \"name\" ]\n\n    _label [ _for_' \"bio\" ] [ _text \"Bio\" ]\n    _textarea [ _name_ \"id\"; _name_ \"bio\" ] []\n\n    _label [ _for_' \"hobbies\" ] [ _text \"Hobbies\" ]\n    _select [ _id_ \"hobbies\"; _name_ \"hobbies\"; _multiple_ ] [\n        _option [ _value_ \"programming\" ] [ _text \"Programming\" ]\n        _option [ _value_ \"diy\" ] [ _text \"DIY\" ]\n        _option [ _value_ \"basketball\" ] [ _text \"Basketball\" ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text \"Do you like chocolate?\" ]\n        _label [] [\n            _text \"Yes\"\n            _input [ _typeRadio_; _name_ \"chocolate\"; _value_ \"yes\" ] ]\n        _label [] [\n            _text \"No\"\n            _input [ _typeRadio_; _name_ \"chocolate\"; _value_ \"no\" ] ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text \"Subscribe to our newsletter\" ]\n        _label [] [\n            _text \"Receive updates about product\"\n            _input [ _typeCheckbox_; _name_ \"newsletter\"; _value_ \"product\" ] ]\n        _label [] [\n            _text \"Receive updates about company\"\n            _input [ _typeCheckbox_; _name_ \"newsletter\"; _value_ \"company\" ] ]\n    ]\n\n    _input [ _typeSubmit_ ]\n]\n```\n\nA simple but useful _meta_-element `_control` can reduce the verbosity required to create form outputs. The same form would look like:\n\n```fsharp\n_form [ _methodPost_; _action_ \"/submit\" ] [\n    _control \"name\" [] [ _text \"Name\" ]\n\n    _controlTextarea \"bio\" [] [ _text \"Bio\" ] []\n\n    _controlSelect \"hobbies\" [ _multiple_ ] [ _text \"Hobbies\" ] [\n        _option [ _value_ \"programming\" ] [ _text \"Programming\" ]\n        _option [ _value_ \"diy\" ] [ _text \"DIY\" ]\n        _option [ _value_ \"basketball\" ] [ _text \"Basketball\" ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text \"Do you like chocolate?\" ]\n        _control \"chocolate\" [ _id_ \"chocolate_yes\"; _typeRadio_ ] [ _text \"yes\" ]\n        _control \"chocolate\" [ _id_ \"chocolate_no\"; _typeRadio_ ] [ _text \"no\" ]\n    ]\n\n    _fieldset [] [\n        _legend [] [ _text \"Subscribe to our newsletter\" ]\n        _control \"newsletter\" [ _id_ \"newsletter_product\"; _typeCheckbox_ ] [ _text \"Receive updates about product\" ]\n        _control \"newsletter\" [ _id_ \"newsletter_company\"; _typeCheckbox_ ] [ _text \"Receive updates about company\" ]\n    ]\n\n    _input [ _typeSubmit_ ]\n]\n```\n\n### Attribute Value\n\nOne of the more common places of sytanctic complexity is with `_value_` which expects, like all `Attr` functions, `string` input. Some helpers exist to simplify this.\n\n```fsharp\nlet dt = DateTime.Now\n\n_input [ _typeDate_; _valueStringf_ \"yyyy-MM-dd\" dt ]\n\n// you could also just use:\n_input [ _typeDate_; _valueDate_ dt ] // formatted to ISO-8601 yyyy-MM-dd\n\n// or,\n_input [ _typeMonth_; _valueMonth_ dt ] // formatted to ISO-8601 yyyy-MM\n\n// or,\n_input [ _typeWeek_; _valueWeek_ dt ] // formatted to Gregorian yyyy-W#\n\n// it works for TimeSpan too:\nlet ts = TimeSpan(12,12,0)\n_input [ _typeTime_; _valueTime_ ts ] // formatted to hh:mm\n\n// there is a helper for Option too:\nlet someTs = Some ts\n_input [ _typeTime_; _valueOption_ _valueTime_ someTs ]\n```\n\n### Merging Attributes\n\nThe markup module allows you to easily create components, an excellent way to reduce code repetition in your UI. To support runtime customization, it is advisable to ensure components (or reusable markup blocks) retain a similar function \"shape\" to standard elements. That being, `XmlAttribute list -> XmlNode list -> XmlNode`.\n\nThis means that you will inevitably end up needing to combine your predefined `XmlAttribute list` with a list provided at runtime. To facilitate this, the `Attr.merge` function will group attributes by key, and intelligently concatenate the values in the case of additive attributes (i.e., `class`, `style` and `accept`).\n\n```fsharp\nopen Falco.Markup\n\n// Components\nlet heading (attrs : XmlAttribute list) (content : XmlNode list) =\n    // safely combine the default XmlAttribute list with those provided\n    // at runtime\n    let attrs' =\n        Attr.merge [ _class_ \"text-large\" ] attrs\n\n    _div [] [\n        _h1 [ attrs' ] content\n    ]\n\n// Template\nlet master (title : string) (content : XmlNode list) =\n    _html [ _lang_ \"en\" ] [\n        _head [] [\n            _title [] [ _text title ]\n        ]\n        _body [] content\n    ]\n\n// Views\nlet homepage =\n    master \"Homepage\" [\n        heading [ _class_ \"red\" ] [ _text \"Welcome to the homepage\" ]\n        _p' \"Lorem ipsum dolor sit amet, consectetur adipiscing.\"\n    ]\n\nlet homepage =\n    master \"About Us\" [\n        heading [ _class_ \"purple\" ] [ _text \"This is what we're all about\" ]\n        _p' \"Lorem ipsum dolor sit amet, consectetur adipiscing.\"\n    ]\n```\n\n## Custom Elements & Attributes\n\nEvery effort has been taken to ensure the HTML and SVG specs are mapped to functions in the module. In the event an element or attribute you need is missing, you can either file an [issue](https://github.com/pimbrouwers/Falco.Markup/issues), or more simply extend the module in your project.\n\nAn example creating custom XML elements and using them to create a structured XML document:\n\n```fsharp\nopen Falco.Makrup\n\nmodule XmlElem =\n    let books = Attr.create \"books\"\n    let book = Attr.create \"book\"\n    let name = Attr.create \"name\"\n\nmodule XmlAttr =\n    let soldOut = Attr.createBool \"soldOut\"\n\nlet xmlDoc =\n    XmlElem.books [] [\n        XmlElem.book [ XmlAttr.soldOut ] [\n            XmlElem.name [] [ _text \"To Kill A Mockingbird\" ]\n        ]\n    ]\n\nlet xml = renderXml xmlDoc\n```\n\n## Template Fragments\n\nThere are circumstances where you may want to render only a portion of your view. Especially common in [hypermedia driven](https://htmx.org/essays/hypermedia-driven-applications/) applications. Supporting [template fragments](https://htmx.org/essays/template-fragments/) is helpful in maintaining locality of behaviour, because it allows you to decompose a particular view for partial updates internally without pulling fragments of the template out to separate files for rendering, creating a large number of individual templates.\n\nFalco.Markup supports this pattern by way of the `renderFragment` function, which will traverse the provided `XmlNode` tree and render only the child node matching the provided `id`. Otherwise, gracefully returning an empty string if no match is found.\n\n```fsharp\nopen Falco.Markup\n\nlet view =\n    _div [ _id_ \"my-div\"; _class_ \"my-class\" ] [\n        _h1 [ _id_ \"my-heading\" ] [ _text \"hello\" ] ]\n\nlet render = renderFragment doc \"my-heading\"\n// produces: <h1 id=\"my-heading\">hello</h1>\n```\n\n## SVG\n\nMuch of the SVG spec has been mapped to element and attributes functions. There is also an SVG template to help initialize a new drawing with a valid viewbox.\n\n```fsharp\nopen Falco.Markup\nopen Falco.Markup.Svg\n\n// https://developer.mozilla.org/en-US/docs/Web/SVG/Element/text#example\nlet svgDrawing =\n    Templates.svg (0, 0, 240, 80) [\n        _style [] [\n            _text \".small { font: italic 13px sans-serif; }\"\n            _text \".heavy { font: bold 30px sans-serif; }\"\n            _text \".Rrrrr { font: italic 40px serif; fill: red; }\"\n        ]\n        _text [ _x_ \"20\"; _y_ \"35\"; _class_ \"small\" ] [ _text \"My\" ]\n        _text [ _x_ \"40\"; _y_ \"35\"; _class_ \"heavy\" ] [ _text \"cat\" ]\n        _text [ _x_ \"55\"; _y_ \"55\"; _class_ \"small\" ] [ _text \"is\" ]\n        _text [ _x_ \"65\"; _y_ \"55\"; _class_ \"Rrrrr\" ] [ _text \"Grumpy!\" ]\n    ]\n\nlet svg = renderNode svgDrawing\n```\n\n[Next: Cross-site Request Forgery (XSRF)](cross-site-request-forgery.md)\n"
  },
  {
    "path": "documentation/migrating-from-v4-to-v5.md",
    "content": "# Migrating from v4.x to v5.x\n\nWith Falco v5.x the main objective was to simplify the API and improve the overall devlopment experience long term. The idea being provide only what is necessary, or provides the most value in the most frequently developed areas.\n\nThis document will attempt to cover the anticipated transformations necessary to upgrade from v4.x to v5.x. Pull requests are welcome for missing scenarios, thank you in advance for your help.\n\n## `webHost` expression\n\nPerhaps the most significant change is the removal of the `webHost` expression, which attempted to make web application server construction more pleasant. Microsoft has made really nice strides in this area (i.e., `WebApplication`) and it's been difficult at times to stay sync with the breaking changes to the underlying interfaces. As such, we elected to remove it altogether.\n\nBelow demonstrates how to migrate a \"hello world\" app from v4 to v5 by replacing the `webHost` expression with the Microsoft provided `WebApplicationBuilder`.\n\n<table>\n<tr>\n<td>\n\n```fsharp\n// Falco v4.x\nopen Falco\n\nwebHost args {\n\n    use_static_files\n\n    endpoints [\n        get \"/\"\n            (Response.ofPlainText \"hello world\")\n    ]\n}\n```\n\n</td>\n<td>\n\n```fsharp\n// Falco v5.x\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n// ^-- this import adds many useful extensions\n\nlet endpoints =\n    [\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n        // ^-- associate GET / to plain text HttpHandler\n    ]\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    // ^-- activate Falco endpoint source\n    .Run()\n```\n\n</td>\n</tr>\n</table>\n\n## `configuration` expression\n\nThe configuration expression has also been removed. Again, the idea being to try and get in the way of potentially evolving APIs as much as possible. Even more so in the areas where the code was mostly decorative.\n\n> Note: This example is entirely trivial since the `WebApplication.CreateBuilder()` configures a host with common, sensible defaults.\n\n<table>\n<tr>\n<td>\n\n```fsharp\nopen Falco\nopen Falco.HostBuilder\n\nlet config = configuration [||] {\n    required_json \"appsettings.json\"\n    optional_json \"appsettings.Development.json\"\n}\n\nwebHost [||] {\n    endpoints []\n}\n```\n\n</td>\n<td>\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.Configuration\n// ^-- this import adds access to Configuration\n\nlet bldr = WebApplication.CreateBuilder()\nlet conf =\n    bldr.Configuration\n        .AddJsonFile(\"appsettings.json\", optional = false)\n        .AddJsonFile(\"appsettings.Development.json\")\n\nlet wapp = WebApplication.Create()\n\nlet endpoints = []\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\n</td>\n</tr>\n</table>\n\n## `StringCollectionReader` replaced by `RequestData`\n\nFor the most part, this upgrade won't require any changes for the end user. Especially if the continuation-style functions in the `Request` module were used.\n\nExplicit references to: `CookieCollectionReader`, `HeaderCollectionReader`, `RouteCollectionReader`, `QueryCollectionReader` will need to be updated to `RequestData`. `FormCollectionReader` has been replaced by `FormData`.\n\n## Form Streaming\n\nFalco now automatically detects whether the form is transmiting `multipart/form-data`, which means deprecating the `Request` module streaming functions.\n\n- `Request.streamForm` becomes -> `Request.mapForm`\n- `Request.streamFormSecure` becomes -> `Request.mapFormSecure`\n- `Request.mapFormStream`  becomes -> `Request.mapForm`\n- `Request.mapFormStreamSecure` becomes -> `Request.mapFormSecure`\n\n## Removed `Services.inject<'T1 .. 'T5>`\n\nThis type was removed because it continued to pose problems for certain code analysis tools. To continue using the service locator pattern, you can now use the more versatile `HttpContext` extension method `ctx.Plug<T>()`. For example:\n\n```fsharp\nlet myHandler : HttpHandler =\n    Services.inject<MyService> (fun myService ctx ->\n        let message = myService.CreateMessage()\n        Response.ofPlainText $\"{message}\" ctx)\n\n// becomes\nlet myHandler : HttpHandler = fun ctx ->\n    let myService = ctx.Plug<MyService>()\n    let message = myService.CreateMessage()\n    Response.ofPlainText $\"{message}\" ctx\n\n```\n\n## `Xss` module renamed to `Xsrf`\n\nThe `Xss` module has been renamed to `Xsrf` to better describe it's intent.\n\n```fsharp\n    //before: Xss.antiforgeryInput\n    Xsrf.antiforgeryInput // ..\n\n    //before: Xss.getToken\n    Xsrf.getToken // ..\n\n    //before: Xss.validateToken\n    Xsrf.validateToken // ..\n```\n\n## `Crypto` module removed\n\nThe Crypto module provided functionality for: random numbers, salt generation and key derivation. The code in this module was really a veneer on top of the cryptographic providers in the base library. Extracting this code into your project would be dead simple. The [source](https://github.com/FalcoFramework/Falco/blob/25d828d832c0fde2dfff04775bea1eced9050458/src/Falco/Security.fs#L3) is permalinked here for such purposes.\n\n## `Auth` module removed\n\nThe `Auth` module functionality was ported one-to-one to the `Response` module.\n"
  },
  {
    "path": "documentation/migrating-from-v5-to-v6.md",
    "content": "# Migrating from v5.x to v6.x\n\nThe objective of Falco v6.x is to continue to simplify the API and improve the overall development experience long term. The idea being provide only what is necessary, or provides the most value in the most frequently developed areas.\n\n## `Request.getFormSecure`\n\n<table>\n<tr>\n<td>\n\n```fsharp\n// Falco v5.x\nlet myHandler : HttpHandler = fun ctx ->\n    task {\n        let! form = Request.getFormSecure ctx\n\n        match form with\n        | Some form -> // do something with form\n        | None -> // handle failed csrf validation\n    }\n```\n\n</td>\n<td>\n\n```fsharp\n// Falco v6.x\nlet myHandler : HttpHandler = fun ctx ->\n    task {\n        let! form = Request.getForm ctx\n\n        match form.IsValid with\n        | true -> // do something with form\n        | false -> // handle failed csrf validation\n    }\n```\n\n</td>\n</tr>\n</table>\n\n## `Request.mapFormSecure`\n\n\n<table>\n<tr>\n<td>\n\n```fsharp\n// Falco v5.x\ntype MyForm =\n    { Name : string }\n\nlet myHandler : HttpHandler =\n    let formMap (form : FormData) : MyForm  =\n        // do something with form\n\n    let handler (myForm : MyForm) : HttpHandler =\n        // do something with myForm\n\n    let handleInvalid : HttpHandler =\n        // handle failed csrf validation\n\n    Request.mapFormSecure formMap handler handleInvalid\n```\n\n</td>\n<td>\n\n```fsharp\n// Falco v6.x\ntype MyForm =\n    { Name : string }\n\nlet myHandler : HttpHandler =\n    let formMap (form : FormData) : MyForm  =\n        match form.IsValid with\n        | true -> Some // do something with form\n        | false -> None // handle failed csrf validation\n\n    let handler (myForm : MyForm option) : HttpHandler =\n        match myForm with\n        | Some myForm -> // do something with myForm\n        | None -> // handle failed csrf validation\n\n    Request.mapForm formMap handler\n```\n\n</td>\n</tr>\n</table>\n"
  },
  {
    "path": "documentation/readme.md",
    "content": "# Welcome to Falco's Documentation\n\nVisit the [getting started](get-started.md) page for installation and a brief overview. There are also more detailed [examples](example-hello-world.md) that shows how to create a small but complete application with Falco. The rest of the docs describe each component of Falco in detail.\n\n## Guides\n\nFalco depends only on the high-performance base components of .NET and ASP.NET Core, and provides a toolset to build a working full-stack web application. This section of the documentation explains the different parts of Falco and how they can be used, customized, and extended.\n\n- [Getting Started](get-started.md)\n- [Routing](routing.md)\n- [Writing responses](response.md)\n- [Accessing request data](request.md)\n- [View engine](markup.md)\n- Security\n    - [Cross Site Request Forgery (XSRF)](cross-site-request-forgery.html)\n    - [Authentication & Authorization](authentication.html)\n- [Host Configuration](host-configuration.md)\n- [Deployment](deployment.md)\n- Examples\n    - [Hello World](example-hello-world.md)\n    - [Hello World MVC](example-hello-world-mvc.md)\n    - [Dependency Injection](example-dependency-injection.md)\n    - [External View Engine](example-external-view-engine.md)\n    - [Basic REST API](example-basic-rest-api.md)\n    - [Open API](example-open-api.md)\n    - [HTMX](example-htmx.md)\n"
  },
  {
    "path": "documentation/request.md",
    "content": "# Request Handling\n\nFalco exposes a __uniform API__ to obtain typed values from `IFormCollection`, `IQueryCollection`, `RouteValueDictionary`, `IHeaderCollection`, and `IRequestCookieCollection`. This is achieved by means of the `RequestData` type and it's derivative `FormData`. These abstractions are intended to make it easier to work with the url-encoded key/value collections.\n\n> Take note of the similarities when interacting with the different sources of request data.\n\n## A brief aside on the key/value semantics\n\n`RequestData` is supported by a recursive discriminated union called `RequestValue` which represents a parsed key/value collection.\n\nThe `RequestValue` parsing process provides some simple, yet powerful, syntax to submit objects and collections over-the-wire, to facilitate complex form and query submissions.\n\n### Key Syntax: Object Notation\n\nKeys using dot notation are interpreted as complex (i.e., nested values) objects.\n\nConsider the following POST request:\n\n```\nPOST /my-form HTTP/1.1\nHost: foo.example\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 46\n\nuser.name=john%20doe&user.email=abc@def123.com\n```\n\nThis will be intepreted as the following `RequestValue`:\n\n```fsharp\nRObject [\n    \"user\", RObject [\n        \"name\", RString \"john doe\"\n        \"email\", RString \"abc@def123.com\"\n    ]\n]\n```\n\nSee [form binding](#form-binding) for details on interacting with form data.\n\n### Key Syntax: List Notation\n\nKeys using square bracket notation are interpreted as lists, which can include both primitives and [complex objects](#key-syntax-object-notation). Both indexed and non-indexed variants are supported.\n\nConsider the following request:\n\n```\nGET /my-search?name=john&season[0]=summer&season[1]=winter&hobbies[]=hiking HTTP/1.1\nHost: foo.example\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 68\n```\n\nThis will be interpreted as the following `RequestValue`:\n\n```fsharp\nRObject [\n    \"name\", RString \"john\"\n    \"season\", RList [ RString \"summer\"; RString \"winter\" ]\n    \"hobbies\", RList [ RString \"hking\" ]\n]\n```\n\nSee [query binding](#query-binding) for details on interacting with form data.\n\n## Request Data Access\n\n`RequestData` provides the ability to safely read primitive types from flat and nested key/value collections.\n\n```fsharp\nlet requestData : RequestData = // From: Route | Query | Form\n\n// Retrieve primitive options\nlet str : string option = requestData.TryGetString \"name\"\nlet flt : float option = requestData.TryGetFloat \"temperature\"\n\n// Retrieve primitive, or default\nlet str : string = requestData.GetString \"name\"\nlet strOrDefault : string = requestData.GetString (\"name\", \"John Doe\")\nlet flt : float = requestData.GetFloat \"temperature\"\n\n// Retrieve primitive list\nlet strList : string list = requestData.GetStringList \"hobbies\"\nlet grades : int list = requestData.GetInt32List \"grades\"\n\n// Dynamic access, useful for nested/complex collections\n// Equivalent to:\n// requestData.Get(\"user\").Get(\"email_address\").AsString()\nlet userEmail = requestData?user?email_address.AsString()\n\n```\n\n## Route Binding\n\nProvides access to the values found in the `RouteValueDictionary`.\n\n```fsharp\nopen Falco\n\n// Assuming a route pattern of /{Name}\nlet manualRouteHandler : HttpHandler = fun ctx ->\n    let r = Request.getRoute ctx\n    let name = r.GetString \"Name\"\n    // Or, let name = r?Name.AsString()\n    // Or, let name = r.TryGetString \"Name\" |> Option.defaultValue \"\"\n    Response.ofPlainText name ctx\n\nlet mapRouteHandler : HttpHandler =\n    Request.mapRoute (fun r ->\n        r.GetString \"Name\")\n        Response.ofPlainText\n\n```\n\n## Query Binding\n\nProvides access to the values found in the `IQueryCollection`, as well as the `RouteValueDictionary`. In the case of matching keys, the values in the `IQueryCollection` take precedence.\n\n```fsharp\nopen Falco\n\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet form : HttpHandler =\n    Response.ofHtmlCsrf view\n\nlet manualQueryHandler : HttpHandler = fun ctx ->\n    let q = Request.getQuery ctx\n\n    let person =\n        { FirstName = q.GetString (\"FirstName\", \"John\") // Get value or return default value\n          LastName  = q.GetString (\"LastName\", \"Doe\") }\n\n    Response.ofJson person ctx\n\nlet mapQueryHandler : HttpHandler =\n    Request.mapQuery (fun q ->\n        let first = q.GetString (\"FirstName\", \"John\") // Get value or return default value\n        let last = q.GetString (\"LastName\", \"Doe\")\n        { FirstName = first; LastName = last })\n        Response.ofJson\n```\n\n## Form Binding\n\nProvides access to the values found in he `IFormCollection`, as well as the `RouteValueDictionary`. In the case of matching keys, the values in the `IFormCollection` take precedence.\n\nThe `FormData` inherits from `RequestData` type also exposes the `IFormFilesCollection` via the `_.Files` member and `_.TryGetFile(name : string)` method.\n\n```fsharp\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet manualFormHandler : HttpHandler = fun ctx ->\n    task {\n        let! f : FormData = Request.getForm ctx\n\n        let person =\n            { FirstName = f.GetString (\"FirstName\", \"John\") // Get value or return default value\n              LastName = f.GetString (\"LastName\", \"Doe\") }\n\n        return! Response.ofJson person ctx\n    }\n\nlet mapFormHandler : HttpHandler =\n    Request.mapForm (fun f ->\n        let first = f.GetString (\"FirstName\", \"John\") // Get value or return default value\n        let last = f.GetString (\"LastName\", \"Doe\")\n        { FirstName = first; LastName = last })\n        Response.ofJson\n\nlet mapFormSecureHandler : HttpHandler =\n    Request.mapFormSecure (fun f -> // `Request.mapFormSecure` will automatically validate CSRF token for you.\n        let first = f.GetString (\"FirstName\", \"John\") // Get value or return default value\n        let last = f.GetString (\"LastName\", \"Doe\")\n        { FirstName = first; LastName = last })\n        Response.ofJson\n        (Response.withStatusCode 400 >> Response.ofEmpty)\n\n```\n\n### `multipart/form-data` Binding\n\nMicrosoft defines [large upload](https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads#upload-large-files-with-streaming) as anything **> 64KB**, which well... is most uploads. Anything beyond this size and they recommend streaming the multipart data to avoid excess memory consumption.\n\nTo make this process **a lot** easier Falco's form handlers will attempt to stream multipart form-data, or return an error message indicating the likely problem.\n\n```fsharp\nlet imageUploadHandler : HttpHandler =\n    let formBinder (f : FormData) : IFormFile option =\n        f.TryGetFormFile \"profile_image\"\n\n    let uploadImage (profileImage : IFormFile option) : HttpHandler =\n        // Process the uploaded file ...\n\n    // Safely buffer the multipart form submission\n    Request.mapForm formBinder uploadImage\n\nlet secureImageUploadHandler : HttpHandler =\n    let formBinder (f : FormData) : IFormFile option =\n        f.TryGetFormFile \"profile_image\"\n\n    let uploadImage (profileImage : IFormFile option) : HttpHandler =\n        // Process the uploaded file ...\n\n    let handleInvalidCsrf : HttpHandler =\n        Response.withStatusCode 400 >> Response.ofEmpty\n\n    // Safely buffer the multipart form submission\n    Request.mapFormSecure formBinder uploadImage handleInvalidCsrf\n```\n\n## JSON\n\nThese handlers use the .NET built-in `System.Text.Json.JsonSerializer`.\n\n```fsharp\ntype Person =\n    { FirstName : string\n      LastName : string }\n\nlet jsonHandler : HttpHandler =\n    Response.ofJson {\n        FirstName = \"John\"\n        LastName = \"Doe\" }\n\nlet mapJsonHandler : HttpHandler =\n    let handleOk person : HttpHandler =\n        let message = sprintf \"hello %s %s\" person.First person.Last\n        Response.ofPlainText message\n\n    Request.mapJson handleOk\n\nlet mapJsonOptionsHandler : HttpHandler =\n    let options = JsonSerializerOptions()\n    options.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull\n\n    let handleOk person : HttpHandler =\n        let message = sprintf \"hello %s %s\" person.First person.Last\n        Response.ofPlainText message\n\n    Request.mapJsonOption options handleOk\n```\n\n[Next: View engine](markup.md)\n"
  },
  {
    "path": "documentation/response.md",
    "content": "# Response Writing\n\nThe `HttpHandler` type is used to represent the processing of a request. It can be thought of as the eventual (i.e., asynchronous) completion and processing of an HTTP request, defined in F# as: `HttpContext -> Task`. Handlers will typically involve some combination of: [route inspection](request.md#route-binding), [form](request.md#form-binding)/[query](request.md#query-binding) binding, business logic and finally response writing. With access to the `HttpContext` you are able to inspect all components of the request, and manipulate the response in any way you choose.\n\n## Plain Text responses\n\n```fsharp\nlet textHandler : HttpHandler =\n    Response.ofPlainText \"hello world\"\n```\n\n## HTML responses\n\nWrite your views in plain F#, directly in your assembly, using the [Markup](markup.md) module. A performant F# DSL capable of generating any angle-bracket markup. Also available directly as a standalone [NuGet](https://www.nuget.org/packages/Falco.Markup) package.\n\n```fsharp\nlet htmlHandler : HttpHandler =\n    let html =\n        _html [ _lang_ \"en\" ] [\n            _head [] []\n            _body [] [\n                _h1' \"Sample App\" // shorthand for: `_h1 [] [ Text.raw \"Sample App\" ]`\n            ]\n        ]\n\n    Response.ofHtml html\n\n// Automatically protect against XSS attacks\nlet secureHtmlHandler : HttpHandler =\n    let html token =\n        _html [] [\n            _body [] [\n                _form [ _method_ \"post\" ] [\n                    _input [ _name_ \"first_name\" ]\n                    _input [ _name_ \"last_name\" ]\n                    // using the CSRF HTML helper\n                    Xsrf.antiforgeryInput token\n                    _input [ _type_ \"submit\"; _value_ \"Submit\" ]\n                ]\n            ]\n        ]\n\n    Response.ofHtmlCsrf html\n```\n\nAlternatively, if you're using an external view engine and want to return an HTML response from a string literal, then you can use `Response.ofHtmlString`.\n\n```fsharp\nlet htmlHandler : HttpHandler =\n    Response.ofHtmlString \"<html>...</html>\"\n```\n\n## Template Fragments\n\nIf you want to return a [fragment of HTML](https://htmx.org/essays/template-fragments/), for example when working with [htmx](https://htmx.org/), you can use `Response.ofFragment` (or `Response.ofFragmentCsrf`). This function takes an element ID as its first argument, and a `XmlNode` as its second argument. The server will return only the contents of the node with the specified ID.\n\n```fsharp\nlet fragmentHandler : HttpHandler =\n    let html =\n        _div [ _id_ \"greeting\" ] [\n            _h1 [ _id_ \"heading\" ] [ _text \"Hello, World!\" ]\n        ]\n\n    Response.ofFragment \"heading\" html\n```\n\nThis will return only the contents of the `h1` element, i.e. `<h1 id=\"heading\">Hello, World!</h1>`. In the case of multiple elements with the same ID, the first one found will be returned. If no element with the specified ID is found, an empty response will be returned.\n\n## JSON responses\n\nThese handlers use the .NET built-in `System.Text.Json.JsonSerializer`.\n\n```fsharp\ntype Person =\n    { First : string\n      Last  : string }\n\nlet jsonHandler : HttpHandler =\n    let name = { First = \"John\"; Last = \"Doe\" }\n    Response.ofJson name\n\nlet jsonOptionsHandler : HttpHandler =\n    let options = JsonSerializerOptions()\n    options.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull\n    let name = { First = \"John\"; Last = \"Doe\" }\n    Response.ofJsonOptions options name\n```\n\n## Redirect (301/302) Response\n\n```fsharp\nlet oldUrlHandler : HttpHandler =\n    Response.redirectPermanently \"/new-url\" // HTTP 301\n\nlet redirectUrlHandler : HttpHandler =\n    Response.redirectTemporarily \"/new-url\" // HTTP 302\n```\n\n## Content Disposition\n\n```fsharp\nlet inlineBinaryHandler : HttpHandler =\n    let contentType = \"image/jpeg\"\n    let headers = [ HeaderNames.CacheControl,  \"no-store, max-age=0\" ]\n    let bytes = // ... binary data\n    Response.ofBinary contentType headers bytes\n\nlet attachmentHandler : HttpHandler =\n    let filename = \"profile.jpg\"\n    let contentType = \"image/jpeg\"\n    let headers = [ HeaderNames.CacheControl,  \"no-store, max-age=0\" ]\n    let bytes = // ... binary data\n    Response.ofAttachment filename contentType headers bytes\n```\n\n## Response Modifiers\n\nResponse modifiers can be thought of as the in-and-out modification of the `HttpResponse`. A preamble to writing and returning. Since these functions receive the `Httpcontext` as input and return it as the only output, they can take advantage of function compoistion.\n\n### Set the status code of the response\n\n```fsharp\nlet notFoundHandler : HttpHandler =\n    Response.withStatusCode 404\n    >> Response.ofPlainText \"Not found\"\n```\n\n### Add a header(s) to the response\n\n```fsharp\nlet handlerWithHeaders : HttpHandler =\n    Response.withHeaders [ \"Content-Language\", \"en-us\" ]\n    >> Response.ofPlainText \"Hello world\"\n```\n\n### Add a cookie to the response\n\n> IMPORTANT: *Do not* use this for authentication. Instead use the `Response.signInAndRedirect` and `Response.signOutAndRedirect` functions found in the [Authentication](authenication.md) module.\n\n```fsharp\nlet handlerWithCookie : HttpHandler =\n    Response.withCookie \"greeted\" \"1\"\n    >> Response.ofPlainText \"Hello world\"\n\nlet handlerWithCookieOptions : HttpHandler =\n    let options = CookieOptions()\n    options.Expires <- DateTime.Now.Minutes(15)\n    Response.withCookie options \"greeted\" \"1\"\n    >> Response.ofPlainText \"Hello world\"\n```\n\n[Next: Request Handling](request.md)\n"
  },
  {
    "path": "documentation/routing.md",
    "content": "# Routing\n\nRouting is responsible for matching incoming HTTP requests and dispatching those requests to the app's `HttpHandler`s. The breakdown of [Endpoint Routing](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#configuring-endpoint-metadata) is simple. Associate a specific [route pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-template-reference) and an HTTP verb to an [`HttpHandler`](request.md) which represents the ongoing processing (and eventual return) of a request.\n\nBearing this in mind, routing can practically be represented by a list of these \"mappings\" known in Falco as an `HttpEndpoint` which bind together: a route, verb and handler.\n\n> Note: All of the following examples are _fully functioning_ web apps.\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [ get \"/\" (Response.ofPlainText \"hello world\") ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\nThe preceding example includes a single `HttpEndpoint`:\n- When an HTTP `GET` request is sent to the root URL `/`:\n    - The `HttpHandler` shown executes.\n    - `Hello World!` is written to the HTTP response using the [Response](response.md) module.\n- If the request method is not `GET` or the URL is not `/`, no route matches and an HTTP 404 is returned.\n\nThe following example shows a more sophisticated `HttpEndpoint`:\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get \"/hello/{name:alpha}\" (fun ctx ->\n            let route = Request.getRoute ctx\n            let name = route.GetString \"name\"\n            let message = sprintf \"Hello %s\" name\n            Response.ofPlainText message ctx)\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\nThe string `/hello/{name:alpha}` is a **route template**. It is used to configure how the endpoint is matched. In this case, the template matches:\n\n- A URL like `/hello/Ryan`\n- Any URL path that begins with `/hello/` followed by a sequence of alphabetic characters. `:alpha` applies a route constraint that matches only alphabetic characters.\n  - Full route constraint reference: [https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-constraint-reference](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing#route-constraint-reference).\n\nThe second segment of the URL path, `{name:alpha}`:\n\n- Is bound to the `name` parameter.\n- Is captured and stored in `HttpRequest.RouteValues`, which Falco exposes through a [uniform API](request.md) to obtain primitive typed values.\n\nAn alternative way to express the `HttEndpoint` above is seen below.\n\n```fsharp\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nlet greetingHandler name : HttpHandler =\n    let message = sprintf \"Hello %s\" name\n    Response.ofPlainText message\n\nlet endpoints =\n    [ mapGet \"/hello/{name:alpha}\" (fun route -> route.GetString \"name\") greetingHandler ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\n## Multi-method Endpoints\n\nThere are scenarios where you may want to accept multiple HTTP verbs to single a URL. For example, a `GET`/`POST` form submission.\n\nTo create a \"multi-method\" endpoint, the `all` function accepts a list of HTTP Verb and HttpHandler pairs.\n\n```fsharp\nopen Falco\nopen Falco.Markup\nopen Microsoft.AspNetCore.Builder\n\nlet form =\n    Templates.html5 \"en\" [] [\n        _form [ _method_ \"post\" ] [\n            _input [ _name_ \"name\" ]\n            _input [ _type_ \"submit\" ] ] ]\n\nlet wapp = WebApplication.Create()\n\nlet endpoints =\n    [\n        get \"/\" (Response.ofPlainText \"Hello from /\")\n        all \"/form\" [\n            GET, Response.ofHtml form\n            POST, Response.ofEmpty ]\n    ]\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n```\n\n[Next: Response Writing](response.md)\n"
  },
  {
    "path": "examples/BasicRestApi/BasicRestApi.fs",
    "content": "namespace BasicRestApi\n\nopen System.Data\nopen Donald\n// ^-- external package that makes using databases simpler\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\nopen System.Data.SQLite\n// ^-- official SQLite package\n\ntype Error =\n    { Code : string\n      Message : string }\n\ntype User =\n    { Username : string\n      FullName : string }\n\ntype IDbConnectionFactory =\n    abstract member Create : unit -> IDbConnection\n\ntype IStore<'TKey, 'TItem> =\n    abstract member List : unit   -> 'TItem list\n    abstract member Create : 'TItem -> Result<unit, Error>\n    abstract member Read : 'TKey -> 'TItem option\n    abstract member Delete : 'TKey -> Result<unit, Error>\n\ntype UserStore(dbConnection : IDbConnectionFactory) =\n    let userOfDataReader (rd : IDataReader) =\n        { Username = rd.ReadString \"username\"\n          FullName = rd.ReadString \"full_name\" }\n\n    interface IStore<string, User> with\n        member _.List() =\n            use conn = dbConnection.Create()\n            conn\n            |> Db.newCommand \"SELECT username, full_name FROM user\"\n            |> Db.query userOfDataReader\n\n        member _.Create(user : User) =\n            use conn = dbConnection.Create()\n            try\n                conn\n                |> Db.newCommand \"\n                    INSERT INTO user (username, full_name)\n                    SELECT    @username\n                            , @full_name\n                    WHERE     @username NOT IN (\n                                SELECT username FROM user)\"\n                |> Db.setParams [\n                    \"username\", SqlType.String user.Username\n                    \"full_name\", SqlType.String user.FullName ]\n                |> Db.exec\n                |> Ok\n            with\n            | :? DbExecutionException ->\n                Error { Code = \"FAILED\"; Message = \"Could not add user\" }\n\n        member _.Read(username : string) =\n            use conn = dbConnection.Create()\n            conn\n            |> Db.newCommand \"\n                SELECT    username\n                        , full_name\n                FROM      user\n                WHERE     username = @username\"\n            |> Db.setParams [ \"username\", SqlType.String username ]\n            |> Db.querySingle userOfDataReader\n\n        member _.Delete(username : string) =\n            use conn = dbConnection.Create()\n            try\n                conn\n                |> Db.newCommand \"DELETE FROM user WHERE username = @username\"\n                |> Db.setParams [ \"username\", SqlType.String username ]\n                |> Db.exec\n                |> Ok\n            with\n            | :? DbExecutionException ->\n                Error { Code = \"FAILED\"; Message = \"Could not add user\" }\n\nmodule ErrorResponse =\n    let badRequest error : HttpHandler =\n        Response.withStatusCode 400\n        >> Response.ofJson error\n\n    let notFound : HttpHandler =\n        Response.withStatusCode 404 >>\n        Response.ofJson { Code = \"404\"; Message = \"Not Found\" }\n\n    let serverException : HttpHandler =\n        Response.withStatusCode 500 >>\n        Response.ofJson { Code = \"500\"; Message = \"Server Error\" }\n\nmodule Route =\n    let userIndex = \"/users\"\n    let userAdd = \"/users\"\n    let userView = \"/users/{username}\"\n    let userRemove = \"/users/{username}\"\n\nmodule UserEndpoint =\n    let index : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let allUsers = userStore.List()\n        Response.ofJson allUsers ctx\n\n    let add : HttpHandler = fun ctx -> task {\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let! userJson = Request.getJson<User> ctx\n        let userAddResponse =\n            match userStore.Create(userJson) with\n            | Ok result -> Response.ofJson result ctx\n            | Error error -> ErrorResponse.badRequest error ctx\n        return! userAddResponse }\n\n    let view : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Read(username) with\n        | Some user -> Response.ofJson user ctx\n        | None -> ErrorResponse.notFound ctx\n\n    let remove : HttpHandler = fun ctx ->\n        let userStore = ctx.Plug<IStore<string, User>>()\n        let route = Request.getRoute ctx\n        let username = route?username.AsString()\n        match userStore.Delete(username) with\n        | Ok result -> Response.ofJson result ctx\n        | Error error -> ErrorResponse.badRequest error ctx\n\nmodule App =\n    let endpoints =\n        [ get Route.userIndex UserEndpoint.index\n          post Route.userAdd UserEndpoint.add\n          get Route.userView UserEndpoint.view\n          delete Route.userRemove UserEndpoint.remove ]\n\nmodule Program =\n    [<EntryPoint>]\n    let main args =\n        let dbConnectionFactory =\n            { new IDbConnectionFactory with\n                member _.Create() = new SQLiteConnection(\"Data Source=BasicRestApi.sqlite3\") }\n\n        let initializeDatabase (dbConnection : IDbConnectionFactory) =\n            use conn = dbConnection.Create()\n            conn\n            |> Db.newCommand \"CREATE TABLE IF NOT EXISTS user (username, full_name)\"\n            |> Db.exec\n\n        initializeDatabase dbConnectionFactory\n\n        let bldr = WebApplication.CreateBuilder(args)\n\n        bldr.Services\n            .AddAntiforgery()\n            .AddSingleton<IDbConnectionFactory>(dbConnectionFactory)\n            .AddScoped<IStore<string, User>, UserStore>()\n            |> ignore\n\n        let wapp = bldr.Build()\n\n        let isDevelopment = wapp.Environment.EnvironmentName = \"Development\"\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorResponse.serverException)\n            .UseRouting()\n            .UseFalco(App.endpoints)\n            .Run(ErrorResponse.notFound)\n\n        0\n"
  },
  {
    "path": "examples/BasicRestApi/BasicRestApi.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Donald\" Version=\"10.*\" />\n    <PackageReference Include=\"System.Data.SQLite\" Version=\"2.*\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"BasicRestApi.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "examples/BasicRestApi/appsettings.json",
    "content": "{\n    \"Logging\": {\n        \"LogLevel\": {\n            \"Default\": \"Information\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/DependencyInjection/DependencyInjection.fs",
    "content": "open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\n\ntype IGreeter =\n    abstract member Greet : name : string -> string\n\ntype FriendlyGreeter() =\n    interface IGreeter with\n        member _.Greet(name : string) =\n            $\"Hello {name} 😀\"\n\nlet bldr = WebApplication.CreateBuilder()\n// ^-- create a configurable web application builder\n\nbldr.Services\n    .AddSingleton<IGreeter, FriendlyGreeter>()\n    // ^-- register the greeter as singleton in the container\n    |> ignore\n\nlet endpoints =\n    [\n        mapGet \"/{name?}\"\n            (fun r -> r?name.AsStringNonEmpty(\"world\"))\n            (fun name ctx ->\n                let greeter = ctx.Plug<IGreeter>()\n                // ^-- access our dependency from the container\n\n                let greeting = greeter.Greet(name)\n                // ^-- invoke our greeter.Greet(name) method\n\n                Response.ofPlainText greeting ctx)\n    ]\n\nlet wapp = bldr.Build()\n// ^-- manifest our WebApplication\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n    .Run()\n"
  },
  {
    "path": "examples/DependencyInjection/DependencyInjection.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Include=\"DependencyInjection.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "examples/ExternalViewEngine/ExternalViewEngine.fs",
    "content": "module Falco.Scriban.Program\n\nopen Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\nopen Scriban\n\ntype ITemplate =\n    abstract member Render : template: string * model: obj -> string\n\ntype ScribanTemplate() =\n    interface ITemplate with\n        member _.Render(template, model) =\n            let tmpl = Template.Parse template\n            tmpl.Render(model)\n\nmodule Pages =\n    let private renderPage pageTitle template viewModel : HttpHandler = fun ctx ->\n        let templateService = ctx.Plug<ITemplate>()\n        // ^-- obtain our template service from the dependency container\n\n        let pageContent = templateService.Render(template, viewModel)\n        // ^-- render our template with the provided view model as string literal\n\n        let htmlTemplate = \"\"\"\n            <!DOCTYPE html>\n            <html>\n            <head>\n                <meta charset=\"utf-8\">\n                <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n                <title>{{ title }}</title>\n            </head>\n            <body>\n                {{ content }}\n            </body>\n            </html>\n        \"\"\"\n        // ^-- these triple quoted strings auto-escape characters like double quotes for us\n        //     very practical for things like HTML\n\n        let html = templateService.Render(htmlTemplate, {| Title = pageTitle; Content = pageContent |})\n\n        Response.ofHtmlString html ctx\n        // ^-- return template literal as \"text/html; charset=utf-8\" response\n\n    let homepage : HttpHandler = fun ctx ->\n        let query = Request.getQuery ctx\n        // ^-- obtain access to strongly-typed representation of the query string\n\n        let viewModel = {| Name = query?name.AsStringNonEmpty(\"World\") |}\n        // ^-- access 'name' from query, or default to 'World'\n\n        let template = \"\"\"\n            <h1>Hello {{ name }}!</h1>\n        \"\"\"\n        renderPage $\"Hello {viewModel.Name}\" template viewModel ctx\n\n    let notFound : HttpHandler =\n        let template = \"\"\"\n            <h1>Page not found</h1>\n        \"\"\"\n        renderPage \"Page Not Found\" template {||}\n\n[<EntryPoint>]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddSingleton<ITemplate, ScribanTemplate>()\n        // ^-- register ITemplates implementation as a dependency\n        |> ignore\n\n    let wapp = bldr.Build()\n\n    let endpoints =\n        [ get \"/\" Pages.homepage ]\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .Run(Pages.notFound)\n\n    0 // Exit code\n"
  },
  {
    "path": "examples/ExternalViewEngine/ExternalViewEngine.fsproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n    <PackageReference Include=\"Scriban\" Version=\"5.*\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Include=\"ExternalViewEngine.fs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Include=\"Views\\*\" CopyToOutputDirectory=\"PreserveNewest\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "examples/Falco.Examples.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.0.31903.59\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"BasicRestApi\", \"BasicRestApi\\BasicRestApi.fsproj\", \"{1F90D83C-08A1-45AE-8354-43A67103C4E0}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Falco\", \"..\\src\\Falco\\Falco.fsproj\", \"{172FD81C-526D-4AFF-851B-22AD79011C92}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"Htmx\", \"Htmx\\Htmx.fsproj\", \"{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"HelloWorldMvc\", \"HelloWorldMvc\\HelloWorldMvc.fsproj\", \"{473C763D-B566-4FA4-928A-49710C521429}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"HelloWorld\", \"HelloWorld\\HelloWorld.fsproj\", \"{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"DependencyInjection\", \"DependencyInjection\\DependencyInjection.fsproj\", \"{467C1D39-C674-4011-9F89-06276717A229}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"OpenApi\", \"OpenApi\\OpenApi.fsproj\", \"{02615E3C-0917-4D78-B79C-3BCC06081D2F}\"\nEndProject\nProject(\"{F2A71F9B-5D33-465A-A702-920D77279786}\") = \"ExternalViewEngine\", \"ExternalViewEngine\\ExternalViewEngine.fsproj\", \"{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tDebug|x64 = Debug|x64\n\t\tDebug|x86 = Debug|x86\n\t\tRelease|Any CPU = Release|Any CPU\n\t\tRelease|x64 = Release|x64\n\t\tRelease|x86 = Release|x86\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|x64.Build.0 = Release|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{1F90D83C-08A1-45AE-8354-43A67103C4E0}.Release|x86.Build.0 = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|x64.Build.0 = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{172FD81C-526D-4AFF-851B-22AD79011C92}.Release|x86.Build.0 = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|x64.Build.0 = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{6C0D3898-42FB-4EEA-975F-52675D7D0AA0}.Release|x86.Build.0 = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|x64.Build.0 = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{473C763D-B566-4FA4-928A-49710C521429}.Release|x86.Build.0 = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|x64.Build.0 = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{031A5120-0837-4EE8-BFDB-0E6C33D4ADF6}.Release|x86.Build.0 = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|x64.Build.0 = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{467C1D39-C674-4011-9F89-06276717A229}.Release|x86.Build.0 = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|x64.Build.0 = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{02615E3C-0917-4D78-B79C-3BCC06081D2F}.Release|x86.Build.0 = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|x64.ActiveCfg = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|x64.Build.0 = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|x86.ActiveCfg = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Debug|x86.Build.0 = Debug|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|Any CPU.Build.0 = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|x64.ActiveCfg = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|x64.Build.0 = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|x86.ActiveCfg = Release|Any CPU\n\t\t{AC590F56-9EBB-46EA-AE36-91CA1E2EAC54}.Release|x86.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\nEndGlobal\n"
  },
  {
    "path": "examples/HelloWorld/HelloWorld.fs",
    "content": "open Falco\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco([\n        get \"/\" (Response.ofPlainText \"Hello World!\")\n    ])\n    .Run(Response.ofPlainText \"Not found\")\n"
  },
  {
    "path": "examples/HelloWorld/HelloWorld.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Include=\"HelloWorld.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "examples/HelloWorldMvc/HelloWorldMvc.fs",
    "content": "namespace HelloWorldMvc\n\nopen Falco\nopen Falco.Markup\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\n\nmodule Model =\n    type NameGreeting =\n        { Name : string }\n\n    type Greeting =\n        { Message : string }\n\nmodule Route =\n    let index = \"/\"\n    let greetPlainText = \"/greet/text/{name?}\"\n    let greetJson = \"/greet/json/{name?}\"\n    let greetHtml = \"/greet/html/{name?}\"\n\nmodule Url =\n    let greetPlainText name = Route.greetPlainText.Replace(\"{name?}\", name)\n    let greetJson name = Route.greetJson.Replace(\"{name?}\", name)\n    let greetHtml name = Route.greetHtml.Replace(\"{name?}\", name)\n\nmodule View =\n    open Model\n\n    let layout content =\n        Templates.html5 \"en\"\n            [ _link [ _href_ \"/style.css\"; _rel_ \"stylesheet\" ] ]\n            content\n\n    module GreetingView =\n        let detail greeting =\n            layout [\n                _h1' $\"Hello {greeting.Name} using HTML\"\n                _hr []\n                _p' \"Greet other ways:\"\n                _nav [] [\n                    _a\n                        [ _href_ (Url.greetHtml greeting.Name) ]\n                        [ _text \"Greet in HTML\"]\n                    _text \" | \"\n                    _a\n                        [ _href_ (Url.greetPlainText greeting.Name) ]\n                        [ _text \"Greet in plain text\"]\n                    _text \" | \"\n                    _a\n                        [ _href_ (Url.greetJson greeting.Name) ]\n                        [ _text \"Greet in JSON \" ]\n                ]\n            ]\n\nmodule Controller =\n    open Model\n    open View\n\n    /// Error page(s)\n    module ErrorController =\n        let notFound : HttpHandler =\n            Response.withStatusCode 404 >>\n            Response.ofHtml (layout [ _h1' \"Not Found\" ])\n\n        let serverException : HttpHandler =\n            Response.withStatusCode 500 >>\n            Response.ofHtml (layout [ _h1' \"Server Error\" ])\n\n        let endpoints =\n            [ get \"/error/not-found\" notFound\n              get \"/error/server-exception\" serverException ]\n\n    module GreetingController =\n        let index name =\n            { Name = name }\n            |> GreetingView.detail\n            |> Response.ofHtml\n\n        let plainTextDetail name =\n            Response.ofPlainText $\"Hello {name} using plain text\"\n\n        let jsonDetail name =\n            let message = { Message = $\"Hello {name} using JSON\" }\n            Response.ofJson message\n\n        let endpoints =\n            let mapRoute (r : RequestData) =\n                r?name.AsStringNonEmpty(\"you\")\n\n            [ mapGet Route.index mapRoute index\n              mapGet Route.greetPlainText mapRoute plainTextDetail\n              mapGet Route.greetJson mapRoute jsonDetail\n              mapGet Route.greetHtml mapRoute index ]\n\nmodule App =\n    open Controller\n\n    let endpoints =\n        ErrorController.endpoints\n        @ GreetingController.endpoints\n\nmodule Program =\n    open Controller\n\n    [<EntryPoint>]\n    let main args =\n        let wapp = WebApplication.Create(args)\n\n        let isDevelopment = wapp.Environment.EnvironmentName = \"Development\"\n\n        wapp.UseIf(isDevelopment, DeveloperExceptionPageExtensions.UseDeveloperExceptionPage)\n            .UseIf(not(isDevelopment), FalcoExtensions.UseFalcoExceptionHandler ErrorController.serverException)\n            .Use(StaticFileExtensions.UseStaticFiles)\n            .UseRouting()\n            .UseFalco(App.endpoints)\n            .Run(ErrorController.notFound)\n\n        0\n"
  },
  {
    "path": "examples/HelloWorldMvc/HelloWorldMvc.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"HelloWorldMvc.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "examples/HelloWorldMvc/appsettings.json",
    "content": "{\n    \"Logging\": {\n        \"LogLevel\": {\n            \"Default\": \"Information\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/HelloWorldMvc/wwwroot/style.css",
    "content": "﻿body { font-family: 'Comic Sans MS' }\n"
  },
  {
    "path": "examples/Htmx/Htmx.fs",
    "content": "﻿open System\nopen Falco\nopen Falco.Markup\nopen Falco.Routing\nopen Falco.Htmx\nopen Microsoft.AspNetCore.Builder\n\nmodule View =\n    let template content =\n        _html [ _lang_ \"en\" ] [\n            _head [] [\n                _script [ _src_ HtmxScript.cdnSrc ] [] ]\n            _body [] content ]\n\n    let clickAndSwap =\n        template [\n            _h1' \"Example: Click & Swap\"\n            _div [ _id_ \"content\" ] [\n                _button [\n                    _id_ \"clicker\"\n                    Hx.get \"/click\"\n                    Hx.swapOuterHtml ]\n                    [ _text \"Click Me\" ] ] ]\n\n    module Components =\n        let resetter =\n            _div [ _id_ \"resetter\" ] [\n                _h2' \"Way to go! You clicked it!\"\n                _br []\n                _button [\n                    Hx.get \"/reset\"\n                    Hx.swapOuterHtml\n                    Hx.targetCss \"#resetter\" ]\n                    [ _text \"Reset\" ] ]\n\n\nmodule App =\n    let handleIndex : HttpHandler =\n        Response.ofHtml View.clickAndSwap\n\n    let handleClick : HttpHandler =\n        Response.ofHtml View.Components.resetter\n\n    let handleReset : HttpHandler =\n        Response.ofFragment \"clicker\" View.clickAndSwap\n\n    let endpoints =\n        [\n            get \"/\" handleIndex\n            get \"/click\" handleClick\n            get \"/reset\" handleReset\n        ]\n\nlet wapp = WebApplication.Create()\n\nwapp.UseRouting()\n    .UseFalco(App.endpoints)\n    .Run()\n"
  },
  {
    "path": "examples/Htmx/Htmx.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Falco.Htmx\" Version=\"1.*\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Htmx.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "examples/Htmx/appsettings.json",
    "content": "{\n    \"Logging\": {\n        \"LogLevel\": {\n            \"Default\": \"Information\"\n        }\n    }\n}\n"
  },
  {
    "path": "examples/OpenApi/OpenApi.fs",
    "content": "open Falco\nopen Falco.OpenApi\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\nopen Microsoft.Extensions.Hosting\n\ntype FortuneInput =\n    { Name : string }\n\ntype Fortune =\n    { Description : string }\n\nmodule Fortune =\n    let create age input =\n        match age with\n        | Some age when age > 0 ->\n            { Description = $\"{input.Name}, you will experience great success when you are {age + 3}.\" }\n        | _ ->\n            { Description = $\"{input.Name}, your future is unclear.\" }\n\n[<EntryPoint>]\nlet main args =\n    let bldr = WebApplication.CreateBuilder(args)\n\n    bldr.Services\n        .AddFalcoOpenApi()\n        .AddSwaggerGen()\n        |> ignore\n\n    let wapp = bldr.Build()\n\n    wapp.UseHttpsRedirection()\n        .UseSwagger()\n        .UseSwaggerUI()\n    |> ignore\n\n    let endpoints =\n        [\n            mapPost \"/fortune\"\n                (fun r -> r?age.AsIntOption())\n                (fun ageOpt ->\n                    Request.mapJson<FortuneInput> (Fortune.create ageOpt >> Response.ofJson))\n                |> OpenApi.name \"Fortune\"\n                |> OpenApi.summary \"A mystic fortune teller\"\n                |> OpenApi.description \"Get a glimpse into your future, if you dare.\"\n                |> OpenApi.query [\n                    { Type = typeof<int>; Name = \"Age\"; Required = false } ]\n                |> OpenApi.acceptsType typeof<FortuneInput>\n                |> OpenApi.returnType typeof<Fortune>\n        ]\n\n    wapp.UseRouting()\n        .UseFalco(endpoints)\n        .Run()\n\n    0\n"
  },
  {
    "path": "examples/OpenApi/OpenApi.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Falco.OpenApi\" Version=\"1.*\" />\n    <PackageReference Include=\"Swashbuckle.AspNetCore\" Version=\"6.*\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"OpenApi.fs\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "global.json",
    "content": "{\n    \"sdk\": {\n        \"version\": \"10.0.100\",\n        \"rollForward\": \"latestMinor\",\n        \"allowPrerelease\": false\n    }\n}"
  },
  {
    "path": "site/Site.fs",
    "content": "open System\nopen System.IO\nopen System.Text.RegularExpressions\nopen Falco.Markup\nopen Markdig\nopen Markdig.Syntax.Inlines\nopen Markdig.Renderers\nopen Markdig.Syntax\n\ntype LayoutModel =\n    { Title : string\n      MainContent : string }\n\ntype LayoutTwoColModel =\n    { Title : string\n      SideContent : XmlNode list\n      MainContent : string }\n\ntype ParsedMarkdownDocument =\n    { Title : string\n      Body  : string }\n\nmodule Markdown =\n    let render (markdown : string) : ParsedMarkdownDocument =\n        // Render Markdown as HTML\n        let pipeline =\n            MarkdownPipelineBuilder()\n                .UseAutoIdentifiers()\n                .UsePipeTables()\n                .UseAutoLinks()\n                .Build()\n\n        use sw = new StringWriter()\n        let renderer = HtmlRenderer(sw)\n\n        pipeline.Setup(renderer) |> ignore\n\n        let doc = Markdown.Parse(markdown, pipeline)\n\n        renderer.Render(doc) |> ignore\n        sw.Flush() |> ignore\n        let renderedMarkdown = sw.ToString()\n\n        // Extract title\n        let title =\n            doc.Descendants<HeadingBlock>()\n            |> Seq.tryFind (fun x -> x.Level = 1)\n            |> Option.bind (fun x ->\n                x.Inline\n                |> Seq.tryPick (fun i ->\n                    match i with\n                    | :? LiteralInline as literal -> Some(literal.ToString())\n                    | _ -> None))\n\n\n        // Rewrite direct markdown doc links\n        let body = Regex.Replace(renderedMarkdown.Replace(\"\\\"documentation/\", \"\\\"docs/\"), \"([a-zA-Z\\-]+)\\.md\", \"$1.html\")\n\n        { Title = title |> Option.defaultValue \"\"\n          Body = body }\n\n    let renderFile (path : string) =\n        render (File.ReadAllText(path))\n\nmodule View =\n    let docsLinks =\n        [\n            _h3 [] [ _text \"Project Links\" ]\n            _a [ _href_ \"/\"] [ _text \"Project Homepage\" ]\n            _a [ _class_ \"db\"; _href_ \"https://github.com/FalcoFramework/Falco\"; _targetBlank_ ]\n                [ _text \"Source Code\" ]\n            _a [ _class_ \"db\"; _href_ \"https://github.com/FalcoFramework/Falco/issues\"; _targetBlank_ ]\n                [ _text \"Issue Tracker\" ]\n            _a [ _class_ \"db\"; _href_ \"https://github.com/FalcoFramework/Falco/discussions\"; _targetBlank_ ]\n                [ _text \"Discussion\" ]\n            _a [ _class_ \"db\"; _href_ \"https://twitter.com/falco_framework\"; _targetBlank_ ]\n                [ _text \"Twitter\" ]\n        ]\n\n    let docsNav =\n        [\n            Text.h3 \"Contents\"\n            _ul [ _class_ \"nl3 f6\" ] [\n                _li [] [ _a [ _href_ \"get-started.html\" ] [ _text \"Getting Started\" ] ]\n                _li [] [ _a [ _href_ \"routing.html\" ] [ _text \"Routing\" ] ]\n                _li [] [ _a [ _href_ \"response.html\" ] [ _text \"Response Writing\" ] ]\n                _li [] [ _a [ _href_ \"request.html\" ] [ _text \"Request Handling\" ] ]\n                _li [] [ _a [ _href_ \"markup.html\" ] [ _text \"Markup\" ] ]\n                _li [] [\n                    _text \"Security\"\n                    _ul [] [\n                        _li [] [ _a [ _href_ \"cross-site-request-forgery.html\" ] [ _text \"Cross Site Request Forgery (XSRF)\" ] ]\n                        _li [] [ _a [ _href_ \"authentication.html\" ] [ _text \"Authentication & Authorization\" ] ]\n                    ]\n                ]\n                _li [] [ _a [ _href_ \"host-configuration.html\" ] [ _text \"Host Configuration\" ] ]\n                _li [] [ _a [ _href_ \"deployment.html\" ] [ _text \"Deployment\" ] ]\n                _li [] [\n                    _text \"Examples\"\n                    _ul [] [\n                        _li [] [ _a [ _href_ \"example-hello-world.html\" ] [ _text \"Hello World\" ] ]\n                        _li [] [ _a [ _href_ \"example-hello-world-mvc.html\" ] [ _text \"Hello World MVC\" ] ]\n                        _li [] [ _a [ _href_ \"example-dependency-injection.html\" ] [ _text \"Dependency Injection\" ] ]\n                        _li [] [ _a [ _href_ \"example-external-view-engine.html\" ] [ _text \"External View Engine\" ] ]\n                        _li [] [ _a [ _href_ \"example-basic-rest-api.html\" ] [ _text \"Basic REST API\" ] ]\n                        _li [] [ _a [ _href_ \"example-open-api.html\" ] [ _text \"Open API\" ] ]\n                        _li [] [ _a [ _href_ \"example-htmx.html\" ] [ _text \"htmx\" ] ]\n                    ]\n                ]\n                _li [] [ _a [ _href_ \"migrating-from-v4-to-v5.html\" ] [ _text \"V5 Migration Guide\" ] ]\n            ]\n        ]\n\n    let private _layoutHead title =\n        let title =\n            if String.IsNullOrWhiteSpace(title) then\n                \"Falco - F# web toolkit for ASP.NET Core\"\n            else\n                $\"{title} - Falco Documentation\"\n\n        [\n            _meta  [ _charset_ \"UTF-8\" ]\n            _meta  [ _httpEquiv_ \"X-UA-Compatible\"; _content_ \"IE=edge, chrome=1\" ]\n            _meta  [ _name_ \"viewport\"; _content_ \"width=device-width, initial-scale=1\" ]\n            _title [] [ _text title ]\n            _meta  [ _name_ \"description\"; _content_ \"A functional-first toolkit for building brilliant ASP.NET Core applications using F#.\" ]\n\n            _link [ _rel_ \"shortcut icon\"; _href_ \"/favicon.ico\"; _type_ \"image/x-icon\" ]\n            _link [ _rel_ \"icon\"; _href_ \"/favicon.ico\"; _type_ \"image/x-icon\" ]\n            _link [ _rel_ \"preconnect\"; _href_ \"https://fonts.gstatic.com\" ]\n            _link [ _href_ \"https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap\"; _rel_ \"stylesheet\" ]\n            _link [ _href_ \"/prism.css\"; _rel_ \"stylesheet\" ]\n            _link [ _href_ \"/tachyons.css\"; _rel_ \"stylesheet\" ]\n            _link [ _href_ \"/style.css\"; _rel_ \"stylesheet\" ]\n\n            _script [ _async_; _src_ \"https://www.googletagmanager.com/gtag/js?id=G-D62HSJHMNZ\" ] []\n            _script [] [ _text \"\"\"window.dataLayer = window.dataLayer || [];\n                function gtag(){dataLayer.push(arguments);}\n                gtag('js', new Date());\n                gtag('config', 'G-D62HSJHMNZ');\"\"\"\n            ]\n        ]\n\n    let private _layoutFooter =\n        _footer [ _class_ \"cl pa3 bg-merlot\" ] [\n            _div [ _class_ \"f7 tc white-70\" ]\n                [ _text $\"&copy; 2020-{DateTime.Now.Year} Pim Brouwers & contributors.\" ]\n        ]\n\n    let layout (model : LayoutModel) =\n        let topBar =\n            _div [] [\n                _nav [ _class_ \"flex flex-column flex-row-l items-center\" ] [\n                    _a [ _href_ \"/\" ]\n                        [ _img [ _src_ \"/icon.svg\"; _class_ \"w3 pb3 pb0-l o-80 hover-o-100\" ] ]\n                    _div [ _class_ \"flex-grow-1-l tc tr-l\" ] [\n                        _a [ _href_ \"/docs\"; _title_ \"Overview of Falco's key features\"; _class_ \"dib mh2 mh3-l no-underline white-90 hover-white\" ]\n                            [ _text \"docs\" ]\n                        _a [ _href_ \"https://github.com/FalcoFramework/Falco\"; _title_ \"Fork Falco on GitHub\"; _alt_ \"Falco GitHub Link\"; _targetBlank_; _class_ \"dib mh2 ml3-l no-underline white-90 hover-white\" ]\n                            [ _text \"code\" ]\n                        _a [ _href_ \"https://github.com/FalcoFramework/Falco/tree/master/examples\"; _title_ \"Falco code samples\"; _alt_ \"Faclo code samples link\"; _class_ \"dib ml2 mh3-l no-underline white-90 hover-white\" ]\n                            [ _text \"samples\" ]\n                        _a [ _href_ \"https://github.com/FalcoFramework/Falco/discussions\"; _title_ \"Need help?\"; _alt_ \"Faclo GitHub discussions link\"; _class_ \"dib ml2 mh3-l no-underline white-90 hover-white\" ]\n                            [ _text \"help\" ]\n                    ]\n                ]\n            ]\n\n        let greeting =\n            _div [ _class_ \"mw6 center pb5 noto tc fw4 lh-copy white\" ] [\n                _h1 [ _class_ \"mt4 mb3 fw4 f2\" ]\n                    [ _text \"Meet Falco.\" ]\n                _h2 [ _class_ \"mt0 mb4 fw4 f4 f3-l\" ]\n                    [ _text \"Falco is a toolkit for building fast and functional-first web applications using F#.\" ]\n\n                _div [ _class_ \"tc\" ] [\n                    _a [ _href_ \"/docs/get-started.html\"; _title_ \"Learn how to get started using Falco\"; _class_ \"dib mh2 mb2 ph3 pv2 merlot bg-white ba b--white br2 no-underline\" ]\n                        [ _text \"Get Started\" ]\n                    _a [ _href_ \"#falco\"; _class_ \"dib mh2 ph3 pv2 white ba b--white br2 no-underline\" ]\n                        [ _text \"Learn More\" ]\n                ]\n            ]\n\n        let releaseInfo =\n            _div [ _class_ \"mb4 bt b--white-20 tc lh-solid\" ] [\n                _a [ _href_ \"https://www.nuget.org/packages/Falco\"; _class_ \"dib center ph1 ph4-l pv3 bg-merlot white no-underline ty--50\"; _targetBlank_ ]\n                    [ _text \"Latest release: 5.2.0 (December, 21, 2025)\" ]\n            ]\n\n        let benefits =\n            _div [ _class_ \"cf tc lh-copy\" ] [\n                _div [ _class_ \"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\" ] [\n                    _img [ _src_ \"/icons/fast.svg\"; _class_ \"w4 o-90\" ]\n                    _h3 [ _class_ \"mv2 white\" ]\n                        [ _text \"Fast & Lightweight\" ]\n                    _div [ _class_ \"mb3 white-90\" ]\n                        [ _text \"Optimized for speed and low memory usage.\" ]\n                    _a [ _href_ \"https://web-frameworks-benchmark.netlify.app/result?l=fsharp\"; _targetBlank_; _class_ \"dib mh2 pa2 f6 white ba b--white br2 no-underline\" ]\n                        [ _text \"Learn More\" ]\n                 ]\n\n                _div [ _class_ \"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\" ] [\n                    _img [ _src_ \"/icons/easy.svg\"; _class_ \"w4 o-90\" ]\n                    _h3 [ _class_ \"mv2 white\" ] [ _text \"Easy to Learn\" ]\n                    _div [ _class_ \"mb3 white-90\" ] [ _text \"Simple, predictable, and easy to pick up.\" ]\n                    _a [ _href_ \"/docs/get-started.html\"; _title_ \"Learn how to get started using Falco\"; _class_ \"dib mh2 pa2 f6 white ba b--white br2 no-underline\" ]\n                        [ _text \"Get Started\" ]\n                 ]\n\n                _div [ _class_ \"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l br-l b--white-20\" ] [\n                    _img [ _src_ \"/icons/view.svg\"; _class_ \"w4 o-90\" ]\n                    _h3 [ _class_ \"mv2 white\" ] [ _text \"Native View Engine\" ]\n                    _div [ _class_ \"mb3 white-90\" ] [ _text \"Markup is written in F# and compiled.\" ]\n                    _a [ _href_ \"/docs/markup.html\"; _title_ \"View examples of Falco markup module\"; _class_ \"dib mh2 pa2 f6 white ba b--white br2 no-underline\" ]\n                        [ _text \"See Examples\" ]\n                 ]\n\n                _div [ _class_ \"fl-l mw5 mw-none-l w-25-l center mb4 ph4-l\" ] [\n                    _img [ _src_ \"/icons/integrate.svg\"; _class_ \"w4 o-90\" ]\n                    _h3 [ _class_ \"mv2 white\" ] [ _text \"Customizable\" ]\n                    _div [ _class_ \"mb3 white-90\" ] [ _text \"Seamlessly integrates with ASP.NET.\" ]\n                    _a [ _href_ \"https://github.com/FalcoFramework/Falco/tree/master/samples/ScribanExample\"; _targetBlank_; _title_ \"Example of incorporating a third-party view engine\"; _class_ \"dib mh2 pa2 f6 white ba b--white br2 no-underline\" ]\n                        [ _text \"Explore How\" ]\n                 ]\n            ]\n\n        _html [ _lang_ \"en\"; ] [\n            _head [] (_layoutHead model.Title)\n            _body [ _class_ \"noto bg-merlot bg-dots bg-parallax\" ] [\n                _header [ _class_ \"pv3\" ] [\n                    _div [ _class_ \"mw8 center pa3\" ] [\n                        topBar\n                        greeting\n                        releaseInfo\n                        benefits\n                    ]\n                ]\n\n                _div [ _class_ \"h100vh bg-white\" ] [\n                    _div [ _class_ \"cf mw8 center pv4 ph3\" ] [\n                        _main [] [ _text model.MainContent ]\n                    ]\n                ]\n\n                _layoutFooter\n\n                _script [ _src_ \"/prism.js\" ] []\n            ]\n        ]\n\n    let layoutTwoCol (model : LayoutTwoColModel) =\n        _html [ Attr.lang \"en\"; ] [\n            _head [] (_layoutHead model.Title)\n            _body [ _class_ \"noto lh-copy\" ] [\n                _div [ _class_ \"min-vh-100 mw9 center pa3 overflow-hidden\" ] [\n                    _nav [ _class_ \"sidebar w-20-l fl-l mb3 mb0-l\" ] [\n                        _div [ _class_ \"flex items-center\" ] [\n                            _a [ _href_ \"/docs\"; _class_ \"db w3 w4-l\" ]\n                                [ _img [ _src_ \"/brand.svg\"; _class_ \"br3\" ] ]\n                            _h2 [ _class_ \"dn-l mt3 ml3 fw4 gray\" ]\n                                [ _text \"Falco Documentation\" ]\n                        ]\n                        _div [ _class_ \"dn db-l\" ] model.SideContent\n                    ]\n                    _main [ _class_ \"w-80-l fl-l pl3-l\" ] [ _text model.MainContent ]\n                ]\n                _layoutFooter\n                _script [ _src_ \"/prism.js\" ] []\n            ]\n        ]\n\nmodule Docs =\n    let build (docs : FileInfo[]) (buildDir : DirectoryInfo) =\n        if not buildDir.Exists then buildDir.Create()\n\n        for file in docs do\n            let buildFilename, sideContent =\n                if file.Name = \"readme.md\" then\n                    \"index.html\", View.docsLinks\n                else\n                    Path.ChangeExtension(file.Name, \".html\"), View.docsNav\n\n            let parsedMarkdownDocument = Markdown.renderFile file.FullName\n\n            let html =\n                { Title = parsedMarkdownDocument.Title\n                  SideContent = sideContent\n                  MainContent = parsedMarkdownDocument.Body }\n                |> View.layoutTwoCol\n                |> renderHtml\n\n            File.WriteAllText(Path.Join(buildDir.FullName, buildFilename), html)\n\n[<EntryPoint>]\nlet main args =\n    if args.Length = 0 then\n        failwith \"Must provide the working directory as the first argument\"\n\n    let workingDir = DirectoryInfo(if args.Length = 2 then args[1] else args[0])\n\n    // Clean build\n    let buildDirPath = DirectoryInfo(Path.Join(workingDir.FullName, \"../docs\"))\n    printfn \"Clearing build directory...\\n  %s\" buildDirPath.FullName\n\n    if buildDirPath.Exists then\n        for file in buildDirPath.EnumerateFiles(\"*.html\", EnumerationOptions(RecurseSubdirectories = true)) do\n            file.Delete()\n    else\n        buildDirPath.Create ()\n\n    printfn \"Rendering homepage...\"\n    let indexMarkdown = Path.Join(workingDir.FullName, \"../README.md\") |> File.ReadAllText\n    let mainContent = Markdown.render indexMarkdown\n    let mainWithoutTitle =\n        Regex.Replace(mainContent.Body, \"<h1.*?</h1>\", \"\", RegexOptions.Singleline).Trim()\n    { Title = String.Empty\n      MainContent = mainWithoutTitle }\n    |> View.layout\n    |> renderHtml\n    |> fun text -> File.WriteAllText(Path.Join(buildDirPath.FullName, \"index.html\"), text)\n\n    let docsDir = DirectoryInfo(Path.Join(workingDir.FullName, \"../documentation\"))\n    let docsBuildDir = DirectoryInfo(Path.Join(buildDirPath.FullName, \"../docs/docs\"))\n    printfn \"Rendering docs...\\n  From: %s\\n  To:   %s\" docsDir.FullName docsBuildDir.FullName\n    Docs.build (docsDir.GetFiles \"*.md\") docsBuildDir\n\n    // Additional languages\n    let languageCodes = []\n\n    for languageCode in languageCodes do\n        printfn \"Rendering /%s docs\" languageCode\n        let languageDir = DirectoryInfo(Path.Join(docsDir.FullName, languageCode))\n        let languageBuildDir = DirectoryInfo(Path.Join(docsBuildDir.FullName, languageCode))\n        Docs.build (languageDir.GetFiles()) languageBuildDir\n\n    0\n"
  },
  {
    "path": "site/Site.fsproj",
    "content": "﻿<Project Sdk=\"Microsoft.NET.Sdk\">\n\n  <PropertyGroup>\n    <OutputType>Exe</OutputType>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Compile Include=\"Site.fs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Falco.Markup\" Version=\"1.*\" />\n    <PackageReference Include=\"Markdig\" Version=\"0.*\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/Falco/Core.fs",
    "content": "namespace Falco\n\nopen System\nopen System.Threading.Tasks\nopen Microsoft.AspNetCore.Http\n\n/// The eventual return of asynchronous HttpContext processing.\ntype HttpHandler = HttpContext -> Task\n\nmodule HttpHandler =\n    /// Convert HttpHandler to a RequestDelegate.\n    let toRequestDelegate (handler : HttpHandler) =\n        new RequestDelegate(handler)\n\n/// In-and-out processing of a HttpContext.\ntype HttpResponseModifier = HttpContext -> HttpContext\n\n/// Http verb\ntype HttpVerb =\n    | GET\n    | HEAD\n    | POST\n    | PUT\n    | PATCH\n    | DELETE\n    | OPTIONS\n    | TRACE\n    | ANY\n\n    override x.ToString() =\n        match x with\n        | GET     -> HttpMethods.Get\n        | HEAD    -> HttpMethods.Head\n        | POST    -> HttpMethods.Post\n        | PUT     -> HttpMethods.Put\n        | PATCH   -> HttpMethods.Patch\n        | DELETE  -> HttpMethods.Delete\n        | OPTIONS -> HttpMethods.Options\n        | TRACE   -> HttpMethods.Trace\n        | ANY     -> String.Empty\n"
  },
  {
    "path": "src/Falco/Falco.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <AssemblyName>Falco</AssemblyName>\n    <Version>6.0.0-alpha1</Version>\n\n    <!-- General info -->\n    <Description>A functional-first toolkit for building brilliant ASP.NET Core applications using F#.</Description>\n    <Copyright>Copyright 2025 Pim Brouwers</Copyright>\n    <Authors>Pim Brouwers and contributors</Authors>\n    <NeutralLanguage>en-CA</NeutralLanguage>\n\n    <!-- Build config -->\n    <TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>\n    <DebugType>embedded</DebugType>\n    <OutputType>Library</OutputType>\n    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>\n    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>\n    <GenerateDocumentationFile>true</GenerateDocumentationFile>\n\n    <!-- NuGet config -->\n    <PackageId>Falco</PackageId>\n    <PackageTags>fsharp;functional;asp.net core;asp.net;.net core;routing;view engine;web;falco;falco-sharp;</PackageTags>\n    <PackageProjectUrl>https://github.com/FalcoFramework/Falco</PackageProjectUrl>\n    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>\n    <PackageIcon>icon.png</PackageIcon>\n    <PackageReadmeFile>README.md</PackageReadmeFile>\n    <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>\n    <RepositoryType>git</RepositoryType>\n    <RepositoryUrl>https://github.com/FalcoFramework/Falco</RepositoryUrl>\n\n    <!-- SourceLink settings -->\n    <PublishRepositoryUrl>true</PublishRepositoryUrl>\n    <EmbedUntrackedSources>true</EmbedUntrackedSources>\n    <IncludeSymbols>true</IncludeSymbols>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <FrameworkReference Include=\"Microsoft.AspNetCore.App\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Falco.Markup\" Version=\"1.4.*\" />\n    <PackageReference Update=\"FSharp.Core\" Version=\"6.0.0\" />\n    <PackageReference Include=\"Microsoft.SourceLink.GitHub\" Version=\"1.*\" PrivateAssets=\"All\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <Compile Include=\"String.fs\" />\n    <Compile Include=\"RequestValue.fs\" />\n    <Compile Include=\"RequestData.fs\" />\n    <Compile Include=\"Multipart.fs\" />\n    <Compile Include=\"Security.fs\" />\n    <Compile Include=\"Core.fs\" />\n    <Compile Include=\"Request.fs\" />\n    <Compile Include=\"Response.fs\" />\n    <Compile Include=\"Routing.fs\" />\n    <Compile Include=\"WebApplication.fs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <None Include=\"../../assets/icon.png\">\n      <Pack>true</Pack>\n      <PackagePath>$(PackageIconUrl)</PackagePath>\n    </None>\n    <None Include=\"../../README.md\" Pack=\"true\" PackagePath=\"\\\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <InternalsVisibleTo Include=\"Falco.Tests\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "src/Falco/Multipart.fs",
    "content": "namespace Falco\n\nopen System\nopen System.IO\nopen System.Net\nopen System.Threading\nopen System.Threading.Tasks\nopen Microsoft.AspNetCore.Http\nopen Microsoft.AspNetCore.WebUtilities\nopen Microsoft.Extensions.Primitives\nopen Microsoft.Net.Http.Headers\n\nmodule Multipart =\n    [<Literal>]\n    let DefaultMaxSize = 32L * 1024L * 1024L // 32mb\n\n    type private MultipartSectionData =\n        | NoMultipartData\n        | FormValueData of key : string * value : string\n        | FormFileData of FormFile\n\n    type private MultipartSection with\n        /// Attempts to obtain encoding from content type, default to UTF8.\n        static member private GetEncodingFromContentType(section : MultipartSection) =\n            match MediaTypeHeaderValue.TryParse(StringSegment(section.ContentType)) with\n            | false, _     -> System.Text.Encoding.UTF8\n            | true, parsed -> parsed.Encoding\n\n        /// Safely obtains the content disposition header value.\n        static member private TryGetContentDisposition(section : MultipartSection) =\n            match ContentDispositionHeaderValue.TryParse(StringSegment(section.ContentDisposition)) with\n            | false, _     -> None\n            | true, parsed -> Some parsed\n\n        /// Stream with size check to avoid unbounded memory growth.\n        /// Streams the current section asynchronously with a specified maximum size.\n        ///\n        /// - `ct`: A `CancellationToken` to observe while waiting for the task to complete.\n        /// - `maxSize`: The maximum size, in total bytes, allowed for the section being streamed.\n        member private x.StreamSectionAsync(ct : CancellationToken, maxSize : int64) =\n            task {\n                match MultipartSection.TryGetContentDisposition(x) with\n                | Some cd when cd.IsFileDisposition() && cd.FileName.HasValue && cd.Name.HasValue  ->\n\n                    // cannot be disposed until the FormFile is fully read\n                    let str = new MemoryStream()\n\n                    let mutable bytesRead = 0L\n                    let buffer = Array.zeroCreate 65536 // 64KB chunks\n                    let mutable shouldRead = true\n\n                    while shouldRead do\n                        let! count = x.Body.ReadAsync(buffer, 0, buffer.Length, ct)\n                        match count with\n                        | 0 -> shouldRead <- false\n                        | n ->\n                            bytesRead <- bytesRead + int64 n\n                            if bytesRead > maxSize then\n                                raise (InvalidOperationException $\"File exceeds maximum size of {maxSize} bytes\")\n                            do! str.WriteAsync(buffer, 0, n, ct)\n\n                    let safeFileName = WebUtility.HtmlEncode cd.FileName.Value\n                    let file = new FormFile(str, int64 0, str.Length, cd.Name.Value, safeFileName)\n\n                    // necessary to prevent null reference exception when setting\n                    // properties below. See:\n                    // https://github.com/dotnet/aspnetcore/blob/ca2238e75173d1f04ff0664c53dc443716a01b9d/src/Http/Http/src/FormFile.cs#L48\n                    file.Headers <- new HeaderDictionary()\n\n                    file.ContentType <- x.ContentType\n                    file.ContentDisposition <- x.ContentDisposition\n\n                    return FormFileData file\n\n                | Some cd when cd.IsFormDisposition() && cd.Name.HasValue ->\n                    let key = HeaderUtilities.RemoveQuotes(cd.Name).Value\n                    let encoding = MultipartSection.GetEncodingFromContentType(x)\n                    use str = new StreamReader(x.Body, encoding, true, 8192, true)\n                    let! requestValue = str.ReadToEndAsync()\n\n                    return FormValueData (key, requestValue)\n\n                | Some _\n                | None ->\n                    return NoMultipartData\n            }\n\n    type MultipartReader with\n        /// Streams the multipart sections and accumulates form values and files into an `IFormCollection`.\n        ///\n        /// - `ct`: A `CancellationToken` to observe while waiting for the task to complete.\n        /// - `maxSize`: The maximum size, in total bytes, allowed for each individual section being streamed.\n        member x.StreamSectionsAsync(ct : CancellationToken, ?maxSize : int64) =\n            task {\n                let formData = new KeyValueAccumulator()\n                let formFiles = new FormFileCollection()\n\n                let mutable shouldContinue = true\n\n                while shouldContinue do\n                    let! section = x.ReadNextSectionAsync(ct)\n\n                    match isNull section with\n                    | true ->\n                        shouldContinue <- false\n\n                    | false ->\n                        // default to max file size if not provided\n                        let! sectionData = section.StreamSectionAsync(ct, defaultArg maxSize DefaultMaxSize)\n\n                        match sectionData with\n                        | FormFileData file -> formFiles.Add(file)\n                        | FormValueData (key, value) -> formData.Append(key, value)\n                        | NoMultipartData -> shouldContinue <- false\n\n                let formCollection = FormCollection(formData.GetResults(), formFiles) :> IFormCollection\n                return formCollection\n            }\n\n    type HttpRequest with\n        /// Determines if the content type contains multipart.\n        member internal x.IsMultipart () : bool =\n            x.ContentType.IndexOf(\"multipart/\", StringComparison.OrdinalIgnoreCase) >= 0\n\n        member private x.GetBoundary() =\n            // Content-Type: multipart/form-data; boundary=\"----WebKitFormBoundarymx2fSWqWSd0OxQqq\"\n            // The spec at https://tools.ietf.org/html/rfc2046#section-5.1 states that 70 characters is a reasonable limit.\n            let lengthLimit = 70\n            let contentType = MediaTypeHeaderValue.Parse(StringSegment(x.ContentType))\n            let boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;\n            match boundary with\n            | b when isNull b -> None\n            | b when b.Length > lengthLimit -> None\n            | b -> Some b\n\n        /// Attempts to stream the HttpRequest body into IFormCollection.\n        member x.StreamFormAsync (ct : CancellationToken, ?maxFileSize : int64) : Task<IFormCollection> =\n            task {\n                match x.IsMultipart(), x.GetBoundary() with\n                | true, Some boundary ->\n                    let multipartReader = new MultipartReader(boundary, x.Body)\n\n                    // default to 32mb max file size if not provided\n                    let! formCollection = multipartReader.StreamSectionsAsync(ct, defaultArg maxFileSize DefaultMaxSize)\n                    return formCollection\n\n                | _, None\n                | false, _ -> return FormCollection.Empty\n            }\n"
  },
  {
    "path": "src/Falco/Request.fs",
    "content": "[<RequireQualifiedAccess>]\nmodule Falco.Request\n\nopen System\nopen System.IO\nopen System.Security.Claims\nopen System.Text\nopen System.Text.Json\nopen System.Threading\nopen System.Threading.Tasks\nopen Microsoft.AspNetCore.Authentication\nopen Microsoft.AspNetCore.Http\nopen Falco.Multipart\nopen Falco.Security\nopen Falco.StringUtils\n\nlet internal defaultJsonOptions =\n    let options = JsonSerializerOptions()\n    options.AllowTrailingCommas <- true\n    options.PropertyNameCaseInsensitive <- true\n    options.TypeInfoResolver <- JsonSerializerOptions.Default.TypeInfoResolver\n    options.MakeReadOnly() // optimize for reuse\n    options\n\n/// Obtains the `HttpVerb` of the request.\nlet getVerb (ctx : HttpContext) : HttpVerb =\n    match ctx.Request.Method with\n    | m when strEquals m HttpMethods.Get     -> GET\n    | m when strEquals m HttpMethods.Head    -> HEAD\n    | m when strEquals m HttpMethods.Post    -> POST\n    | m when strEquals m HttpMethods.Put     -> PUT\n    | m when strEquals m HttpMethods.Patch   -> PATCH\n    | m when strEquals m HttpMethods.Delete  -> DELETE\n    | m when strEquals m HttpMethods.Options -> OPTIONS\n    | m when strEquals m HttpMethods.Trace   -> TRACE\n    | _ -> ANY\n\n/// Streams the request body into a string, up to the maximum size defined by `maxSize`.\n/// Cannot be called after the body has already been read, and will throw an\n/// exception if the body is not seekable and empty.\n///\n/// - `maxSize`: The maximum size, in total bytes, allowed for the body being read.\nlet getBodyStringOptions (maxSize : int64) (ctx : HttpContext) : Task<string> =\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let str = new MemoryStream()\n\n        let mutable bytesRead = 0L\n        let buffer = Array.zeroCreate 65536 // 64KB chunks\n        let mutable shouldRead = true\n\n        while shouldRead do\n            let! count = ctx.Request.Body.ReadAsync(buffer, 0, buffer.Length, tokenSource.Token)\n            match count with\n            | 0 -> shouldRead <- false\n            | n ->\n                bytesRead <- bytesRead + int64 n\n                if bytesRead > maxSize then\n                    raise (InvalidOperationException $\"Body exceeds maximum size of {maxSize} bytes\")\n                do! str.WriteAsync(buffer, 0, n, tokenSource.Token)\n\n        str.Seek(0L, SeekOrigin.Begin) |> ignore\n        return Encoding.UTF8.GetString(str.ToArray())\n    }\n\n/// Streams the request body into a string, up to a maximum body size of `Multipart.DefaultMaxSize`.\n/// Cannot be called after the body has already been read, and will throw an\n/// exception if the body is not seekable and empty.\n///\n/// Note: `Multipart.DefaultMaxSize` is used as the default maximum body size for\n/// consistency with multipart form data processing, but can be overridden by using\n/// `getBodyStringOptions` directly.\nlet getBodyString (ctx : HttpContext) : Task<string> =\n    getBodyStringOptions Multipart.DefaultMaxSize ctx\n\n/// Retrieves the cookie from the request. Returns a `RequestData` containing the cookie values.\nlet getCookies (ctx : HttpContext) : RequestData =\n    RequestValue.parseCookies ctx.Request.Cookies\n    |> RequestData\n\n/// Retrieves the headers from the request. Returns a `RequestData` containing the header values.\nlet getHeaders (ctx : HttpContext) : RequestData  =\n    RequestValue.parseHeaders ctx.Request.Headers\n    |> RequestData\n\n/// Retrieves all route values from the request, including query string. Returns a `RequestData` containing the route and query values.\nlet getRoute (ctx : HttpContext) : RequestData =\n    RequestValue.parseRoute (ctx.Request.RouteValues, ctx.Request.Query)\n    |> RequestData\n\n/// Retrieves the query string and route values from the request. Returns a `RequestData` containing the query values.\nlet getQuery (ctx : HttpContext) : RequestData =\n    RequestValue.parseQuery ctx.Request.Query\n    |> RequestData\n\n/// Retrieves the form collection and route values from the request.\n///\n/// Performs CSRF validation for POST, PUT, PATCH, DELETE requests, if antiforgery\n/// services are registered and a token is provided in the request.\n///\n/// Automatically detects if request is multipart/form-data, and will enable streaming.\n///\n/// Note: Consumes the request body, so should not be called after body has already been read.\n///\n/// - `maxSize`: The maximum size, in total bytes, allowed for the body being read.\nlet getFormOptions (maxSize : int64) (ctx : HttpContext) : Task<FormData> =\n    task {\n        if ctx.Request.ContentLength.HasValue && ctx.Request.ContentLength.Value > maxSize then\n            return FormData.Invalid\n        else\n            let! isAuth = Xsrf.validateToken ctx\n\n            if isAuth then\n                use tokenSource = new CancellationTokenSource()\n\n                let! form =\n                    if ctx.Request.IsMultipart() then\n                        ctx.Request.StreamFormAsync (tokenSource.Token, maxSize)\n                    else\n                        ctx.Request.ReadFormAsync tokenSource.Token\n\n                let files = if isNull form.Files then None else Some form.Files\n\n                let requestValue = RequestValue.parseForm (form, Some ctx.Request.RouteValues)\n\n                return FormData(requestValue, files)\n\n            else\n                return FormData.Invalid\n    }\n\n/// Retrieves the form collection and route values from the request.\n///\n/// Performs CSRF validation for POST, PUT, PATCH, DELETE requests, if antiforgery\n/// services are registered and a token is provided in the request.\n///\n/// Automatically detects if request is multipart/form-data, and will enable streaming.\n///\n/// Uses a default maximum body size of `Multipart.DefaultMaxSize` for consistency\n/// with multipart form data processing, but can be overridden by using\n/// `getFormOptions` directly.\n///\n/// Note: Consumes the request body, so should not be called after body has already been read.\nlet getForm (ctx : HttpContext) : Task<FormData> =\n    getFormOptions Multipart.DefaultMaxSize ctx\n\n/// Attempts to bind request body using System.Text.Json and provided\n/// JsonSerializerOptions. If the body is empty or not JSON, returns the default\n/// value of 'T.\n///\n/// - `options`: The `JsonSerializerOptions` to use during deserialization.\nlet getJsonOptions<'T>\n    (options : JsonSerializerOptions)\n    (ctx : HttpContext) : Task<'T> = task {\n        try\n            if not (ctx.Request.HasJsonContentType()) then\n                ctx.Response.StatusCode <- StatusCodes.Status415UnsupportedMediaType\n                return JsonSerializer.Deserialize<'T>(\"{}\", options)\n\n            elif ctx.Request.Body.CanSeek && ctx.Request.Body.Length = 0L then\n                return JsonSerializer.Deserialize<'T>(\"{}\", options)\n\n            else\n                use tokenSource = new CancellationTokenSource()\n                let! json = JsonSerializer.DeserializeAsync<'T>(ctx.Request.Body, options, tokenSource.Token).AsTask()\n                return json\n\n        with\n        | :? NotSupportedException as _ ->\n            return JsonSerializer.Deserialize<'T>(\"{}\", options)\n    }\n\n/// Attempts to bind request body using System.Text.Json and default\n/// `JsonSerializerOptions`. If the body is empty or not JSON, returns the default\n/// value of 'T.\nlet getJson<'T> (ctx : HttpContext) =\n    getJsonOptions<'T> defaultJsonOptions ctx\n\n// ------------\n// Handlers\n// ------------\n\n/// Buffers the current HttpRequest body into a string and provides to next `HttpHandler`.\n/// Note: Uses `getBodyString`, which has a default maximum body size of `Multipart.DefaultMaxSize`\n/// for consistency with multipart form data processing, but can be overridden\n/// by using `getBodyStringOptions` directly.\n///\n/// - `next`: The next `HttpHandler` to invoke, which takes the buffered body string as input.\nlet bodyString\n    (next : string -> HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! body = getBodyString ctx\n        return! next body ctx\n    }\n\n/// Projects cookie values onto 'T and provides to next HttpHandler.\n///\n/// - `map`: A function that maps the cookie values from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapCookies\n    (map : RequestData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    getCookies ctx\n    |> map\n    |> fun route -> next route ctx\n\n/// Projects header values onto 'T and provides to next HttpHandler.\n///\n/// - `map`: A function that maps the header values from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapHeaders\n    (map : RequestData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    getHeaders ctx\n    |> map\n    |> fun route -> next route ctx\n\n/// Projects route values onto 'T and provides to next HttpHandler.\n///\n/// - `map`: A function that maps the route values from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapRoute\n    (map : RequestData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    getRoute ctx\n    |> map\n    |> fun route -> next route ctx\n\n/// Projects query string onto 'T and provides to next HttpHandler.\n///\n/// - `map`: A function that maps the query string values from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapQuery\n    (map : RequestData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    getQuery ctx\n    |> map\n    |> fun query -> next query ctx\n\n/// Projects form data onto 'T and provides to next HttpHandler.\n///\n/// Performs CSRF validation for HTTP POST, PUT, PATCH, DELETE verbs, if antiforgery\n/// services are registered and a token is provided in the request.\n///\n/// Automatically detects if request is content-type: multipart/form-data, and\n/// if so, will enable streaming.\n///\n/// - `maxSize`: The maximum size, in total bytes, allowed for the body being read. Should be set according to expected form data size and server limits.\n/// - `map`: A function that maps the form data from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapFormOptions\n    (maxSize : int64)\n    (map : FormData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! form = getFormOptions maxSize ctx\n        return! next (map form) ctx\n    }\n\n/// Projects form dta onto 'T and provides to next HttpHandler.\n///\n/// Performs CSRF validation for HTTP POST, PUT, PATCH, DELETE verbs, if antiforgery\n/// services are registered and a token is provided in the request.\n///\n/// Automatically detects if request is content-type: multipart/form-data, and\n/// if so, will enable streaming.\n///\n/// Uses `getForm` with a default maximum body size of `Multipart.DefaultMaxSize` for consistency\n/// with multipart form data processing, but can be overridden by using `mapFormOptions` directly.\n///\n/// - `map`: A function that maps the form data from the request into a new type 'T.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapForm\n    (map : FormData -> 'T)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! form = getForm ctx\n        return! next (map form) ctx\n    }\n\n/// Validates the CSRF of the current request.\n///\n/// - `handleOk`: The `HttpHandler` to invoke if the CSRF token is valid or if validation is not applicable.\n/// - `handleInvalidToken`: The `HttpHandler` to invoke if the CSRF token\nlet validateCsrfToken\n    (handleOk : HttpHandler)\n    (handleInvalidToken : HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! isValid = Xsrf.validateToken ctx\n\n        let respondWith =\n            match isValid with\n            | true  -> handleOk\n            | false -> handleInvalidToken\n\n        return! respondWith ctx\n    }\n\n/// Projects JSON using custom JsonSerializerOptions onto 'T and provides to next\n/// `HttpHandler`, throws `JsonException` if errors occur during deserialization.\n/// If the body is empty or not JSON, returns the default value of 'T.\n///\n/// - `options`: The `JsonSerializerOptions` to use during deserialization.\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapJsonOptions<'T>\n    (options : JsonSerializerOptions)\n    (next : 'T -> HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! json = getJsonOptions options ctx\n        return! next json ctx\n    }\n\n/// Projects JSON onto 'T and provides to next `HttpHandler`, throws `JsonException`\n/// if errors occur during deserialization. If the body is empty or not JSON, returns\n/// the default value of 'T.\n///\n/// Uses `getJson` with default `JsonSerializerOptions`, but can be overridden by using `mapJsonOptions` directly.\n///\n/// - `next`: The next `HttpHandler` to invoke, which takes the mapped 'T as input.\nlet mapJson<'T>\n    (next : 'T -> HttpHandler) : HttpHandler =\n    mapJsonOptions<'T> defaultJsonOptions next\n\n// ------------\n// Authentication\n// ------------\n\n/// Attempts to authenticate the current request using the provided scheme and\n/// passes AuthenticateResult into next HttpHandler. Does not modify the\n/// `HttpContext.User` or authentication status, and does not challenge or forbid on its own.\n///\n/// - `authScheme`: The authentication scheme to use when authenticating the request. This should match the scheme used in your authentication configuration.\n/// - `next`: The next `HttpHandler` to invoke, which takes the `AuthenticateResult` as input.\nlet authenticate\n    (authScheme : string)\n    (next : AuthenticateResult -> HttpHandler) : HttpHandler = fun ctx ->\n    task {\n        let! authenticateResult = ctx.AuthenticateAsync authScheme\n        return! next authenticateResult ctx\n    }\n\n/// Authenticate the current request using the default authentication scheme. Proceeds\n/// if the authentication status of current `IPrincipal` is true.\n///\n/// Note: The default authentication scheme can be configured using\n/// `Microsoft.AspNetCore.Authentication.AuthenticationOptions.DefaultAuthenticateScheme.`\n///\n/// - `authScheme`: The authentication scheme to use when authenticating the request. This should match the scheme used in your authentication configuration.\n/// - `handleOk`: The `HttpHandler` to invoke if the user is authenticated. If the user is not authenticated, a 403 Forbidden response will be returned.\nlet ifAuthenticated\n    (authScheme : string)\n    (handleOk : HttpHandler) : HttpHandler =\n    authenticate authScheme (fun authenticateResult ctx ->\n        if authenticateResult.Succeeded then\n            handleOk ctx\n        else\n            ctx.ForbidAsync())\n\n/// Proceeds if the authentication status of current `IPrincipal` is true and\n/// they exist in a list of roles.\n///\n/// The roles are checked using `ClaimsPrincipal.IsInRole`, so the role claim type\n/// is determined by the authentication handler in use. For example, with JWT Bearer\n/// authentication, the role claim type is typically \"roles\" or \"role\", but with\n/// cookie authentication it may be different depending on how claims are set up.\n///\n/// Note: This function assumes that the authentication handler populates the user's\n/// claims with their roles in a way that `ClaimsPrincipal.IsInRole` can check. Make\n/// sure your authentication setup is configured accordingly for role-based authorization\n/// to work with this function.\n///\n/// - `authScheme`: The authentication scheme to use when authenticating the request. This should match the scheme used in your authentication configuration.\n/// - `roles`: A sequence of roles to check against the authenticated user's claims. If the user is in any of the specified roles, they will be allowed to proceed.\n/// - `handleOk`: The `HttpHandler` to invoke if the user is authenticated and in one of the specified roles. If the user is not authenticated or not in any of the roles, a 403 Forbidden response will be returned.\nlet ifAuthenticatedInRole\n    (authScheme : string)\n    (roles : string seq)\n    (handleOk : HttpHandler) : HttpHandler =\n    authenticate authScheme (fun authenticateResult ctx ->\n        let isInRole = Seq.exists authenticateResult.Principal.IsInRole roles\n        match authenticateResult.Succeeded, isInRole with\n        | true, true ->\n            handleOk ctx\n        | _ ->\n            ctx.ForbidAsync())\n\n/// Proceeds if the authentication status of current IPrincipal is true and has\n/// a specific scope.\n///\n/// The scope is checked by looking for a claim of type \"scope\" with the specified\n/// value and issuer. This is commonly used in token-based authentication scenarios,\n/// such as with JWTs, where scopes are included as claims in the token. The issuer\n/// is also checked to ensure that the scope claim is coming from the expected authority.\n///\n/// Note: This function assumes that the authentication handler populates the user's\n/// claims with their scopes in a claim of type \"scope\". Make sure your authentication\n/// setup is configured accordingly for scope-based authorization to work with this function.\n///\n/// - `authScheme`: The authentication scheme to use when authenticating the request. This should match the scheme used in your authentication configuration.\n/// - `issuer`: The expected issuer of the scope claim to check against. This should match the authority that issues the tokens containing the scope claims.\n/// - `scope`: The specific scope value to check for in the user's claims. The user must have a claim of type \"scope\" with this value (and the correct issuer) to be allowed to proceed.\n/// - `handleOk`: The `HttpHandler` to invoke if the user is authenticated and has the specified scope. If the user is not authenticated or does not have the required scope, a 403 Forbidden response will be returned.\nlet ifAuthenticatedWithScope\n    (authScheme : string)\n    (issuer : string)\n    (scope : string)\n    (handleOk : HttpHandler) : HttpHandler =\n    authenticate authScheme (fun authenticateResult ctx ->\n        if authenticateResult.Succeeded then\n            let hasScope =\n                let predicate (claim : Claim) = strEquals claim.Issuer issuer && strEquals claim.Type \"scope\"\n                match Seq.tryFind predicate authenticateResult.Principal.Claims with\n                | Some claim -> Array.contains scope (strSplit [|' '|] claim.Value)\n                | None -> false\n            if hasScope then\n                handleOk ctx\n            else\n                ctx.ForbidAsync()\n        else\n            ctx.ForbidAsync())\n\n/// Proceeds if the authentication status of current IPrincipal is false.\n///\n/// This can be used to allow access to certain handlers only for unauthenticated\n/// users, such as a login or registration page. If the user is authenticated, a\n/// 403 Forbidden response will be returned.\n///\n/// Note: This function checks if the authentication attempt succeeded, which means\n/// the user is authenticated. If the authentication attempt did not succeed\n/// (i.e., the user is not authenticated), it allows access to the specified handler.\n/// Make sure your authentication configuration is set up correctly for this to\n/// work as intended.\n///\n/// - `authScheme`: The authentication scheme to use when authenticating the request. This should match the scheme used in your authentication configuration.\n/// - `handleOk`: The `HttpHandler` to invoke if the user is not authenticated. If the user is authenticated, a 403 Forbidden response will be returned.\nlet ifNotAuthenticated\n    (authScheme : string)\n    (handleOk : HttpHandler) : HttpHandler =\n    authenticate authScheme (fun authenticateResult ctx ->\n        if authenticateResult.Succeeded then\n            ctx.ForbidAsync()\n        else\n            handleOk ctx)\n"
  },
  {
    "path": "src/Falco/RequestData.fs",
    "content": "namespace Falco\n\nopen System\nopen System.Collections.Generic\nopen Microsoft.AspNetCore.Http\nopen Microsoft.FSharp.Core.Operators\nopen Falco.StringPatterns\n\nmodule private RequestData =\n    let epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)\n    let trueValues = HashSet<string>([| \"true\"; \"on\"; \"yes\" |], StringComparer.OrdinalIgnoreCase)\n    let falseValues = HashSet<string>([| \"false\"; \"off\"; \"no\" |], StringComparer.OrdinalIgnoreCase)\n\n[<AutoOpen>]\nmodule private RequestValueExtensions =\n    let tryGet name fn requestValue =\n        match requestValue with\n        | RObject props ->\n            props\n            |> List.tryFind (fun (k, _) -> String.Equals(k, name, StringComparison.OrdinalIgnoreCase))\n            |> Option.bind (fun (_, v) -> fn v)\n        | _ -> None\n\n    let orDefault maybe defaultValue opt =\n        match opt, maybe with\n        | Some x, _ -> x\n        | None, Some v -> v\n        | _ -> defaultValue\n\n    let asOr asFn defaultValue requestValue =\n        asFn requestValue |> Option.defaultWith (fun _ -> defaultValue)\n\n    let bindList bind requestValue =\n        match requestValue with\n        | RList slist -> List.choose bind slist\n        | RNull | RObject _ -> []\n        | v -> bind v |> Option.toList\n\n    let tryGetOrElse name fn inputValue defaultValue requestValue =\n        tryGet name fn requestValue |> Option.orElse inputValue |> Option.defaultValue defaultValue\n\n    let tryGetList name fn requestValue =\n        requestValue |> tryGet name (fn >> Some) |> Option.defaultValue []\n\n    let asObject requestValue =\n        match requestValue with\n        | RObject properties -> Some properties\n        | _ -> None\n\n    let asList requestValue =\n        match requestValue with\n        | RList a -> Some a\n        | _ -> None\n\n    let asRequestPrimitive requestValue =\n        let rec asPrimitive requestValue =\n            match requestValue with\n            | RNull\n            | RBool _\n            | RNumber _\n            | RString _ -> Some requestValue\n            | RList lst -> List.tryHead lst |> Option.bind asPrimitive\n            | _ -> None\n\n        asPrimitive requestValue\n\n    let asString requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNull) -> Some \"\"\n        | Some (RBool b) -> Some (if b then \"true\" else \"false\")\n        | Some (RNumber n) -> Some (string n)\n        | Some (RString s) -> Some s\n        | _ -> None\n\n    let asStringNonEmpty requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RBool b) -> Some (if b then \"true\" else \"false\")\n        | Some (RNumber n) -> Some (string n)\n        | Some (RString s) -> Some s\n        | _ -> None\n\n    let asInt16 requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber x) when x >= float Int16.MinValue && x <= float Int16.MaxValue -> Some (Convert.ToInt16 x)\n        | Some (RString x) -> StringParser.parseInt16 x\n        | _ -> None\n\n    let asInt32 requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber x) when x >= float Int32.MinValue && x <= float Int32.MaxValue -> Some (Convert.ToInt32 x)\n        | Some (RString x) -> StringParser.parseInt32 x\n        | _ -> None\n\n    let asInt64 requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber x) when x >= float Int64.MinValue && x <= float Int64.MaxValue -> Some (Convert.ToInt64 x)\n        | Some (RString x) -> StringParser.parseInt64 x\n        | _ -> None\n\n    let asBoolean requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RBool x) when x -> Some true\n        | Some (RBool x) when not x -> Some false\n        | Some (RNumber x) when x = 0. -> Some false\n        | Some (RNumber x) when x = 1. -> Some true\n        | Some (RString x) when RequestData.trueValues.Contains x -> Some true\n        | Some (RString x) when RequestData.falseValues.Contains x -> Some false\n        | _ -> None\n\n    let asFloat requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber x) -> Some x\n        | Some (RString x) -> StringParser.parseFloat x\n        | _ -> None\n\n    let asDecimal requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber x) -> Some (decimal x)\n        | Some (RString x) -> StringParser.parseDecimal x\n        | _ -> None\n\n    let asDateTime requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber n) when n >= float Int64.MinValue && n <= float Int64.MaxValue ->\n            Some (RequestData.epoch.AddMilliseconds(n))\n        | Some (RString s) ->\n            StringParser.parseDateTime s\n        | _ -> None\n\n    let asDateTimeOffset requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RNumber n) when n >= float Int64.MinValue && n <= float Int64.MaxValue ->\n            Some (DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64 n))\n        | Some (RString s) ->\n            StringParser.parseDateTimeOffset s\n        | _ -> None\n\n    let asTimeSpan requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RString s) -> StringParser.parseTimeSpan s\n        | _ -> None\n\n    let asGuid requestValue =\n        match asRequestPrimitive requestValue with\n        | Some (RString s) -> StringParser.parseGuid s\n        | _ -> None\n\n    let asStringList requestValue =\n        bindList asString requestValue\n\n    let asStringNonEmptyList requestValue =\n        bindList asStringNonEmpty requestValue\n\n    let asInt16List requestValue =\n        bindList asInt16 requestValue\n\n    let asInt32List requestValue =\n        bindList asInt32 requestValue\n\n    let asInt64List requestValue =\n        bindList asInt64 requestValue\n\n    let asBooleanList requestValue =\n        bindList asBoolean requestValue\n\n    let asFloatList requestValue =\n        bindList asFloat requestValue\n\n    let asDecimalList requestValue =\n        bindList asDecimal requestValue\n\n    let asDateTimeList requestValue =\n        bindList asDateTime requestValue\n\n    let asDateTimeOffsetList requestValue =\n        bindList asDateTimeOffset requestValue\n\n    let asTimeSpanList requestValue =\n        bindList asTimeSpan requestValue\n\n    let asGuidList requestValue =\n        bindList asGuid requestValue\n\ntype RequestData(requestValue : RequestValue) =\n    new(requestData : IDictionary<string, string seq>) = RequestData(RequestValue.parse requestData)\n    new(keyValues : (string * string seq) seq) = RequestData(dict keyValues)\n\n    static member Empty = RequestData RNull\n\n    member _.TryGet(name : string) : RequestData option =\n        tryGet name (RequestData >> Some) requestValue\n\n    member _.Get(name : string) : RequestData =\n        tryGet name (RequestData >> Some) requestValue\n        |> Option.defaultValue RequestData.Empty\n\n    member _.AsKeyValues() =\n        asOr (asObject >> Option.map (List.map (fun (k, v) -> k, RequestData v))) [] requestValue\n\n    member _.AsList() =\n        asOr (asList >> Option.map (List.map RequestData)) [] requestValue\n\n    member _.AsString ?defaultValue = requestValue |> asString |> orDefault defaultValue \"\"\n    member _.AsStringNonEmpty ?defaultValue = requestValue |> asStringNonEmpty |> orDefault defaultValue \"\"\n    member _.AsInt16 ?defaultValue = requestValue |> asInt16 |> orDefault defaultValue 0s\n    member _.AsInt32 ?defaultValue = requestValue |> asInt32 |> orDefault defaultValue 0\n    member x.AsInt ?defaultValue = x.AsInt32(?defaultValue = defaultValue)\n    member _.AsInt64 ?defaultValue = requestValue |> asInt64 |> orDefault defaultValue 0L\n    member _.AsBoolean ?defaultValue = requestValue |> asBoolean |> orDefault defaultValue false\n    member _.AsFloat ?defaultValue = requestValue |> asFloat |> orDefault defaultValue 0.\n    member _.AsDecimal ?defaultValue = requestValue |> asDecimal |> orDefault defaultValue 0.M\n    member _.AsDateTime ?defaultValue = requestValue |> asDateTime |> orDefault defaultValue DateTime.MinValue\n    member _.AsDateTimeOffset ?defaultValue = requestValue |> asDateTimeOffset |> orDefault defaultValue DateTimeOffset.MinValue\n    member _.AsTimeSpan ?defaultValue = requestValue |> asTimeSpan |> orDefault defaultValue TimeSpan.MinValue\n    member _.AsGuid ?defaultValue = requestValue |> asGuid |> orDefault defaultValue Guid.Empty\n\n    member _.AsStringOption() = asString requestValue\n    member _.AsStringNonEmptyOption() = asStringNonEmpty requestValue\n    member _.AsInt16Option() = asInt16 requestValue\n    member _.AsInt32Option() = asInt32 requestValue\n    member x.AsIntOption() = x.AsInt32Option()\n    member _.AsInt64Option() = asInt64 requestValue\n    member _.AsBooleanOption() = asBoolean requestValue\n    member _.AsFloatOption() = asFloat requestValue\n    member _.AsDecimalOption() = asDecimal requestValue\n    member _.AsDateTimeOption() = asDateTime requestValue\n    member _.AsDateTimeOffsetOption() = asDateTimeOffset requestValue\n    member _.AsTimeSpanOption() = asTimeSpan requestValue\n    member _.AsGuidOption() = asGuid requestValue\n\n    member _.AsStringList() = asStringList requestValue\n    member _.AsStringNonEmptyList() = asStringNonEmptyList requestValue\n    member _.AsInt16List() = asInt16List requestValue\n    member _.AsInt32List() = asInt32List requestValue\n    member _.AsIntList() = asInt32List requestValue\n    member _.AsInt64List() = asInt64List requestValue\n    member _.AsBooleanList() = asBooleanList requestValue\n    member _.AsFloatList() = asFloatList requestValue\n    member _.AsDecimalList() = asDecimalList requestValue\n    member _.AsDateTimeList() = asDateTimeList requestValue\n    member _.AsDateTimeOffsetList() = asDateTimeOffsetList requestValue\n    member _.AsGuidList() = asGuidList requestValue\n    member _.AsTimeSpanList() = asTimeSpanList requestValue\n\n    member _.TryGetString (name : string) = tryGet name asString requestValue\n    member _.TryGetStringNonEmpty (name : string) = tryGet name asStringNonEmpty requestValue\n    member _.TryGetInt16 (name : string) = tryGet name asInt16 requestValue\n    member _.TryGetInt32 (name : string) = tryGet name asInt32 requestValue\n    member _.TryGetInt (name : string) = tryGet name asInt32 requestValue\n    member _.TryGetInt64 (name : string) = tryGet name asInt64 requestValue\n    member _.TryGetBoolean (name : string) = tryGet name asBoolean requestValue\n    member _.TryGetFloat (name : string) = tryGet name asFloat requestValue\n    member _.TryGetDecimal (name : string) = tryGet name asDecimal requestValue\n    member _.TryGetDateTime (name : string) = tryGet name asDateTime requestValue\n    member _.TryGetDateTimeOffset (name : string) = tryGet name asDateTimeOffset requestValue\n    member _.TryGetGuid (name : string) = tryGet name asGuid requestValue\n    member _.TryGetTimeSpan (name : string) = tryGet name asTimeSpan requestValue\n\n    member _.GetString (name : string, ?defaultValue : String) = tryGetOrElse name asString defaultValue \"\" requestValue\n    member _.GetStringNonEmpty (name : string, ?defaultValue : String) = tryGetOrElse name asStringNonEmpty defaultValue \"\" requestValue\n    member _.GetInt16 (name : string, ?defaultValue : Int16) = tryGetOrElse name asInt16 defaultValue 0s requestValue\n    member _.GetInt32 (name : string, ?defaultValue : Int32) = tryGetOrElse name asInt32 defaultValue 0 requestValue\n    member _.GetInt (name : string, ?defaultValue : Int32) = tryGetOrElse name asInt32 defaultValue 0 requestValue\n    member _.GetInt64 (name : string, ?defaultValue : Int64) = tryGetOrElse name asInt64 defaultValue 0L requestValue\n    member _.GetBoolean (name : string, ?defaultValue : Boolean) = tryGetOrElse name asBoolean defaultValue false requestValue\n    member _.GetFloat (name : string, ?defaultValue : float) = tryGetOrElse name asFloat defaultValue 0 requestValue\n    member _.GetDecimal (name : string, ?defaultValue : Decimal) = tryGetOrElse name asDecimal defaultValue 0M requestValue\n    member _.GetDateTime (name : string, ?defaultValue : DateTime) = tryGetOrElse name asDateTime defaultValue DateTime.MinValue requestValue\n    member _.GetDateTimeOffset (name : string, ?defaultValue : DateTimeOffset) = tryGetOrElse name asDateTimeOffset defaultValue DateTimeOffset.MinValue requestValue\n    member _.GetGuid (name : string, ?defaultValue : Guid) = tryGetOrElse name asGuid defaultValue Guid.Empty requestValue\n    member _.GetTimeSpan (name : string, ?defaultValue : TimeSpan) = tryGetOrElse name asTimeSpan defaultValue TimeSpan.MinValue requestValue\n\n    member _.GetStringList (name : string) = tryGetList name asStringList requestValue\n    member _.GetStringNonEmptyList (name : string) = tryGetList name asStringNonEmptyList requestValue\n    member _.GetInt16List (name : string) = tryGetList name asInt16List requestValue\n    member _.GetInt32List (name : string) = tryGetList name asInt32List requestValue\n    member _.GetIntList (name : string) = tryGetList name asInt32List requestValue\n    member _.GetInt64List (name : string) = tryGetList name asInt64List requestValue\n    member _.GetBooleanList (name : string) = tryGetList name asBooleanList requestValue\n    member _.GetFloatList (name : string) = tryGetList name asFloatList requestValue\n    member _.GetDecimalList (name : string) = tryGetList name asDecimalList requestValue\n    member _.GetDateTimeList (name : string) = tryGetList name asDateTimeList requestValue\n    member _.GetDateTimeOffsetList (name : string) = tryGetList name asDateTimeOffsetList requestValue\n    member _.GetGuidList (name : string) = tryGetList name asGuidList requestValue\n    member _.GetTimeSpanList (name : string) = tryGetList name asTimeSpanList requestValue\n\n[<AutoOpen>]\nmodule RequestDataOperators =\n    let inline (?) (requestData : RequestData) (name : string) =\n        requestData.Get name\n\n[<Sealed>]\ntype FormData(requestValue : RequestValue, files : IFormFileCollection option, ?isValid : bool) =\n    inherit RequestData(requestValue)\n\n    static member Empty = FormData(RequestValue.RNull, None, isValid = true)\n\n    static member Invalid = FormData(RequestValue.RNull, None, isValid = false)\n\n    /// Indicates whether the form passed antiforgery validation.\n    member _.IsValid = defaultArg isValid true\n\n    /// Indicates whether the form failed antiforgery validation.\n    member x.IsInvalid = not x.IsValid\n\n    /// The uploaded files included in the form, if any.\n    member _.Files = files\n\n    /// Attempts to retrieve a file from the form by name, returning `None` if\n    /// the file is not found or if there are no files.\n    member _.TryGetFile(name : string) =\n        match files, name with\n        | _, IsNullOrWhiteSpace _\n        | None, _ -> None\n        | Some files, name ->\n            match files.GetFile name with\n            | f when isNull f -> None\n            | f -> Some f\n"
  },
  {
    "path": "src/Falco/RequestValue.fs",
    "content": "namespace Falco\n\nopen System\nopen System.Collections.Generic\nopen System.Net\nopen System.Text.RegularExpressions\nopen Microsoft.AspNetCore.Http\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.FSharp.Core.Operators\nopen Falco.StringPatterns\n\ntype RequestValue =\n    | RNull\n    | RBool of bool\n    | RNumber of float\n    | RString of string\n    | RList of elements : RequestValue list\n    | RObject of keyValues : (string * RequestValue) list\n\nmodule internal RequestValueParser =\n    let private (|IsFlatKey|_|) (x : string) =\n        if not(x.EndsWith \"[]\") && not(x.Contains(\".\")) then Some x\n        else None\n\n    let private (|IsListKey|_|) (x : string) =\n        if x.EndsWith \"[]\" then Some (x.Substring(0, x.Length - 2))\n        else None\n\n    let private indexedListKeyRegex =\n        Regex(@\".\\[(\\d+)\\]$\", Text.RegularExpressions.RegexOptions.Compiled)\n\n    let private (|IsIndexedListKey|_|) (x : string) =\n\n        if x.EndsWith \"]\" then\n            match indexedListKeyRegex.Match x with\n            | m when m.Groups.Count = 2 ->\n                let capture = m.Groups[1].Value\n                Some (int capture, x.Substring(0, x.Length - capture.Length - 2))\n            | _ -> None\n        else None\n\n    let private extractRequestDataKeys (key : string, isSingle : bool) =\n        key\n        |> WebUtility.UrlDecode\n        |> fun key -> key.Split('.', StringSplitOptions.RemoveEmptyEntries)\n        |> List.ofArray\n        |> function\n        | [IsFlatKey key] when not isSingle ->[$\"{key}[]\"]\n        | x -> x\n\n    let private newRequestAcc () =\n        Dictionary<string, RequestValue>()\n\n    let private requestAccToValues (x : Dictionary<string, RequestValue>) =\n        x |> Seq.map (fun (kvp) -> kvp.Key, kvp.Value) |> List.ofSeq |> RObject\n\n    let private requestDatasToAcc (x : (string * RequestValue) list) =\n        let acc = newRequestAcc()\n        for key, value in x do\n            acc.TryAdd(key, value) |> ignore\n        acc\n\n    let private parseRequestPrimitive (x : string) =\n        let decoded = WebUtility.UrlDecode x\n        match decoded with\n        | IsNullOrWhiteSpace _ -> RNull\n        | IsTrue x\n        | IsFalse x -> RBool x\n        // Don't parse integers with leading zeros (except \"0\" itself) as floats\n        | _ when x.Length > 1 && x.StartsWith '0' && not(x.Contains '.') && not(x.Contains ',') -> RString x\n        // Don't parse large numerics as floats\n        | _ when x.Length > 15 -> RString x\n        | IsFloat x -> RNumber x\n        | x -> RString x\n\n    let private parseRequestPrimitiveList values =\n        values\n        |> Seq.map parseRequestPrimitive\n        |> List.ofSeq\n        |> RList\n\n    let private parseRequestPrimitiveSingle values =\n        values\n        |> Seq.tryHead\n        |> Option.map parseRequestPrimitive\n        |> Option.defaultValue RNull\n\n    let private parseExistingRequestIndexedList index values (requestList : RequestValue list) =\n        let requestArray = List.toArray requestList\n        let lstAccLen = if index >= requestList.Length then index + 1 else requestList.Length\n        let lstAcc : RequestValue array = Array.zeroCreate (lstAccLen)\n        for i = 0 to lstAccLen - 1 do\n            let lstRequestValue =\n                if i <> index then\n                    match Array.tryItem i requestArray with\n                    | Some x -> x\n                    | None -> RNull\n                else\n                    parseRequestPrimitiveSingle values\n\n            lstAcc[i] <- lstRequestValue\n\n        RList (List.ofArray lstAcc)\n\n    let private parseRequestIndexedList index values =\n        let lstAcc : RequestValue array = Array.zeroCreate (index + 1)\n        for i = 0 to index do\n            lstAcc[i] <- if i <> index then RNull else parseRequestPrimitiveSingle values\n        RList (List.ofArray lstAcc)\n\n    let parse (requestData : IDictionary<string, string seq>) : RequestValue =\n        let rec parseNested (acc : Dictionary<string, RequestValue>) (keys : string list) (values : string seq) =\n            match keys with\n            | [] -> ()\n            | [IsListKey key] ->\n                // list of primitives\n                values\n                |> parseRequestPrimitiveList\n                |> fun x -> acc.TryAdd(key, x) |> ignore\n\n            | [IsIndexedListKey (index, key)] ->\n                // indexed list of primitives\n                match acc.TryGetValue key with\n                | true, RList requestList ->\n                    parseExistingRequestIndexedList index values requestList\n                    |> fun x -> acc[key] <- x\n\n                | _ when index = 0 ->\n                    // first item in indexed list, initialize\n                    RList [ parseRequestPrimitiveSingle values ]\n                    |> fun x -> acc.TryAdd(key, x) |> ignore\n\n                | _ ->\n                    parseRequestIndexedList index values\n                    |> fun x -> acc.TryAdd(key, x) |> ignore\n\n            | [key] ->\n                // primitive\n                values\n                |> parseRequestPrimitiveSingle\n                |> fun x -> acc.TryAdd(key, x) |> ignore\n\n            | IsListKey key :: remainingKeys ->\n                // list of complex types\n                match acc.TryGetValue key with\n                | true, RList requestList ->\n                    requestList\n                    |> Seq.collect (fun requestData ->\n                        match requestData with\n                        | RObject requestObject ->\n                            let requestObjectAcc = requestDatasToAcc requestObject\n                            parseNested requestObjectAcc remainingKeys values\n                            Seq.singleton (requestObjectAcc |> requestAccToValues)\n                        | _ -> Seq.empty)\n                    |> List.ofSeq\n                    |> RList\n                    |> fun x -> acc[key] <- x\n\n                | _ ->\n                    values\n                    |> Seq.map (fun value ->\n                        let listValueAcc = newRequestAcc()\n                        parseNested listValueAcc remainingKeys (seq { value })\n                        listValueAcc\n                        |> requestAccToValues)\n                    |> List.ofSeq\n                    |> RList\n                    |> fun x -> acc.TryAdd(key, x) |> ignore\n\n            | key :: remainingKeys ->\n                // complex type\n                match acc.TryGetValue key with\n                | true, RObject requestObject ->\n                    let requestObjectAcc = requestDatasToAcc requestObject\n                    parseNested requestObjectAcc remainingKeys values\n                    acc[key] <- requestObjectAcc |> requestAccToValues\n\n                | _ ->\n                    let requestObjectAcc = newRequestAcc()\n                    parseNested requestObjectAcc remainingKeys values\n                    acc.TryAdd(key, requestObjectAcc |> requestAccToValues) |> ignore\n\n        let requestAcc = newRequestAcc()\n\n        for kvp in requestData do\n            let keys = extractRequestDataKeys (kvp.Key, Seq.tryItem 1 kvp.Value = None)\n            parseNested requestAcc keys kvp.Value\n\n        requestAcc\n        |> requestAccToValues\n\nmodule RequestValue =\n    /// Parses the provided request data into a `RequestValue` structure.\n    ///\n    /// - `requestData`: A dictionary where the key is a string representing the request data key (e.g., form field name, query parameter name) and the value is a sequence of strings representing the values associated with that key. This allows for handling multiple values for the same key, such as in the case of checkboxes or multi-select form fields.\n    let parse (requestData : IDictionary<string, string seq>) : RequestValue =\n        RequestValueParser.parse requestData\n\n    /// Parses a URL-encoded key-value string (e.g., \"key1=value1&key2=value2\") into a `RequestValue` structure.\n    ///\n    /// - `keyValueString`: A URL-encoded string containing key-value pairs, where keys and values are separated by '=' and pairs are separated by '&'. Keys without values (e.g., \"key1&key2=value2\") are treated as having an empty string value. The keys and values are URL-decoded before parsing.\n    let parseString (keyValueString : string) : RequestValue =\n        let requestDataPairs = Dictionary<string, IList<string>>()\n\n        let addOrSet (acc : Dictionary<string, IList<string>>) key value =\n            if acc.ContainsKey key then\n                acc[key].Add value\n            else\n                acc.Add(key, List<string>(Seq.singleton value))\n            ()\n\n        for kv in keyValueString.Split '&' do\n            // Handle keys without values (e.g. \"key1&key2=value2\") by treating them as having an empty string value\n            match kv.IndexOf '=' with\n            | -1 ->\n                addOrSet requestDataPairs kv String.Empty\n            | idx ->\n                let key = kv.Substring(0, idx)\n                let value = if idx + 1 < kv.Length then kv.Substring(idx + 1) else String.Empty\n                addOrSet requestDataPairs key value\n\n        requestDataPairs\n        |> Seq.map (fun kvp -> kvp.Key, kvp.Value :> IEnumerable<string>)\n        |> dict\n        |> parse\n\n    /// Parses the cookies from the request into a `RequestValue` structure.\n    ///\n    /// - `cookies`: An `IRequestCookieCollection` containing the cookies from the HTTP request. Each cookie's key and value are URL-decoded before parsing. Multiple cookies with the same key are not expected, as cookie keys are unique within a request, but if they do occur, they will be treated as multiple values for the same key.\n    let parseCookies (cookies : IRequestCookieCollection) : RequestValue =\n        cookies\n        |> Seq.map (fun kvp -> kvp.Key, seq { kvp.Value })\n        |> dict\n        |> parse\n\n    /// Parses the headers from the request into a `RequestValue` structure.\n    ///\n    /// - `headers`: An `IHeaderDictionary` containing the headers from the HTTP request. Each header's key and value(s) are URL-decoded before parsing. Headers can have multiple values for the same key, and all values will be included in the resulting `RequestValue`.\n    let parseHeaders (headers : IHeaderDictionary) : RequestValue =\n        headers\n        |> Seq.map (fun kvp -> kvp.Key, kvp.Value :> string seq)\n        |> dict\n        |> parse\n\n    let private routeKeyValues (route : RouteValueDictionary) =\n        route\n        |> Seq.map (fun kvp ->\n            kvp.Key, seq { Convert.ToString(kvp.Value, Globalization.CultureInfo.InvariantCulture) })\n\n    let private queryKeyValues (query : IQueryCollection) =\n        query\n        |> Seq.map (fun kvp -> kvp.Key, kvp.Value :> string seq)\n\n    /// Parses the route values and query string from the request into a `RequestValue` structure.\n    ///\n    /// - `route`: A `RouteValueDictionary` containing the route values from the HTTP request. Each route value's key and value are URL-decoded before parsing. Route values are typically defined in the route template and can be used to capture dynamic segments of the URL.\n    /// - `query`: An `IQueryCollection` containing the query string parameters from the HTTP request. Each query parameter's key and value(s) are URL-decoded before parsing. Query parameters can have multiple values for the same key, and all values will be included in the resulting `RequestValue`.\n    let parseRoute (route : RouteValueDictionary, query : IQueryCollection) : RequestValue =\n        Seq.concat [\n            route\n            |> routeKeyValues\n\n            query\n            |> queryKeyValues ]\n        |> dict\n        |> parse\n\n    /// Parses the query string from the request into a `RequestValue` structure.\n    ///\n    /// - `query`: An `IQueryCollection` containing the query string parameters from the HTTP request. Each query parameter's key and value(s) are URL-decoded before parsing. Query parameters can have multiple values for the same key, and all values will be included in the resulting `RequestValue`.\n    let parseQuery (query : IQueryCollection) : RequestValue =\n        query\n        |> queryKeyValues\n        |> dict\n        |> parse\n\n    /// Parses the form data from the request into a `RequestValue` structure.\n    ///\n    /// - `form`: An `IFormCollection` containing the form data from the HTTP request. Each form field's key and value(s) are URL-decoded before parsing. Form fields can have multiple values for the same key (e.g., in the case of checkboxes or multi-select fields), and all values will be included in the resulting `RequestValue`. The `route` parameter is optional and can be used to include route values in the parsing process, which is useful when you want to combine route values with form data.\n    /// - `route`: An optional `RouteValueDictionary` containing the route values from the HTTP request. Each route value's key and value are URL-decoded before parsing. Route values are typically defined in the route template and can be used to capture dynamic segments of the URL. If provided, the route values will be combined with the form data during parsing, allowing you to access both sets of values in the resulting `RequestValue`.\n    let parseForm (form : IFormCollection, route : RouteValueDictionary option) : RequestValue =\n        let routeKeyValues = route |> Option.map routeKeyValues |> Option.defaultValue Seq.empty\n\n        let formKeyValues =\n            form\n            |> Seq.map (fun kvp -> kvp.Key, kvp.Value :> string seq)\n\n        Seq.concat [ routeKeyValues; formKeyValues ]\n        |> dict\n        |> parse\n"
  },
  {
    "path": "src/Falco/Response.fs",
    "content": "[<RequireQualifiedAccess>]\nmodule Falco.Response\n\nopen System\nopen System.IO\nopen System.Security.Claims\nopen System.Text\nopen System.Text.Json\nopen Falco.Markup\nopen Falco.Security\nopen Microsoft.AspNetCore.Antiforgery\nopen Microsoft.AspNetCore.Authentication\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Primitives\nopen Microsoft.Net.Http.Headers\n\n// ------------\n// Modifiers\n// ------------\n\n/// Sets multiple headers for response.\n///\n/// Headers provided will replace any existing headers with the same name.\n///\n/// - `headers` - A list of header name and value pairs to add to the response.\nlet withHeaders\n    (headers : (string * string) list) : HttpResponseModifier = fun ctx ->\n    headers\n    |> List.iter (fun (name, content : string) ->\n        ctx.Response.Headers[name] <- StringValues(content))\n    ctx\n\n/// Sets ContentType header for response.\n///\n/// - `contentType` - The value to set for the Content-Type header.\nlet withContentType\n    (contentType : string) : HttpResponseModifier = fun ctx ->\n    ctx.Response.ContentType <- contentType\n    withHeaders [ HeaderNames.ContentType, contentType ] ctx\n\n/// Set StatusCode for response.\n///\n/// - `statusCode` - The HTTP status code to set for the response.\nlet withStatusCode\n    (statusCode : int) : HttpResponseModifier = fun ctx ->\n    ctx.Response.StatusCode <- statusCode\n    ctx\n\n/// Adds cookie to response.\n///\n/// - `key` - The name of the cookie to add to the response.\n/// - `value` - The value of the cookie to add to the response.\nlet withCookie\n    (key : string)\n    (value : string) : HttpResponseModifier = fun ctx ->\n    ctx.Response.Cookies.Append(key, value)\n    ctx\n\n/// Adds a configured cookie to response, via CookieOptions.\n///\n/// - `options` - The CookieOptions to apply when adding the cookie to the response.\n/// - `key` - The name of the cookie to add to the response.\n/// - `value` - The value of the cookie to add to the response.\nlet withCookieOptions\n    (options : CookieOptions)\n    (key : string)\n    (value : string) : HttpResponseModifier = fun ctx ->\n    ctx.Response.Cookies.Append(key, value, options)\n    ctx\n\n// ------------\n// Handlers\n// ------------\n\n/// Flushes any remaining response headers or data and returns empty response.\nlet ofEmpty : HttpHandler = fun ctx ->\n    ctx.Response.ContentLength <- 0\n    ctx.Response.CompleteAsync()\n\ntype private RedirectType =\n    | PermanentlyTo of url: string\n    | TemporarilyTo of url: string\n\nlet private redirect\n    (redirectType: RedirectType): HttpHandler = fun ctx ->\n    let (permanent, url) =\n        match redirectType with\n        | PermanentlyTo url -> (true, url)\n        | TemporarilyTo url -> (false, url)\n\n    task {\n        ctx.Response.Redirect(url, permanent)\n        do! ctx.Response.CompleteAsync()\n    }\n\n/// Returns a redirect (301) to client.\n///\n/// - `url` - The URL to which the client will be redirected.\nlet redirectPermanently (url: string) =\n    withStatusCode 301\n    >> redirect (PermanentlyTo url)\n\n/// Returns a redirect (302) to client.\n///\n/// - `url` - The URL to which the client will be redirected.\nlet redirectTemporarily (url: string) =\n    withStatusCode 302\n    >> redirect (TemporarilyTo url)\n\nlet private writeBytes\n    (bytes : byte[]) : HttpHandler = fun ctx ->\n        task {\n            ctx.Response.ContentLength <- bytes.LongLength\n            do! ctx.Response.Body.WriteAsync(bytes, 0, bytes.Length)\n        }\n\n/// Returns an inline binary (i.e., Byte[]) response with the specified\n/// Content-Type.\n///\n/// Note: Automatically sets \"content-disposition: inline\".\n///\n/// - `contentType` - The value to set for the Content-Type header.\n/// - `headers` - A list of additional header name and value pairs to add to the response.\n/// - `bytes` - The binary content to write to the response body.///\nlet ofBinary\n    (contentType : string)\n    (headers : (string * string) list)\n    (bytes : Byte[]) : HttpHandler =\n    let headers = (HeaderNames.ContentDisposition, \"inline\") :: headers\n    withContentType contentType\n    >> withHeaders headers\n    >> writeBytes bytes\n\n/// Returns a binary (i.e., Byte[]) attachment response with the specified\n/// Content-Type and optional filename.\n///\n/// Note: Automatically sets \"content-disposition: attachment\" and includes\n/// filename if provided.\n///\n/// - `filename` - The name of the file to be used in the Content-Disposition header. If empty or null, no filename will be included.\n/// - `contentType` - The value to set for the Content-Type header.\n/// - `headers` - A list of additional header name and value pairs to add to the response.\n/// - `bytes` - The binary content to write to the response body.\nlet ofAttachment\n    (filename : string)\n    (contentType : string)\n    (headers :\n    (string * string) list)\n    (bytes : Byte[]) : HttpHandler =\n    let contentDisposition =\n        if StringUtils.strNotEmpty filename then\n            let escapedFilename = HeaderUtilities.EscapeAsQuotedString filename\n            StringUtils.strConcat [ \"attachment; filename=\"; string escapedFilename ]\n        else \"attachment\"\n\n    let headers = (HeaderNames.ContentDisposition, contentDisposition) :: headers\n\n    withContentType contentType\n    >> withHeaders headers\n    >> writeBytes bytes\n\n/// Writes string to response body with provided encoding.\n///\n/// - `encoding` - The encoding to use when converting the string to bytes for the response body.\n/// - `str` - The string content to write to the response body. If null, empty, or whitespace, an empty response will be returned.\nlet ofString\n    (encoding : Encoding)\n    (str : string) : HttpHandler =\n    if String.IsNullOrWhiteSpace str then ofEmpty\n    else writeBytes (encoding.GetBytes(str))\n\n/// Returns a \"text/plain; charset=utf-8\" response with provided string to client.\n///\n/// - `str` - The string content to write to the response body. If null, empty, or whitespace, an empty response will be returned.\nlet ofPlainText\n    (str : string) : HttpHandler =\n    withContentType \"text/plain; charset=utf-8\"\n    >> ofString Encoding.UTF8 str\n\n/// Returns a \"text/html; charset=utf-8\" response with provided HTML string to client.\n///\n/// - `html` - The HTML string content to write to the response body. If null, empty, or whitespace, an empty response will be returned.\nlet ofHtmlString\n    (html : string) : HttpHandler =\n    withContentType \"text/html; charset=utf-8\"\n    >> ofString Encoding.UTF8 html\n\n/// Returns a \"text/html; charset=utf-8\" response with provided HTML to client.\n///\n/// - `html` - The HTML content to write to the response body. If null, empty, or whitespace, an empty response will be returned.\nlet ofHtml\n    (html : XmlNode) : HttpHandler =\n    ofHtmlString (renderHtml html)\n\nlet private withCsrfToken handleToken : HttpHandler = fun ctx ->\n    let csrfToken = Xsrf.getToken ctx\n    handleToken csrfToken ctx\n\n/// Returns a CSRF token-dependant \"text/html; charset=utf-8\" response with\n/// provided HTML to client.\n///\n/// - `view` - A function that takes an AntiforgeryTokenSet and returns the HTML content to write to the response body. If the returned HTML is null, empty, or whitespace, an empty response will be returned.\nlet ofHtmlCsrf\n    (view : AntiforgeryTokenSet -> XmlNode) : HttpHandler =\n    withCsrfToken (fun token -> token |> view |> ofHtml)\n\n/// Returns a \"text/html; charset=utf-8\" response with provided HTML fragment,\n/// if found, to client. If no element with the provided id is found, an empty\n/// string is returned.\n///\n/// - `id` - The id of the HTML element to render and write to the response body.\n/// - `html` - The HTML content to search for the element with the specified id.\nlet ofFragment\n    (id : string)\n    (html : XmlNode) : HttpHandler =\n    ofHtmlString (renderFragment html id)\n\n/// Returns a CSRF token-dependant \"text/html; charset=utf-8\" response with\n/// provided HTML fragment, if found, to client. If no element with the\n/// provided id is found, an empty string is returned.\n///\n/// - `id` - The id of the HTML element to render and write to the response body.\n/// - `view` - A function that takes an AntiforgeryTokenSet and returns the HTML content to search for the element with the specified id.\nlet ofFragmentCsrf\n    (id : string)\n    (view : AntiforgeryTokenSet -> XmlNode) : HttpHandler =\n    withCsrfToken (fun token -> token |> view |> ofFragment id)\n\n/// Returns an optioned \"application/json; charset=utf-8\" response with the\n/// serialized object provided to the client.\n///\n/// - `options` - The JsonSerializerOptions to use when serializing the object to JSON.\n/// - `obj` - The object to serialize to JSON and write to the response body.\nlet ofJsonOptions\n    (options : JsonSerializerOptions)\n    (obj : 'T) : HttpHandler =\n    withContentType \"application/json; charset=utf-8\"\n    >> fun ctx -> task {\n        use str = new MemoryStream()\n        do! JsonSerializer.SerializeAsync(str, obj, options)\n        ctx.Response.ContentLength <- str.Length\n        str.Position <- 0\n        do! str.CopyToAsync ctx.Response.Body\n    }\n\n/// Returns a \"application/json; charset=utf-8\" response with the serialized\n/// object provided to the client.\n///\n/// - `obj` - The object to serialize to JSON and write to the response body.\nlet ofJson\n    (obj : 'T) : HttpHandler =\n    withContentType \"application/json; charset=utf-8\"\n    >> ofJsonOptions Request.defaultJsonOptions obj\n\n/// Signs in claim principal for provided scheme then responds with a 301 redirect\n/// to provided URL.\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing in the claim principal.\n/// - `claimsPrincipal` - The ClaimsPrincipal to sign in for the specified authentication scheme.\nlet signIn\n    (authScheme : string)\n    (claimsPrincipal : ClaimsPrincipal) : HttpHandler = fun ctx ->\n    task {\n        do! ctx.SignInAsync(authScheme, claimsPrincipal)\n    }\n\n/// Signs in claim principal for provided scheme and options then responds with a\n/// 301 redirect to provided URL (via AuthenticationProperties.RedirectUri).\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing in the claim principal.\n/// - `claimsPrincipal` - The ClaimsPrincipal to sign in for the specified authentication scheme.\n/// - `options` - The AuthenticationProperties to use when signing in the claim principal, which may include a RedirectUri for the post-sign-in redirect URL.\nlet signInOptions\n    (authScheme : string)\n    (claimsPrincipal : ClaimsPrincipal)\n    (options : AuthenticationProperties) : HttpHandler =\n    withHeaders [\n        if not (String.IsNullOrEmpty options.RedirectUri) then\n            HeaderNames.Location, options.RedirectUri ]\n    >> (if not (String.IsNullOrEmpty options.RedirectUri) then withStatusCode 301 else id)\n    >> fun ctx ->\n    task {\n        do! ctx.SignInAsync(authScheme, claimsPrincipal, options)\n    }\n\n/// Signs in claim principal for provided scheme then responds with a 301 redirect\n/// to provided URL (via AuthenticationProperties.RedirectUri).\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing in the claim principal.\n/// - `claimsPrincipal` - The ClaimsPrincipal to sign in for the specified authentication scheme.\n/// - `url` - The URL to which the client will be redirected after signing in, which will be set in the AuthenticationProperties.RedirectUri.\nlet signInAndRedirect\n    (authScheme : string)\n    (claimsPrincipal : ClaimsPrincipal)\n    (url : string) : HttpHandler =\n    let options = AuthenticationProperties(RedirectUri = url)\n    signInOptions authScheme claimsPrincipal options\n\n/// Terminates authenticated context for provided scheme then responds with a 301\n/// redirect to provided URL (via AuthenticationProperties.RedirectUri).\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing out the authenticated context.\nlet signOut\n    (authScheme : string) : HttpHandler = fun ctx ->\n    task {\n        do! ctx.SignOutAsync authScheme\n    }\n\n/// Terminates authenticated context for provided scheme then responds with a 301\n/// redirect to provided URL.\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing out the authenticated context.\n/// - `options` - The AuthenticationProperties to use when signing out, which may include a RedirectUri for the post-sign-out redirect URL.\nlet signOutOptions\n    (authScheme : string)\n    (options : AuthenticationProperties) : HttpHandler =\n    withHeaders [\n        if not (String.IsNullOrEmpty options.RedirectUri) then\n            HeaderNames.Location, options.RedirectUri ]\n    >> (if not (String.IsNullOrEmpty options.RedirectUri) then withStatusCode 301 else id)\n    >> fun ctx ->\n    task {\n        do! ctx.SignOutAsync(authScheme, options)\n    }\n\n/// Terminates authenticated context for provided scheme then responds with a 301\n/// redirect to provided URL.\n///\n/// - `authScheme` - The name of the authentication scheme to use when signing out the authenticated context.\n/// - `url` - The URL to which the client will be redirected after signing out, which will be set in the AuthenticationProperties.RedirectUri.\nlet signOutAndRedirect\n    (authScheme : string)\n    (url : string) : HttpHandler =\n    let options = AuthenticationProperties(RedirectUri = url)\n    signOutOptions authScheme options\n\n/// Challenges the specified authentication scheme.\n/// An authentication challenge can be issued when an unauthenticated user\n/// requests an endpoint that requires authentication. Then given redirectUri is\n/// forwarded to the authentication handler for use after authentication succeeds.\n///\n/// Note: If options.RedirectUri is provided, a 401 status code and Location header\n/// will be included in the response, with the Location header set to the RedirectUri.\n/// Otherwise, no status code or Location header will be included in the response.\n///\n/// - `authScheme` - The name of the authentication scheme to challenge.\n/// - `options` - The AuthenticationProperties to use when challenging, which may include a RedirectUri for the post-challenge redirect URL.\nlet challengeOptions\n    (authScheme : string)\n    (options : AuthenticationProperties) : HttpHandler =\n    withStatusCode 401\n    >> withHeaders [\n        HeaderNames.WWWAuthenticate, authScheme\n        if not (String.IsNullOrEmpty options.RedirectUri) then\n            HeaderNames.Location, options.RedirectUri ]\n    >> fun ctx ->\n    task {\n        do! ctx.ChallengeAsync(authScheme, options)\n    }\n\n/// Challenges the specified authentication scheme.\n/// An authentication challenge can be issued when an unauthenticated user\n/// requests an endpoint that requires authentication. Then given redirectUri is\n/// forwarded to the authentication handler for use after authentication succeeds.\n///\n/// Note: A 401 status code and Location header will be included in the response, with the Location header set to the provided redirectUri.\n///\n/// - `authScheme` - The name of the authentication scheme to challenge.\n/// - `redirectUri` - The URL to which the client will be redirected after the challenge, which will be set in the AuthenticationProperties.RedirectUri.\nlet challengeAndRedirect\n    (authScheme : string)\n    (redirectUri : string) : HttpHandler =\n    let options = AuthenticationProperties(RedirectUri = redirectUri)\n    challengeOptions authScheme options\n"
  },
  {
    "path": "src/Falco/Routing.fs",
    "content": "﻿namespace Falco\n\nopen System\nopen System.Collections.Generic\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.AspNetCore.Http\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.Extensions.FileProviders\nopen Falco.StringUtils\n\n/// Specifies an association of a route pattern to a collection of\n/// HttpEndpointHandler.\ntype HttpEndpoint =\n    { Pattern  : string\n      Handlers : (HttpVerb * HttpHandler) seq\n      Configure : EndpointBuilder -> EndpointBuilder }\n\nmodule Routing =\n    /// Constructor for multi-method HttpEndpoint.\n    ///\n    /// - `pattern` - The route pattern to which the HttpEndpoint will be associated.\n    /// - `handlers` - A sequence of tuples associating an HttpVerb to an HttpHandler. The HttpVerb ANY can be used to match any HTTP method.\n    let all\n        (pattern : string)\n        (handlers : (HttpVerb * HttpHandler) seq) : HttpEndpoint =\n        { Pattern  = pattern\n          Handlers = handlers\n          Configure = id }\n\n    /// Constructor for a singular HttpEndpoint.\n    ///\n    /// - `verb` - The HttpVerb to which the HttpHandler will be associated. The HttpVerb ANY can be used to match any HTTP method.\n    /// - `pattern` - The route pattern to which the HttpEndpoint will be associated.\n    /// - `handler` - The HttpHandler to be associated with the provided HttpVerb and route pattern.\n    let route verb pattern handler =\n        all pattern [ verb, handler ]\n\n    /// HttpEndpoint constructor that matches any HttpVerb.\n    ///\n    /// Note: Use with caution as this will match any HTTP method, which may not be desirable in all cases.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated. The HttpVerb ANY can be used to match any HTTP method.\n    /// - `handler` - The HttpHandler to be associated with the provided route pattern for any HTTP method.\n    let any pattern handler =\n        route ANY pattern handler\n\n    /// GET HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the GET HttpVerb and provided route pattern.\n    let get pattern handler =\n        route GET pattern handler\n\n    /// HEAD HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the HEAD HttpVerb and provided route pattern.\n    let head pattern handler =\n        route HEAD pattern handler\n\n    /// POST HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the POST HttpVerb and provided route pattern.\n    let post pattern handler =\n        route POST pattern handler\n\n    /// PUT HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the PUT HttpVerb and\n    let put pattern handler =\n        route PUT pattern handler\n\n    /// PATCH HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the PATCH HttpVerb and\n    let patch pattern handler =\n        route PATCH pattern handler\n\n    /// DELETE HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the DELETE HttpVerb and provided route pattern.\n    let delete pattern handler =\n        route DELETE pattern handler\n\n    /// OPTIONS HttpEndpoint constructor.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the OPTIONS HttpVerb and provided route pattern.\n    let options pattern handler =\n        route OPTIONS pattern handler\n\n    /// TRACE HttpEndpoint construct.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `handler` - The HttpHandler to be associated with the TRACE HttpVerb and provided route pattern.\n    let trace pattern handler =\n        route TRACE pattern handler\n\n    /// HttpEndpoint constructor that matches any HttpVerb which maps the route\n    /// using the provided `map` function.\n    ///\n    /// Note: Use with caution as this will match any HTTP method, which may not be desirable in all cases.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated. The HttpVerb ANY can be used to match any HTTP method.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the provided route pattern for any HTTP method, which will receive the mapped value in the HttpContext.\n    let mapAny pattern map handler =\n        any pattern (Request.mapRoute map handler)\n\n    /// GET HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the GET HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapGet pattern map handler =\n        get pattern (Request.mapRoute map handler)\n\n    /// HEAD HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the HEAD HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapHead pattern map handler =\n        head pattern (Request.mapRoute map handler)\n\n    /// POST HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the POST HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapPost pattern map handler =\n        post pattern (Request.mapRoute map handler)\n\n    /// PUT HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the PUT HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapPut pattern map handler =\n        put pattern (Request.mapRoute map handler)\n\n    /// PATCH HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the PATCH HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapPatch pattern map handler =\n        patch pattern (Request.mapRoute map handler)\n\n    /// DELETE HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the DELETE HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapDelete pattern map handler =\n        delete pattern (Request.mapRoute map handler)\n\n    /// OPTIONS HttpEndpoint constructor which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the OPTIONS HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapOptions pattern map handler =\n        options pattern (Request.mapRoute map handler)\n\n    /// TRACE HttpEndpoint construct which maps the route using the provided\n    /// `map` function.\n    ///\n    /// - `pattern` - The route pattern to which the HttpHandler will be associated.\n    /// - `map` - A function that takes the route pattern and returns a mapped value which will be included in the HttpContext for the HttpHandler to use.\n    /// - `handler` - The HttpHandler to be associated with the TRACE HttpVerb and provided route pattern, which will receive the mapped value in the HttpContext.\n    let mapTrace pattern map handler =\n        trace pattern (Request.mapRoute map handler)\n\n    /// Configure the display name attribute of the endpoint.\n    ///\n    /// Note: The display name is used for endpoint selection and will be included\n    /// in the HttpContext for the HttpHandler to use.\n    ///\n    /// - `displayName` - The display name to be associated with the endpoint, which will be included in the HttpContext for the HttpHandler to use.\n    /// - `endpoint` - The HttpEndpoint for which the display name will be set.\n    let setDisplayName (displayName : string) (endpoint : HttpEndpoint) =\n        let configure (builder : EndpointBuilder) =\n            (builder :?> RouteEndpointBuilder).DisplayName <- displayName\n            builder\n\n        { endpoint with Configure = endpoint.Configure >> configure }\n\n    /// Set an explicit order for the endpoint.\n    ///\n    /// Note: The order is used for endpoint selection and will be included in the\n    /// HttpContext for the HttpHandler to use. Endpoints with lower order values\n    /// will be selected before those with higher values.\n    ///\n    /// - `n` - The order to be associated with the endpoint, which will be included in the HttpContext for the HttpHandler to use.\n    /// - `endpoint` - The HttpEndpoint for which the order will be set.\n    let setOrder (n : int) (endpoint : HttpEndpoint) =\n        let configure (builder : EndpointBuilder) =\n            (builder :?> RouteEndpointBuilder).Order <- n\n            builder\n\n        { endpoint with Configure = endpoint.Configure >> configure }\n\n[<Sealed>]\ntype FalcoEndpointDataSource(httpEndpoints : HttpEndpoint seq) =\n    inherit EndpointDataSource()\n\n    let conventions = List<Action<EndpointBuilder>>()\n\n    new() = FalcoEndpointDataSource([])\n\n    member val FalcoEndpoints = List<HttpEndpoint>()\n\n    override x.Endpoints with get() = x.BuildEndpoints()\n\n    override _.GetChangeToken() = NullChangeToken.Singleton\n\n    member private this.BuildEndpoints () =\n        let endpoints = List<Endpoint>()\n\n        for endpoint in Seq.concat [ httpEndpoints; this.FalcoEndpoints ] do\n            let routePattern = Patterns.RoutePatternFactory.Parse endpoint.Pattern\n\n            for (verb, handler) in endpoint.Handlers do\n                let verbStr = verb.ToString()\n\n                let displayName =\n                    if strEmpty verbStr then endpoint.Pattern\n                    else strConcat [|verbStr; \" \"; endpoint.Pattern|]\n\n                let endpointBuilder = RouteEndpointBuilder(\n                    requestDelegate = HttpHandler.toRequestDelegate handler,\n                    routePattern = routePattern,\n                    order = 0,\n                    DisplayName = displayName)\n\n                endpointBuilder.DisplayName <- displayName\n                endpoint.Configure endpointBuilder |> ignore\n\n                for convention in conventions do\n                    convention.Invoke(endpointBuilder)\n\n                let routeNameMetadata = RouteNameMetadata(endpoint.Pattern)\n                endpointBuilder.Metadata.Add(routeNameMetadata)\n\n                let httpMethodMetadata =\n                    match verb with\n                    | ANY -> HttpMethodMetadata [||]\n                    | _   -> HttpMethodMetadata [|verbStr|]\n\n                endpointBuilder.Metadata.Add(httpMethodMetadata)\n\n                endpoints.Add(endpointBuilder.Build())\n\n        endpoints\n\n    interface IEndpointConventionBuilder with\n        member _.Add(convention: Action<EndpointBuilder>) : unit =\n            conventions.Add(convention)\n\n        member _.Finally (_: Action<EndpointBuilder>): unit =\n            ()\n"
  },
  {
    "path": "src/Falco/Security.fs",
    "content": "module Falco.Security\n\nmodule Xsrf =\n    open System\n    open System.Threading.Tasks\n    open Falco.Markup\n    open Microsoft.AspNetCore.Antiforgery\n    open Microsoft.AspNetCore.Http\n    open Microsoft.Extensions.DependencyInjection\n\n    /// Outputs an antiforgery <input type=\"hidden\" />.\n    ///\n    /// This should be used within a form to include the antiforgery token as part\n    /// of the form submission. The token is generated and stored in the user's\n    /// cookies by calling `getToken` and then passed to this function to create\n    /// the hidden input field.\n    ///\n    /// - `token` - The antiforgery token set containing the form field name and request token value.\n    let antiforgeryInput\n        (token : AntiforgeryTokenSet) =\n        Elem.input [\n            Attr.type' \"hidden\"\n            Attr.name token.FormFieldName\n            Attr.value token.RequestToken ]\n\n    /// Generates an antiforgery token and stores it in the user's cookies.\n    let getToken (ctx : HttpContext) : AntiforgeryTokenSet =\n        let antiFrg = ctx.RequestServices.GetRequiredService<IAntiforgery>()\n        antiFrg.GetAndStoreTokens ctx\n\n    /// Validates the antiforgery token within the provided HttpContext.\n    let validateToken (ctx : HttpContext) : Task<bool> =\n        if ctx.Request.Method = HttpMethods.Get\n           || ctx.Request.Method = HttpMethods.Options\n           || ctx.Request.Method = HttpMethods.Head\n           || ctx.Request.Method = HttpMethods.Trace then\n           Task.FromResult true\n        else\n            task {\n                try\n                    let antiFrg = ctx.RequestServices.GetRequiredService<IAntiforgery>()\n                    do! antiFrg.ValidateRequestAsync ctx\n                    return true\n                with\n                | :? InvalidOperationException ->\n                    return true  // Antiforgery not registered, consider valid\n                | :? AntiforgeryValidationException ->\n                    return false  // Token present but invalid\n            }\n"
  },
  {
    "path": "src/Falco/String.fs",
    "content": "namespace Falco\n\nopen System\nopen System.Collections.Generic\nopen System.Globalization\n\nmodule internal StringUtils =\n    /// Checks if string is null or whitespace.\n    let strEmpty str =\n        String.IsNullOrWhiteSpace(str)\n\n    /// Checks if string is not null or whitespace.\n    let strNotEmpty str =\n        not(strEmpty str)\n\n    /// Case & culture insensitive string equality.\n    let strEquals s1 s2 =\n        String.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase)\n\n    /// Concats strings.\n    let strConcat (lst : string seq) =\n        // String.Concat uses a StringBuilder when provided an IEnumerable\n        // Url: https://github.com/microsoft/referencesource/blob/master/mscorlib/system/string.cs#L161\n        String.Concat(lst)\n\n    /// Splits string into substrings based on separator.\n    let strSplit (sep : char array) (str : string) =\n        str.Split(sep, StringSplitOptions.RemoveEmptyEntries)\n\nmodule internal StringParser =\n    /// Helper to wrap .NET tryParser's.\n    let private tryParseWith (tryParseFunc: string -> bool * _) (str : string) =\n        let parsedResult = tryParseFunc str\n        match parsedResult with\n        | true, v    -> Some v\n        | false, _   -> None\n\n    let parseBoolean (value : string) =\n        // we explicitly do not support on/off boolean values\n        // see: https://github.com/falcoframework/Falco/issues/129#issuecomment-2496081776\n        match value with\n        | x when String.Equals(\"true\", x, StringComparison.OrdinalIgnoreCase) -> Some true\n        | x when String.Equals(\"false\", x, StringComparison.OrdinalIgnoreCase) -> Some false\n        | v -> tryParseWith Boolean.TryParse v\n\n    let parseInt16 = tryParseWith Int16.TryParse\n    let parseInt64 = tryParseWith Int64.TryParse\n    let parseInt32 = tryParseWith Int32.TryParse\n    let parseFloat = tryParseWith Double.TryParse\n    let parseDecimal = tryParseWith (fun x -> Decimal.TryParse(x, NumberStyles.Number ||| NumberStyles.AllowExponent ||| NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture))\n    let parseDateTime = tryParseWith (fun x -> DateTime.TryParse(x, null, DateTimeStyles.RoundtripKind))\n    let parseDateTimeOffset = tryParseWith (fun x -> DateTimeOffset.TryParse(x, null, DateTimeStyles.RoundtripKind))\n    let parseTimeSpan = tryParseWith TimeSpan.TryParse\n    let parseGuid = tryParseWith Guid.TryParse\n\nmodule internal StringPatterns =\n    let (|IsBool|_|) = StringParser.parseBoolean\n    let (|IsInt16|_|) = StringParser.parseInt16\n    let (|IsInt64|_|) = StringParser.parseInt64\n    let (|IsInt32|_|) = StringParser.parseInt32\n    let (|IsFloat|_|) (x : string) = StringParser.parseFloat x\n    let (|IsDecimal|_|) = StringParser.parseDecimal\n    let (|IsDateTime|_|) = StringParser.parseDateTime\n    let (|IsDateTimeOffset|_|) = StringParser.parseDateTimeOffset\n    let (|IsTimeSpan|_|) = StringParser.parseTimeSpan\n    let (|IsGuid|_|) = StringParser.parseGuid\n\n    let (|IsTrue|_|) =\n        function\n        | IsBool x when x = true -> Some true\n        | _ -> None\n\n    let (|IsFalse|_|) =\n        function\n        | IsBool x when x = false -> Some false\n        | _ -> None\n\n    let (|IsNullOrWhiteSpace|_|) (x : string) =\n        match String.IsNullOrWhiteSpace x with\n        | true -> Some ()\n        | false -> None\n"
  },
  {
    "path": "src/Falco/WebApplication.fs",
    "content": "namespace Falco\n\n[<AutoOpen>]\nmodule Extensions =\n    open Microsoft.AspNetCore.Builder\n    open Microsoft.AspNetCore.Http\n    open Microsoft.AspNetCore.Routing\n    open Microsoft.Extensions.Configuration\n    open Microsoft.Extensions.DependencyInjection\n    open Microsoft.Extensions.Logging\n\n    type IEndpointRouteBuilder with\n        /// Registers a sequence of `Falco.HttpEndpoint` with the endpoint route builder.\n        member this.UseFalcoEndpoints(endpoints : HttpEndpoint seq) : IEndpointRouteBuilder =\n            let falcoDataSource =\n                let registeredDataSource = this.ServiceProvider.GetService<FalcoEndpointDataSource>()\n                if obj.ReferenceEquals(registeredDataSource, null) then\n                    FalcoEndpointDataSource([])\n                else\n                    registeredDataSource\n\n            for endpoint in endpoints do\n                falcoDataSource.FalcoEndpoints.Add(endpoint)\n\n            this.DataSources.Add(falcoDataSource)\n\n            this\n\n    type WebApplicationBuilder with\n        member this.AddConfiguration(fn : IConfigurationBuilder -> IConfigurationBuilder) : WebApplicationBuilder =\n            fn this.Configuration |> ignore\n            this\n\n        member this.AddLogging(fn : ILoggingBuilder -> ILoggingBuilder) : WebApplicationBuilder =\n            fn this.Logging |> ignore\n            this\n\n        /// Apply `fn` to `WebApplicationBuilder.Services :> IServiceCollection`  if `predicate` is true.\n        member this.AddServicesIf(predicate : bool, fn : IConfiguration -> IServiceCollection -> IServiceCollection) : WebApplicationBuilder =\n            if predicate then fn this.Configuration this.Services |> ignore\n            this\n\n        member this.AddServices(fn : IConfiguration -> IServiceCollection -> IServiceCollection) : WebApplicationBuilder =\n            this.AddServicesIf(true, fn)\n\n    type IApplicationBuilder with\n        /// Apply `fn` to `WebApplication :> IApplicationBuilder` if `predicate` is true.\n        member this.UseIf(predicate : bool, fn : IApplicationBuilder -> IApplicationBuilder) : IApplicationBuilder =\n            if predicate then fn this |> ignore\n            this\n\n        /// Analagous to `IApplicationBuilder.Use` but returns `WebApplication`.\n        member this.Use(fn : IApplicationBuilder -> IApplicationBuilder) : IApplicationBuilder =\n            this.UseIf(true, fn)\n\n        /// Activates Falco integration with IEndpointRouteBuilder.\n        ///\n        /// This is the default way to enable the library.\n        member this.UseFalco(endpoints : HttpEndpoint seq) : IApplicationBuilder =\n            this.UseEndpoints(fun endpointBuilder ->\n                endpointBuilder.UseFalcoEndpoints(endpoints) |> ignore)\n\n        /// Registers a `Falco.HttpHandler` as terminal middleware (i.e., not found).\n        /// This should be registered at the end of the middleware pipeline to catch any\n        /// requests that were not handled by any other middleware. The provided\n        /// handler will be executed for any requests that reach this point in\n        /// the pipeline, allowing you to return a custom 404 response or perform\n        /// other actions as needed.\n        member this.UseFalcoNotFound(notFoundHandler : HttpHandler) : unit =\n            this.Run(handler = HttpHandler.toRequestDelegate notFoundHandler)\n\n        /// Registers a `Falco.HttpHandler` as exception handler lambda.\n        /// See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?#exception-handler-lambda\n        member this.UseFalcoExceptionHandler(exceptionHandler : HttpHandler) : IApplicationBuilder =\n            let configure (appBuilder : IApplicationBuilder) =\n                appBuilder.Run(HttpHandler.toRequestDelegate exceptionHandler)\n\n            this.UseExceptionHandler(configure) |> ignore\n            this\n\n    type WebApplication with\n        /// Registers a `Falco.HttpHandler` as terminal middleware (i.e., not found)\n        /// then runs application, blocking the calling thread until host shutdown.\n        member this.Run(terminalHandler : HttpHandler) : unit =\n            this.UseFalcoNotFound(terminalHandler) |> ignore\n            this.Run()\n\n        /// Apply `fn` to `WebApplication :> IApplicationBuilder` if `predicate` is true.\n        member this.UseIf(predicate : bool, fn : IApplicationBuilder -> IApplicationBuilder) : WebApplication =\n            (this :> IApplicationBuilder).UseIf(predicate, fn) |> ignore\n            this\n\n        /// Analagous to `IApplicationBuilder.Use` but returns `WebApplication`.\n        member this.Use(fn : IApplicationBuilder -> IApplicationBuilder) : WebApplication =\n            this.UseIf(true, fn)\n\n        member this.UseRouting() : WebApplication =\n            (this :> IApplicationBuilder).UseRouting() |> ignore\n            this\n\n        /// Activates Falco integration with IEndpointRouteBuilder.\n        ///\n        /// This is the default way to enable the package.\n        member this.UseFalco(endpoints : HttpEndpoint seq) : WebApplication =\n            (this :> IApplicationBuilder).UseFalco(endpoints) |> ignore\n            this\n\n        /// Registers a `Falco.HttpHandler` as exception handler lambda.\n        /// See: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?#exception-handler-lambda\n        member this.UseFalcoExceptionHandler(exceptionHandler : HttpHandler) : WebApplication =\n            (this :> IApplicationBuilder).UseFalcoExceptionHandler(exceptionHandler) |> ignore\n            this\n\n        /// Registers a `Falco.HttpHandler` as terminal middleware (i.e., not found).\n        member this.UseFalcoNotFound(notFoundHandler : HttpHandler) : WebApplication =\n            (this :> IApplicationBuilder).UseFalcoNotFound(notFoundHandler) |> ignore\n            this\n\n    type FalcoExtensions =\n        /// Registers a `Falco.HttpHandler` as global exception handler.\n        static member UseFalcoExceptionHandler\n            (exceptionHandler : HttpHandler)\n            (app : IApplicationBuilder) =\n            app.UseFalcoExceptionHandler exceptionHandler\n\n    type HttpContext with\n        /// Attempts to obtain dependency from IServiceCollection\n        /// Throws InvalidDependencyException on missing.\n        member this.Plug<'T>() =\n            this.RequestServices.GetRequiredService<'T>()\n"
  },
  {
    "path": "test/Falco.IntegrationTests/Falco.IntegrationTests.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <IsPackable>false</IsPackable>\n    <GenerateProgramFile>false</GenerateProgramFile>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Compile Include=\"Program.fs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"FsUnit.Xunit\" Version=\"6.*\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Mvc.Testing\" Version=\"8.*\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.*\" />\n    <PackageReference Include=\"xunit\" Version=\"2.*\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.*\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"coverlet.collector\" Version=\"6.*\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n    <ProjectReference Include=\"../Falco.IntegrationTests.App/Falco.IntegrationTests.App.fsproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "test/Falco.IntegrationTests/Program.fs",
    "content": "namespace Falco.IntegrationTests\n\nopen System.Net.Http\nopen System.Text\nopen System.Text.Json\nopen Microsoft.AspNetCore.Mvc.Testing\nopen Xunit\nopen Falco.IntegrationTests.App\n\nmodule FalcoOpenApiTestServer =\n    let createFactory() =\n        new WebApplicationFactory<Program>()\n\nmodule Tests =\n    let private factory = FalcoOpenApiTestServer.createFactory ()\n\n    [<Fact>]\n    let ``Receive plain-text response from: GET /hello``() =\n        use client = factory.CreateClient ()\n        let content = client.GetStringAsync(\"/\").Result\n        Assert.Equal(\"Hello World!\", content)\n\n    [<Fact>]\n    let ``Receive text/html response from GET /html`` () =\n        use client = factory.CreateClient ()\n        let content = client.GetStringAsync(\"/html\").Result\n        Assert.Equal(\"\"\"<!DOCTYPE html><html><head></head><body><h1>hello world</h1></body></html>\"\"\", content)\n\n\n    [<Fact>]\n    let ``Receive application/json response from GET /json`` () =\n        use client = factory.CreateClient ()\n        let content = client.GetStringAsync(\"/json\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"hello world\"}\"\"\", content)\n\n    [<Fact>]\n    let ``Receive mapped application/json response from: GET /hello/name?`` () =\n        use client = factory.CreateClient ()\n        let content = client.GetStringAsync(\"/hello\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello world!\"}\"\"\", content)\n\n        let content = client.GetStringAsync(\"/hello/John\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John!\"}\"\"\", content)\n\n        let content = client.GetStringAsync(\"/hello/John?age=42\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John, you are 42 years old!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``Receive mapped application/json response from: POST /hello/name?`` () =\n        use client = factory.CreateClient ()\n        use form = new FormUrlEncodedContent([])\n        let response = client.PostAsync(\"/hello\", form).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello world!\"}\"\"\", content)\n\n        let response = client.PostAsync(\"/hello/John\", form).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John!\"}\"\"\", content)\n\n        use form = new FormUrlEncodedContent(dict [ (\"age\", \"42\") ])\n        let response = client.PostAsync(\"/hello/John\", form).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John, you are 42 years old!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``Receive utf8 text/plain response from: GET /plug/name?`` () =\n        use client = factory.CreateClient ()\n        let content = client.GetStringAsync(\"/plug\").Result\n        Assert.Equal(\"Hello world 😀\", content)\n\n        let content = client.GetStringAsync(\"/plug/John\").Result\n        Assert.Equal(\"Hello John 😀\", content)\n\n    [<Fact>]\n    let ``Receive application/json request body and return from: GET /api/message`` () =\n        use client = factory.CreateClient ()\n\n        use body = new StringContent(JsonSerializer.Serialize { Message = \"Hello /api/message\" }, Encoding.UTF8, \"application/json\")\n        let response = client.PostAsync(\"/api/message\", body).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello /api/message\"}\"\"\", content)\n\n        use body = new StringContent(\"\", Encoding.UTF8, \"application/json\")\n        let response = client.PostAsync(\"/api/message\", body).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{}\"\"\", content)\n\n    [<Fact>]\n    let ``GET /hello/ (trailing slash) returns default greeting`` () =\n        use client = factory.CreateClient()\n        let content = client.GetStringAsync(\"/hello/\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello world!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``GET /hello/{name} decodes url-encoded name`` () =\n        use client = factory.CreateClient()\n        let content = client.GetStringAsync(\"/hello/John%20Doe\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John Doe!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``GET /hello/{name}?age=invalid ignores invalid age`` () =\n        use client = factory.CreateClient()\n        let content = client.GetStringAsync(\"/hello/John?age=not-a-number\").Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello John!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``POST /hello/{name} with invalid age ignores age`` () =\n        use client = factory.CreateClient()\n        use form = new FormUrlEncodedContent(dict [ (\"age\", \"not-a-number\") ])\n        let response = client.PostAsync(\"/hello/Jane\", form).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello Jane!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``POST /hello with age but no name uses default world`` () =\n        use client = factory.CreateClient()\n        use form = new FormUrlEncodedContent(dict [ (\"age\", \"7\") ])\n        let response = client.PostAsync(\"/hello\", form).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello world, you are 7 years old!\"}\"\"\", content)\n\n    [<Fact>]\n    let ``POST /api/message with non-json content type should fail`` () =\n        use client = factory.CreateClient()\n        use body = new StringContent(\"Message=Hello\", Encoding.UTF8, \"text/plain\")\n        let response = client.PostAsync(\"/api/message\", body).Result\n        Assert.False(response.IsSuccessStatusCode)\n\n    [<Fact>]\n    let ``POST /api/message echoes input`` () =\n        use client = factory.CreateClient()\n        let payload = \"\"\"{\"Message\":\"Hello /api/message\",\"Extra\":\"ignored\"}\"\"\"\n        use body = new StringContent(payload, Encoding.UTF8, \"application/json\")\n        let response = client.PostAsync(\"/api/message\", body).Result\n        let content = response.Content.ReadAsStringAsync().Result\n        Assert.Equal(\"\"\"{\"Message\":\"Hello /api/message\",\"Extra\":\"ignored\"}\"\"\", content)\n\n    [<Fact>]\n    let ``GET /json returns application/json content type`` () =\n        use client = factory.CreateClient()\n        let response = client.GetAsync(\"/json\").Result\n        let ct = response.Content.Headers.ContentType.ToString()\n        Assert.Contains(\"application/json\", ct)\n\n    [<Fact>]\n    let ``GET /hello returns application/json content type`` () =\n        use client = factory.CreateClient()\n        let response = client.GetAsync(\"/hello\").Result\n        let ct = response.Content.Headers.ContentType.ToString()\n        Assert.Contains(\"application/json\", ct)\n\n    [<Fact>]\n    let ``GET / returns text/plain content type`` () =\n        use client = factory.CreateClient()\n        let response = client.GetAsync(\"/\").Result\n        let ct = response.Content.Headers.ContentType.ToString()\n        Assert.Contains(\"text/plain\", ct)\n\nmodule Program = let [<EntryPoint>] main _ = 0\n"
  },
  {
    "path": "test/Falco.IntegrationTests.App/Falco.IntegrationTests.App.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n  </PropertyGroup>\n  <ItemGroup>\n    <PackageReference Include=\"Swashbuckle.AspNetCore\" Version=\"6.*\" />\n  </ItemGroup>\n  <ItemGroup>\n    <ProjectReference Include=\"../../src/Falco/Falco.fsproj\" />\n  </ItemGroup>\n  <ItemGroup>\n    <Compile Include=\"Program.fs\" />\n  </ItemGroup>\n  <ItemGroup>\n     <InternalsVisibleTo Include=\"Falco.IntegrationTests\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "test/Falco.IntegrationTests.App/Program.fs",
    "content": "module Falco.IntegrationTests.App\n\nopen Falco\nopen Falco.Markup\nopen Falco.Routing\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.Extensions.DependencyInjection\nopen Microsoft.Extensions.Hosting\n\ntype IGreeter =\n    abstract member Greet : name : string -> string\n\ntype FriendlyGreeter() =\n    interface IGreeter with\n        member _.Greet(name : string) =\n            $\"Hello {name} 😀\"\n\ntype Person =\n    { Name : string\n      Age : int option }\n\ntype Greeting =\n    { Message : string }\n\nlet endpoints =\n    let mapRouteData (data : RequestData) =\n        { Name = data?name.AsStringNonEmpty(\"world\")\n          Age = None }\n\n    let mapRequestData (person : Person) (data : RequestData) =\n        let person = { person with Age = data?age.AsIntOption() }\n        let message =\n            match person.Age with\n            | Some a -> $\"Hello {person.Name}, you are {a} years old!\"\n            | _ -> $\"Hello {person.Name}!\"\n        { Message = message }\n\n    [\n        get \"/\"\n            (Response.ofPlainText \"Hello World!\")\n\n        get \"/html\"\n            (Response.ofHtml\n                (Elem.html [] [\n                    Elem.head [] []\n                    Elem.body [] [ Text.h1 \"hello world\" ] ]))\n\n        get \"/json\"\n            (Response.ofJson { Message = \"hello world\" })\n\n        mapGet \"/hello/{name?}\" mapRouteData\n            (fun person -> Request.mapQuery (mapRequestData person) Response.ofJson)\n\n        mapPost \"/hello/{name?}\" mapRouteData\n            (fun person -> Request.mapForm (mapRequestData person) Response.ofJson)\n\n        mapGet \"/plug/{name?}\"\n            (fun r -> r?name.AsStringNonEmpty(\"world\"))\n            (fun name ctx ->\n                let greeter = ctx.Plug<IGreeter>() // <-- access our dependency from the container\n                let greeting = greeter.Greet(name) // <-- invoke our greeter.Greet(name) method\n                Response.ofPlainText greeting ctx)\n\n        post \"/api/message\"\n            (Request.mapJson Response.ofJson)\n    ]\n\nlet bldr = WebApplication.CreateBuilder()\n\nbldr.Services\n    .AddSingleton<IGreeter, FriendlyGreeter>()\n|> ignore\n\nlet wapp = bldr.Build()\n\nwapp.UseHttpsRedirection()\n|> ignore\n\nwapp.UseRouting()\n    .UseFalco(endpoints)\n|> ignore\n\nwapp.Run()\n\ntype Program() = class end\n"
  },
  {
    "path": "test/Falco.Tests/Common.fs",
    "content": "﻿[<AutoOpen>]\nmodule Falco.Tests.Common\n\n#nowarn \"44\"\n\nopen System\nopen System.IO\nopen System.IO.Pipelines\nopen System.Security.Claims\nopen System.Threading.Tasks\nopen FsUnit.Xunit\nopen Microsoft.AspNetCore.Antiforgery\nopen Microsoft.AspNetCore.Authentication\nopen Microsoft.AspNetCore.Authentication.Cookies\nopen Microsoft.AspNetCore.Http\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.Extensions.DependencyInjection\nopen Microsoft.Net.Http.Headers\nopen NSubstitute\nopen System.Collections.Generic\n\nlet shouldBeSome pred (option : Option<'a>) =\n    match option with\n    | Some o -> pred o\n    | None   -> sprintf \"Should not be None\" |> should equal false\n\nlet shouldBeNone (option : Option<'a>) =\n    match option with\n    | Some o -> sprintf \"Should not be Some\" |> should equal false\n    | None   -> ()\n\n[<CLIMutable>]\ntype FakeRecord = { Name : string }\n\nlet getResponseBody (ctx : HttpContext) =\n    task {\n        ctx.Response.Body.Position <- 0L\n        use reader = new StreamReader(ctx.Response.Body)\n        return! reader.ReadToEndAsync()\n    }\n\n[<Literal>]\nlet AuthScheme = \"Testing\"\n\nlet CookieScheme = CookieAuthenticationDefaults.AuthenticationScheme\n\nlet AuthRoles = [\"admin\"; \"user\"]\n\nlet getHttpContextWriteable (authenticated : bool) =\n    let ctx = Substitute.For<HttpContext>()\n\n    let req = Substitute.For<HttpRequest>()\n    req.Headers.Returns(Substitute.For<HeaderDictionary>()) |> ignore\n    req.RouteValues.Returns(Substitute.For<RouteValueDictionary>()) |> ignore\n\n    let resp = Substitute.For<HttpResponse>()\n    resp.Headers.Returns(Substitute.For<HeaderDictionary>()) |> ignore\n\n    let respBody = new MemoryStream()\n    resp.BodyWriter.Returns(PipeWriter.Create respBody) |> ignore\n    resp.Body <- respBody\n\n    let antiforgery = Substitute.For<IAntiforgery>()\n    antiforgery.GetAndStoreTokens(Arg.Any<HttpContext>()).Returns(\n        AntiforgeryTokenSet(\"requestToken\", \"cookieToken\", \"formFieldName\", \"headerName\")\n    ) |> ignore\n    antiforgery.IsRequestValidAsync(ctx).Returns(Task.FromResult(true)) |> ignore\n\n    let authService = Substitute.For<IAuthenticationService>()\n\n    let claims = AuthRoles |> List.map (fun role -> Claim(ClaimTypes.Role, role))\n    let identity = ClaimsIdentity(claims, AuthScheme)\n    let principal = ClaimsPrincipal identity\n    let authResult =\n        if authenticated\n        then AuthenticateResult.Success(AuthenticationTicket(principal, AuthScheme))\n        else AuthenticateResult.NoResult()\n\n    authService.AuthenticateAsync(Arg.Any<HttpContext>(), Arg.Any<string>())\n        .Returns(Task.FromResult(authResult)) |> ignore\n\n    authService.SignInAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<ClaimsPrincipal>(), Arg.Any<AuthenticationProperties>())\n        .Returns(Task.CompletedTask) |> ignore\n\n    authService.SignOutAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<AuthenticationProperties>())\n        .Returns(Task.CompletedTask) |> ignore\n\n    authService.ChallengeAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<AuthenticationProperties>())\n        .Returns(fun args ->\n            let ctx = args.Arg<HttpContext>()\n            let scheme = args.Arg<string>()\n            Task.CompletedTask\n        ) |> ignore\n\n    let serviceCollection = ServiceCollection()\n\n    serviceCollection\n        .AddLogging()\n        .AddAuthorization()\n        .AddSingleton<IAuthenticationService>(authService)\n        .AddSingleton<IAntiforgery>(antiforgery)\n        |> ignore\n\n    if authenticated then\n        serviceCollection\n            .AddAuthentication(AuthScheme)\n            .AddCookie(CookieScheme)\n    else\n        serviceCollection\n            .AddAuthentication(AuthScheme)\n            .AddCookie(CookieScheme)\n    |> ignore\n\n    let provider = serviceCollection.BuildServiceProvider()\n\n    ctx.Request.Returns req |> ignore\n    ctx.Response.Returns resp |> ignore\n    ctx.RequestServices\n        .GetService(Arg.Any<Type>())\n        .Returns(fun args ->\n            let serviceType = args.Arg<Type>()\n            provider.GetService(serviceType)\n        ) |> ignore\n\n    ctx\n\nlet cookieCollection cookies =\n  { new IRequestCookieCollection with\n    member __.ContainsKey(key: string) = Map.containsKey key cookies\n    member __.Count = Map.count cookies\n    member __.GetEnumerator() = (Map.toSeq cookies |> Seq.map KeyValuePair).GetEnumerator()\n    member __.GetEnumerator() = __.GetEnumerator() :> Collections.IEnumerator\n    member __.Item with get (key: string): string = Map.find key cookies\n    member __.Keys = Map.toSeq cookies |> Seq.map fst |> ResizeArray :> Collections.Generic.ICollection<string>\n    member __.TryGetValue(key: string, value: byref<string>): bool =\n      match Map.tryFind key cookies with\n      | Some _ -> true\n      | _ -> false }\n"
  },
  {
    "path": "test/Falco.Tests/Falco.Tests.fsproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk\">\n  <PropertyGroup>\n    <TargetFramework>net10.0</TargetFramework>\n    <IsPackable>false</IsPackable>\n    <GenerateProgramFile>false</GenerateProgramFile>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Compile Include=\"Common.fs\" />\n    <Compile Include=\"StringTests.fs\" />\n    <Compile Include=\"RequestValueTests.fs\" />\n    <Compile Include=\"RequestDataTests.fs\" />\n    <Compile Include=\"MultipartTests.fs\" />\n    <Compile Include=\"SecurityTests.fs\" />\n    <Compile Include=\"RequestTests.fs\" />\n    <Compile Include=\"ResponseTests.fs\" />\n    <Compile Include=\"RoutingTests.fs\" />\n    <Compile Include=\"WebApplicationTests.fs\" />\n    <Compile Include=\"Program.fs\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"FsUnit.Xunit\" Version=\"6.*\" />\n    <PackageReference Include=\"Microsoft.NET.Test.Sdk\" Version=\"17.*\" />\n    <PackageReference Include=\"NSubstitute\" Version=\"5.*\" />\n    <PackageReference Include=\"xunit\" Version=\"2.*\" />\n    <PackageReference Include=\"xunit.runner.visualstudio\" Version=\"2.*\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n    <PackageReference Include=\"coverlet.collector\" Version=\"6.*\">\n      <PrivateAssets>all</PrivateAssets>\n      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>\n    </PackageReference>\n  </ItemGroup>\n\n  <ItemGroup>\n    <ProjectReference Include=\"..\\..\\src\\Falco\\Falco.fsproj\" />\n  </ItemGroup>\n</Project>\n"
  },
  {
    "path": "test/Falco.Tests/MultipartTests.fs",
    "content": "module Falco.Tests.Multipart\n\nopen System\nopen System.IO\nopen System.Text\nopen System.Threading\nopen System.Threading.Tasks\nopen Falco\nopen Falco.Multipart\nopen FsUnit.Xunit\nopen Xunit\nopen Microsoft.AspNetCore.WebUtilities\nopen Microsoft.Extensions.Primitives\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync()`` () =\n    let onePartBody =\n        \"--9051914041544843365972754266\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"name\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"falco\\r\\n\" +\n        \"--9051914041544843365972754266--\\r\\n\";\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(onePartBody))\n\n    let rd = new MultipartReader(\"9051914041544843365972754266\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize) // 10mb max file size\n        form.Files.Count |> should equal 0\n\n        let formData = FormData(RequestValue.parseForm(form, None), Some form.Files)\n        let requestValue = formData?name.AsString()\n        requestValue |> should equal \"falco\"\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() with 3-part body`` () =\n    let threePartBody =\n            \"--9051914041544843365972754266\\r\\n\" +\n            \"Content-Disposition: form-data; name=\\\"name\\\"\\r\\n\" +\n            \"\\r\\n\" +\n            \"falco\\r\\n\" +\n            \"--9051914041544843365972754266\\r\\n\" +\n            \"Content-Disposition: form-data; name=\\\"file1\\\"; filename=\\\"a.txt\\\"\\r\\n\" +\n            \"Content-Type: text/plain\\r\\n\" +\n            \"\\r\\n\" +\n            \"Content of a.txt.\\r\\n\" +\n            \"\\r\\n\" +\n            \"--9051914041544843365972754266\\r\\n\" +\n            \"Content-Disposition: form-data; name=\\\"file2\\\"; filename=\\\"a.html\\\"\\r\\n\" +\n            \"Content-Type: text/html\\r\\n\" +\n            \"\\r\\n\" +\n            \"<!DOCTYPE html><title>Content of a.html.</title>\\r\\n\" +\n            \"\\r\\n\" +\n            \"--9051914041544843365972754266--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(threePartBody))\n\n    let rd = new MultipartReader(\"9051914041544843365972754266\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize) // 10mb max file size\n        form.Files.Count |> should equal 2\n\n        // can we access the files?\n        use ms = new MemoryStream()\n        use st1 = form.Files.[0].OpenReadStream()\n        st1.CopyTo(ms)\n\n        ms.SetLength(0)\n        use st2 = form.Files.[1].OpenReadStream()\n        st1.CopyTo(ms)\n\n        let formData = FormData(RequestValue.parseForm(form, None), Some form.Files)\n        let requestValue = formData?name.AsString()\n        requestValue |> should equal \"falco\"\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should reject file exceeding max size`` () =\n    let largeFileBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"large.txt\\\"\\r\\n\" +\n        \"Content-Type: text/plain\\r\\n\" +\n        \"\\r\\n\" +\n        (String.replicate (11 * 1024 * 1024) \"x\") + \"\\r\\n\" +  // Actually 11MB\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(largeFileBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let maxSize = 10L * 1024L * 1024L  // 10MB max\n\n        // Should throw InvalidOperationException\n        let! ex = Assert.ThrowsAsync<InvalidOperationException>(\n            fun () -> rd.StreamSectionsAsync(tokenSource.Token, maxSize) :> Task)\n\n        ex.Message.Contains(\"exceeds maximum size\") |> should equal true\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should handle empty form`` () =\n    let emptyBody = \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(emptyBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        form.Count |> should equal 0\n        form.Files.Count |> should equal 0\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should handle multiple form fields`` () =\n    let multiFieldBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"field1\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"value1\\r\\n\" +\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"field2\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"value2\\r\\n\" +\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"field3\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"value3\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(multiFieldBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        form.Count |> should equal 3\n        form[\"field1\"] |> should equal (StringValues(\"value1\"))\n        form[\"field2\"] |> should equal (StringValues(\"value2\"))\n        form[\"field3\"] |> should equal (StringValues(\"value3\"))\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should handle duplicate field names`` () =\n    let duplicateBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"tags\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"tag1\\r\\n\" +\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"tags\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"tag2\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(duplicateBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        form[\"tags\"].Count |> should equal 2\n        form[\"tags\"].[0] |> should equal \"tag1\"\n        form[\"tags\"].[1] |> should equal \"tag2\"\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should handle mixed files and fields`` () =\n    let mixedBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"username\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"john_doe\\r\\n\" +\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"avatar\\\"; filename=\\\"avatar.png\\\"\\r\\n\" +\n        \"Content-Type: image/png\\r\\n\" +\n        \"\\r\\n\" +\n        \"PNG_DATA_HERE\\r\\n\" +\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"bio\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"A short bio\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(mixedBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        form.Count |> should equal 2  // username, bio\n        form.Files.Count |> should equal 1\n        form[\"username\"] |> should equal (StringValues(\"john_doe\"))\n        form[\"bio\"] |> should equal (StringValues(\"A short bio\"))\n        form.Files[0].FileName |> should equal \"avatar.png\"\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should preserve file content type`` () =\n    let fileBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"document\\\"; filename=\\\"doc.json\\\"\\r\\n\" +\n        \"Content-Type: application/json\\r\\n\" +\n        \"\\r\\n\" +\n        \"{\\\"key\\\":\\\"value\\\"}\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(fileBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        form.Files.Count |> should equal 1\n        form.Files[0].ContentType |> should equal \"application/json\"\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should skip sections with missing name`` () =\n    let malformedBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; filename=\\\"file.txt\\\"\\r\\n\" +  // Missing name\n        \"Content-Type: text/plain\\r\\n\" +\n        \"\\r\\n\" +\n        \"content\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(malformedBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        // Should be skipped entirely\n        form.Count |> should equal 0\n        form.Files.Count |> should equal 0\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() should skip file sections with missing filename`` () =\n    let malformedBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"file\\\"\\r\\n\" +  // Missing filename\n        \"Content-Type: text/plain\\r\\n\" +\n        \"\\r\\n\" +\n        \"content\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(malformedBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, DefaultMaxSize)\n\n        // Should be skipped (no filename = not a file)\n        form.Files.Count |> should equal 0\n    }\n\n[<Fact>]\nlet ``MultipartReader.StreamSectionsAsync() respects custom maxFileSize parameter`` () =\n    let fileBody =\n        \"--boundary\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"test.txt\\\"\\r\\n\" +\n        \"Content-Type: text/plain\\r\\n\" +\n        \"\\r\\n\" +\n        \"small content\\r\\n\" +\n        \"--boundary--\\r\\n\"\n\n    use body = new MemoryStream(Encoding.UTF8.GetBytes(fileBody))\n    let rd = new MultipartReader(\"boundary\", body)\n\n    task {\n        use tokenSource = new CancellationTokenSource()\n        let! form = rd.StreamSectionsAsync(tokenSource.Token, 1024L)  // 1KB max\n\n        form.Files.Count |> should equal 1\n        form.Files[0].Length |> should lessThan 1024L\n    }\n"
  },
  {
    "path": "test/Falco.Tests/Program.fs",
    "content": "module Program = let [<EntryPoint>] main _ = 0\n"
  },
  {
    "path": "test/Falco.Tests/RequestDataTests.fs",
    "content": "module Falco.Tests.RequestData\n\nopen System\nopen System.Globalization\nopen System.IO\nopen System.Threading\nopen System.Threading.Tasks\nopen Falco\nopen FsUnit.Xunit\nopen Xunit\nopen Microsoft.AspNetCore.Http\n\n[<Fact>]\nlet ``RequestData extensions can convert RString primitives`` () =\n    let requestData = RequestData(RObject [\n        \"lowercase-on\", RString \"on\"\n        \"uppercase-on\", RString \"ON\"\n        \"pascalcase-on\", RString \"On\"\n\n        \"lowercase-yes\", RString \"yes\"\n        \"uppercase-yes\", RString \"YES\"\n        \"pascalcase-yes\", RString \"Yes\"\n\n        \"lowercase-off\", RString \"off\"\n        \"uppercase-off\", RString \"OFF\"\n        \"pascalcase-off\", RString \"Off\"\n\n        \"lowercase-no\", RString \"no\"\n        \"uppercase-no\", RString \"NO\"\n        \"pascalcase-no\", RString \"No\"\n\n        \"leadingzero\", RString \"012345\" ])\n\n    requestData.GetBoolean \"lowercase-on\" |> should equal true\n    requestData.GetBoolean \"uppercase-on\" |> should equal true\n    requestData.GetBoolean \"pascalcase-on\" |> should equal true\n    requestData.GetBoolean \"lowercase-yes\" |> should equal true\n    requestData.GetBoolean \"uppercase-yes\" |> should equal true\n    requestData.GetBoolean \"pascalcase-yes\" |> should equal true\n    requestData.GetBoolean \"lowercase-off\" |> should equal false\n    requestData.GetBoolean \"uppercase-off\" |> should equal false\n    requestData.GetBoolean \"pascalcase-off\" |> should equal false\n    requestData.GetBoolean \"lowercase-no\" |> should equal false\n    requestData.GetBoolean \"uppercase-no\" |> should equal false\n    requestData.GetBoolean \"pascalcase-no\" |> should equal false\n    requestData.GetInt \"leadingzero\" |> should equal 12345\n    requestData.GetFloat \"leadingzero\" |> should equal 12345.\n\n\ntype City = { Name : string; YearFounded : int option }\ntype CityResult = { Count : int; Results : City list }\ntype Weather = { Season : string; Temperature : float; Effects : string list; Cities : CityResult }\n\n[<Fact>]\nlet ``RequestData extensions should work`` () =\n    let expected =\n        { Season = \"summer\"\n          Temperature = 23.5\n          Effects = [ \"overcast\"; \"wind gusts\" ]\n          Cities = {\n            Count = 2\n            Results = [ { Name = \"Toronto\"; YearFounded = Some 123 }; { Name = \"Tokyo\"; YearFounded = None } ] } }\n\n    let requestValue = RObject [\n        \"season\", RString \"summer\"\n        \"temperature\", RNumber 23.5\n        \"effects\", RList [ RString \"overcast\"; RString \"wind gusts\"]\n        \"cities\", RObject [\n            \"count\", RNumber 2\n            \"results\", RList [\n                RObject [ \"name\", RString \"Toronto\"; \"year_founded\", RNumber 123 ]\n                RObject [ \"name\", RString \"Tokyo\" ] ] ] ]\n\n    let r = RequestData(requestValue)\n\n    { Season = r?season.AsString()\n      Temperature = r.GetFloat \"temperature\"\n      Effects = [\n        for e in r?effects.AsList() do\n            e.AsString() ]\n      Cities = {\n        Count = r?cities?count.AsInt()\n        Results = [\n            for c in r?cities?results.AsList() do\n                { Name = c?name.AsString()\n                  YearFounded = c?year_founded.AsIntOption() } ] } }\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestData value lookups are case-insensitive`` () =\n    let values =\n        dict [\n            \"FString\", seq {\"John Doe\"; \"Jane Doe\" }\n        ]\n    let scr = RequestData(values)\n\n    // single values\n    scr.GetString \"FSTRING\" |> should equal \"John Doe\"\n    scr.GetString \"FString\" |> should equal \"John Doe\"\n    scr.GetString \"fstriNG\" |> should equal \"John Doe\"\n\n    // arrays\n    scr.GetStringList \"FSTRING\" |> should equal [\"John Doe\";\"Jane Doe\"]\n    scr.GetStringList \"fString\" |> should equal [\"John Doe\";\"Jane Doe\"]\n    scr.GetStringList \"fstriNG\" |> should equal [\"John Doe\";\"Jane Doe\"]\n\n[<Fact>]\nlet ``RequestData collection should resolve primitives`` () =\n    let dt = DateTime(1986, 12, 12)\n    let dtStr = dt.ToString(\"o\")\n    let dtOffsetStr = DateTimeOffset(dt).ToString(\"o\")\n    let timespanStr = TimeSpan.FromSeconds(1.0).ToString()\n    let guidStr = Guid.NewGuid().ToString()\n\n    let values =\n        dict [\n            \"emptystring\", seq { \"\" }\n            \"fstring\", seq { \"John Doe\"; \"\";\"\"; \"Jane Doe\";\"\" }\n            \"fint16\", seq { \"16\";\"\";\"17\" }\n            \"fint32\", seq { \"32\";\"\";\"\";\"\";\"\";\"33\" }\n            \"fint64\", seq { \"64\";\"65\";\"\";\"\" }\n            \"fbool\", seq { \"true\";\"false\" }\n            \"ffloat\", seq { \"1.234\";\"1.235\" }\n            \"fdecimal\", seq { \"4.567\";\"4.568\" }\n            \"fdatetime\", seq { dtStr }\n            \"fdatetimeoffset\", seq { dtOffsetStr }\n            \"ftimespan\", seq { timespanStr }\n            \"fguid\", seq { guidStr }\n        ]\n\n    let scr = RequestData(values)\n\n    // single values\n    scr.GetString \"_fstring\"                |> should equal \"\"\n    scr.GetString \"fstring\"                 |> should equal \"John Doe\"\n    scr.GetStringNonEmpty \"fstring\"         |> should equal \"John Doe\"\n    scr.GetInt16 \"fint16\"                   |> should equal 16s\n    scr.GetInt32 \"fint32\"                   |> should equal 32\n    scr.GetInt \"fint32\"                     |> should equal 32\n    scr.GetInt64 \"fint64\"                   |> should equal 64L\n    scr.GetBoolean \"fbool\"                  |> should equal true\n    scr.GetFloat \"ffloat\"                   |> should equal 1.234\n    scr.GetDecimal \"fdecimal\"               |> should equal 4.567M\n    scr.GetDateTime \"fdatetime\"             |> should equal (DateTime.Parse(dtStr, null, DateTimeStyles.RoundtripKind))\n    // TODO uncomment this when DateTimeOffset is supported properly on linux distros, see https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-round-trip-date-and-time-values\n    // scr.GetDateTimeOffset \"fdatetimeoffset\" |> should equal (DateTimeOffset.Parse(dtOffsetStr, null, DateTimeStyles.RoundtripKind))\n    scr.GetTimeSpan \"ftimespan\"             |> should equal (TimeSpan.Parse(timespanStr))\n    scr.GetGuid \"fguid\"                     |> should equal (Guid.Parse(guidStr))\n\n    // default values\n    scr.GetString(\"_fstring\", \"default_value\")                         |> should equal \"default_value\"\n    scr.GetStringNonEmpty(\"_fstring\", \"default_value\")                 |> should equal \"default_value\"\n    scr.GetInt16(\"_fint16\", -1s)                                       |> should equal -1s\n    scr.GetInt32(\"_fint32\", -1)                                        |> should equal -1\n    scr.GetInt(\"_fint32\", -1)                                          |> should equal -1\n    scr.GetInt64(\"_fint64\", 1L)                                        |> should equal 1L\n    scr.GetBoolean(\"_fbool\", false)                                    |> should equal false\n    scr.GetFloat(\"_ffloat\", 0.0)                                       |> should equal 0.0\n    scr.GetDecimal(\"_fdecimal\", 0.0M)                                  |> should equal 0.0M\n    scr.GetDateTime(\"_fdatetime\", DateTime.MinValue)                   |> should equal DateTime.MinValue\n    scr.GetDateTimeOffset(\"_fdatetimeoffset\", DateTimeOffset.MinValue) |> should equal DateTimeOffset.MinValue\n    scr.GetTimeSpan(\"_ftimespan\", TimeSpan.MinValue)                   |> should equal TimeSpan.MinValue\n    scr.GetGuid(\"_fguid\", Guid.Empty)                                  |> should equal Guid.Empty\n\n    // array values\n    scr.GetStringList \"_fstring\"                |> List.isEmpty |> should equal true\n    scr.GetStringList \"fstriNg\"                 |> should equal [\"John Doe\"; \"\"; \"\"; \"Jane Doe\"; \"\"]\n    scr.GetStringNonEmptyList \"fstring\"         |> should equal [\"John Doe\";\"Jane Doe\"]\n    scr.GetInt16List \"fint16\"                   |> should equal [16s;17s]\n    scr.GetInt32List \"fint32\"                   |> should equal [32;33]\n    scr.GetIntList \"fint32\"                     |> should equal [32;33]\n    scr.GetInt64List \"fint64\"                   |> should equal [64L;65L]\n    scr.GetBooleanList \"fbool\"                  |> should equal [true;false]\n    scr.GetFloatList \"ffloat\"                   |> should equal [1.234;1.235]\n    scr.GetDecimalList \"fdecimal\"               |> should equal [4.567M;4.568M]\n    scr.GetDateTimeList \"fdatetime\"             |> should equal [DateTime.Parse(dtStr, null, DateTimeStyles.RoundtripKind)]\n    // TODO uncomment this when DateTimeOffset is supported properly on linux distros, see https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-round-trip-date-and-time-values\n    // scr.GetDateTimeOffsetList \"fdatetimeoffset\" |> should equal [DateTimeOffset.Parse(dtOffsetStr, null, DateTimeStyles.RoundtripKind)]\n    scr.GetTimeSpanList \"ftimespan\"             |> should equal [TimeSpan.Parse(timespanStr)]\n    scr.GetGuidList \"fguid\"                     |> should equal [Guid.Parse(guidStr)]\n\n[<Fact>]\nlet ``RequestData Empty should return defaults and empty collections`` () =\n    let r = RequestData.Empty\n    r.AsString() |> should equal \"\"\n    r.AsStringOption() |> should equal (Some \"\")\n    r.AsIntOption() |> should equal None\n    r.AsStringList() |> Seq.length |> should equal 0\n    r.AsList() |> Seq.length |> should equal 0\n    r.AsKeyValues() |> Seq.length |> should equal 0\n\n[<Fact>]\nlet ``RequestData should allow list access on non-list primitives`` () =\n    let r1 = RequestData(RString \"hello\")\n    let r2 = RequestData(RString \"123\")\n    r1.AsStringList() |> should equal [\"hello\"]\n    r2.AsInt32List() |> should equal [123]\n\n[<Fact>]\nlet ``RequestData nested lookup should be case-insensitive`` () =\n    let r =\n        RequestData(\n            RObject [\n                \"Foo\", RObject [\n                    \"Bar\", RString \"baz\"\n                ]\n            ])\n    r?foo?bar.AsString() |> should equal \"baz\"\n    r?FOO?BAR.AsString() |> should equal \"baz\"\n\n[<Fact>]\nlet ``RequestData missing nested key should yield None for options`` () =\n    let r = RequestData(RObject [ \"foo\", RObject [] ])\n    r?foo?missing.AsIntOption() |> should equal None\n    r?foo?missing.AsGuidOption() |> should equal None\n\n[<Fact>]\nlet ``RequestData should parse boolean from numeric`` () =\n    RequestData(RNumber 0.).AsBoolean() |> should equal false\n    RequestData(RNumber 1.).AsBoolean() |> should equal true\n\n[<Fact>]\nlet ``RequestData should parse DateTime from epoch milliseconds`` () =\n    let r = RequestData(RNumber 0.)\n    r.AsDateTime() |> should equal (DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc))\n\n[<Fact>]\nlet ``RequestData should return None for invalid Guid and TimeSpan`` () =\n    let r1 = RequestData(RString \"not-a-guid\")\n    let r2 = RequestData(RString \"not-a-timespan\")\n    r1.AsGuidOption() |> should equal None\n    r2.AsTimeSpanOption() |> should equal None\n\n[<Fact>]\nlet ``Can make FormData from IFormCollection`` () =\n    FormData(RequestValue.RNull, Some (FormFileCollection() :> IFormFileCollection))\n    |> should not' throw\n\n[<Fact>]\nlet ``Can safely get IFormFile from IFormCollection`` () =\n    let formFileName = \"abc.txt\"\n\n    let emptyFormData = FormData(RequestValue.RNull, Some (FormFileCollection() :> IFormFileCollection))\n    emptyFormData.TryGetFile formFileName\n    |> shouldBeNone\n\n    let formFile =\n        { new IFormFile with\n            member _.ContentDisposition = String.Empty\n            member _.ContentType = String.Empty\n            member _.FileName = String.Empty\n            member _.Headers = HeaderDictionary()\n            member _.Length = Int64.MinValue\n            member _.Name = formFileName\n            member _.CopyTo (target: Stream) : unit = ()\n            member _.CopyToAsync (target: Stream, cancellationToken: CancellationToken) : Task = Task.CompletedTask\n            member _.OpenReadStream  () :  Stream = System.IO.Stream.Null }\n\n    let formFileCollection = FormFileCollection()\n    formFileCollection.Add(formFile)\n    let formFileData = new FormData(RequestValue.RNull, Some(formFileCollection))\n\n    formFileData.TryGetFile formFileName\n    |> shouldBeSome (fun _ ->  ())\n\n[<Fact>]\nlet ``FormData TryGetFile should return None for null or whitespace name`` () =\n    let fd = FormData(RequestValue.RNull, Some (FormFileCollection() :> IFormFileCollection))\n    fd.TryGetFile null |> shouldBeNone\n    fd.TryGetFile \"\" |> shouldBeNone\n    fd.TryGetFile \"   \" |> shouldBeNone\n\n// ...existing code...\n\n[<Fact>]\nlet ``RequestData RNull conversions should return defaults`` () =\n    let r = RequestData(RNull)\n    r.AsString() |> should equal \"\"\n    r.AsInt32() |> should equal 0\n    r.AsBoolean() |> should equal false\n    r.AsFloat() |> should equal 0.\n    r.AsDecimal() |> should equal 0M\n    r.AsGuid() |> should equal Guid.Empty\n    r.AsDateTime() |> should equal DateTime.MinValue\n    r.AsTimeSpan() |> should equal TimeSpan.MinValue\n\n[<Fact>]\nlet ``RequestData RBool conversions to string`` () =\n    let rTrue = RequestData(RBool true)\n    let rFalse = RequestData(RBool false)\n    rTrue.AsString() |> should equal \"true\"\n    rFalse.AsString() |> should equal \"false\"\n\n[<Fact>]\nlet ``RequestData RNumber conversions to string`` () =\n    let r = RequestData(RNumber 42.5)\n    r.AsString() |> should equal \"42.5\"\n\n[<Fact>]\nlet ``RequestData TryGet methods return None on invalid conversions`` () =\n    let r = RequestData(RString \"not-a-number\")\n    r.TryGetInt32(\"value\") |> should equal None\n    r.TryGetBoolean(\"value\") |> should equal None\n    r.TryGetGuid(\"value\") |> should equal None\n\n[<Fact>]\nlet ``RequestData empty string should convert to RNull equivalent`` () =\n    let r = RequestData(RString \"\")\n    r.AsString() |> should equal \"\"\n    r.AsStringNonEmpty() |> should equal \"\"\n\n[<Fact>]\nlet ``RequestData whitespace string should convert to empty for AsString`` () =\n    let r = RequestData(RString \"   \")\n    r.AsString() |> should equal \"   \"  // preserves whitespace\n    r.AsStringNonEmpty() |> should equal \"   \"  // preserves whitespace\n\n[<Fact>]\nlet ``RequestData RObject accessed as primitive should return defaults`` () =\n    let r = RequestData(RObject [ \"key\", RString \"value\" ])\n    r.AsString() |> should equal \"\"\n    r.AsInt32() |> should equal 0\n    r.AsBoolean() |> should equal false\n\n[<Fact>]\nlet ``RequestData negative numbers in lists`` () =\n    let r = RequestData(RList [ RNumber -5.; RNumber -10.; RNumber -15. ])\n    r.AsInt32List() |> should equal [-5; -10; -15]\n\n[<Fact>]\nlet ``RequestData very large numbers should overflow gracefully`` () =\n    let r = RequestData(RNumber (float System.Int32.MaxValue + 1000.))\n    r.AsInt32Option() |> should equal None  // out of range\n    r.AsInt64Option() |> should not' (equal None)  // should fit in Int64\n\n[<Fact>]\nlet ``RequestData float precision edge cases`` () =\n    let r1 = RequestData(RNumber Double.NaN)\n    let r2 = RequestData(RNumber Double.PositiveInfinity)\n    let r3 = RequestData(RNumber Double.NegativeInfinity)\n    r1.AsFloat() |> Double.IsNaN |> should equal true\n    r2.AsFloat() |> should equal Double.PositiveInfinity\n    r3.AsFloat() |> should equal Double.NegativeInfinity\n\n[<Fact>]\nlet ``RequestData empty list`` () =\n    let r = RequestData(RList [])\n    r.AsStringList() |> Seq.length |> should equal 0\n    r.AsInt32List() |> Seq.length |> should equal 0\n    r.AsList() |> Seq.length |> should equal 0\n\n[<Fact>]\nlet ``RequestData list with mixed null and valid values`` () =\n    let r = RequestData(RList [ RString \"hello\"; RNull; RString \"world\" ])\n    r.AsStringList() |> should equal [\"hello\"; \"\"; \"world\"]\n\n[<Fact>]\nlet ``RequestData single-item list`` () =\n    let r = RequestData(RList [ RNumber 42. ])\n    r.AsInt32List() |> should equal [42]\n\n[<Fact>]\nlet ``RequestData very large list`` () =\n    let large = List.init 1000 (fun i -> RNumber (float i))\n    let r = RequestData(RList large)\n    r.AsInt32List() |> List.length |> should equal 1000\n    r.AsInt32List() |> List.head |> should equal 0\n    r.AsInt32List() |> List.last |> should equal 999\n\n[<Fact>]\nlet ``RequestData deeply nested objects`` () =\n    let r = RequestData(\n        RObject [\n            \"level1\", RObject [\n                \"level2\", RObject [\n                    \"level3\", RString \"deep\"\n                ]\n            ]\n        ])\n    r?level1?level2?level3.AsString() |> should equal \"deep\"\n\n[<Fact>]\nlet ``RequestData RNumber 0 and 1 as boolean`` () =\n    let r0 = RequestData(RNumber 0.)\n    let r1 = RequestData(RNumber 1.)\n    r0.AsBoolean() |> should equal false\n    r1.AsBoolean() |> should equal true\n\n[<Fact>]\nlet ``RequestData RNumber other than 0/1 as boolean should return None`` () =\n    let r = RequestData(RNumber 2.)\n    r.AsBooleanOption() |> should equal None\n\n[<Fact>]\nlet ``RequestData invalid Guid string`` () =\n    let r = RequestData(RString \"not-a-valid-guid\")\n    r.AsGuidOption() |> should equal None\n    r.AsGuid() |> should equal Guid.Empty\n\n[<Fact>]\nlet ``RequestData invalid TimeSpan string`` () =\n    let r = RequestData(RString \"not-a-timespan\")\n    r.AsTimeSpanOption() |> should equal None\n    r.AsTimeSpan() |> should equal TimeSpan.MinValue\n\n[<Fact>]\nlet ``RequestData Get vs TryGet difference`` () =\n    let r = RequestData(RObject [ \"name\", RString \"John\" ])\n    let getResult = r.Get \"missing\"\n    let tryResult = r.TryGet \"missing\"\n    getResult.AsString() |> should equal \"\"  // Get returns Empty\n    tryResult |> should equal None  // TryGet returns None\n\n[<Fact>]\nlet ``RequestData custom default values`` () =\n    let r = RequestData(RString \"invalid\")\n    r.AsInt32(defaultValue = 999) |> should equal 999\n    r.AsString(defaultValue = \"default\") |> should equal \"invalid\"\n    r.AsBoolean(defaultValue = true) |> should equal true\n"
  },
  {
    "path": "test/Falco.Tests/RequestTests.fs",
    "content": "﻿module Falco.Tests.Request\n\nopen System\nopen System.Collections.Generic\nopen System.IO\nopen System.Text\nopen System.Text.Json\nopen System.Text.Json.Serialization\nopen System.Threading\nopen Falco\nopen FsUnit.Xunit\nopen NSubstitute\nopen Xunit\nopen Microsoft.AspNetCore.Authentication\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.Net.Http.Headers\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Primitives\n\n[<Fact>]\nlet ``Request.getVerb should return HttpVerb from HttpContext`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Method <- \"GET\"\n\n    Request.getVerb ctx\n    |> should equal GET\n\n[<Fact>]\nlet ``Request.getVerb should handle all HTTP methods`` () =\n    let ctx = getHttpContextWriteable false\n\n    ctx.Request.Method <- \"POST\"\n    Request.getVerb ctx |> should equal POST\n\n    ctx.Request.Method <- \"PUT\"\n    Request.getVerb ctx |> should equal PUT\n\n    ctx.Request.Method <- \"PATCH\"\n    Request.getVerb ctx |> should equal PATCH\n\n    ctx.Request.Method <- \"DELETE\"\n    Request.getVerb ctx |> should equal DELETE\n\n    ctx.Request.Method <- \"OPTIONS\"\n    Request.getVerb ctx |> should equal OPTIONS\n\n[<Fact>]\nlet ``Request.getVerb should return ANY for unknown methods`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Method <- \"CUSTOM\"\n\n    Request.getVerb ctx |> should equal ANY\n\n[<Fact>]\nlet ``Request.bodyString handler should provide body string`` () =\n    let ctx = getHttpContextWriteable false\n    let bodyContent = \"test content\"\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes(bodyContent))\n    ctx.Request.Body <- ms\n\n    let handle body : HttpHandler =\n        body |> should equal bodyContent\n        Response.ofEmpty\n\n    Request.bodyString handle ctx\n\n[<Fact>]\nlet ``Request.getBodyString should read request body as string`` () =\n    let ctx = getHttpContextWriteable false\n    let bodyContent = \"Hello, World!\"\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes(bodyContent))\n    ctx.Request.Body <- ms\n\n    task {\n        let! body = Request.getBodyString ctx\n        body |> should equal bodyContent\n    }\n\n[<Fact>]\nlet ``Request.getBodyStringOptions should enforce max size limit`` () =\n    let ctx = getHttpContextWriteable false\n    let largeContent = String.replicate (11 * 1024 * 1024) \"x\"\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes(largeContent))\n    ctx.Request.Body <- ms\n\n    task {\n        let maxSize = 10L * 1024L * 1024L\n        let! ex = Assert.ThrowsAsync<InvalidOperationException>(\n            fun () -> Request.getBodyStringOptions maxSize ctx)\n\n        ex.Message.Contains \"exceeds maximum size\" |> should equal true\n    }\n\n[<Fact>]\nlet ``Request.getBodyString should handle empty body`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream()\n    ctx.Request.Body <- ms\n\n    task {\n        let! body = Request.getBodyString ctx\n        body |> should equal \"\"\n    }\n\n[<Fact>]\nlet ``Request.getCookies`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Cookies <- Map.ofList [\"name\", \"falco\"] |> cookieCollection\n\n    let cookies= Request.getCookies ctx\n    cookies?name.AsString() |> should equal \"falco\"\n\n[<Fact>]\nlet ``Request.getCookies should handle multiple values`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Cookies <-\n        Map.ofList [\"session\", \"abc123\"; \"theme\", \"dark\"]\n        |> cookieCollection\n\n    let cookies = Request.getCookies ctx\n    cookies.GetString \"session\" |> should equal \"abc123\"\n    cookies.GetString \"theme\" |> should equal \"dark\"\n\n[<Fact>]\nlet ``Request.getHeaders should work for present and missing header names`` () =\n    let serverName = \"Kestrel\"\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Headers.Add(HeaderNames.Server, StringValues(serverName))\n\n    let headers =  Request.getHeaders ctx\n\n    headers.GetString HeaderNames.Server |> should equal serverName\n    headers.TryGetString \"missing\" |> should equal None\n\n[<Fact>]\nlet ``Request.getHeaders should be case-insensitive`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Headers.Add(\"X-Custom-Header\", StringValues(\"value123\"))\n\n    let headers = Request.getHeaders ctx\n    headers.GetString \"x-custom-header\" |> should equal \"value123\"\n    headers.GetString \"X-CUSTOM-HEADER\" |> should equal \"value123\"\n\n[<Fact>]\nlet ``Request.getRouteValues should return Map<string, string> from HttpContext`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.RouteValues <- RouteValueDictionary({|name=\"falco\"|})\n\n    let route = Request.getRoute ctx\n\n    route.GetString \"name\"\n    |> should equal \"falco\"\n\n[<Fact>]\nlet ``Request.getRoute should preserve large int64 values as strings`` () =\n    // Regression test for https://github.com/falcoframework/Falco/issues/149\n    let ctx = getHttpContextWriteable false\n    ctx.Request.RouteValues <- RouteValueDictionary()\n    ctx.Request.RouteValues.Add(\"id\", \"9223372036854775807\") // Int64.MaxValue as string\n\n    let route = Request.getRoute ctx\n\n    // Should return the original string, not scientific notation\n    route.GetString \"id\"\n    |> should equal \"9223372036854775807\"\n\n    // Should also be parseable as int64\n    route.GetInt64 \"id\"\n    |> should equal 9223372036854775807L\n\n[<Fact>]\nlet ``Request.getQuery should exclude route values`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.RouteValues <- RouteValueDictionary({|id=\"123\"|})\n\n    let query = Dictionary<string, StringValues>()\n    query.Add(\"filter\", StringValues(\"active\"))\n    ctx.Request.Query <- QueryCollection(query)\n\n    let queryData = Request.getQuery ctx\n    queryData.GetString \"filter\" |> should equal \"active\"\n    queryData.TryGetString \"id\" |> should equal None\n\n[<Fact>]\nlet ``Request.getForm should handle urlencoded form data`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.ContentType <- \"application/x-www-form-urlencoded\"\n\n    let form = Dictionary<string, StringValues>()\n    form.Add(\"username\", StringValues(\"john\"))\n    form.Add(\"password\", StringValues(\"secret\"))\n    let f = FormCollection(form)\n\n    ctx.Request.ReadFormAsync().Returns(f) |> ignore\n    ctx.Request.ReadFormAsync(Arg.Any<CancellationToken>()).Returns(f) |> ignore\n\n    task {\n        let! formData = Request.getForm ctx\n        formData.GetString \"password\" |> should equal \"secret\"\n        formData.GetString \"username\" |> should equal \"john\"\n    }\n\n[<Fact>]\nlet ``Request.getForm should detect multipart form data and stream`` () =\n    let ctx = getHttpContextWriteable false\n    let body =\n        \"--9051914041544843365972754266\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"name\\\"\\r\\n\" +\n        \"\\r\\n\" +\n        \"falco\\r\\n\" +\n        \"--9051914041544843365972754266\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"file1\\\"; filename=\\\"a.txt\\\"\\r\\n\" +\n        \"Content-Type: text/plain\\r\\n\" +\n        \"\\r\\n\" +\n        \"Content of a.txt.\\r\\n\" +\n        \"\\r\\n\" +\n        \"--9051914041544843365972754266\\r\\n\" +\n        \"Content-Disposition: form-data; name=\\\"file2\\\"; filename=\\\"a.html\\\"\\r\\n\" +\n        \"Content-Type: text/html\\r\\n\" +\n        \"\\r\\n\" +\n        \"<!DOCTYPE html><title>Content of a.html.</title>\\r\\n\" +\n        \"\\r\\n\" +\n        \"--9051914041544843365972754266--\\r\\n\";\n\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes(body))\n    ctx.Request.Body.Returns(ms) |> ignore\n\n    let contentType = \"multipart/form-data;boundary=\\\"9051914041544843365972754266\\\"\"\n    ctx.Request.ContentType <- contentType\n\n    task {\n        let! formData = Request.getForm ctx\n        formData.GetString \"name\" |> should equal \"falco\"\n        formData.Files |> Option.map Seq.length |> Option.defaultValue 0 |> should equal 2\n\n        // read file 1\n        use file1Stream = formData.Files.Value.[0].OpenReadStream()\n        use reader1 = new StreamReader(file1Stream)\n        let! file1Content = reader1.ReadToEndAsync()\n        file1Content |> should equal \"Content of a.txt.\\r\\n\"\n\n        // read file 2\n        use file2Stream = formData.Files.Value.[1].OpenReadStream()\n        use reader2 = new StreamReader(file2Stream)\n        let! file2Content = reader2.ReadToEndAsync()\n        file2Content |> should equal \"<!DOCTYPE html><title>Content of a.html.</title>\\r\\n\"\n    }\n\n[<Fact>]\nlet ``Request.getJson should deserialize with case insensitive property names`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"NAME\\\":\\\"falco\\\"}\")\n    ctx.Request.ContentType <- \"application/json\"\n    ctx.Request.Body <- ms\n\n    task {\n        let! json = Request.getJson ctx\n        json.Name |> should equal \"falco\"\n    }\n\n[<Fact>]\nlet ``Request.getJson should allow trailing commas`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"name\\\":\\\"falco\\\",}\")\n    ctx.Request.ContentType <- \"application/json\"\n    ctx.Request.Body <- ms\n\n    task {\n        let! json = Request.getJson ctx\n        json.Name |> should equal \"falco\"\n    }\n\n[<Fact>]\nlet ``Request.mapJson`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"name\\\":\\\"falco\\\"}\")\n    ctx.Request.ContentType <- \"application/json\"\n    ctx.Request.ContentLength.Returns(13L) |> ignore\n    ctx.Request.Body.Returns(ms) |> ignore\n\n    let handle json : HttpHandler =\n        json.Name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapJson handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapJson should handle empty body`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream()\n    ctx.Request.ContentType <- \"application/json\"\n    ctx.Request.Body <- ms\n\n    let handle json : HttpHandler =\n        json.Name |> should equal null\n        Response.ofEmpty\n\n    task {\n        do! Request.mapJson handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapJsonOption`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"name\\\":\\\"falco\\\",\\\"age\\\":null}\")\n    ctx.Request.ContentType <- \"application/json\"\n    ctx.Request.ContentLength.Returns 22L |> ignore\n    ctx.Request.Body.Returns(ms) |> ignore\n\n    let handle json : HttpHandler =\n        json.Name |> should equal \"falco\"\n        Response.ofEmpty\n\n    let options = JsonSerializerOptions()\n    options.AllowTrailingCommas <- true\n    options.PropertyNameCaseInsensitive <- true\n    options.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull\n\n    task {\n        do! Request.mapJsonOptions options handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapJsonOptions with null value should deserialize`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"name\\\":null}\")\n    ctx.Request.Body <- ms\n\n    let handle json : HttpHandler =\n        json.Name |> should equal null\n        Response.ofEmpty\n\n    let options = JsonSerializerOptions(PropertyNameCaseInsensitive = true)\n\n    task {\n        do! Request.mapJsonOptions options handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapJson Transfer-Encoding: chunked`` () =\n    let ctx = getHttpContextWriteable false\n    use ms = new MemoryStream(Encoding.UTF8.GetBytes \"{\\\"name\\\":\\\"falco\\\"}\")\n    ctx.Request.ContentType <- \"application/json\"\n    // Simulate chunked transfer encoding\n    ctx.Request.Headers.Add(HeaderNames.TransferEncoding, \"chunked\")\n\n    ctx.Request.Body.Returns(ms) |> ignore\n\n    let handle json : HttpHandler =\n        json.Name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapJson handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapCookies`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Cookies <- Map.ofList [\"name\", \"falco\"] |> cookieCollection\n\n    let handle name : HttpHandler =\n        name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapCookies (fun r -> r.GetString \"name\") handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapHeaders`` () =\n    let serverName = \"Kestrel\"\n    let ctx = getHttpContextWriteable false\n    ctx.Request.Headers.Add(HeaderNames.Server, StringValues(serverName))\n\n    let handle server : HttpHandler =\n        server |> should equal serverName\n        Response.ofEmpty\n\n    task {\n        do! Request.mapHeaders (fun r -> r.GetString HeaderNames.Server) handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapRoute`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Request.RouteValues <- RouteValueDictionary {|name=\"falco\"|}\n\n    let handle name : HttpHandler =\n        name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapRoute (fun r -> r.GetString \"name\") handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapQuery`` () =\n    let ctx = getHttpContextWriteable false\n    let query = Dictionary<string, StringValues>()\n    query.Add(\"name\", StringValues \"falco\")\n    ctx.Request.Query <- QueryCollection query\n\n    let handle name : HttpHandler =\n        name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapQuery (fun c -> c.GetString \"name\") handle ctx\n    }\n\n[<Fact>]\nlet ``Request.mapForm`` () =\n    let ctx = getHttpContextWriteable false\n    let form = Dictionary<string, StringValues>()\n    form.Add(\"name\", StringValues \"falco\")\n    let f = FormCollection(form)\n\n    ctx.Request.ReadFormAsync().Returns(f) |> ignore\n    ctx.Request.ReadFormAsync(Arg.Any<CancellationToken>()).Returns(f) |> ignore\n\n    let handle name : HttpHandler =\n        name |> should equal \"falco\"\n        Response.ofEmpty\n\n    task {\n        do! Request.mapForm (fun f -> f?name.AsString()) handle ctx\n    }\n\n[<Fact>]\nlet ``Request.authenticate should call AuthenticateAsync`` () =\n    let ctx = getHttpContextWriteable true\n\n    let handle (result: AuthenticateResult) : HttpHandler =\n        result.Succeeded |> should equal true\n        Response.ofEmpty\n\n    task {\n        do! Request.authenticate AuthScheme handle ctx\n    }\n\n[<Fact>]\nlet ``Request.ifAuthenticated should allow authenticated users`` () =\n    let ctx = getHttpContextWriteable true\n\n    let mutable visited = false\n\n    let handle : HttpHandler = fun ctx ->\n        visited <- true\n        Response.ofEmpty ctx\n\n    task {\n        do! Request.ifAuthenticated AuthScheme handle ctx\n        visited |> should equal true\n    }\n\n\n[<Fact>]\nlet ``Request.ifNotAuthenticated should block authenticated users`` () =\n    let ctx = getHttpContextWriteable false\n\n    let mutable visited = false\n\n    let handle : HttpHandler = fun ctx ->\n        visited <- true\n        Response.ofEmpty ctx\n\n    task {\n        do! Request.ifNotAuthenticated AuthScheme handle ctx\n        visited |> should equal true\n    }\n\n[<Fact>]\nlet ``Request.ifAuthenticatedInRole should allow users in correct role`` () =\n    let ctx = getHttpContextWriteable true\n\n    let mutable visited = false\n\n    let handle : HttpHandler = fun ctx ->\n        visited <- true\n        Response.ofEmpty ctx\n\n    task {\n        do! Request.ifAuthenticatedInRole AuthScheme (List.take 1 Common.AuthRoles) handle ctx\n        visited |> should equal true\n    }\n\n[<Fact>]\nlet ``Request.ifAuthenticatedInRole should block users not in role`` () =\n    let ctx = getHttpContextWriteable true\n\n    let mutable visited = false\n\n    let handle : HttpHandler = fun ctx ->\n        visited <- true\n        Response.ofEmpty ctx\n\n    task {\n        do! Request.ifAuthenticatedInRole AuthScheme [\"admin2\"] handle ctx\n        visited |> should equal false\n    }\n"
  },
  {
    "path": "test/Falco.Tests/RequestValueTests.fs",
    "content": "module Falco.Tests.RequestValue\n\nopen Falco\nopen FsUnit.Xunit\nopen Xunit\n\n[<Fact>]\nlet ``RequestValue should parse empty string as RNull`` () =\n    let expected = RObject [ \"value\", RNull ]\n    RequestValue.parseString \"value=\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse whitespace-only string as RNull`` () =\n    let expected = RObject [ \"value\", RNull ]\n    RequestValue.parseString \"value=   \" |> should equal expected\n\n[<Theory>]\n[<InlineData(\"-1\", -1.)>]\n[<InlineData(\"-123.456\", -123.456)>]\n[<InlineData(\"-0.001\", -0.001)>]\nlet ``RequestValue should parse negative numbers as RNumber`` (input, expected) =\n    let expectedValue = RObject [ \"value\", RNumber expected ]\n    RequestValue.parseString $\"value={input}\" |> should equal expectedValue\n\n[<Theory>]\n[<InlineData(\"0.0\", 0.0)>]\n[<InlineData(\"0.00001\", 0.00001)>]\n[<InlineData(\"999999.999999\", 999999.999999)>]\nlet ``RequestValue should parse small decimal values as RNumber`` (input, expected) =\n    let expectedValue = RObject [ \"value\", RNumber expected ]\n    RequestValue.parseString $\"value={input}\" |> should equal expectedValue\n\n[<Theory>]\n[<InlineData(\"1e10\", 1e10)>]\n[<InlineData(\"-1e-5\", -1e-5)>]\n[<InlineData(\"3.14e2\", 314.)>]\n[<InlineData(\"1.5e-5\", 1.5e-5)>]\nlet ``RequestValue should parse scientific notation as RString`` (input, expected) =\n    let expectedValue = RObject [ \"value\", RNumber input ]\n    RequestValue.parseString $\"value={input}\" |> should equal expectedValue\n\n[<Fact>]\nlet ``RequestValue should return empty RObject for incomplete request body`` () =\n    let expected = RObject []\n    \"\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse simple pair`` () =\n    let expected = RObject [ \"name\", RString \"john doe\" ]\n\n    \"name=john%20doe\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Theory>]\n[<InlineData(\"true\", true)>]\n[<InlineData(\"TRUE\", true)>]\n[<InlineData(\"True\", true)>]\n[<InlineData(\"false\", false)>]\n[<InlineData(\"FALSE\", false)>]\n[<InlineData(\"False\", false)>]\nlet ``RequestValue should parse RBool`` (input, expected) =\n    let expected = RObject [ \"code\", RBool expected ]\n\n    $\"code={input}\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Theory>]\n[<InlineData(\"on\")>]\n[<InlineData(\"ON\")>]\n[<InlineData(\"On\")>]\n[<InlineData(\"off\")>]\n[<InlineData(\"OFF\")>]\n[<InlineData(\"Off\")>]\n[<InlineData(\"012345\")>]\nlet ``RequestValue should parse RString`` (input) =\n    let expected = RObject [ \"code\", RString input ]\n\n    $\"code={input}\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n\n[<Theory>]\n[<InlineData(\"0\", 0.)>]\n[<InlineData(\"1\", 1.)>]\n[<InlineData(\"-1\", -1.)>]\n[<InlineData(\"0.123456\", 0.123456)>]\nlet ``RequestValue should parse RNumber`` (input, expected) =\n    let expected = RObject [ \"code\", RNumber expected ]\n\n    $\"code={input}\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse int with leading zero as string`` () =\n    let expected = RObject [ \"code\", RString \"0123456\" ]\n\n    \"code=0123456\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse large int64 values as string to preserve precision`` () =\n    // Int64.MaxValue = 9223372036854775807 has 19 digits, exceeds float64 precision\n    let expected = RObject [ \"id\", RString \"9223372036854775807\" ]\n\n    \"id=9223372036854775807\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse 15 digit integers as RNumber`` () =\n    // 15 digits is within float64 precision\n    let expected = RObject [ \"id\", RNumber 123456789012345. ]\n\n    \"id=123456789012345\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse 16+ digit integers as RString`` () =\n    // 16+ digits exceeds float64 precision\n    let expected = RObject [ \"id\", RString \"1234567890123456\" ]\n\n    \"id=1234567890123456\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse multiple simple pairs`` () =\n    let expected = RObject [\n        \"season\", RString \"summer\"\n        \"orders\", RNumber 2 ]\n\n    \"season=summer&orders=2\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse explicit list`` () =\n    let expected = RObject [\n        \"season\", RList [ RString \"summer\"; RString \"winter\" ] ]\n    \"season[]=summer&season[]=winter\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse indexed list`` () =\n    let expected = RObject [\n        \"season\", RList [ RString \"summer\"; RString \"winter\" ] ]\n    \"season[0]=summer&season[1]=winter\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse out-of-order indexed list`` () =\n    let expected = RObject [\n        \"season\", RList [ RString \"summer\"; RString \"winter\" ] ]\n    \"season[1]=winter&season[0]=summer\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse jagged indexed list`` () =\n    let expected = RObject [\n        \"season\", RList [ RString \"summer\"; RNull; RString \"winter\" ] ]\n    \"season[0]=summer&season[2]=winter\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse out-of-order, jagged indexed list`` () =\n    let expected = RObject [\n        \"season\", RList [ RString \"summer\"; RNull; RString \"winter\" ] ]\n    \"season[2]=winter&season[0]=summer\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse object with indexed list`` () =\n    let expected = RObject [\n        \"user\", RObject [\n            \"name\", RString \"john doe\"\n            \"hobbies\", RList [ RString \"cycling\"; RString \"hiking\" ] ] ]\n    \"user.name=john%20doe&user.hobbies[0]=cycling&user.hobbies[1]=hiking\"\n    |> RequestValue.parseString\n    |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse complex`` () =\n    let expected = RObject [\n        \"season\", RString \"summer\"\n        \"orders\", RNumber 2\n        \"tags\", RList [ RString \"clothing\"; RString \"shoes\"]\n        \"user\", RObject [\n            \"name\", RString \"john\"\n            \"age\", RNumber 97\n            \"hobbies\", RList [ RString \"cycling\"; RString \"hiking\" ]\n            \"cards\", RList [\n                RObject [\n                    \"num\", RNumber 123\n                    \"kind\", RString \"visa\" ]\n                RObject [\n                    \"num\", RNumber 456\n                    \"kind\", RString \"visa\" ] ] ] ]\n\n    let requestValue =\n        seq {\n            \"season\", seq { \"summer\" }\n            \"orders\", seq { \"2\" }\n            \"tags[]\", seq { \"clothing\"; \"shoes\" }\n            \"user.name\", seq { \"john\" }\n            \"user.age\", seq { \"97\" }\n            \"user.hobbies[]\", seq { \"cycling\"; \"hiking\" }\n            \"user.cards[].num\", seq { \"123\"; \"456\" }\n            \"user.cards[].kind\", seq { \"visa\"; \"amex\" }\n        }\n        |> dict\n        |> RequestValue.parse\n\n    requestValue |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse nested objects without lists`` () =\n    let expected = RObject [\n        \"user\", RObject [\n            \"name\", RString \"john\";\n            \"age\", RNumber 30.0\n        ]\n    ]\n    RequestValue.parseString \"user.name=john&user.age=30\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse deeply nested structures (3+ levels)`` () =\n    let expected = RObject [\n        \"user\", RObject [\n            \"profile\", RObject [\n                \"address\", RObject [\n                    \"city\", RString \"NYC\"\n                ]\n            ]\n        ]\n    ]\n    RequestValue.parseString \"user.profile.address.city=NYC\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should parse mixed flat keys and nested keys`` () =\n    let expected = RObject [\n        \"id\", RNumber 123.0;\n        \"user\", RObject [\n            \"name\", RString \"john\"\n        ]\n    ]\n    RequestValue.parseString \"id=123&user.name=john\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should handle empty list values`` () =\n    let expected = RObject [\n        \"items\", RList [ RNull; RString \"value\" ]\n    ]\n    RequestValue.parseString \"items[]=&items[]=value\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should handle invalid indexed list syntax`` () =\n    // Non-numeric index should be treated as literal key\n    let expected = RObject [ \"items[abc]\", RString \"value\" ]\n    RequestValue.parseString \"items[abc]=value\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should handle sparse indexed lists`` () =\n    // items[0]=a&items[2]=c should have RNull at index 1\n    let expected = RObject [ \"items\", RList [ RString \"a\"; RNull; RString \"c\" ] ]\n    RequestValue.parseString \"items[0]=a&items[2]=c\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should handle duplicate keys concatenation`` () =\n    // Last value should win for flat keys\n    let expected = RObject [ \"name\", RList [ RString \"john\"; RString \"jane\" ] ]\n    RequestValue.parseString \"name=john&name=jane\" |> should equal expected\n\n[<Fact>]\nlet ``RequestValue should handle empty input`` () =\n    let expected = RObject []\n    RequestValue.parseString \"\" |> should equal expected\n"
  },
  {
    "path": "test/Falco.Tests/ResponseTests.fs",
    "content": "﻿module Falco.Tests.Response\n\nopen System.Security.Claims\nopen System.Text\nopen System.Text.Json\nopen System.Text.Json.Serialization\nopen Falco\nopen Falco.Markup\nopen FsUnit.Xunit\nopen Microsoft.AspNetCore.Antiforgery\nopen Microsoft.AspNetCore.Authentication\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Primitives\nopen Microsoft.Net.Http.Headers\nopen NSubstitute\nopen Xunit\n\n[<Fact>]\nlet ``Response.withStatusCode should modify HttpResponse StatusCode`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = 204\n\n    task {\n        do! ctx\n            |> (Response.withStatusCode expected >> Response.ofEmpty)\n\n        ctx.Response.StatusCode\n        |> should equal expected\n    }\n\n[<Fact>]\nlet ``Response.withHeaders should set header`` () =\n    let serverName = \"Kestrel\"\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx\n            |> (Response.withHeaders [ HeaderNames.Server, serverName ] >> Response.ofEmpty)\n\n        ctx.Response.Headers.[HeaderNames.Server][0]\n        |> should equal serverName\n    }\n\n[<Fact>]\nlet ``Response.withHeaders should set multiple headers`` () =\n    let ctx = getHttpContextWriteable false\n\n    let headers =\n        [ HeaderNames.Server, \"Kestrel\"\n          HeaderNames.CacheControl, \"no-cache\" ]\n\n    task {\n        do! ctx\n            |> (Response.withHeaders headers >> Response.ofEmpty)\n\n        ctx.Response.Headers.[HeaderNames.Server][0]\n        |> should equal \"Kestrel\"\n\n        ctx.Response.Headers.[HeaderNames.CacheControl][0]\n        |> should equal \"no-cache\"\n    }\n\n[<Fact>]\nlet ``Response.withHeaders should overwrite existing header`` () =\n    let ctx = getHttpContextWriteable false\n    ctx.Response.Headers.[HeaderNames.Server] <- \"InitialValue\"\n    let serverName = \"Kestrel\"\n\n    task {\n        do! ctx\n            |> (Response.withHeaders [ HeaderNames.Server, serverName ] >> Response.ofEmpty)\n\n        ctx.Response.Headers.[HeaderNames.Server][0]\n        |> should equal serverName\n    }\n\n[<Fact>]\nlet ``Response.withContentType should set header`` () =\n    let contentType = \"text/plain; charset=utf-8\"\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx\n            |> (Response.withContentType contentType>> Response.ofEmpty)\n\n        ctx.Response.Headers.[HeaderNames.ContentType][0]\n        |> should equal contentType\n    }\n\n[<Fact>]\nlet ``Response.withStatusCode with multiple modifiers`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx\n            |> (Response.withStatusCode 201\n                >> Response.withContentType \"application/json\"\n                >> Response.ofEmpty)\n\n        ctx.Response.StatusCode |> should equal 201\n        ctx.Response.Headers.[HeaderNames.ContentType][0] |> should equal \"application/json\"\n    }\n\n[<Fact>]\nlet ``Response.withCookie should add cookie to response`` () =\n    let ctx = getHttpContextWriteable false\n    let key = \"sessionId\"\n    let value = \"abc123\"\n\n    task {\n        do! ctx\n            |> (Response.withCookie key value >> Response.ofEmpty)\n\n        ctx.Response.Cookies.Received().Append(key, value) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.withCookieOptions should add cookie with options`` () =\n    let ctx = getHttpContextWriteable false\n    let key = \"sessionId\"\n    let value = \"abc123\"\n    let options = CookieOptions()\n    options.HttpOnly <- true\n    options.Secure <- true\n\n    task {\n        do! ctx\n            |> (Response.withCookieOptions options key value >> Response.ofEmpty)\n\n        ctx.Response.Cookies.Received().Append(key, value, options) |> ignore\n    }\n\n[<Fact>]\nlet ``Response chaining multiple modifiers`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx\n            |> (Response.withStatusCode 200\n                >> Response.withContentType \"application/json\"\n                >> Response.withHeaders [ \"X-Custom\", \"value\" ]\n                >> Response.ofEmpty)\n\n        ctx.Response.StatusCode |> should equal 200\n        ctx.Response.Headers.[HeaderNames.ContentType][0] |> should equal \"application/json\"\n        ctx.Response.Headers.[\"X-Custom\"][0] |> should equal \"value\"\n    }\n\n[<Fact>]\nlet ``Response modifiers are composable`` () =\n    let ctx = getHttpContextWriteable false\n    let modifier = Response.withStatusCode 201 >> Response.withContentType \"text/custom\"\n\n    task {\n        do! ctx |> (modifier >> Response.ofEmpty)\n        ctx.Response.StatusCode |> should equal 201\n        ctx.Response.ContentType |> should equal \"text/custom\"\n    }\n\n[<Fact>]\nlet ``Response.redirectPermanentlyTo invokes HttpRedirect with permanently moved resource`` () =\n    let ctx = getHttpContextWriteable false\n    let permanentRedirect = true\n    task {\n        do! ctx\n            |> Response.redirectPermanently \"/\"\n        ctx.Response.Received().Redirect(\"/\", permanentRedirect)\n        ctx.Response.StatusCode |> should equal 301\n    }\n\n[<Fact>]\nlet ``Response.redirectTemporarilyTo invokes HttpRedirect with temporarily moved resource`` () =\n    let ctx = getHttpContextWriteable false\n    let permanentRedirect = false\n    task {\n        do! ctx\n            |> Response.redirectTemporarily \"/\"\n        ctx.Response.Received().Redirect(\"/\", permanentRedirect)\n        ctx.Response.StatusCode |> should equal 302\n    }\n\n[<Fact>]\nlet ``Response.ofEmpty produces empty response`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofEmpty\n        ctx.Response.ContentLength |> should equal 0L\n    }\n\n[<Fact>]\nlet ``Response.ofString with whitespace-only string`` () =\n    let ctx = getHttpContextWriteable false\n    let whitespace = \"   \\t\\n  \"\n\n    task {\n        do! ctx |> Response.ofString Encoding.UTF8 whitespace\n        let! body = getResponseBody ctx\n        body |> should equal \"\"  // IsNullOrWhiteSpace check\n    }\n\n[<Fact>]\nlet ``Response.ofPlainText produces text/plain result`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = \"hello\"\n\n    task {\n        do! ctx\n            |> Response.ofPlainText expected\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"text/plain; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.ofPlainText with empty string`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofPlainText \"\"\n        let! body = getResponseBody ctx\n        body |> should equal \"\"\n    }\n\n[<Fact>]\nlet ``Response.ofPlainText with null string`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofPlainText null\n        let! body = getResponseBody ctx\n        body |> should equal \"\"\n    }\n\n[<Fact>]\nlet ``Response.ofPlainText with multiline content`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"line1\\nline2\\nline3\"\n\n    task {\n        do! ctx |> Response.ofPlainText expected\n        let! body = getResponseBody ctx\n        body |> should equal expected\n    }\n\n[<Fact>]\nlet ``Response.ofPlainText with special characters`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"hello\\r\\nworld\\t!\\x00end\"\n\n    task {\n        do! ctx |> Response.ofPlainText expected\n        let! body = getResponseBody ctx\n        body |> should equal expected\n    }\n\n[<Fact>]\nlet ``Response.ofBinary produces valid inline result from Byte[]`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"falco\"\n    let contentType = \"text/plain; charset=utf-8\"\n\n    task {\n        do! ctx\n            |> Response.ofBinary contentType [] (expected |> Encoding.UTF8.GetBytes)\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n\n        body               |> should equal expected\n        contentType        |> should equal contentType\n        contentDisposition |> should equal \"inline\"\n    }\n\n[<Fact>]\nlet ``Response.ofBinary with special characters in content type`` () =\n    let ctx = getHttpContextWriteable false\n    let contentType = \"application/octet-stream; charset=utf-8\"\n    let bytes = Array.zeroCreate<byte> 100\n\n    task {\n        do! ctx |> Response.ofBinary contentType [] bytes\n        ctx.Response.ContentType |> should equal contentType\n    }\n\n[<Fact>]\nlet ``Response.ofBinary should preserve valid UTF-8 content`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"hello\"\n    let bytes = Encoding.UTF8.GetBytes(expected)\n\n    task {\n        do! ctx |> Response.ofBinary \"text/plain\" [] bytes\n        let! body = getResponseBody ctx\n        body |> should equal expected\n    }\n\n[<Fact>]\nlet ``Response.ofAttachment produces valid attachment result from Byte[]`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"falco\"\n    let contentType = \"text/plain; charset=utf-8\"\n\n    task {\n        do! ctx\n            |> Response.ofAttachment \"falco.txt\" contentType [] (expected |> Encoding.UTF8.GetBytes)\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n\n        body               |> should equal expected\n        contentType        |> should equal contentType\n        contentDisposition |> should equal \"attachment; filename=\\\"falco.txt\\\"\"\n    }\n\n[<Fact>]\nlet ``Response.ofAttachment with special characters in filename`` () =\n    let ctx = getHttpContextWriteable false\n    let filename = \"file with spaces & quotes.txt\"\n    let contentType = \"text/plain; charset=utf-8\"\n\n    task {\n        do! ctx\n            |> Response.ofAttachment filename contentType [] (Encoding.UTF8.GetBytes(\"content\"))\n\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n        // Should have escaped quotes if necessary\n        contentDisposition.Contains(\"attachment\") |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofAttachment with empty filename`` () =\n    let ctx = getHttpContextWriteable false\n    let contentType = \"text/plain; charset=utf-8\"\n\n    task {\n        do! ctx\n            |> Response.ofAttachment \"\" contentType [] (Encoding.UTF8.GetBytes(\"content\"))\n\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n        contentDisposition |> should equal \"attachment\"\n    }\n\n[<Fact>]\nlet ``Response.ofAttachment preserves file extension`` () =\n    let ctx = getHttpContextWriteable false\n    let filename = \"document.pdf\"\n    let contentType = \"application/pdf\"\n\n    task {\n        do! ctx\n            |> Response.ofAttachment filename contentType [] (Encoding.UTF8.GetBytes(\"PDF content\"))\n\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n        contentDisposition.Contains(\"document.pdf\") |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofAttachment with quotes in filename`` () =\n    let ctx = getHttpContextWriteable false\n    let filename = \"file\\\"with\\\"quotes.txt\"\n    let contentType = \"text/plain\"\n\n    task {\n        do! ctx |> Response.ofAttachment filename contentType [] (Encoding.UTF8.GetBytes(\"test\"))\n        let contentDisposition = ctx.Response.Headers.[HeaderNames.ContentDisposition][0]\n        // Should be properly escaped\n        contentDisposition.Contains \"attachment\" |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofJson produces applicaiton/json result`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = \"{\\\"Name\\\":\\\"John Doe\\\"}\"\n\n    task {\n        do! ctx\n            |> Response.ofJson { Name = \"John Doe\"}\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"application/json; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.ofJson with null object`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofJson null\n        let! body = getResponseBody ctx\n        body |> should equal \"null\"\n    }\n\n[<Fact>]\nlet ``Response.ofJson with empty list`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofJson []\n        let! body = getResponseBody ctx\n        body |> should equal \"[]\"\n    }\n\n[<Fact>]\nlet ``Response.ofJson with nested objects`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"{\\\"name\\\":\\\"John\\\",\\\"nested\\\":{\\\"age\\\":30}}\"\n\n    task {\n        do! ctx |> Response.ofJson {| name = \"John\"; nested = {| age = 30 |} |}\n        let! body = getResponseBody ctx\n        body |> should equal expected\n    }\n\n[<Fact>]\nlet ``Response.ofJsonOptions with custom serialization settings`` () =\n    let ctx = getHttpContextWriteable false\n    let options = JsonSerializerOptions()\n    options.WriteIndented <- true\n\n    task {\n        do! ctx |> Response.ofJsonOptions options {| test = \"value\" |}\n        let! body = getResponseBody ctx\n        body.Contains(\"test\") |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofJsonOptions produces applicaiton/json result ignoring nulls`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = \"{}\"\n\n    task {\n        let jsonOptions = JsonSerializerOptions()\n        jsonOptions.DefaultIgnoreCondition <- JsonIgnoreCondition.WhenWritingNull\n\n        do! ctx\n            |> Response.ofJsonOptions jsonOptions { Name = null }\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"application/json; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.ofJson with large object`` () =\n    let ctx = getHttpContextWriteable false\n    let largeList = List.init 10000 (fun i -> {| id = i; name = $\"item{i}\" |})\n\n    task {\n        do! ctx |> Response.ofJson largeList\n        let! body = getResponseBody ctx\n        body.Contains(\"\\\"id\\\"\") |> should equal true\n        body.Contains(\"9999\") |> should equal true  // Check last item serialized\n    }\n\n[<Fact>]\nlet ``Response.ofHtml produces text/html result`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = \"<!DOCTYPE html><html><div class=\\\"my-class\\\"><h1>hello</h1></div></html>\"\n\n    let doc =\n        _html [] [\n                _div [ _class_ \"my-class\" ] [\n                        _h1 [] [ _text \"hello\" ]\n                    ]\n            ]\n\n    task {\n        do! ctx\n            |> Response.ofHtml doc\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"text/html; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.ofHtml with empty document`` () =\n    let ctx = getHttpContextWriteable false\n    let doc = _html [] []\n\n    task {\n        do! ctx |> Response.ofHtml doc\n        let! body = getResponseBody ctx\n        body |> should equal \"<!DOCTYPE html><html></html>\"\n    }\n\n[<Fact>]\nlet ``Response.ofHtml with attributes`` () =\n    let ctx = getHttpContextWriteable false\n    let doc = _html [_lang_ \"en\"] [_body [] []]\n\n    task {\n        do! ctx |> Response.ofHtml doc\n        let! body = getResponseBody ctx\n        body.Contains(\"lang=\\\"en\\\"\") |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofHtmlString with empty string`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx |> Response.ofHtmlString \"\"\n        let! body = getResponseBody ctx\n        body |> should equal \"\"\n    }\n\n[<Fact>]\nlet ``Response.ofHtmlString produces text/html result`` () =\n    let ctx = getHttpContextWriteable false\n\n    let expected = \"<!DOCTYPE html><html><div class=\\\"my-class\\\"><h1>hello</h1></div></html>\"\n\n    task {\n        do! ctx\n            |> Response.ofHtmlString expected\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"text/html; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.ofFragment with non-existent fragment`` () =\n    let ctx = getHttpContextWriteable false\n    let html = _div [ _id_ \"fragment1\" ] [ _text \"content\" ]\n\n    task {\n        do! ctx |> Response.ofFragment \"nonexistent\" html\n        let! body = getResponseBody ctx\n        body |> should equal \"\"\n    }\n\n[<Fact>]\nlet ``Response.ofFragment with multiple matching fragments`` () =\n    let ctx = getHttpContextWriteable false\n    let html =\n        _div [] [\n            _div [ _id_ \"fragment\" ] [ _text \"first\" ]\n            _div [ _id_ \"fragment\" ] [ _text \"second\" ]\n        ]\n\n    task {\n        do! ctx |> Response.ofFragment \"fragment\" html\n        let! body = getResponseBody ctx\n        // Should return first match\n        body.Contains \"first\" |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofFragment returns the specified fragment`` () =\n    let ctx = getHttpContextWriteable false\n    let expected = \"\"\"<div id=\"fragment1\">1</div>\"\"\"\n\n    let html =\n        Elem.div [] [\n            _div [ _id_ \"fragment1\" ] [ _text \"1\" ]\n            _div [ _id_ \"fragment2\" ] [ _text \"2\" ]\n        ]\n\n    task {\n        do! ctx |> Response.ofFragment \"fragment1\" html\n\n        let! body = getResponseBody ctx\n        let contentLength = ctx.Response.ContentLength\n        let contentType = ctx.Response.Headers.[HeaderNames.ContentType][0]\n\n        body          |> should equal expected\n        contentLength |> should equal (int64 (Encoding.UTF8.GetByteCount(expected)))\n        contentType   |> should equal \"text/html; charset=utf-8\"\n    }\n\n[<Fact>]\nlet ``Response.signIn should call SignInAsync`` () =\n    let ctx = getHttpContextWriteable true\n    let principal = ClaimsPrincipal(ClaimsIdentity([], CookieScheme))\n\n    task {\n        do! ctx |> Response.signIn CookieScheme principal\n        ctx.Received().SignInAsync(CookieScheme, principal) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.signInOptions should call SignInAsync with options`` () =\n    let ctx = getHttpContextWriteable true\n    let principal = ClaimsPrincipal(ClaimsIdentity([], CookieScheme))\n    let options = AuthenticationProperties()\n    options.IsPersistent <- true\n\n    task {\n        do! ctx |> Response.signInOptions CookieScheme principal options\n        ctx.Received().SignInAsync(CookieScheme, principal, options) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.signInAndRedirect should set redirect URI`` () =\n    let ctx = getHttpContextWriteable true\n    let principal = ClaimsPrincipal(ClaimsIdentity([], CookieScheme))\n    let redirectUri = \"/dashboard\"\n\n    task {\n        do! ctx |> Response.signInAndRedirect CookieScheme principal redirectUri\n        ctx.Received().SignInAsync(CookieScheme, principal) |> ignore\n        ctx.Response.Headers.Location.ToArray() |> should contain redirectUri\n    }\n\n[<Fact>]\nlet ``Response.signOut should call SignOutAsync`` () =\n    let ctx = getHttpContextWriteable true\n\n    task {\n        do! ctx |> Response.signOut CookieScheme\n        ctx.Received().SignOutAsync(CookieScheme) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.signOutOptions should call SignOutAsync with options`` () =\n    let ctx = getHttpContextWriteable true\n    let options = AuthenticationProperties()\n    options.RedirectUri <- \"/goodbye\"\n\n    task {\n        do! ctx |> Response.signOutOptions CookieScheme options\n        ctx.Received().SignOutAsync(CookieScheme, options) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.signOutAndRedirect should set redirect URI`` () =\n    let ctx = getHttpContextWriteable true\n    let redirectUri = \"/goodbye\"\n\n    task {\n        do! ctx |> Response.signOutAndRedirect CookieScheme redirectUri\n        ctx.Received().SignOutAsync(CookieScheme) |> ignore\n        ctx.Response.Headers.Location.ToArray() |> should contain redirectUri\n    }\n\n[<Fact>]\nlet ``Response.challengeOptions should call ChallengeAsync with options`` () =\n    let ctx = getHttpContextWriteable false\n    let options = AuthenticationProperties()\n    options.RedirectUri <- \"/login\"\n\n    task {\n        do! ctx |> Response.challengeOptions CookieScheme options\n        ctx.Received().ChallengeAsync(CookieScheme, options) |> ignore\n    }\n\n[<Fact>]\nlet ``Response.challengeAndRedirect`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        do! ctx\n            |> Response.challengeAndRedirect AuthScheme \"/\"\n        ctx.Response.StatusCode |> should equal 401\n        ctx.Response.Headers.WWWAuthenticate.ToArray() |> should contain AuthScheme\n        ctx.Response.Headers.Location.ToArray() |> should contain \"/\"\n    }\n\n[<Fact>]\nlet ``Response.ofHtmlCsrf should include CSRF token`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        let view (token : AntiforgeryTokenSet) =\n            _html [] [\n                _body [] [ Security.Xsrf.antiforgeryInput token ] ]\n\n        do! ctx |> Response.ofHtmlCsrf view\n        let! body = getResponseBody ctx\n        body.Contains(\"input\") |> should equal true\n    }\n\n[<Fact>]\nlet ``Response.ofFragmentCsrf should include CSRF token`` () =\n    let ctx = getHttpContextWriteable false\n\n    task {\n        let view (token : AntiforgeryTokenSet) =\n            _div [ _id_ \"myFragment\" ] [\n                Security.Xsrf.antiforgeryInput token\n            ]\n\n        do! ctx |> Response.ofFragmentCsrf \"myFragment\" view\n        let! body = getResponseBody ctx\n        body.Contains(\"input\") |> should equal true\n    }\n"
  },
  {
    "path": "test/Falco.Tests/RoutingTests.fs",
    "content": "﻿module Falco.Tests.Routing\n\nopen Xunit\nopen Falco\nopen Falco.Routing\nopen FsUnit.Xunit\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.AspNetCore.Routing\nopen System\n\nlet emptyHandler : HttpHandler = Response.ofPlainText \"\"\n\n// -----------------\n// HttpEndpoint Tests\n// -----------------\n\n[<Fact>]\nlet ``route function should return valid HttpEndpoint`` () =\n    let routeVerb = GET\n    let routePattern = \"/\"\n\n    let endpoint = route routeVerb routePattern emptyHandler\n    endpoint.Pattern |> should equal routePattern\n\n    let verb, handler = Seq.head endpoint.Handlers\n    verb |> should equal routeVerb\n    handler |> should be instanceOfType<HttpHandler>\n\n[<Fact>]\nlet ``all function should create endpoint with multiple handlers`` () =\n    let pattern = \"/api/users\"\n    let handlers = [\n        GET, emptyHandler\n        POST, emptyHandler\n        DELETE, emptyHandler\n    ]\n\n    let endpoint = all pattern handlers\n    endpoint.Pattern |> should equal pattern\n    endpoint.Handlers |> Seq.length |> should equal 3\n\n    let verbs = endpoint.Handlers |> Seq.map fst |> List.ofSeq\n    verbs |> should contain GET\n    verbs |> should contain POST\n    verbs |> should contain DELETE\n\n[<Fact>]\nlet ``HTTP verb helpers create correct endpoints`` () =\n    let testEndpointFunction\n        (mapEndPoint: string -> HttpHandler -> HttpEndpoint)\n        (expectedVerb : HttpVerb) =\n        let pattern = \"/test\"\n        let endpoint = mapEndPoint pattern emptyHandler\n        endpoint.Pattern |> should equal pattern\n        let (verb, handler) = Seq.head endpoint.Handlers\n        verb |> should equal expectedVerb\n        handler |> should be instanceOfType<HttpHandler>\n\n    [\n        any, ANY\n        get, GET\n        head, HEAD\n        post, POST\n        put, PUT\n        patch, PATCH\n        delete, DELETE\n        options, OPTIONS\n        trace, TRACE\n    ]\n    |> List.iter (fun (fn, verb) -> testEndpointFunction fn verb)\n\n[<Fact>]\nlet ``mapGet should compose route mapping with handler`` () =\n    let pattern = \"/users/{id}\"\n    let mutable capturedId = \"\"\n\n    let handler (id: string) : HttpHandler =\n        capturedId <- id\n        Response.ofEmpty\n\n    let endpoint = mapGet pattern (fun r -> r.GetString \"id\") handler\n    endpoint.Pattern |> should equal pattern\n\n    let (verb, _) = Seq.head endpoint.Handlers\n    verb |> should equal GET\n\n[<Fact>]\nlet ``mapPost should compose route mapping with handler`` () =\n    let pattern = \"/users\"\n    let endpoint = mapPost pattern (fun _ -> \"test\") (fun _ -> emptyHandler)\n\n    let (verb, _) = Seq.head endpoint.Handlers\n    verb |> should equal POST\n\n[<Fact>]\nlet ``all map* functions create correct verb endpoints`` () =\n    let pattern = \"/test/{id}\"\n    let map = fun (r: RequestData) -> r.GetString \"id\"\n\n    [\n        mapAny, ANY\n        mapGet, GET\n        mapHead, HEAD\n        mapPost, POST\n        mapPut, PUT\n        mapPatch, PATCH\n        mapDelete, DELETE\n        mapOptions, OPTIONS\n        mapTrace, TRACE\n    ]\n    |> List.iter (fun (fn, verb) ->\n        let endpoint = fn pattern map (fun _ -> emptyHandler)\n        let (endpointVerb, _) = Seq.head endpoint.Handlers\n        endpointVerb |> should equal verb\n    )\n\n[<Fact>]\nlet ``setDisplayName should configure endpoint display name`` () =\n    let endpoint =\n        route GET \"/\" emptyHandler\n        |> setDisplayName \"MyCustomEndpoint\"\n\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n\n    builtEndpoint.DisplayName |> should equal \"MyCustomEndpoint\"\n\n[<Fact>]\nlet ``setOrder should configure endpoint order`` () =\n    let endpoint =\n        route GET \"/\" emptyHandler\n        |> setOrder 42\n\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n\n    builtEndpoint.Order |> should equal 42\n\n[<Fact>]\nlet ``setDisplayName and setOrder can be chained`` () =\n    let endpoint =\n        route GET \"/\" emptyHandler\n        |> setDisplayName \"TestEndpoint\"\n        |> setOrder 99\n\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n\n    builtEndpoint.DisplayName |> should equal \"TestEndpoint\"\n    builtEndpoint.Order |> should equal 99\n\n// -----------------\n// FalcoEndpointDataSource Tests\n// -----------------\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with empty constructor`` () =\n    let dataSource = FalcoEndpointDataSource()\n    dataSource.Endpoints |> should haveCount 0\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with single endpoint`` () =\n    let endpoint = route GET \"/\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    dataSource.Endpoints |> should haveCount 1\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.RoutePattern.RawText |> should equal \"/\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with multiple endpoints`` () =\n    let endpoints = [\n        route GET \"/\" emptyHandler\n        route POST \"/users\" emptyHandler\n        route DELETE \"/users/{id}\" emptyHandler\n    ]\n    let dataSource = FalcoEndpointDataSource(endpoints)\n\n    dataSource.Endpoints |> should haveCount 3\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with multi-verb endpoint creates separate route endpoints`` () =\n    let endpoint = all \"/api/users\" [\n        GET, emptyHandler\n        POST, emptyHandler\n        PUT, emptyHandler\n    ]\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    // Should create 3 separate RouteEndpoints (one per verb)\n    dataSource.Endpoints |> should haveCount 3\n\n    let routeEndpoints = dataSource.Endpoints |> Seq.cast<RouteEndpoint> |> List.ofSeq\n    routeEndpoints |> List.iter (fun re ->\n        re.RoutePattern.RawText |> should equal \"/api/users\"\n    )\n\n[<Fact>]\nlet ``FalcoEndpointDataSource builds endpoints with correct HTTP method metadata`` () =\n    let endpoint = route GET \"/test\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    let httpMethodMetadata =\n        builtEndpoint.Metadata.GetMetadata<HttpMethodMetadata>()\n\n    httpMethodMetadata |> should not' (be Null)\n    httpMethodMetadata.HttpMethods |> should contain \"GET\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with ANY verb creates empty HTTP methods metadata`` () =\n    let endpoint = route ANY \"/test\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    let httpMethodMetadata =\n        builtEndpoint.Metadata.GetMetadata<HttpMethodMetadata>()\n\n    httpMethodMetadata |> should not' (be Null)\n    httpMethodMetadata.HttpMethods |> should be Empty\n\n[<Fact>]\nlet ``FalcoEndpointDataSource builds endpoints with route name metadata`` () =\n    let pattern = \"/api/users\"\n    let endpoint = route GET pattern emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    let routeNameMetadata =\n        builtEndpoint.Metadata.GetMetadata<RouteNameMetadata>()\n\n    routeNameMetadata |> should not' (be Null)\n    routeNameMetadata.RouteName |> should equal pattern\n\n[<Fact>]\nlet ``FalcoEndpointDataSource default display name includes verb and pattern`` () =\n    let endpoint = route POST \"/users\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.DisplayName |> should equal \"POST /users\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource ANY verb display name is pattern only`` () =\n    let endpoint = route ANY \"/test\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.DisplayName |> should equal \"/test\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with route parameters`` () =\n    let endpoint = route GET \"/users/{id}/posts/{postId}\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.RoutePattern.Parameters |> should haveCount 2\n\n    let paramNames =\n        builtEndpoint.RoutePattern.Parameters\n        |> Seq.map (fun p -> p.Name)\n        |> List.ofSeq\n\n    paramNames |> should contain \"id\"\n    paramNames |> should contain \"postId\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource GetChangeToken returns NullChangeToken`` () =\n    let dataSource = FalcoEndpointDataSource()\n    let changeToken = dataSource.GetChangeToken()\n\n    changeToken |> should be instanceOfType<Microsoft.Extensions.FileProviders.NullChangeToken>\n\n[<Fact>]\nlet ``FalcoEndpointDataSource.FalcoEndpoints can be added dynamically`` () =\n    let dataSource = FalcoEndpointDataSource()\n    dataSource.FalcoEndpoints.Add(route GET \"/dynamic\" emptyHandler)\n\n    dataSource.Endpoints |> should haveCount 1\n\n[<Fact>]\nlet ``FalcoEndpointDataSource combines constructor and FalcoEndpoints`` () =\n    let initialEndpoint = route GET \"/initial\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ initialEndpoint ])\n\n    dataSource.FalcoEndpoints.Add(route POST \"/added\" emptyHandler)\n\n    dataSource.Endpoints |> should haveCount 2\n\n[<Fact>]\nlet ``FalcoEndpointDataSource applies IEndpointConventionBuilder conventions`` () =\n    let endpoint = route GET \"/test\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let mutable conventionApplied = false\n    let convention = Action<EndpointBuilder>(fun _ -> conventionApplied <- true)\n\n    (dataSource :> IEndpointConventionBuilder).Add(convention)\n\n    // Force endpoint building\n    dataSource.Endpoints |> ignore\n\n    conventionApplied |> should equal true\n\n[<Fact>]\nlet ``FalcoEndpointDataSource request delegate executes handler`` () =\n    let mutable handlerExecuted = false\n    let testHandler : HttpHandler = fun ctx ->\n        handlerExecuted <- true\n        Response.ofEmpty ctx\n\n    let endpoint = route GET \"/test\" testHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.RequestDelegate |> should not' (be Null)\n\n[<Fact>]\nlet ``FalcoEndpointDataSource preserves endpoint pattern casing`` () =\n    let endpoint = route GET \"/API/Users\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.RoutePattern.RawText |> should equal \"/API/Users\"\n\n[<Fact>]\nlet ``FalcoEndpointDataSource with complex route patterns`` () =\n    let patterns = [\n        \"/users\"\n        \"/users/{id:int}\"\n        \"/users/{id}/posts/{postId:guid}\"\n        \"/api/v{version:apiVersion}/users\"\n    ]\n\n    let endpoints = patterns |> List.map (fun p -> route GET p emptyHandler)\n    let dataSource = FalcoEndpointDataSource(endpoints)\n\n    dataSource.Endpoints |> should haveCount 4\n\n    let builtPatterns =\n        dataSource.Endpoints\n        |> Seq.cast<RouteEndpoint>\n        |> Seq.map (fun e -> e.RoutePattern.RawText)\n        |> List.ofSeq\n\n    patterns |> List.iter (fun p -> builtPatterns |> should contain p)\n\n[<Fact>]\nlet ``FalcoEndpointDataSource default order is 0`` () =\n    let endpoint = route GET \"/test\" emptyHandler\n    let dataSource = FalcoEndpointDataSource([ endpoint ])\n\n    let builtEndpoint = Seq.head dataSource.Endpoints :?> RouteEndpoint\n    builtEndpoint.Order |> should equal 0\n"
  },
  {
    "path": "test/Falco.Tests/SecurityTests.fs",
    "content": "﻿module Falco.Tests.SecurityTests\n\nopen FsUnit.Xunit\nopen Xunit\nopen Falco.Security\nopen Falco.Markup\nopen Microsoft.AspNetCore.Antiforgery\n\nmodule Xsrf =\n    [<Fact>]\n    let ``antiforgetInput should return valid XmlNode`` () =\n        let token = AntiforgeryTokenSet(\"REQUEST_TOKEN\", \"COOKIE_TOKEN\", \"FORM_FIELD_NAME\", \"HEADER_NAME\")\n        let input = Xsrf.antiforgeryInput token\n\n        let expected = \"<input type=\\\"hidden\\\" name=\\\"FORM_FIELD_NAME\\\" value=\\\"REQUEST_TOKEN\\\" />\"\n\n        match input with\n        | TextNode _\n        | ParentNode _  ->\n            false |> should equal true\n\n        | input ->\n            renderNode input\n            |> should equal expected\n"
  },
  {
    "path": "test/Falco.Tests/StringTests.fs",
    "content": "namespace Falco.Tests.String\n\nopen System\nopen System.Collections.Generic\nopen System.IO\nopen System.Text\nopen System.Text.Json\nopen System.Text.Json.Serialization\nopen Falco\nopen FsUnit.Xunit\nopen NSubstitute\nopen Xunit\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.Net.Http.Headers\nopen Microsoft.AspNetCore.Http\nopen Microsoft.Extensions.Primitives\n\nmodule StringUtils =\n    [<Fact>]\n    let ``StringUtils.strEmpty should return true for null or empty strings`` () =\n        StringUtils.strEmpty null |> should be True\n        StringUtils.strEmpty \"\" |> should be True\n        StringUtils.strEmpty \"   \" |> should be True\n\n    [<Fact>]\n    let ``StringUtils.strNotEmpty should return true for non-empty strings`` () =\n        StringUtils.strNotEmpty \"hello\" |> should be True\n        StringUtils.strNotEmpty \"   \" |> should be False\n\n    [<Fact>]\n    let ``StringUtils.strEquals should compare strings case-insensitively`` () =\n        StringUtils.strEquals \"hello\" \"HELLO\" |> should be True\n        StringUtils.strEquals \"hello\" \"world\" |> should be False\n\n    [<Fact>]\n    let ``StringUtils.strConcat should concatenate a sequence of strings`` () =\n        StringUtils.strConcat [\"hello\"; \" \"; \"world\"] |> should equal \"hello world\"\n        StringUtils.strConcat [] |> should equal \"\"\n\n    [<Fact>]\n    let ``StringUtils.strSplit should split a string by given separators`` () =\n        StringUtils.strSplit [|','; ' '|] \"hello, world\" |> should equal [|\"hello\"; \"world\"|]\n        StringUtils.strSplit [|','|] \" hello, world\" |> should equal [|\" hello\"; \" world\"|]\n        StringUtils.strSplit [|' '|] \"hello world\" |> should equal [|\"hello\"; \"world\"|]\n        StringUtils.strSplit [|','|] \"hello world\" |> should equal [|\"hello world\"|]\n        StringUtils.strSplit [|','; ' '|] \"   \" |> should equal [||]\n\nmodule StringParser =\n    [<Fact>]\n    let ``StringParser.parseBoolean should parse true/false case-insensitively`` () =\n        StringParser.parseBoolean \"true\" |> should equal (Some true)\n        StringParser.parseBoolean \"True\" |> should equal (Some true)\n        StringParser.parseBoolean \"TRUE\" |> should equal (Some true)\n        StringParser.parseBoolean \"false\" |> should equal (Some false)\n        StringParser.parseBoolean \"False\" |> should equal (Some false)\n        StringParser.parseBoolean \"FALSE\" |> should equal (Some false)\n        StringParser.parseBoolean \"notabool\" |> should equal None\n        StringParser.parseBoolean \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseInt16 should parse valid int16`` () =\n        StringParser.parseInt16 \"123\" |> should equal (Some 123s)\n        StringParser.parseInt16 \"-32768\" |> should equal (Some -32768s)\n        StringParser.parseInt16 \"32767\" |> should equal (Some 32767s)\n        StringParser.parseInt16 \"notanint\" |> should equal None\n        StringParser.parseInt16 \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseInt32 should parse valid int32`` () =\n        StringParser.parseInt32 \"123\" |> should equal (Some 123)\n        StringParser.parseInt32 \"-2147483648\" |> should equal (Some -2147483648)\n        StringParser.parseInt32 \"2147483647\" |> should equal (Some 2147483647)\n        StringParser.parseInt32 \"notanint\" |> should equal None\n        StringParser.parseInt32 \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseInt64 should parse valid int64`` () =\n        StringParser.parseInt64 \"123\" |> should equal (Some 123L)\n        StringParser.parseInt64 \"-9223372036854775808\" |> should equal (Some -9223372036854775808L)\n        StringParser.parseInt64 \"9223372036854775807\" |> should equal (Some 9223372036854775807L)\n        StringParser.parseInt64 \"notanint\" |> should equal None\n        StringParser.parseInt64 \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseFloat should parse valid floats`` () =\n        StringParser.parseFloat \"123.45\" |> should equal (Some 123.45)\n        StringParser.parseFloat \"-123.45\" |> should equal (Some -123.45)\n        StringParser.parseFloat \"1e10\" |> should equal (Some 1e10)\n        StringParser.parseFloat \"notafloat\" |> should equal None\n        StringParser.parseFloat \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseDecimal should parse valid decimals`` () =\n        StringParser.parseDecimal \"123.45\" |> should equal (Some 123.45M)\n        StringParser.parseDecimal \"-123.45\" |> should equal (Some -123.45M)\n        StringParser.parseDecimal \"1e10\" |> should equal (Some 1e10M)\n        StringParser.parseDecimal \"notadecimal\" |> should equal None\n        StringParser.parseDecimal \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseDateTime should parse valid DateTime`` () =\n        StringParser.parseDateTime \"2021-01-01T12:00:00Z\" |> should equal (Some (DateTime(2021, 1, 1, 12, 0, 0, DateTimeKind.Utc)))\n        StringParser.parseDateTime \"notadatetime\" |> should equal None\n        StringParser.parseDateTime \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseDateTimeOffset should parse valid DateTimeOffset`` () =\n        StringParser.parseDateTimeOffset \"2021-01-01T12:00:00Z\" |> should equal (Some (DateTimeOffset(2021, 1, 1, 12, 0, 0, TimeSpan.Zero)))\n        StringParser.parseDateTimeOffset \"notadatetimeoffset\" |> should equal None\n        StringParser.parseDateTimeOffset \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseTimeSpan should parse valid TimeSpan`` () =\n        StringParser.parseTimeSpan \"12:00:00\" |> should equal (Some (TimeSpan(12, 0, 0)))\n        StringParser.parseTimeSpan \"notatimespan\" |> should equal None\n        StringParser.parseTimeSpan \"\" |> should equal None\n\n    [<Fact>]\n    let ``StringParser.parseGuid should parse valid GUIDs`` () =\n        let guidStr = \"d3b07384-d9a0-4c19-9a0c-0305e1b1c8f2\"\n        let guid = Guid.Parse(guidStr)\n        StringParser.parseGuid guidStr |> should equal (Some guid)\n        StringParser.parseGuid \"notaguid\" |> should equal None\n        StringParser.parseGuid \"\" |> should equal None\n\nmodule StringPatterns =\n    [<Fact>]\n    let ``StringPatterns.IsBool should match valid boolean strings`` () =\n        match \"true\" with\n        | StringPatterns.IsBool b -> b |> should equal true\n        | _ -> failwith \"Did not match\"\n\n        match \"FALSE\" with\n        | StringPatterns.IsBool b -> b |> should equal false\n        | _ -> failwith \"Did not match\"\n\n        match \"notabool\" with\n        | StringPatterns.IsBool _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsTrue`` () =\n        match \"true\" with\n        | StringPatterns.IsTrue _ -> () // Expected\n        | _ -> failwith \"Did not match\"\n\n        match \"false\" with\n        | StringPatterns.IsTrue _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsFalse`` () =\n        match \"false\" with\n        | StringPatterns.IsFalse _ -> () // Expected\n        | _ -> failwith \"Did not match\"\n\n        match \"true\" with\n        | StringPatterns.IsFalse _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsNullOrWhiteSpace should match null or whitespace strings`` () =\n        let isNullOrWhiteSpace (str: string) =\n            match str with\n            | StringPatterns.IsNullOrWhiteSpace -> true\n            | _ -> false\n        isNullOrWhiteSpace null |> should be True\n        isNullOrWhiteSpace \"\" |> should be True\n        isNullOrWhiteSpace \"   \" |> should be True\n        isNullOrWhiteSpace \"hello\" |> should be False\n        isNullOrWhiteSpace \"  hello  \" |> should be False\n\n    [<Fact>]\n    let ``StringPatterns.IsInt16 should match valid int16 strings`` () =\n        match \"123\" with\n        | StringPatterns.IsInt16 i -> i |> should equal 123s\n        | _ -> failwith \"Did not match\"\n\n        match \"notanint\" with\n        | StringPatterns.IsInt16 _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsInt32 should match valid int32 strings`` () =\n        match \"123456\" with\n        | StringPatterns.IsInt32 i -> i |> should equal 123456\n        | _ -> failwith \"Did not match\"\n\n        match \"notanint\" with\n        | StringPatterns.IsInt32 _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsInt64 should match valid int64 strings`` () =\n        match \"1234567890123\" with\n        | StringPatterns.IsInt64 i -> i |> should equal 1234567890123L\n        | _ -> failwith \"Did not match\"\n\n        match \"notanint\" with\n        | StringPatterns.IsInt64 _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsFloat should match valid float strings`` () =\n        match \"123.45\" with\n        | StringPatterns.IsFloat f -> f |> should equal 123.45\n        | _ -> failwith \"Did not match\"\n\n        match \"notafloat\" with\n        | StringPatterns.IsFloat _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsDecimal should match valid decimal strings`` () =\n        match \"123.45\" with\n        | StringPatterns.IsDecimal d -> d |> should equal 123.45M\n        | _ -> failwith \"Did not match\"\n\n        match \"notadecimal\" with\n        | StringPatterns.IsDecimal _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsDateTime should match valid DateTime strings`` () =\n        let dateTimeStr = \"2021-01-01T12:00:00Z\"\n        let expectedDateTime = DateTime(2021, 1, 1, 12, 0, 0, DateTimeKind.Utc)\n\n        match dateTimeStr with\n        | StringPatterns.IsDateTime dt -> dt |> should equal expectedDateTime\n        | _ -> failwith \"Did not match\"\n\n        match \"notadatetime\" with\n        | StringPatterns.IsDateTime _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsDateTimeOffset should match valid DateTimeOffset strings`` () =\n        let dtoStr = \"2021-01-01T12:00:00Z\"\n        let expectedDto = DateTimeOffset(2021, 1, 1, 12, 0, 0, TimeSpan.Zero)\n\n        match dtoStr with\n        | StringPatterns.IsDateTimeOffset dto -> dto |> should equal expectedDto\n        | _ -> failwith \"Did not match\"\n\n        match \"notadatetimeoffset\" with\n        | StringPatterns.IsDateTimeOffset _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n\n    [<Fact>]\n    let ``StringPatterns.IsGuid should match valid GUID strings`` () =\n        let guidStr = \"d3b07384-d9a0-4c19-9a0c-0305e1b1c8f2\"\n        let expectedGuid = Guid.Parse(guidStr)\n\n        match guidStr with\n        | StringPatterns.IsGuid g -> g |> should equal expectedGuid\n        | _ -> failwith \"Did not match\"\n\n        match \"notaguid\" with\n        | StringPatterns.IsGuid _ -> failwith \"Should not have matched\"\n        | _ -> () // Expected\n"
  },
  {
    "path": "test/Falco.Tests/WebApplicationTests.fs",
    "content": "module Falco.Tests.WebApplication\n\nopen System\nopen System.Collections.Generic\nopen Xunit\nopen FsUnit.Xunit\nopen Microsoft.AspNetCore.Builder\nopen Microsoft.AspNetCore.Http\nopen Microsoft.AspNetCore.Routing\nopen Microsoft.Extensions.Configuration\nopen Microsoft.Extensions.DependencyInjection\nopen Microsoft.Extensions.Logging\nopen Falco\nopen Falco.Routing\n\ntype IFakeIntService =\n    abstract member GetValue : unit -> int\n\ntype IFakeBoolService =\n    abstract member GetValue : unit -> bool\n\ntype FakeIntService() =\n    interface IFakeIntService with\n        member _.GetValue() = 42\n\ntype FakeBoolService() =\n    interface IFakeBoolService with\n        member _.GetValue() = true\n\n// -----------------\n// Test Helpers\n// -----------------\n\nlet emptyHandler : HttpHandler = Response.ofPlainText \"\"\n\nlet createTestBuilder() =\n    let bldr = WebApplication.CreateBuilder([||])\n    bldr.Services.AddRouting() |> ignore\n    bldr\n\n\nlet createTestApp() =\n    let builder = createTestBuilder()\n    builder.Build()\n\n// -----------------\n// WebApplicationBuilder Extensions\n// -----------------\n\n[<Fact>]\nlet ``AddConfiguration should apply configuration builder function`` () =\n    let builder = createTestBuilder()\n    let mutable functionCalled = false\n\n    builder.AddConfiguration(fun config ->\n        functionCalled <- true\n        config\n    ) |> ignore\n\n    functionCalled |> should equal true\n\n[<Fact>]\nlet ``AddConfiguration should modify configuration`` () =\n    let builder = createTestBuilder()\n\n    builder.AddConfiguration(fun config ->\n        config.AddInMemoryCollection([\n            KeyValuePair(\"TestKey\", \"TestValue\")\n        ])\n    ) |> ignore\n\n    let app = builder.Build()\n    app.Configuration.[\"TestKey\"] |> should equal \"TestValue\"\n\n[<Fact>]\nlet ``AddLogging should apply logging builder function`` () =\n    let builder = createTestBuilder()\n    let mutable functionCalled = false\n\n    builder.AddLogging(fun logging ->\n        functionCalled <- true\n        logging\n    ) |> ignore\n\n    functionCalled |> should equal true\n\n[<Fact>]\nlet ``AddLogging should configure logging`` () =\n    let builder = createTestBuilder()\n\n    builder.AddLogging(fun logging ->\n        logging.ClearProviders().SetMinimumLevel(LogLevel.Warning)\n    ) |> ignore\n\n    let app = builder.Build()\n    let logger = app.Services.GetRequiredService<ILogger<obj>>()\n    logger |> should not' (be Null)\n\n[<Fact>]\nlet ``AddServices should register services`` () =\n    let builder = createTestBuilder()\n\n    builder.AddServices(fun config services ->\n        services.AddSingleton<string>(\"test-service\")\n    ) |> ignore\n\n    let app = builder.Build()\n    let service = app.Services.GetRequiredService<string>()\n    service |> should equal \"test-service\"\n\n[<Fact>]\nlet ``AddServices should receive configuration`` () =\n    let builder = createTestBuilder()\n    builder.Configuration.[\"TestKey\"] <- \"TestValue\"\n\n    let mutable receivedConfig : IConfiguration = null\n\n    builder.AddServices(fun config services ->\n        receivedConfig <- config\n        services\n    ) |> ignore\n\n    receivedConfig |> should not' (be Null)\n    receivedConfig.[\"TestKey\"] |> should equal \"TestValue\"\n\n[<Fact>]\nlet ``AddServicesIf should apply when predicate is true`` () =\n    let builder = createTestBuilder()\n\n    builder.AddServicesIf(true, fun _ services ->\n        services.AddSingleton<string>(\"conditional\")\n    ) |> ignore\n\n    let app = builder.Build()\n    let service = app.Services.GetRequiredService<string>()\n    service |> should equal \"conditional\"\n\n[<Fact>]\nlet ``AddServicesIf should not apply when predicate is false`` () =\n    let builder = createTestBuilder()\n\n    builder.AddServicesIf(false, fun _ services ->\n        services.AddSingleton<string>(\"should-not-exist\")\n    ) |> ignore\n\n    let app = builder.Build()\n    let service = app.Services.GetService<string>()\n    service |> should be Null\n\n[<Fact>]\nlet ``AddServices and AddServicesIf can be chained`` () =\n    let builder = createTestBuilder()\n\n    builder\n        .AddServices(fun _ services -> services.AddSingleton<IFakeIntService>(FakeIntService()))\n        .AddServicesIf(true, fun _ services -> services.AddSingleton<string>(\"text\"))\n        .AddServicesIf(false, fun _ services -> services.AddSingleton<IFakeBoolService>(FakeBoolService()))\n        |> ignore\n\n    let app = builder.Build()\n    app.Services.GetRequiredService<IFakeIntService>() |> should be ofExactType<FakeIntService>\n    app.Services.GetRequiredService<string>() |> should equal \"text\"\n    app.Services.GetService<IFakeBoolService>() |> should be null\n\n// -----------------\n// IApplicationBuilder Extensions\n// -----------------\n\n[<Fact>]\nlet ``IApplicationBuilder.UseIf should apply middleware when predicate is true`` () =\n    let app = createTestApp()\n    let mutable middlewareCalled = false\n\n    (app :> IApplicationBuilder).UseIf(true, fun appBuilder ->\n        middlewareCalled <- true\n        appBuilder\n    ) |> ignore\n\n    middlewareCalled |> should equal true\n\n[<Fact>]\nlet ``IApplicationBuilder.UseIf should not apply middleware when predicate is false`` () =\n    let app = createTestApp()\n    let mutable middlewareCalled = false\n\n    (app :> IApplicationBuilder).UseIf(false, fun appBuilder ->\n        middlewareCalled <- true\n        appBuilder\n    ) |> ignore\n\n    middlewareCalled |> should equal false\n\n[<Fact>]\nlet ``IApplicationBuilder.UseFalco should register endpoints`` () =\n    let app = createTestApp()\n    let endpoints = [\n        route GET \"/\" emptyHandler\n        route POST \"/users\" emptyHandler\n    ]\n\n    app.UseRouting().UseFalco(endpoints) |> ignore\n\n    let mutable sum = 0\n    for ds in (app :> IEndpointRouteBuilder).DataSources do\n        sum <- sum + ds.Endpoints.Count\n\n    sum |> should equal endpoints.Length\n\n[<Fact>]\nlet ``IApplicationBuilder.UseFalcoExceptionHandler should register exception handler`` () =\n    let builder = createTestBuilder()\n    let app = builder.Build()\n\n    let exceptionHandler : HttpHandler = fun ctx ->\n        ctx.Response.StatusCode <- 500\n        Response.ofPlainText \"Error occurred\" ctx\n\n    (app :> IApplicationBuilder).UseFalcoExceptionHandler(exceptionHandler) |> ignore\n\n    // This is hard to verify without actually triggering an exception\n    // Just verify it doesn't throw\n    true |> should equal true\n\n// -----------------\n// WebApplication Extensions\n// -----------------\n\n[<Fact>]\nlet ``WebApplication.UseIf should apply when predicate is true`` () =\n    let app = createTestApp()\n    let mutable middlewareCalled = false\n\n    app.UseIf(true, fun appBuilder ->\n        middlewareCalled <- true\n        appBuilder\n    ) |> ignore\n\n    middlewareCalled |> should equal true\n\n[<Fact>]\nlet ``WebApplication.UseIf should not apply when predicate is false`` () =\n    let app = createTestApp()\n    let mutable middlewareCalled = false\n\n    app.UseIf(false, fun appBuilder ->\n        middlewareCalled <- true\n        appBuilder\n    ) |> ignore\n\n    middlewareCalled |> should equal false\n\n// -----------------\n// IEndpointRouteBuilder Extensions\n// -----------------\n\n[<Fact>]\nlet ``UseFalcoEndpoints should add endpoints to data source`` () =\n    let builder = createTestBuilder()\n    let app = builder.Build()\n\n    app.UseRouting() |> ignore\n\n    app.UseEndpoints(fun endpoints ->\n        let falcoEndpoints = [\n            route GET \"/\" emptyHandler\n            route POST \"/users\" emptyHandler\n        ]\n\n        endpoints.UseFalcoEndpoints(falcoEndpoints) |> ignore\n    ) |> ignore\n\n    (app :> IEndpointRouteBuilder).DataSources\n    |> Seq.tryFind (fun ds -> ds :? FalcoEndpointDataSource)\n    |> Option.isSome\n    |> should equal true\n\n[<Fact>]\nlet ``UseFalcoEndpoints should create new data source if not registered`` () =\n    let builder = createTestBuilder()\n    let app = builder.Build()\n\n    app.UseRouting() |> ignore\n\n    app.UseEndpoints(fun endpoints ->\n        endpoints.UseFalcoEndpoints([ route GET \"/\" emptyHandler ]) |> ignore\n    ) |> ignore\n\n    (app :> IEndpointRouteBuilder).DataSources\n    |> Seq.tryFind (fun ds -> ds :? FalcoEndpointDataSource)\n    |> Option.isSome\n    |> should equal true\n\n[<Fact>]\nlet ``UseFalcoEndpoints should reuse registered data source`` () =\n    let builder = createTestBuilder()\n    let dataSource = FalcoEndpointDataSource()\n    builder.Services.AddSingleton<FalcoEndpointDataSource>(dataSource) |> ignore\n\n    let app = builder.Build()\n    app.UseRouting() |> ignore\n\n    app.UseEndpoints(fun endpoints ->\n        endpoints.UseFalcoEndpoints([ route GET \"/test\" emptyHandler ]) |> ignore\n    ) |> ignore\n\n    (app :> IEndpointRouteBuilder).DataSources\n    |> Seq.tryFind (fun ds -> ds :? FalcoEndpointDataSource)\n    |> Option.isSome\n    |> should equal true\n\n// -----------------\n// HttpContext Extensions\n// -----------------\n\n[<Fact>]\nlet ``HttpContext.Plug should resolve registered service`` () =\n    let builder = createTestBuilder()\n    builder.Services.AddSingleton<string>(\"test-dependency\") |> ignore\n    let app = builder.Build()\n\n    let ctx = DefaultHttpContext()\n    ctx.RequestServices <- app.Services\n\n    let dependency = ctx.Plug<string>()\n    dependency |> should equal \"test-dependency\"\n\n[<Fact>]\nlet ``HttpContext.Plug should throw on missing service`` () =\n    let app = createTestApp()\n\n    let ctx = DefaultHttpContext()\n    ctx.RequestServices <- app.Services\n\n    (fun () -> ctx.Plug<IDisposable>() |> ignore)\n    |> should throw typeof<InvalidOperationException>\n\n[<Fact>]\nlet ``HttpContext.Plug should resolve different service types`` () =\n    let builder = createTestBuilder()\n    builder.Services.AddSingleton<IFakeIntService>(FakeIntService()) |> ignore\n    builder.Services.AddSingleton<string>(\"text\") |> ignore\n    builder.Services.AddSingleton<IFakeBoolService>(FakeBoolService()) |> ignore\n    let app = builder.Build()\n\n    let ctx = DefaultHttpContext()\n    ctx.RequestServices <- app.Services\n\n    ctx.Plug<IFakeIntService>() |> should be ofExactType<FakeIntService>\n    ctx.Plug<string>() |> should equal \"text\"\n    ctx.Plug<IFakeBoolService>() |> should be ofExactType<FakeBoolService>\n\n// -----------------\n// Integration Tests\n// -----------------\n\n[<Fact>]\nlet ``Full pipeline with builder and app extensions`` () =\n    let builder = createTestBuilder()\n\n    builder\n        .AddConfiguration(fun config ->\n            config.AddInMemoryCollection([\n                KeyValuePair(\"AppName\", \"TestApp\")\n            ])\n        )\n        .AddServices(fun config services ->\n            services.AddSingleton<string>(config.[\"AppName\"])\n        )\n        |> ignore\n\n    let app = builder.Build()\n\n    app\n        .UseRouting()\n        .UseFalco([\n            route GET \"/\" (fun ctx ->\n                let appName = ctx.Plug<string>()\n                Response.ofPlainText appName ctx\n            )\n        ])\n        |> ignore\n\n    let appName = app.Services.GetRequiredService<string>()\n    appName |> should equal \"TestApp\"\n\n[<Fact>]\nlet ``Multiple UseFalco calls should accumulate endpoints`` () =\n    let app = createTestApp()\n    app.UseRouting() |> ignore\n\n    app.UseFalco([ route GET \"/first\" emptyHandler ]) |> ignore\n    app.UseFalco([ route POST \"/second\" emptyHandler ]) |> ignore\n\n    let mutable sum = 0\n    for ds in (app :> IEndpointRouteBuilder).DataSources do\n        sum <- sum + ds.Endpoints.Count\n\n    sum |> should equal 2\n"
  }
]