Git Product home page Git Product logo

swaggerforfsharp's Introduction

SwaggerForFsharp

Swagger for F# project is destinated to produce libraries generating Swagger's documentation with REST frameworks like Giraffe and Suave.

This project needs feedbacks and is not really production ready.

Swagger for Giraffe

NuGet

Waiting maturity I only published the library on my feed MyGet.

myget

You can use NuGet to install the library:

https://www.myget.org/feed/romcyber/package/nuget/SwaggerForFsharp.Giraffe

History

In this project I propose a solution to generate a swagger for Giraffe. Issue giraffe-fsharp/Giraffe#79 has label help wanted ๐Ÿ˜ƒ . Contributing direclty to Giraffe seems to be less reactive than creating my own project (see PR #218 )

My solution for Suave was effectively not really easy to use. Documentation and service implementation were too strongly coupled and the DSL was really verbose.

The good news is that we still have to declare our API routes the same way as before but to enable the route analysis we have to surround the app declaration with quotation marks.

With that in place we can decouple the app declaration from the analysis required to generate the swagger documentation. In other words this solution has the avantage to avoid corrupting your service implementation.

Getting started

Create the project

You can create your project with following steps.

dotnet new console --lang F#
dotnet add package SwaggerForFsharp.Giraffe --version 1.0.0-CI00004 --source https://www.myget.org/F/romcyber/api/v3/index.json

Open your .fsproj and edit your package references.

You should have something like:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Compile Include="Program.fs" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="FSharp.Core" Version="4.5.*" />
    <PackageReference Include="Giraffe" Version="1.1.0" />
    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
    <PackageReference Include="SwaggerForFsharp.Giraffe" Version="1.0.0-CI00004" />
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.*" />
    <PackageReference Include="TaskBuilder.fs" Version="2.0.0" />
  </ItemGroup>

</Project>

Code

Edit Program.fs

module SwaggerGiraffeTesting.App


open System
open System.IO
open Microsoft.AspNetCore
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Authentication.Cookies
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection
open Giraffe
open SwaggerForFsharp.Giraffe
open SwaggerForFsharp.Giraffe.Common
open SwaggerForFsharp.Giraffe.Generator
open SwaggerForFsharp.Giraffe.Dsl

let errorHandler (ex : Exception) (logger : ILogger) =
    logger.LogError(EventId(), ex, "An unhandled exception has occurred while executing the request.")
    clearResponse >=> setStatusCode 500 >=> text ex.Message
let authScheme = CookieAuthenticationDefaults.AuthenticationScheme
let time() = System.DateTime.Now.ToString()
let bonjour (firstName, lastName) =
    let message = sprintf "%s %s, vous avez le bonjour de Giraffe !" lastName firstName
    text message

let httpFailWith message =
    setStatusCode 500 >=> text message

let docAddendums =
    fun (route:Analyzer.RouteInfos) (path:string,verb:HttpVerb,pathDef:PathDefinition) ->
    
        // routef params are automatically added to swagger, but you can customize their names like this 
        let changeParamName oldName newName (parameters:ParamDefinition list) =
            parameters |> Seq.find (fun p -> p.Name = oldName) |> fun p -> { p with Name = newName }
    
        match path,verb,pathDef with
        | _,_, def when def.OperationId = "say_hello_in_french" ->
            let firstname = def.Parameters |> changeParamName "arg0" "Firstname"
            let lastname = def.Parameters |> changeParamName "arg1" "Lastname"
            "/hello/{Firstname}/{Lastname}", verb, { def with Parameters = [firstname; lastname] }
        | _ -> path,verb,pathDef
let port = 5000

let docsConfig c = 
    let describeWith desc = 
        { desc
            with
                Title="Sample 1"
                Description="Create a swagger with Giraffe"
                TermsOfService="Coucou"
        } 
    
    { c with 
        Description = describeWith
        Host = sprintf "localhost:%d" port
        DocumentationAddendums = docAddendums
    }

let webApp =
    swaggerOf
        ( choose [
              GET >=>
                 choose [
                      route  "/"           >=> text "index" 
                      route  "/ping"       >=> text "pong"
                      // Swagger operation id can be defined like this or with DocumentationAddendums
                      operationId "say_hello_in_french" ==> 
                          routef "/hello/%s/%s" bonjour
                 ]
              RequestErrors.notFound (text "Not Found") ]
       ) |> withConfig docsConfig

// ---------------------------------
// Main
// ---------------------------------

let cookieAuth (o : CookieAuthenticationOptions) =
    do
        o.Cookie.HttpOnly     <- true
        o.Cookie.SecurePolicy <- CookieSecurePolicy.SameAsRequest
        o.SlidingExpiration   <- true
        o.ExpireTimeSpan      <- TimeSpan.FromDays 7.0

let configureApp (app : IApplicationBuilder) =
    
    app.UseGiraffeErrorHandler(errorHandler)
       .UseStaticFiles()
       .UseAuthentication()
       .UseGiraffe webApp

let configureServices (services : IServiceCollection) =
    services
        .AddGiraffe()
        .AddAuthentication(authScheme)
        .AddCookie(cookieAuth)   |> ignore
    services.AddDataProtection() |> ignore
    
let configureLogging (loggerBuilder : ILoggingBuilder) =
    loggerBuilder.AddFilter(fun lvl -> lvl.Equals LogLevel.Error)
                 .AddConsole()
                 .AddDebug() |> ignore

[<EntryPoint>]
let main _ =
    let contentRoot = Directory.GetCurrentDirectory()
    let webRoot     = Path.Combine(contentRoot, "WebRoot")
    let url = sprintf "http://+:%d" port
    
    WebHost.CreateDefaultBuilder()
        .UseUrls(url)
        .UseWebRoot(webRoot)
        .Configure(Action<IApplicationBuilder> configureApp)
        .ConfigureServices(configureServices)
        .ConfigureLogging(configureLogging)
        .Build()
        .Run()
    0

Build and run

Run with

dotnet build
dotnet run

Go to url http://localhost:5000/swaggerui/index.html

How does it work ?

I introduced the documents function that takes two arguments:

  1. the quotation expression containing webservice implementation.
  2. a DocumentationConfig argument.

This function does the analysis of your quotation to generate Swagger documentation.

DocumentationConfig contains the following properties:

  • MethodCallRules: allow you to provide custom functions to enrich DSL and / or quotation analysis.
  • DocumentationAddendums: allow you to add more informations to the documentation without introducing service implementation modification.

I introduced ==> operator that gives the possibility to add decorations in routes implementations.

Examples

There are 2 solutions to add documentation for a route.

See example

...
operationId "send_a_car" ==>
	consumes tcar ==>
		produces typeof<Car> ==>
			route "/car2" >=> submitCar
...

using DocumentationAddendums

...
route "/car" >=> submitCar
...

let docAddendums =
    fun (route:Analyzer.RouteInfos) (path:string,verb:HttpVerb,pathDef:PathDefinition) ->
        match path,verb,pathDef with
        | "/car", HttpVerb.Post,def ->
            let ndef = 
                (def.AddConsume "model" "application/json" Body typeof<Car>)
                    .AddResponse 200 "application/json" "A car" typeof<Car>
            path, verb, ndef
...

Next steps

SwaggerUi

In futur, SwaggerUi could be a submodule of the repository (if you like and accept this PR ๐Ÿ˜„ ).

Quotations and Giraffe

Some features could be missing and some quotations could be difficult to parse. For the moment, analyzer works with most basics default httphandlers.

I only implemented:

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • route
  • routeCi
  • routef
  • setStatusCode
  • text
  • json
  • choose
  • subRouteCi
  • subRoute

You can build and run SwaggerSample/Program.fs and go to http://localhost:5000/swaggerui/

screen_giraffe_swagger1

Suave

Next step will consist to add genericity and implement a version for Suave.io

swaggerforfsharp's People

Contributors

rflechner avatar

Watchers

James Cloos avatar Nat Elkins avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.