Skip to content

Commit

Permalink
Http handlers and adapter for Giraffe
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaid-Ajaj committed Oct 20, 2017
1 parent 0683b34 commit 818fe86
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 82 deletions.
4 changes: 2 additions & 2 deletions .paket/Paket.Restore.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 30,7 @@
<DisableImplicitSystemValueTupleReference>true</DisableImplicitSystemValueTupleReference>
</PropertyGroup>

<Target Name="PaketRestore" BeforeTargets="_GenerateDotnetCliToolReferenceSpecs;_GenerateProjectRestoreGraphPerFramework;_GenerateRestoreGraphWalkPerFramework;CollectPackageReferences" >
<Target Name="PaketRestore" Condition="'$(PaketRestoreDisabled)' != 'True'" BeforeTargets="_GenerateDotnetCliToolReferenceSpecs;_GenerateProjectRestoreGraphPerFramework;_GenerateRestoreGraphWalkPerFramework;CollectPackageReferences" >

<!-- Step 1 Check if lockfile is properly restored -->
<PropertyGroup>
Expand Down Expand Up @@ -88,7 88,7 @@
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)"' Condition=" '$(PaketRestoreRequired)' == 'true' " ContinueOnError="false" />

<!-- This shouldn't actually happen, but just to be sure. -->
<Error Condition=" !Exists('$(PaketResolvedFilePath)') AND '$(TargetFramework)' != '' " Text="A paket file for the framework '$(TargetFramework)' is missing. Please delete 'paket-files/paket.restore.cached' and call 'paket restore'." />
<Error Condition=" !Exists('$(PaketResolvedFilePath)') AND '$(TargetFramework)' != '' AND '$(ResolveNuGetPackages)' != 'False' " Text="Paket file '$(PaketResolvedFilePath)' is missing while restoring $(MSBuildProjectFile). Please delete 'paket-files/paket.restore.cached' and call 'paket restore'." />

<!-- Step 4 forward all msbuild properties (PackageReference, DotNetCliToolReference) to msbuild -->
<ReadLinesFromFile Condition="Exists('$(PaketResolvedFilePath)')" File="$(PaketResolvedFilePath)" >
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
Expand All @@ -6,9 7,8 @@
<Compile Include="src/QUnit.fs" />
<Compile Include="src/App.fs" />
</ItemGroup>

<Import Project="..\.paket\Paket.Restore.targets" />
<ItemGroup>
<ProjectReference Include="../Fable.Remoting.Client/Fable.Remoting.Client.fsproj" />
</ItemGroup>
<Import Project="..\.paket\Paket.Restore.targets" />
</Project>
19 changes: 15 additions & 4 deletions Fable.Remoting.Giraffe.Tests/FableGiraffeAdapterTests.fs
Original file line number Diff line number Diff line change
@@ -1,5 1,15 @@
module FableGiraffeAdapterTests

open System
open System.Net
open System.Net.Http
open System.IO
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.TestHost
open Microsoft.Extensions.DependencyInjection
open Giraffe.Middleware
open Giraffe.HttpHandlers
open Fable.Remoting.Giraffe
open System.Net.Http
open System
Expand All @@ -13,10 23,11 @@ let pass () = Expect.equal true true ""
let fail () = Expect.equal false true ""

//FableGirrafeAdapter.logger <- Some (printfn "%s")
//let app = FableGirrafeAdapter.webPartFor implementation
let postContent (input: string) = new StringContent(input, System.Text.Encoding.UTF8)


let app : HttpHandler = FableGirrafeAdapter.webPartFor implementation
let postContent (input: string) = new StringContent(input, Text.Encoding.UTF8)
let createHost() =
WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
let fableGiraffeAdapterTests =
testList "FableGiraffeAdapter tests" [
testCase "Sending string as input works" <| fun () ->
Expand Down
3 changes: 3 additions & 0 deletions Fable.Remoting.Giraffe.Tests/paket.references
Original file line number Diff line number Diff line change
@@ -1,3 1,6 @@
FSharp.Core
Newtonsoft.Json
Expecto
Giraffe
Microsoft.AspNetCore.TestHost
Microsoft.AspNetCore.Hosting
89 changes: 88 additions & 1 deletion Fable.Remoting.Giraffe/FableGiraffeAdapter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 4,93 @@ open FSharp.Reflection
open Newtonsoft.Json
open Fable.Remoting.Json
open Fable.Remoting.Server
open Microsoft.AspNetCore.Http
open Giraffe.HttpHandlers
open Giraffe.Tasks

module FableGirrafeAdapter =
let hello = ""

open System.Text
open System.IO

let mutable logger : (string -> unit) option = None
let private fableConverter = FableJsonConverter()
let private writeLn text (sb: StringBuilder) = sb.AppendLine(text) |> ignore; sb
let private write (sb: StringBuilder) text = sb.AppendLine(text) |> ignore

let private logDeserialization (logf: (string -> unit) option) (text: string) (inputType: System.Type) =
logger
|> Option.iter (fun log ->
StringBuilder()
|> writeLn "Fable.Remoting:"
|> writeLn "About to deserialize JSON:"
|> writeLn text
|> writeLn (sprintf "Into .NET Type: %s" inputType.FullName)
|> writeLn ""
|> fun sb -> log (sb.ToString())
)


/// Deserialize a json string using FableConverter
let deserialize (json: string) (inputType: System.Type) =
logDeserialization logger json inputType
let parameterTypes = [| typeof<string>; typeof<System.Type>; typeof<JsonConverter array> |]
let deserialize = typeof<JsonConvert>.GetMethod("DeserializeObject", parameterTypes)
let result = deserialize.Invoke(null, [| json; inputType; [| fableConverter |] |])
result


// serialize an object to json using FableConverter
// json : string -> WebPart
let json value =
let result = JsonConvert.SerializeObject(value, fableConverter)
StringBuilder()
|> writeLn "Fable.Remoting: Returning serialized result back to client"
|> writeLn result
|> fun builder -> Option.iter (fun logf -> logf (builder.ToString())) logger
result

// Get data from request body and deserialize.
// getResourceFromReq : HttpRequest -> obj
let getResourceFromReq (ctx : HttpContext) (inputType: System.Type) =
let requestBodyStream = ctx.Request.Body
use streamReader = new StreamReader(requestBodyStream)
task {
let! requestBodyContent = streamReader.ReadToEndAsync()
return deserialize requestBodyContent inputType
}

let handleRequest methodName serverImplementation =
let inputType = ServerSide.getInputType methodName serverImplementation
let hasArg = inputType.FullName <> "Microsoft.FSharp.Core.Unit"
fun (next : HttpFunc) (ctx : HttpContext) ->
Option.iter (fun logf -> logf (sprintf "Fable.Remoting: Invoking method %s" methodName)) logger
let requestBodyData =
match hasArg with
| true -> getResourceFromReq ctx inputType
| false -> null
let result = ServerSide.dynamicallyInvoke methodName serverImplementation requestBodyData hasArg
task {
let! dynamicResult = result
let serializedResult = json dynamicResult
return! text serializedResult next ctx
}
let webPartWithBuilderFor<'t> (implementation: 't) (routeBuilder: string -> string -> string) : HttpHandler =
let builder = StringBuilder()
let typeName = implementation.GetType().Name
write builder (sprintf "Building Routes for %s" typeName)
implementation.GetType()
|> FSharpType.GetRecordFields
|> Seq.map (fun propInfo ->
let methodName = propInfo.Name
let fullPath = routeBuilder typeName methodName
write builder (sprintf "Record field %s maps to route %s" methodName fullPath)
POST >=> route fullPath >=> handleRequest methodName implementation
)
|> List.ofSeq
|> fun routes ->
logger |> Option.iter (fun logf -> logf (builder.ToString()))
choose routes

let webPartFor<'t> (implementation : 't) : HttpHandler =
webPartWithBuilderFor implementation (sprintf "/%s/%s")
3 changes: 2 additions & 1 deletion Fable.Remoting.Giraffe/paket.references
Original file line number Diff line number Diff line change
@@ -1 1,2 @@
FSharp.Core
FSharp.Core
Giraffe
Loading

0 comments on commit 818fe86

Please sign in to comment.