FsGrpc 1.0.6

dotnet add package FsGrpc --version 1.0.6
NuGet\Install-Package FsGrpc -Version 1.0.6
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FsGrpc" Version="1.0.6" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add FsGrpc --version 1.0.6
#r "nuget: FsGrpc, 1.0.6"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install FsGrpc as a Cake Addin
#addin nuget:?package=FsGrpc&version=1.0.6

// Install FsGrpc as a Cake Tool
#tool nuget:?package=FsGrpc&version=1.0.6

FsGrpc

Idiomatic F# code generation for Protocol Buffers and gRPC

Generate idiomatic F# records from proto3 message definitions, complete with oneofs as discriminated unions, and serialize/deserialize to and from protocol buffer wire format.

Getting Started

There are a couple of approaches you can use to generate code from your protos.

Option 1: Use FsGrpc.Tools

dotnet add package FsGrpc.Tools

Include protos in .fsproj with Protobuf. For example:

<ItemGroup>
    <Protobuf Include="protos\**" />
</ItemGroup>

Option 2: Use the buf cli

buf.gen.yaml

version: v1
plugins:
- remote: buf.build/divisions-maintenance-group/plugins/fsharp
    out: gen
    strategy: all

Then run buf generate

Include generated protos in your .fsproj inside top level <project> element:

<Import Project="gen/Protobuf.targets" />

Add fsgrpc

dotnet add package fsgrpc

Usage in F#

You can create a record by specifying all of the fields or using with syntax as follows:

let message =
	{ MyMessage.empty with
	    Name = "a name value"
	    Description = "some string here" }
You can serialize/deserialize from various formats:

Serializing to/from bytes

let encodedMessage = FsGrpc.Protobuf.encode message 

let decodedMessage: MyMessage = FsGrpc.Protobuf.decode encodedMessage

Serializing to/from a CodedOutputStream

let decodedMessage = MyMessage.Proto.Value.Decode cis

MyMessage.Proto.Value.Encode cos decodedMessage

Serializing to/from json

let serializedMessage = FsGrpc.Json.serialize message

let deserializedMessage : Http = FsGrpc.Json.deserialize serializedMessage

Implementing a GRPC service

Starting from a protobuf service defined like the following:

message HelloRequest {
    string name = 1;
}
  
  message HelloReply {
    string message = 1;
}

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);
    rpc SayHelloServerStreaming (HelloRequest) returns (stream HelloReply);
    rpc SayHelloClientStreaming (stream HelloRequest) returns (HelloReply);
    rpc SayHelloDuplexStreaming (stream HelloRequest) returns (stream HelloReply);
}

The implementation can then look like:

    open FSharp.Control //from the FSharp.Control.AsyncSeq package

    type GRPCService () =
        inherit Greeter.ServiceBase()

        override _.SayHello (request: HelloRequest) (context: ServerCallContext) =
            task { return { Message = "Hello " + request.Name } }

        override _.SayHelloServerStreaming (request: HelloRequest) (writer: IServerStreamWriter<HelloReply>) (context: ServerCallContext) =
            task {
                for i in [1..5] do
                    do! writer.WriteAsync { Message = "Hello " + request.Name }
            }

        override _.SayHelloClientStreaming (requestStream: IAsyncStreamReader<HelloRequest>) (context: ServerCallContext) =
            task {
                let! names =
                    AsyncSeq.ofAsyncEnum (requestStream.ReadAllAsync())
                    |> AsyncSeq.toListAsync

                let names = List.map (fun x -> x.Name) names
                return { Message = $"""Hello {String.concat ", " names}""" }
            }

        override _.SayHelloDuplexStreaming
            (requestStream: IAsyncStreamReader<HelloRequest>)
            (writer: IServerStreamWriter<HelloReply>)
            (context: ServerCallContext)
            =
            AsyncSeq.ofAsyncEnum (requestStream.ReadAllAsync())
            |> AsyncSeq.iterAsync (fun x ->
                writer.WriteAsync { Message = "Hello " + x.Name }
                |> Async.AwaitTask)
            |> fun x -> task { do! x }

        let builder = WebApplication.CreateBuilder()
        do
            builder.Services.AddGrpc() |> ignore
            builder.WebHost
                .ConfigureKestrel(fun serverOptions ->
                    serverOptions.ConfigureEndpointDefaults(fun listenOptions ->
                        listenOptions.Protocols <- Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2))
                .UseShutdownTimeout(TimeSpan.FromSeconds(60))
                |> ignore

            let mutable app = Unchecked.defaultof<WebApplication>
            app <- builder.Build()
            app.MapGrpcService<GRPCService>() |> ignore
            app.StartAsync().Wait()

Usage System Diagram


C4Context
  Container_Boundary(workstation, "Developer Workstation", $link="https://github.com/plantuml-stdlib/C4-PlantUML") {
      Container_Boundary(fsgrpc_repository, "FsGrpc Repository"){
          Component(protoc_fsgrpc_plugin_local, "Protoc FsGrpc Plugin", "local protoc-gen-fsgrpc")
      }
      Container_Boundary(your_fsharp_project, "Your F# project"){
          Component(projectfile, "Web Application fsproj")
          Component(generatedcode, "Generated F# Protobuf Code", "F# representations of your protobuf schema")
      }
  }

  Boundary(nuget, "Nuget"){
      Component(fsgrpc_nuget, "FsGrpc Package", "FsGrpc as a nuget package")
  }

  Boundary(buf, "buf.build"){
      Component(protoc_fsgrpc_plugin, "Protoc FsGrpc Plugin", "Hosted protoc-gen-fsgrpc as a service")
  }

  UpdateElementStyle(workstation, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(your_fsharp_project, $fontColor="blue", $borderColor="blue", $legendTest=" ")

  UpdateElementStyle(protoc_component, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(fsgrpc_component, $fontColor="blue", $borderColor="blue", $legendTest=" ")

  UpdateElementStyle(buf, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(nuget, $fontColor="blue", $borderColor="blue", $legendTest=" ")

  Rel(protoc_fsgrpc_plugin, generatedcode, "generates (Buf)")
  UpdateRelStyle(protoc_fsgrpc_plugin, generatedcode, $textColor="red", $lineColor="red")
  Rel(projectfile, fsgrpc_nuget, "references")
  UpdateRelStyle(projectfile, fsgrpc_nuget, $textColor="red", $lineColor="red")
  Rel(protoc_fsgrpc_plugin_local, generatedcode, "generates (offline)")
  UpdateRelStyle(protoc_fsgrpc_plugin_local, generatedcode, $textColor="red", $lineColor="red")

Status

We are using this for production and it is very stable. See below for status of individual features

The major features intended are:

  • Protobuf Messages as immutable F# record types
  • Oneofs as Discriminated Unions
  • proto3 optional keyword support
  • Support for optional wrapper types (e.g. google.protobuf.UInt32Val)
  • Support for well-known types Duration and Timestamp (represented using NodaTime types)
  • Automatic dependency-sorted inclusion of generated .fs files
  • Buf.build integration
  • Comment pass-through
  • Protocol Buffer reflection
  • Idiomatic functional implementation for gRPC endpoints

Contributing

Development System Diagram


C4Context
  Container_Boundary(workstation, "Developer Workstation", $link="https://github.com/plantuml-stdlib/C4-PlantUML") {
      Container_Boundary(your_fsharp_project, "Your F# project"){
          Component(generatedcode, "Generated F# Protobuf Code", "F# representations of your protobuf schema")
          Component(projectfile, "Web Application fsproj")
      }
      Component(protoc_fsgrpc_plugin_local, "Protoc FsGrpc Plugin", "local protoc-gen-fsgrpc")
  }

  Boundary(fsgrpc_repository, "FsGrpc Repository"){
     Boundary(protoc_component, "protoc FsGrpc Plugin"){
         Component(protoc_gen_fsgrpc,"protoc-gen-fsgrpc", "Generates F# representations of your protobuf schema")
         Component(protoc_gen_fsgrpc_tests, "Tests")
     }
     Boundary(fsgrpc_component, "FsGrpc"){
         Component(fsgrpc, "FsGrpc", "Support Library for generated code")
         Component(fsgrpc_tests, "FsGrpc.Tests")
     }
  }

  Boundary(buf, "buf.build"){
      Component(protoc_fsgrpc_plugin, "Protoc FsGrpc Plugin", "Hosted protoc-gen-fsgrpc as a service")
  }

  Boundary(nuget, "Nuget"){
      Component(fsgrpc_nuget, "FsGrpc Package", "FsGrpc as a nuget package")
  }


  UpdateElementStyle(workstation, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(your_fsharp_project, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  
  UpdateElementStyle(fsgrpc_repository, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(protoc_component, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(fsgrpc_component, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  
  UpdateElementStyle(buf, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  UpdateElementStyle(nuget, $fontColor="blue", $borderColor="blue", $legendTest=" ")
  Rel(generatedcode, fsgrpc, "depends on")
  UpdateRelStyle(generatedcode, fsgrpc, $textColor="red", $lineColor="red")
  Rel(fsgrpc, fsgrpc_nuget, "publishes")
  UpdateRelStyle(fsgrpc, fsgrpc_nuget, $textColor="red", $lineColor="red")
  Rel(protoc_gen_fsgrpc, protoc_fsgrpc_plugin, "Publishes")
  UpdateRelStyle(protoc_gen_fsgrpc, protoc_fsgrpc_plugin, $textColor="red", $lineColor="red")
  Rel(protoc_gen_fsgrpc, protoc_fsgrpc_plugin_local, "publishes (for local-only builds)")
  UpdateRelStyle(protoc_gen_fsgrpc, protoc_fsgrpc_plugin_local, $textColor="red", $lineColor="red")
  Rel(protoc_fsgrpc_plugin, generatedcode, "generates (Buf)")
  UpdateRelStyle(protoc_fsgrpc_plugin, generatedcode, $textColor="red", $lineColor="red")
  Rel(projectfile, fsgrpc_nuget, "references")
  UpdateRelStyle(projectfile, fsgrpc_nuget, $textColor="red", $lineColor="red")
  Rel(protoc_fsgrpc_plugin_local, generatedcode, "generates (offline)")
  UpdateRelStyle(protoc_fsgrpc_plugin_local, generatedcode, $textColor="red", $lineColor="red")
  Rel(protoc_gen_fsgrpc, fsgrpc, "references")
  UpdateRelStyle(protoc_gen_fsgrpc, fsgrpc, $textColor="red", $lineColor="red")
  Rel(fsgrpc_tests, fsgrpc, "references")
  UpdateRelStyle(fsgrpc_tests, fsgrpc, $textColor="red", $lineColor="red")
  Rel(protoc_gen_fsgrpc_tests, protoc_gen_fsgrpc, "references")
  UpdateRelStyle(protoc_gen_fsgrpc_tests, protoc_gen_fsgrpc, $textColor="red", $lineColor="red")
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on FsGrpc:

Package Downloads
FsGrpc.Tools

gRPC and Protocol Buffer compiler for F# projects

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.6 142 4/2/2024
1.0.5 5,702 5/12/2023
1.0.3 244 4/20/2023
1.0.2 949 3/9/2023
1.0.1 954 1/16/2023
1.0.0 738 11/18/2022
0.9.9 335 11/9/2022
0.9.8 410 10/7/2022
0.9.7 1,472 9/13/2022
0.9.6 390 9/1/2022
0.9.5 400 8/23/2022
0.9.4 385 8/22/2022
0.9.3 410 7/12/2022
0.9.2 430 7/8/2022
0.9.1-beta 144 7/7/2022
0.9.0-alpha-2 179 3/21/2022
0.9.0-alpha-1 163 3/19/2022