Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinmoris committed Nov 26, 2020
2 parents 115ea9e + 466f1f8 commit 7f47e64
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 13 deletions.
66 changes: 66 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ An in depth functional reference to all of Giraffe's default features.
- [Serialization](#serialization)
- [JSON](#json)
- [XML](#xml)
- [Testing](#testing)
- [Miscellaneous](#miscellaneous)
- [Short GUIDs and Short IDs](#short-guids-and-short-ids)
- [Common Helper Functions](#common-helper-functions)
Expand Down Expand Up @@ -1048,6 +1049,10 @@ In the above scenario it is not clear which one of the two http handlers a user

If you want to learn more about `Regex` please check the [Regular Expression Language Reference](https://docs.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference).

#### routexp

The `routexp` http handler is a combination of `routex` and `routef`. It resolves a route exactly like `routex`, but then passes the resolved Regex Groups as a `Seq<string>` parameter into the supplied handler function similar to how `routef` invokes the next handler in the pipeline.

#### routeCix

The `routeCix` http handler is the case insensitive version of `routex`:
Expand Down Expand Up @@ -3125,6 +3130,67 @@ let customHandler (dataObj : obj) : HttpHandler =
// ... do more...
```

## Testing

Testing a Giraffe application follows the concept of [ASP.NET Core testing](https://docs.microsoft.com/en-us/aspnet/core/test/middleware?view=aspnetcore-3.1).

### Necessary imports:

```fsharp
open FSharp.Control.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.TestHost
open Microsoft.AspNetCore.Hosting
open System.Net.Http
```

### Build a test host:

```fsharp
let getTestHost() =
WebHostBuilder()
.UseTestServer()
.Configure(Action<IApplicationBuilder> [YourApp].configureApp)
.ConfigureServices([YourApp].configureServices)
.ConfigureLogging([YourApp].configureLogging)
.UseUrls([YourUrl])
```

### Create a helper function to issue test requests:

```fsharp
let testRequest (request : HttpRequestMessage) =
let resp = task {
use server = new TestServer(getTestHost())
use client = server.CreateClient()
let! response = request |> client.SendAsync
return response
}
resp.Result
```

### Examples (using Xunit):

```fsharp
// Import needed for the code below:
open System.Net
[<Fact>]
let ``Hello world endpoint says hello`` () =
let response = testRequest (new HttpRequestMessage(HttpMethod.Get, "/hello-world"))
let content = response.Content.ReadAsStringAsync().Result
Assert.Equal(response.StatusCode, HttpStatusCode.OK)
Assert.Equal(content, "hello")
[<Fact>]
let ``Example HTTP Post`` () =
let request = new HttpRequestMessage(HttpMethod.Post, "/hello-world")
request.Content <- "{\"JsonField\":\"JsonValue\"}"
let response = testRequest request
Assert.Equal(response.StatusCode, HttpStatusCode.OK)
// Check the json content
```

## Miscellaneous

On top of default HTTP related functions such as `HttpContext` extension methods and `HttpHandler` functions Giraffe also provides a few other helper functions which are commonly required in Giraffe web applications.
Expand Down
6 changes: 6 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Release Notes
=============

## 5.0.0-rc-2

- Fixed pre-conditions validation issue (see [#424](https://github.com/giraffe-fsharp/Giraffe/issues/424))
- Fixed parsing issue with Guids and ShortIds in `Giraffe.EndpointRouting` (see [#447](https://github.com/giraffe-fsharp/Giraffe/issues/447))
- Added `routexp` http handler to default router (see [#446](https://github.com/giraffe-fsharp/Giraffe/issues/446))

## 5.0.0-rc-1

Upgraded to .NET 5. The 5.x version of Giraffe is targeting `net5.0` and dropping support for all other target frameworks. If you cannot upgrade a project to .NET 5 yet then stay on an older version of Giraffe until you can. Giraffe has always been a .NET Core centered project and in the .NET Core world (and now .NET 5 world) there is little to no reason why a project should remain on an old .NET Core version for a long time when upgrade paths are mostly as simple as changing the `<TargetFramework>` property in an `.fsproj` file.
Expand Down
3 changes: 3 additions & 0 deletions src/Giraffe/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type DateTimeOffset with
/// <returns>Formatted string value.</returns>
member this.ToIsoString() = this.ToString("o")

member this.CutOffMs() =
DateTimeOffset(this.Year, this.Month, this.Day, this.Hour, this.Minute, this.Second, 0, this.Offset)

// ---------------------------
// Common helper functions
// ---------------------------
Expand Down
5 changes: 2 additions & 3 deletions src/Giraffe/EndpointRouting.fs
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,11 @@ open FSharp.Control.Tasks.V2.ContextInsensitive
// | routeStartsWithf | Can't do, see subRoutef |
// | routeStartsWithCif | Can't do, see subRoutef |


module private RouteTemplateBuilder =
let private guidPattern =
"([0-9A-Fa-f]{8}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{4}\-[0-9A-Fa-f]{12}|[0-9A-Fa-f]{32}|[-_0-9A-Za-z]{22})"
"([0-9A-Fa-f]{{8}}\-[0-9A-Fa-f]{{4}}\-[0-9A-Fa-f]{{4}}\-[0-9A-Fa-f]{{4}}\-[0-9A-Fa-f]{{12}}|[0-9A-Fa-f]{{32}}|[-_0-9A-Za-z]{{22}})"
let private shortIdPattern =
"([-_0-9A-Za-z]{10}[048AEIMQUYcgkosw])"
"([-_0-9A-Za-z]{{10}}[048AEIMQUYcgkosw])"

let private getConstraint (i : int) (c : char) =
let name = sprintf "%c%i" c i
Expand Down
10 changes: 6 additions & 4 deletions src/Giraffe/Preconditional.fs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ type HttpContext with
match lastModified with
| None -> AllConditionsMet
| Some lastModified ->
match requestHeaders.IfUnmodifiedSince.Value > DateTimeOffset.UtcNow
let lastModified = lastModified.CutOffMs()
match requestHeaders.IfUnmodifiedSince.Value > DateTimeOffset.UtcNow.CutOffMs()
|| requestHeaders.IfUnmodifiedSince.Value >= lastModified with
| true -> AllConditionsMet
| false -> ConditionFailed
Expand Down Expand Up @@ -81,7 +82,8 @@ type HttpContext with
match lastModified with
| None -> AllConditionsMet
| Some lastModified ->
match requestHeaders.IfModifiedSince.Value <= DateTimeOffset.UtcNow
let lastModified = lastModified.CutOffMs()
match requestHeaders.IfModifiedSince.Value <= DateTimeOffset.UtcNow.CutOffMs()
&& requestHeaders.IfModifiedSince.Value < lastModified with
| true -> AllConditionsMet
| false -> ResourceNotModified
Expand Down Expand Up @@ -136,8 +138,8 @@ type HttpContext with
| ResourceNotModified -> ResourceNotModified

// Set ETag and Last-Modified in the response
if eTag.IsSome then responseHeaders.ETag <- eTag.Value
if lastModified.IsSome then responseHeaders.LastModified <- Nullable(lastModified.Value)
if eTag.IsSome then responseHeaders.ETag <- eTag.Value
if lastModified.IsSome then responseHeaders.LastModified <- Nullable(lastModified.Value.CutOffMs())

// Validate headers in correct precedence
// RFC: https://tools.ietf.org/html/rfc7232#section-6
Expand Down
34 changes: 28 additions & 6 deletions src/Giraffe/Routing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,28 @@ let routex (path : string) : HttpHandler =
| true -> next ctx
| false -> skipPipeline

/// <summary>
/// Filters an incoming HTTP request based on the request path using Regex (case sensitive).
///
/// If the route matches the incoming HTTP request then the Regex groups will be passed into the supplied `routeHandler`.
///
/// This is similar to routex but also allows to use matched strings as parameters for a controller.
/// </summary>
/// <param name="path">Regex path.</param>
/// <param name="routeHandler">A function which accepts a string sequence of the matched groups and returns a `HttpHandler` function which will subsequently deal with the request.</param>
/// <returns>A Giraffe <see cref="HttpHandler"/> function which can be composed into a bigger web application.</returns>
let routexp (path : string) (routeHandler : seq<string> -> HttpHandler): HttpHandler =
let pattern = sprintf "^%s$" path
let regex = Regex(pattern, RegexOptions.Compiled)

fun (next : HttpFunc) (ctx : Microsoft.AspNetCore.Http.HttpContext) ->
let result = regex.Match (SubRouting.getNextPartOfPath ctx)
match result.Success with
| true ->
let args = result.Groups |> Seq.map (fun x -> x.Value)
routeHandler args next ctx
| false -> skipPipeline

/// <summary>
/// Filters an incoming HTTP request based on the request path using Regex (case insensitive).
/// </summary>
Expand All @@ -123,7 +145,7 @@ let routeCix (path : string) : HttpHandler =
/// <summary>
/// Filters an incoming HTTP request based on the request path (case sensitive).
/// If the route matches the incoming HTTP request then the arguments from the <see cref="Microsoft.FSharp.Core.PrintfFormat"/> will be automatically resolved and passed into the supplied routeHandler.
///
///
/// Supported format chars**
///
/// %b: bool
Expand All @@ -148,7 +170,7 @@ let routef (path : PrintfFormat<_,_,_,_, 'T>) (routeHandler : 'T -> HttpHandler)
/// <summary>
/// Filters an incoming HTTP request based on the request path.
/// If the route matches the incoming HTTP request then the arguments from the <see cref="Microsoft.FSharp.Core.PrintfFormat"/> will be automatically resolved and passed into the supplied routeHandler.
///
///
/// Supported format chars**
///
/// %b: bool
Expand Down Expand Up @@ -226,7 +248,7 @@ let routeStartsWithCi (subPath : string) : HttpHandler =
/// <summary>
/// Filters an incoming HTTP request based on the beginning of the request path (case sensitive).
/// If the route matches the incoming HTTP request then the arguments from the <see cref="Microsoft.FSharp.Core.PrintfFormat"/> will be automatically resolved and passed into the supplied routeHandler.
///
///
/// Supported format chars**
///
/// %b: bool
Expand Down Expand Up @@ -254,7 +276,7 @@ let routeStartsWithf (path : PrintfFormat<_,_,_,_, 'T>) (routeHandler : 'T -> Ht
/// <summary>
/// Filters an incoming HTTP request based on the beginning of the request path (case insensitive).
/// If the route matches the incoming HTTP request then the arguments from the <see cref="Microsoft.FSharp.Core.PrintfFormat"/> will be automatically resolved and passed into the supplied routeHandler.
///
///
/// Supported format chars**
///
/// %b: bool
Expand Down Expand Up @@ -312,7 +334,7 @@ let subRouteCi (path : string) (handler : HttpHandler) : HttpHandler =
/// <summary>
/// Filters an incoming HTTP request based on a part of the request path (case sensitive).
/// If the sub route matches the incoming HTTP request then the arguments from the <see cref="Microsoft.FSharp.Core.PrintfFormat"/> will be automatically resolved and passed into the supplied routeHandler.
///
///
/// Supported format chars
///
/// %b: bool
Expand All @@ -322,7 +344,7 @@ let subRouteCi (path : string) (handler : HttpHandler) : HttpHandler =
/// %d: int64
/// %f: float/double
/// %O: Guid
///
///
/// Subsequent routing handlers inside the given handler function should omit the already validated path.
/// </summary>
/// <param name="path">A format string representing the expected request sub path.</param>
Expand Down

0 comments on commit 7f47e64

Please sign in to comment.