{"id":16630553,"url":"https://github.com/arshia001/fsharp.grpccodegenerator","last_synced_at":"2025-03-16T22:30:58.063Z","repository":{"id":41823462,"uuid":"318194655","full_name":"Arshia001/FSharp.GrpcCodeGenerator","owner":"Arshia001","description":"A protoc plugin to enable generation of F# code + supporting libraries","archived":false,"fork":false,"pushed_at":"2023-06-23T16:47:38.000Z","size":16193,"stargazers_count":81,"open_issues_count":16,"forks_count":9,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-03-06T16:08:35.675Z","etag":null,"topics":["code-generation","fsharp","grpc","protobuf","protobuf-message","protocol-buffers"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Arshia001.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-03T13:00:39.000Z","updated_at":"2024-11-29T16:28:30.000Z","dependencies_parsed_at":"2023-01-29T23:30:59.348Z","dependency_job_id":null,"html_url":"https://github.com/Arshia001/FSharp.GrpcCodeGenerator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arshia001%2FFSharp.GrpcCodeGenerator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arshia001%2FFSharp.GrpcCodeGenerator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arshia001%2FFSharp.GrpcCodeGenerator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Arshia001%2FFSharp.GrpcCodeGenerator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Arshia001","download_url":"https://codeload.github.com/Arshia001/FSharp.GrpcCodeGenerator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243830952,"owners_count":20354854,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","fsharp","grpc","protobuf","protobuf-message","protocol-buffers"],"created_at":"2024-10-12T04:47:55.637Z","updated_at":"2025-03-16T22:30:56.014Z","avatar_url":"https://github.com/Arshia001.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FSharp.GrpcCodeGenerator\n\nThis project is aimed at providing complete integration of gRPC and protocol buffers into the F# language.\nThe project is early in development and, while all features have been implemented, it's not guranteed to work correctly.\n\nSuggestions, bug reports and pull requests are very welcome.\n\n## How do I use it?\n\n* Install the plugin as a **global** dotnet tool: `dotnet tool install -g grpc-fsharp`. This is needed for the build scripts to work.\n\n* Install the `Grpc-FSharp.Tools` package into your project.\n  * If you get an error that looks like\n  `Invalid command line switch for \"...\\tools\\windows_x64\\protoc.exe\". System.ArgumentNullException: Parameter \"message\" cannot be null.`,\n  you probably haven't installed the `grpc-fsharp` tool globally.\n  Verify the tool is installed and available by running `protoc-gen-fsharp` from a terminal window.\n\n* Add your `.proto` files into the project:\n\n  ```xml\n  \u003cItemGroup\u003e\n    \u003cProtobuf Include=\"path\\to\\definition.proto\" GrpcServices=\"Both\" Link=\"greet.proto\" /\u003e\n  \u003c/ItemGroup\u003e\n  ```\n\n  * You can control Grpc service stub generation via the `GrpcServices` setting. It can be one of `Both`, `Server`, `Client` and `None`.\n  * By including the `Link` setting, Visual Studio will include the `.proto` file in the solution explorer.\n  * The `Tools` package's source was taken from the official `Grpc.Tools` package, so any settings that work with that package should also work here.\n\n* Reference the correct nuget packages:\n  * If you only need protobuf serialization, add a reference to the `Protobuf.FSharp` package.\n  * To create a GRPC server with ASP.NET Core or Giraffe, you need the `Grpc-FSharp.AspNetCore` meta-package.\n  * If you're using Saturn, you can use the `Grpc-FSharp.Saturn` meta-package which adds a `use_grpc` custom operation to the `application` builder.\n  * To create a client, you need `Grpc-FSharp.Net.Client`. There is also a `Grpc-FSharp.Net.ClientFactory`, in case you need to use `IHttpClientFactory`.\n\n* If Visual Studio is having trouble building your project, restart it.\nIt sometimes happens when build dependencies are updated.\n\n* Should you need to use the plugin manually for some reason, you can do it:\n  * Run `protoc` with the `--fsharp_out` flag: `protoc my-proto-file.proto --fsharp_out=./generated-sources`\n    * Do not run the tool directly and expect good things to happen. It can only be used by `protoc`.\n    * You can use the `--fsharp_opt=no_server` and `--fsharp_opt=no_client` flags to control GRPC service code generation.\n    * You can use `--fsharp_opt=internal_access` to generate an internal module.\n  * Place the generated files inside your project.\n\n## How does it work?\n\nNow that that's out of the way, let's see how it all works.\n\n### Messages\n\nProtobuf messages are transformed into F# records.\nThis means we get to use F#'s automatic implementations for `Equals`, `ToString` etc.\nNow, you may be used to strictly immutable records, so be warned:\nthe records contain ***mutable*** fields.\nThis is because the .NET protobuf runtime is implemented with mutability in mind, and object are constructed and initialized in separate phases.\nIt'd be a much bigger task to adapt the runtime to strictly immutable messages.\n\n### Fields\n\nAll primitive and message fields are transformed into `ValueOption` fields.\nThis is because any field can simply be missing from a protobuf message,\nand we don't want to start guessing whether a 0 came in the actual message or was filled in as a default.\n\nOn the plus side, we no longer need `HasXXX` and `ClearXXX` calls.\nSimply match the `ValueOption` to test for a field's presence and set it to `ValueNone` to clear it from the message.\nAnother benefit is that the deserializer **never** generates null fields;\nif you see one, it's a bug that needs to be reported.\n\nBut why use value option, I hear you ask?\nPerformance!\nI wouldn't want each field of each message to cause an allocation.\nI know you wouldn't either.\nSomeone *could* implement a switch to override this behaviour, but I won't.\nAllocation is evil.\n\nAnd BTW, if you think handling all those options all over your code is troublesome,\nyou need to stop passing network DTO's into your business logic.\nYou may want to take a look at [Onion architecture](https://www.google.com/search?q=onion+architecture).\n\n### Collections\n\nRepeated and map fields use the built-in `RepeatedField` and `MapField` types from protobuf,\nwhich include special support for the binary protocol.\n\n### Enums\n\nEnums are transformed into CLR enums, not F# unions.\nThis is due to the fact that an enum field is, in the end, a number field,\nwhich is a perfect match for CLR enums, including the possibility of encountering numbers with no assigned name.\n\n### One-of\n\nOne-of fields, however, are transformed into F# union types.\nContrary to the C# plugin which generates separate properties for each one-of entry,\nyou'll get only one record field of type `ValueOption\u003cUnionType\u003e`.\nA `ValueNone` indicates none of the fields were present in the message.\nIf one was present, you'll get a `ValueSome(UnionCase)`.\nIf more than one is present (and you have a malformed message), only the last value will be preserved.\n\n### Helpers\n\nEach message gets an accompanying module. It contains:\n\n* A `Parser` you can use to read binary messages.\n* An `empty()` function which returns an empty record with all fields set to none or empty collections.\nYou can use this to start constructing new messages.\n* Field number and codec definitions.\n\n### Reflection\n\nEach file also gets a `Reflection` module.\nThis module contains the reflection descriptor for the whole file.\nIt also contains descriptors for each message type, should you need to access them by name.\n\n### Services\n\nServices are transformed into classes.\nThis is due to the OO nature of the runtime.\nFor each service, you get a client class and an abstract server base class.\n\nYou also get a `MyService.MyServiceClient.Functions` module with F# functions that take an instance of the client class as input.\nThis is meant to facilitate function composition, since composing instance methods (when possible at all) is kind of a nightmare...\n\nThe one divergence from the C# code generator is that the server base class contains no default logic to return a \"not implemented\" response;\nyou get abstract methods instead.\n\n### What else?\n\n* As you know, the F# compiler is single-pass and takes file order into account.\nThe `Tools` package adds the converted F# sources **before all other sources**, regardless of where in the project file the `\u003cProtobuf\u003e` elements appear.\nThis means that any generated code should be accessible throughout your entire project.\n\n* You may need to be aware of the fact that any type inside the `Google.Protobuf.*` namespace will have its namespace rewritten to `Google.Protobuf.FSharp.*`.\nThis is to keep the types from clashing with the ones provided inside the `Google.Protobuf` package, which you always need to reference.\nSo, for example, you get the `any` type at `Google.Protobuf.FSharp.WellKnownTypes.Any`.\n\n* The code generator respects the `csharp_namespace` option.\nThere is currently no separate `fsharp_namespace` option.\nI don't know whether this behaviour needs to be changed.\n\n* Contrary to the C# version, this code generator has no special handling for the types in `wrappers.proto`,\nsince a `ValueOption\u003cuint64\u003e` is completely adequate for use in place of a `Nullable\u003cuint64\u003e`.\nSee [here](https://docs.microsoft.com/en-us/aspnet/core/grpc/protobuf?view=aspnetcore-5.0#nullable-types) for more info.\n\n## Show me some code\n\nHere you are.\nFirst, reading and writing messages directly:\n\n```fsharp\nopen Google.Protobuf\n\n// Read a message\nuse stdIn = Console.OpenStandardInput()\nlet req = Compiler.CodeGeneratorRequest.Parser.ParseFrom(stdIn)\n\n// Do something with it\nlet files = ... // left out\n\n// Create a response message\nlet resp = { Compiler.CodeGeneratorResponse.empty() with SupportedFeatures = ValueSome \u003c| uint64 Compiler.CodeGeneratorResponse.Types.Feature.Proto3Optional }\nresp.File.AddRange files\n\n// Write it somewhere\nuse stdOut = Console.OpenStandardOutput()\n// WriteTo is provided in Google.Protobuf.MessageExtensions\nresp.WriteTo(stdOut)\n```\n\nHere's a service client:\n\n```fsharp\nuse channel = GrpcChannel.ForAddress(\"https://localhost:5001/\")\nlet client = Greet.GreeterService.GreeterServiceClient(channel)\nlet req = { Greet.HelloRequest.empty() with Name = ValueSome \"World\" }\nlet resp = client.SayHello(req).ResponseAsync.Result\nprintfn \"%s\" resp.Message\n```\n\nAnd a service implementation:\n\n```fsharp\ntype GreeterService() =\n    inherit Greet.GreeterService.GreeterServiceBase()\n\n    override _.SayHello req ctx =\n        let resp =\n            { Greet.HelloReply.empty() with\n                // Notice how we're immediately forced to handle missing fields.\n                // The language itself protects you from the binary protocol's quirks.\n                // How cool is THAT?\n                Message = req.Name |\u003e ValueOption.map (sprintf \"Hello, %s!\")\n            }\n        Threading.Tasks.Task.FromResult(resp)\n```\n\n## A note on protoc plugins\n\nIt took me considerable effort (much more than the ~5 minutes it takes to read the following paragraphs) to figure out how to implement a `protoc` plugin.\nIf there is adequate documentation anywhere, I must have missed it;\nso I'll document what I've learned here.\n\nThe protocol buffers compiler supports plugins in the shape of executables named `protoc-gen-XXXX`, where `XXXX` is the name of the plugin.\nYou enable a plugin by specifying `--XXXX_out=some_directory` and optionally pass it arguments by specifying `--XXXX_opt=opt1=val1,opt2=val2`.\n`protoc` attempts to execute `protoc-gen-XXXX` and write a serialized code generation request to the process's `stdin`.\nIt then waits for the process to write a code generation response back to its `stdout`.\nThe request and response are serialized as (you guessed it!) a protobuf message.\nThe contract for these types is available from `google/protobuf/compiler/plugin.proto`, found inside the `protoc` download archive.\n\n(Fascinatingly, the C# implementation inside `protoc` does not handle GRPC services.\nThose are generated by a separate plugin.\nThis is why in C# you get two source files per `.proto` file instead of one.)\n\nIf you're implementing a plugin in C++, you can use a bunch of utilities the protobuf team have made available,\nbut if you're implementing a plugin in another language,\nyou'll have to be able to understand protobuf in order to understand protobuf, so to speak.\nThis chicken-and-egg situation is the same as with any language which has its primary compiler implemented in the language itself.\nYou'd basically have to implement the initial version in another language and then use *that* to implement the language again, this time in itself.\nI was lucky enough to have the C# protobuf plugin available,\nwhich means I implemented the original code in F#,\nand only had to account for the change from classes to records and such minor cases.\nIt may not be as easy for another language where no support is available.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farshia001%2Ffsharp.grpccodegenerator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farshia001%2Ffsharp.grpccodegenerator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farshia001%2Ffsharp.grpccodegenerator/lists"}