Skip to content

Commit

Permalink
Net7 (#11)
Browse files Browse the repository at this point in the history
* net7
* Deprecate appRun, appMap, appMapWhen
* Deprecate WebPart.choose, prefer choice 
* GitHub Workflow
  • Loading branch information
wallymathieu authored Aug 31, 2023
1 parent ae63e1e commit 1b0b4c9
Show file tree
Hide file tree
Showing 19 changed files with 509 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: csharp
sudo: required
dist: trusty

dotnet: 2.1.401
dotnet: 7.0.101
mono:
- latest # => "stable release"
os:
Expand Down
33 changes: 33 additions & 0 deletions FSharpPlus.AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpPlus.AspNetCore", "src\FSharpPlus.AspNetCore\FSharpPlus.AspNetCore.fsproj", "{5D30E174-2538-47AC-8443-318C8C5DC2C9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}"
ProjectSection(SolutionItems) = preProject
tests\Notes.http = tests\Notes.http
EndProjectSection
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "tests\Tests\Tests.fsproj", "{1CA2E092-2320-451D-A4F0-9ED7C7C528CA}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpPlus.AspNetCore.Suave", "src\FSharpPlus.AspNetCore.Suave\FSharpPlus.AspNetCore.Suave.fsproj", "{BEAAD467-E161-4357-9C60-6AC2218DFCCA}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Notes", "tests\Notes\Notes.fsproj", "{E10EF48E-BF15-4252-80BB-754AD10B9FC4}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Todos", "tests\Todos\Todos.fsproj", "{278E912E-C08B-407F-A091-B3B1CFE4AE60}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -62,10 +69,36 @@ Global
{BEAAD467-E161-4357-9C60-6AC2218DFCCA}.Release|x64.Build.0 = Release|Any CPU
{BEAAD467-E161-4357-9C60-6AC2218DFCCA}.Release|x86.ActiveCfg = Release|Any CPU
{BEAAD467-E161-4357-9C60-6AC2218DFCCA}.Release|x86.Build.0 = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|x64.ActiveCfg = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|x64.Build.0 = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|x86.ActiveCfg = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Debug|x86.Build.0 = Debug|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|Any CPU.Build.0 = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|x64.ActiveCfg = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|x64.Build.0 = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|x86.ActiveCfg = Release|Any CPU
{E10EF48E-BF15-4252-80BB-754AD10B9FC4}.Release|x86.Build.0 = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|Any CPU.Build.0 = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|x64.ActiveCfg = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|x64.Build.0 = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|x86.ActiveCfg = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Debug|x86.Build.0 = Debug|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|Any CPU.ActiveCfg = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|Any CPU.Build.0 = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|x64.ActiveCfg = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|x64.Build.0 = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|x86.ActiveCfg = Release|Any CPU
{278E912E-C08B-407F-A091-B3B1CFE4AE60}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5D30E174-2538-47AC-8443-318C8C5DC2C9} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
{1CA2E092-2320-451D-A4F0-9ED7C7C528CA} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}
{BEAAD467-E161-4357-9C60-6AC2218DFCCA} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
{E10EF48E-BF15-4252-80BB-754AD10B9FC4} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}
{278E912E-C08B-407F-A091-B3B1CFE4AE60} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}
EndGlobalSection
EndGlobal
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.100",
"rollForward": "latestMinor"
"rollForward": "latestFeature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
</ItemGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.2" />
<PackageReference Include="FSharpPlus" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.*" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.*" />
<PackageReference Include="TaskBuilder.fs" Version="2.1.0" />
<PackageReference Update="FSharp.Core" Version="7.0.200" />
<PackageReference Include="FSharpPlus" Version="1.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
</Project>
3 changes: 2 additions & 1 deletion src/FSharpPlus.AspNetCore.Suave/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Microsoft.AspNetCore.Builder
open System.Net
open System.Text
open System
open System.IO
open System.Text.RegularExpressions

// setup something that reminds us of what Suave can work with
Expand All @@ -30,6 +31,7 @@ module WebPart=
/// by iterating the options, applying the context, arg, to the predicate
/// from the list of options, until there's a match/a Some(x) which can be
/// run.
[<Obsolete("Use choice")>]
let choose (options : WebPart<'a> list) = fun x -> choice (List.map ((|>) x) options)
let inline fail (_:'a) : OptionT<Async<'a option>> = OptionT <| async.Return None

Expand Down Expand Up @@ -120,7 +122,6 @@ module Request =



open FSharp.Control.Tasks.V2
let appRun (app:WebPart<Context>) (appBuilder:IApplicationBuilder)=
let appRun (func:HttpContext->#Task) (b: IApplicationBuilder) =
b.Run(RequestDelegate(fun ctx->func ctx :> Task))
Expand Down
10 changes: 5 additions & 5 deletions src/FSharpPlus.AspNetCore/FSharpPlus.AspNetCore.fsproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand All @@ -20,9 +20,9 @@
<Compile Include="Library.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="FSharp.Core" Version="4.6.2" />
<PackageReference Include="FSharpPlus" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.*" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.*" />
<PackageReference Update="FSharp.Core" Version="7.0.200" />
<PackageReference Include="FSharpPlus" Version="1.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
</ItemGroup>
</Project>
3 changes: 3 additions & 0 deletions src/FSharpPlus.AspNetCore/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ open Microsoft.Extensions.DependencyInjection
open System.Threading.Tasks

module HttpAdapter=
[<Obsolete("Prefer app.Map")>]
let appMap (path: string) (map : IApplicationBuilder->unit) (app: IApplicationBuilder): IApplicationBuilder =
app.Map(PathString(path), Action<_>(map))
[<Obsolete("Prefer app.Run")>]
let appRun (func:HttpContext->#Task) (b: IApplicationBuilder) =
b.Run(RequestDelegate(fun ctx->func ctx :> Task))
[<Obsolete("Prefer app.MapWhen")>]
let appMapWhen (when':HttpContext->bool) then' (b: IApplicationBuilder) =
b.MapWhen(Func<_,_>(when'), Action<_>(then'))
22 changes: 22 additions & 0 deletions tests/Notes.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### Create note
# auth: echo "{\"sub\":\"1\"}" | openssl base64
POST http://localhost:5191/v1/notes
Content-Type: application/x-www-form-urlencoded
x-jwt-payload: eyJzdWIiOiIxIn0K

text=my%20text

### Get notes

GET http://localhost:5191/v1/notes
Accept: application/json
x-jwt-payload: eyJzdWIiOiIxIn0K

### Get note

GET http://localhost:5191/v1/notes/1
Accept: application/json
x-jwt-payload: eyJzdWIiOiIxIn0K



206 changes: 206 additions & 0 deletions tests/Notes/Main.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
module Notes
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open System
open System.Net.Http
open System.Text
open System.Collections.Generic
open System.Threading.Tasks

open FSharpPlus
open FSharpPlus.Data

open Fleece.FSharpData
open Fleece.FSharpData.Operators

Check warning on line 15 in tests/Notes/Main.fs

View workflow job for this annotation

GitHub Actions / build

This construct is deprecated. It will open the compatibility portion of the Operators module. Functions here will be removed in future versions. To use current functions, try removing this 'open' and make sure Fleece namespace is opened.

Check warning on line 15 in tests/Notes/Main.fs

View workflow job for this annotation

GitHub Actions / build

This construct is deprecated. It will open the compatibility portion of the Operators module. Functions here will be removed in future versions. To use current functions, try removing this 'open' and make sure Fleece namespace is opened.

open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http

open FSharpPlus.AspNetCore
open FSharpPlus.AspNetCore.Suave
open HttpAdapter
open Successful
open RequestErrors
open Filters
open Writers
type JwtPayload = { subject:string }
with
static member OfJson json:ParseResult<JwtPayload> =
let create sub= { subject =sub }
match json with
| JObject o -> create <!> (o .@ "sub")
| x -> Decode.Fail.objExpected x
let authenticated f =
fun (ctx:Http.Context) ->
match Request.Header.tryGet "x-jwt-payload" ctx.request with
| Some u ->
string u
|> Convert.FromBase64String
|> Encoding.UTF8.GetString
|> parseJson
|> Result.map( fun (payload:JwtPayload)->tryParse payload.subject)
|> function | Ok (Some user)-> f ctx user
| _ -> UNAUTHORIZED "" ctx
| None -> UNAUTHORIZED "" ctx

module Json=
let inline OK v=
OK (string v)
>=> setContentType "application/json; charset=utf-8"
let inline CREATED v=
CREATED (string v)
>=> setContentType "application/json; charset=utf-8"
let inline BAD_REQUEST v =
BAD_REQUEST (string v)
>=> setContentType "application/json; charset=utf-8"
[<Struct>]
type UserId = UserId of string
with
override this.ToString()=match this with UserId uId->uId
[<Struct>]
type NoteId = NoteId of int
with
override this.ToString()=match this with NoteId nId -> nId.ToString()
static member ToJson (NoteId x) = JNumber (decimal x)
static member OfJson json = ofJson json |> Result.map NoteId
type Note = { id: NoteId; text: string }

type Note with
static member JsonObjCodec =
fun id text -> { id = id; text = text }
<!> jreq "id" (fun x -> Some x.id )
<*> jreq "text" (fun x -> Some x.text )
|> Codec.ofConcrete
module Note=
let id (n:Note)=n.id
type NoteList = { notes: Note list; offset: int; chunk: int; total: int }
type NoteList with
static member JsonObjCodec =
fun notes offset chunk total -> { notes = notes; offset = offset; chunk = chunk; total=total }
<!> jreq "notes" (fun x -> Some x.notes )
<*> jreq "offset" (fun x -> Some x.offset )
<*> jreq "chunk" (fun x -> Some x.chunk )
<*> jreq "total" (fun x -> Some x.total )
|> Codec.ofConcrete

type IDb =
abstract member GetUserNotes: UserId -> Async<NoteList>
abstract member AddUserNote: UserId -> string -> Async<Note>
abstract member GetNote: NoteId ->Async<Note option>
abstract member UpdateUserNote: UserId -> NoteId -> string -> Async<Note option>

let webApp (db: IDb) =
let overview =
GET >=> (authenticated <| fun ctx userId -> monad {
let! res = lift (db.GetUserNotes <| UserId userId)
let ovm = toJson res |> string
return! Json.OK ovm ctx
})
let getNote (id:int)=
GET >=> (fun ctx -> monad {
let! maybeNote = lift (db.GetNote <| NoteId id)
return! Json.OK (toJson maybeNote) ctx
})
let getNotePart (id:int, part:int)=
GET >=> (fun ctx -> monad {
let! maybeNote = lift (db.GetNote <| NoteId id)
let json = maybeNote |> map(fun (n:Note)-> n.text.Substring(0,part)) |> toJson
return! Json.OK json ctx
})
let register =
POST >=> hasFormContentType >=> (authenticated <| fun ctx userId -> monad {
match ctx.request |> Request.Form.tryGet "text" with
| Some text ->
let! newNote = lift (db.AddUserNote (UserId userId) (string text))
return! Json.CREATED (toJson newNote) ctx
| None ->
return! BAD_REQUEST "Could not find text" ctx
})
let updateNote (id:int) =
PUT >=> hasFormContentType >=> (authenticated <| fun ctx userId -> monad {
match ctx.request |> Request.Form.tryGet "text" with
| Some text ->
match! lift (db.UpdateUserNote (UserId userId) (NoteId id) (string text)) with
| Some note-> return! Json.OK (toJson note) ctx
| None -> return! NOT_FOUND "Could not find note" ctx
| None ->
return! BAD_REQUEST "Could not find text" ctx
})
let v1=
choice [ path "/" >=> (OK "/")
path "/notes" >=> register
pathScan "/notes/%d" getNote
pathScan "/notes/%d/_/%d" getNotePart
path "/notes" >=> overview ]
let v2=
choice [ path "/" >=> (OK "/")
path "/notes" >=> register
pathScan "/notes/%d" getNote
pathScan "/notes/%d" updateNote
path "/notes" >=> overview ]
(v1,v2)
module HttpAdapter=
let indexHtml = """
<!DOCTYPE html>
<html>
<head>
<title>Some service</title>
<meta charset="utf-8" />
</head>
<body>
</body>
</html>
"""


let index(context: HttpContext) : Task =
context.Response.ContentType <- "text/html; charset=utf-8"
//Console.WriteLine("Index")
context.Response.WriteAsync(indexHtml)
let configuration (svc: IDb) (app: IApplicationBuilder)=
let (webAppV1,webAppV2)=webApp svc
app.Map("/index.html",fun app ->app.Run(index))
.Map("/v1",Suave.appRun webAppV1)
.Map("/v2",Suave.appRun webAppV2)
|> ignore
[<EntryPoint>]
let main argv =
let builder = WebApplication.CreateBuilder(argv);

let app = builder.Build()
let inMemoryDb() =
let withUserId userId = (=) userId << fst
let withId id = (=) id << Note.id<< snd
let mutable notes = []
{new IDb with
member __.GetUserNotes userId =
let notes = List.filter (withUserId userId) notes |> List.map snd
async{ return { notes = notes; offset = 0; chunk = 100; total=notes.Length } }

member __.AddUserNote userId text=
let note = {id=NoteId <| notes.Length+1; text=text}
notes<-(userId,note) :: notes
async { return note }
member __.UpdateUserNote userId id text=
let maybeNote = List.tryFind (withId id) notes |> Option.map snd
match maybeNote with
| Some note->
let nextNote = {note with text=text }
notes<-(userId,note) :: List.filter (not << withId id) notes
async.Return <| Some nextNote
| None -> async.Return None
member __.GetNote id=
let note = List.tryFind (withId id) notes |> Option.map snd
async { return note }
}

HttpAdapter.configuration (inMemoryDb()) app

// Configure the HTTP request pipeline.
if (not <| app.Environment.IsDevelopment()) then
app.UseExceptionHandler("/Error") |> ignore

app.Run()
0
15 changes: 15 additions & 0 deletions tests/Notes/Notes.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Todos</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/FSharpPlus.AspNetCore/FSharpPlus.AspNetCore.fsproj" />
<ProjectReference Include="../../src/FSharpPlus.AspNetCore.Suave/FSharpPlus.AspNetCore.Suave.fsproj" />
<PackageReference Include="Fleece.FSharpData" Version="0.10.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="Main.fs" />
</ItemGroup>
</Project>
Loading

0 comments on commit 1b0b4c9

Please sign in to comment.