├── .gitignore ├── .paket └── paket.bootstrapper.exe ├── .travis.yml ├── LICENSE ├── ProtoTypes.sln ├── README.md ├── appveyor.yml ├── build.cmd ├── build.fsx ├── build.sh ├── paket.dependencies ├── paket.lock └── src ├── ProtoTypes.Tests ├── App.config ├── ProtoTypes.Tests.fsproj ├── ProtoTypesTests.fs ├── paket.references └── proto │ └── person.proto └── ProtoTypes ├── Core ├── Expr.fs ├── Message.fs ├── Naming.fs ├── Option.fs ├── Prelude.fs ├── ResizeArray.fs ├── Types.fs └── ZeroCopyBuffer.fs ├── Generation ├── Codec.fs ├── Deserialization.fs ├── Model.fs ├── OneOf.fs ├── Provided.fs ├── Serialization.fs ├── TypeGen.fs └── TypeResolver.fs ├── ProtoTypes.fs ├── ProtoTypes.fsproj ├── Script.fsx └── paket.references /.gitignore: -------------------------------------------------------------------------------- 1 | packages 2 | .fake 3 | build 4 | bin 5 | obj 6 | paket-files 7 | .vs 8 | *.userprefs 9 | .paket/paket.exe 10 | 11 | -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takemyoxygen/ProtoTypes/cffd08be53a61f6d32f2a96cde71b27e5f7f2448/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | script: 3 | - ./build.sh Test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Uladzimir Makarau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProtoTypes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ProtoTypes", "src\ProtoTypes\ProtoTypes.fsproj", "{0A304644-D2F6-4EE2-8A7D-19335F93FE3E}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "ProtoTypes.Tests", "src\ProtoTypes.Tests\ProtoTypes.Tests.fsproj", "{3E5F27EB-304E-439A-A19C-F6210D793A93}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{358A9B71-4C94-44BD-B2FE-D9AE71994CD6}" 11 | ProjectSection(SolutionItems) = preProject 12 | build.fsx = build.fsx 13 | paket.dependencies = paket.dependencies 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {0A304644-D2F6-4EE2-8A7D-19335F93FE3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {0A304644-D2F6-4EE2-8A7D-19335F93FE3E}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {0A304644-D2F6-4EE2-8A7D-19335F93FE3E}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {0A304644-D2F6-4EE2-8A7D-19335F93FE3E}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {3E5F27EB-304E-439A-A19C-F6210D793A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {3E5F27EB-304E-439A-A19C-F6210D793A93}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {3E5F27EB-304E-439A-A19C-F6210D793A93}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {3E5F27EB-304E-439A-A19C-F6210D793A93}.Release|Any CPU.Build.0 = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(SolutionProperties) = preSolution 33 | HideSolutionNode = FALSE 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProtoTypes 2 | F# type provider for Google Protocol Buffers 3 | 4 | ## This repository is now part of [Froto](https://github.com/ctaggart/froto/) 5 | 6 | ## Build status: 7 | 8 | | Windows | Linux | 9 | |:---:|:---:| 10 | | [![Build status](https://ci.appveyor.com/api/projects/status/tn17l78rxk0dokmh?svg=true)](https://ci.appveyor.com/project/takemyoxygen/prototypes) | [![Build status](https://travis-ci.org/takemyoxygen/ProtoTypes.svg?branch=master)](https://travis-ci.org/takemyoxygen/ProtoTypes) | 11 | 12 | ## Some links: 13 | 14 | 1. Related discussion in [FSharpx.Extras repository](https://github.com/fsprojects/FSharpx.Extras/issues/124) 15 | 2. [FSharpx.Extras branch](https://github.com/fsprojects/FSharpx.Extras/tree/protobuf) where attempt to sketch a type provider was made 16 | 3. [Froto](https://github.com/ctaggart/froto) - a tool that parses and generates code from `.proto` files. Parsing files and creating ASTs might be re-used in type provider implementation. 17 | 18 | ## Current project state: 19 | 20 | 1. Generative type provider with basic support of generating types from single `.proto` file, serialization and deserialization. 21 | 2. Supports `proto2` syntax only. `required` fields are represented as regular properties, `optional` fields are mapped to properties of type `option<'T>`, and `repeated` fields correspond to `list<'T>` properties. 22 | 3. Supports embedded messages, and enums. List of supported scalar types includes `int32`, `string`, `bool` and `double` 23 | 24 | As disscussed [here](https://github.com/ctaggart/froto/issues/3), this repository migth eventually become part of Froto. 25 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.0.{build} 2 | 3 | cache: 4 | - packages -> paket.lock 5 | - paket-files -> paket.lock 6 | 7 | build_script: 8 | - cmd: build.cmd Test 9 | 10 | test: off -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | .paket\paket.bootstrapper.exe 5 | if errorlevel 1 ( 6 | exit /b %errorlevel% 7 | ) 8 | 9 | .paket\paket.exe restore 10 | if errorlevel 1 ( 11 | exit /b %errorlevel% 12 | ) 13 | 14 | packages\FAKE\tools\FAKE.exe build.fsx %* 15 | -------------------------------------------------------------------------------- /build.fsx: -------------------------------------------------------------------------------- 1 | #r "./packages/FAKE/tools/FakeLib.dll" 2 | 3 | open System 4 | open System.Net 5 | 6 | open Fake 7 | open Fake.AppVeyor 8 | open Fake.Testing 9 | 10 | let outputDirectory = __SOURCE_DIRECTORY__ @@ "build" 11 | let solution = !! "ProtoTypes.sln" 12 | let version = "0.1" 13 | let testResultsXml = outputDirectory @@ "TestResult.xml" 14 | 15 | Target "Clean" (fun _ -> 16 | CleanDir outputDirectory 17 | ) 18 | 19 | Target "Build" (fun _ -> 20 | MSBuildDebug outputDirectory "Rebuild" solution 21 | |> Log "AppBuild-Output: " 22 | ) 23 | 24 | Target "RunTests" (fun _ -> 25 | !! "./build/*.Tests.dll" 26 | |> NUnit3 (fun p -> 27 | { p with 28 | OutputDir = testResultsXml; 29 | WorkingDir = outputDirectory }) 30 | ) 31 | 32 | Target "UploadTestResults" (fun _ -> 33 | UploadTestResultsFile TestResultsType.NUnit3 testResultsXml 34 | ) 35 | 36 | Target "Watch" (fun _ -> 37 | use watcher = 38 | !! "src/**/*.fs" 39 | |> WatchChanges (fun changes -> 40 | tracefn "%A" changes 41 | Run "Test") 42 | 43 | Console.ReadLine() |> ignore) 44 | 45 | Target "Test" DoNothing 46 | 47 | "Clean" 48 | ==> "Build" 49 | ==> "RunTests" 50 | ==> "UploadTestResults" 51 | ==> "Test" 52 | 53 | RunTargetOrDefault "Build" 54 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test "$OS" = "Windows_NT" 3 | then 4 | # use .Net 5 | 6 | .paket/paket.bootstrapper.exe 7 | exit_code=$? 8 | if [ $exit_code -ne 0 ]; then 9 | exit $exit_code 10 | fi 11 | 12 | .paket/paket.exe restore 13 | exit_code=$? 14 | if [ $exit_code -ne 0 ]; then 15 | exit $exit_code 16 | fi 17 | 18 | packages/FAKE/tools/FAKE.exe $@ --fsiargs build.fsx 19 | else 20 | # use mono 21 | mono .paket/paket.bootstrapper.exe 22 | exit_code=$? 23 | if [ $exit_code -ne 0 ]; then 24 | exit $exit_code 25 | fi 26 | 27 | mono .paket/paket.exe restore 28 | exit_code=$? 29 | if [ $exit_code -ne 0 ]; then 30 | exit $exit_code 31 | fi 32 | mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx 33 | fi 34 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | 3 | nuget FAKE 4 | nuget Froto.Parser >= 0.2.0 prerelease 5 | nuget FsCheck 6 | nuget FsUnit 7 | nuget NUnit 8 | nuget NUnit.Runners 9 | nuget Froto.Core 10 | 11 | github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypes.fs 12 | github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypes.fsi 13 | github fsprojects/FSharp.TypeProviders.StarterPack src/ProvidedTypesTesting.fs 14 | -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | NUGET 2 | remote: https://www.nuget.org/api/v2 3 | specs: 4 | FAKE (4.25.4) 5 | FParsec (1.0.2) - framework: >= net45 6 | Froto.Core (0.2.1) 7 | Froto.Parser (0.2.1) 8 | FParsec (>= 1.0.2) - framework: >= net45 9 | FsCheck (2.3) 10 | FSharp.Core (>= 3.1.2.5) 11 | FSharp.Core (4.0.0.1) 12 | FsUnit (2.1) 13 | FSharp.Core (>= 3.1.2.5) 14 | NUnit (3.2) 15 | NUnit (3.2) 16 | NUnit.ConsoleRunner (3.2) 17 | NUnit.Extension.NUnitProjectLoader (3.2) 18 | NUnit.Extension.NUnitV2Driver (3.2) 19 | NUnit.Extension.NUnitV2ResultWriter (3.2) 20 | NUnit.Extension.VSProjectLoader (3.2) 21 | NUnit.Runners (3.2) 22 | NUnit.ConsoleRunner (3.2) 23 | NUnit.Extension.NUnitProjectLoader (3.2) 24 | NUnit.Extension.NUnitV2Driver (3.2) 25 | NUnit.Extension.NUnitV2ResultWriter (3.2) 26 | NUnit.Extension.VSProjectLoader (3.2) 27 | GITHUB 28 | remote: fsprojects/FSharp.TypeProviders.StarterPack 29 | specs: 30 | src/ProvidedTypes.fs (ea733039c168a8e2d441e298a1c67d98f63b00f6) 31 | src/ProvidedTypes.fsi (ea733039c168a8e2d441e298a1c67d98f63b00f6) 32 | src/ProvidedTypesTesting.fs (ea733039c168a8e2d441e298a1c67d98f63b00f6) -------------------------------------------------------------------------------- /src/ProtoTypes.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ProtoTypes.Tests/ProtoTypes.Tests.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 3e5f27eb-304e-439a-a19c-f6210d793a93 9 | Library 10 | ProtoTypes.Tests 11 | ProtoTypes.Tests 12 | v4.5 13 | 4.4.0.0 14 | ProtoTypes.Tests 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | ..\..\build 23 | DEBUG;TRACE 24 | 3 25 | bin\Debug\ProtoTypes.Tests.XML 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | ..\..\build 32 | TRACE 33 | 3 34 | bin\Release\ProtoTypes.Tests.XML 35 | 36 | 37 | 11 38 | 39 | 40 | 41 | 42 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 43 | 44 | 45 | 46 | 47 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 48 | 49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | ..\..\packages\Froto.Core\lib\net45\Froto.Core.dll 64 | True 65 | True 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ..\..\packages\FsCheck\lib\net45\FsCheck.dll 75 | True 76 | True 77 | 78 | 79 | 80 | 81 | 82 | 83 | ..\..\packages\FsCheck\lib\portable-net45+netcore45\FsCheck.dll 84 | True 85 | True 86 | 87 | 88 | 89 | 90 | 91 | 92 | ..\..\packages\FsCheck\lib\portable-net45+netcore45+wp8\FsCheck.dll 93 | True 94 | True 95 | 96 | 97 | 98 | 99 | 100 | 101 | ..\..\packages\FsCheck\lib\portable-net45+netcore45+wpa81+wp8\FsCheck.dll 102 | True 103 | True 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ..\..\packages\FsUnit\lib\net45\FsUnit.NUnit.dll 113 | True 114 | True 115 | 116 | 117 | ..\..\packages\FsUnit\lib\net45\NHamcrest.dll 118 | True 119 | True 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | ..\..\packages\NUnit\lib\net20\nunit.framework.dll 129 | True 130 | True 131 | 132 | 133 | 134 | 135 | 136 | 137 | ..\..\packages\NUnit\lib\net40\nunit.framework.dll 138 | True 139 | True 140 | 141 | 142 | 143 | 144 | 145 | 146 | ..\..\packages\NUnit\lib\net45\nunit.framework.dll 147 | True 148 | True 149 | 150 | 151 | 152 | 153 | 154 | 155 | ..\..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll 156 | True 157 | True 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | PreserveNewest 167 | 168 | 169 | 170 | 171 | 172 | 173 | True 174 | 175 | 176 | 177 | 178 | 179 | ProtoTypes 180 | {0a304644-d2f6-4ee2-8a7d-19335f93fe3e} 181 | True 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/ProtoTypes.Tests/ProtoTypesTests.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ProtoTypes.Tests 3 | 4 | open System 5 | open System.Collections.Generic 6 | 7 | open NUnit.Framework 8 | open FsUnit 9 | 10 | open Froto.Core 11 | 12 | open ProtoTypes 13 | open ProtoTypes.Core 14 | open ProtoTypes.Generation 15 | 16 | type Proto = ProtocolBuffersTypeProvider<"proto/person.proto"> 17 | type Sample = Proto.ProtoTypes.Sample 18 | 19 | let private createPerson() = 20 | let address = 21 | Sample.Person.Address( 22 | Address1 = "Street", 23 | HouseNumber = 12, 24 | Whatever = [1; 2; 3], 25 | SomeInts = [Sample.Person.IntContainer(Value = 5); Sample.Person.IntContainer(Value = 7)]) 26 | 27 | Sample.Person( 28 | Name = "Name", 29 | Id = 1, 30 | HasCriminalConvictions = false, 31 | Weight = 82.3, 32 | PersonGender = Sample.Person.Gender.Female, 33 | Email = Some "Email", 34 | PersonAddress = Some address) 35 | 36 | let serializeDeserialize<'T when 'T :> Message> (msg: 'T) (deserialize: ZeroCopyBuffer -> 'T) = 37 | let buffer = ZeroCopyBuffer 1000 38 | msg.Serialize buffer 39 | 40 | let buffer' = ZeroCopyBuffer buffer.AsArraySegment 41 | deserialize buffer' 42 | 43 | [] 44 | let ``Person test``() = 45 | let person = createPerson() 46 | person.Name |> should be (equal "Name") 47 | person.PersonGender |> should be (equal Sample.Person.Gender.Female) 48 | person.PersonAddress.Value.Address1 |> should be (equal "Street") 49 | 50 | 51 | [] 52 | let ``Serialization test``() = 53 | let person = createPerson() 54 | 55 | let buffer = ZeroCopyBuffer 1000 56 | person.Serialize buffer |> ignore 57 | buffer.Position |> should be (greaterThan 0) 58 | 59 | [] 60 | let ``Deserialization test``() = 61 | let person = createPerson() 62 | let person' = serializeDeserialize person Sample.Person.Deserialize 63 | 64 | person'.Name |> should be (equal person.Name) 65 | person'.Id |> should be (equal person.Id) 66 | person'.HasCriminalConvictions |> should be (equal person.HasCriminalConvictions) 67 | person'.Weight |> should be (equal person.Weight) 68 | person'.PersonGender |> should be (equal person.PersonGender) 69 | person'.Email |> should be (equal person.Email) 70 | 71 | person'.PersonAddress.IsSome |> should be True 72 | let address = person.PersonAddress.Value 73 | let address' = person'.PersonAddress.Value 74 | address'.Address1 |> should be (equal address.Address1) 75 | address'.HouseNumber |> should be (equal address.HouseNumber) 76 | address'.Whatever |> should be (equal address.Whatever) 77 | address'.SomeInts |> List.map (fun v -> v.Value) |> should be (equal (address.SomeInts |> List.map(fun v -> v.Value))) 78 | 79 | 80 | [] 81 | let ``Deserialize None optional value``() = 82 | let person = createPerson() 83 | person.PersonAddress <- None 84 | let person' = serializeDeserialize person Sample.Person.Deserialize 85 | 86 | person'.PersonAddress.IsSome |> should be False 87 | 88 | 89 | [] 90 | let ``Deserialize empty repeated value``() = 91 | let person = createPerson() 92 | let address = person.PersonAddress.Value 93 | 94 | address.SomeInts <- [] 95 | address.Whatever <- [] 96 | 97 | let address' = serializeDeserialize address Sample.Person.Address.Deserialize 98 | 99 | address'.SomeInts |> should be Empty 100 | address'.Whatever |> should be Empty 101 | 102 | [] 103 | let ``Primitive types``() = 104 | let container = 105 | Sample.PrimitiveContainer( 106 | DoubleField = 1.2, 107 | Int32Field = 42, 108 | Int64Field = 12351L, 109 | Uint32Field = 123124ul, 110 | Uint64Field = 1146111UL, 111 | Sint32Field = 1112, 112 | Sint64Field = -1236134L, 113 | Fixed32Field = proto_fixed32.MaxValue, 114 | Fixed64Field = proto_fixed64.MaxValue, 115 | Sfixed32Field = proto_sfixed32.MinValue, 116 | Sfixed64Field = proto_sfixed64.MinValue, 117 | BoolField = true, 118 | StringField = "string field value", 119 | BytesField = ArraySegment [| 1uy; 2uy; 42uy |]) 120 | 121 | let container' = serializeDeserialize container Sample.PrimitiveContainer.Deserialize 122 | 123 | container'.DoubleField |> should be (equal container.DoubleField) 124 | 125 | container'.Int32Field |> should be (equal container.Int32Field) 126 | container'.Int64Field |> should be (equal container.Int64Field) 127 | 128 | container'.Uint32Field |> should be (equal container.Uint32Field) 129 | container'.Uint64Field |> should be (equal container.Uint64Field) 130 | 131 | container'.Sint32Field |> should be (equal container.Sint32Field) 132 | container'.Sint64Field |> should be (equal container.Sint64Field) 133 | 134 | container'.Fixed32Field |> should be (equal container.Fixed32Field) 135 | container'.Fixed64Field |> should be (equal container.Fixed64Field) 136 | 137 | container'.Sfixed32Field |> should be (equal container.Sfixed32Field) 138 | container'.Sfixed64Field |> should be (equal container.Sfixed64Field) 139 | 140 | container'.BoolField |> should be (equal container.BoolField) 141 | container'.StringField |> should be (equal container.StringField) 142 | container'.BytesField |> should be (equal container.BytesField) 143 | 144 | type ValueOneofCase = Sample.OneOfContainer.ValueOneofCase 145 | 146 | [] 147 | let ``Oneof properties test``() = 148 | let oneofContainer = Sample.OneOfContainer() 149 | oneofContainer.ValueCase |> should be (equal ValueOneofCase.None) 150 | 151 | oneofContainer.Text <- Some "text" 152 | oneofContainer.Text |> should be (equal <| Some "text") 153 | oneofContainer.ValueCase |> should be (equal ValueOneofCase.Text) 154 | oneofContainer.Identifier.IsSome |> should be False 155 | 156 | oneofContainer.Identifier <- Some 10 157 | oneofContainer.Identifier |> should be (equal <| Some 10) 158 | oneofContainer.Text.IsSome |> should be False 159 | oneofContainer.ValueCase |> should be (equal ValueOneofCase.Identifier) 160 | 161 | oneofContainer.Identifier <- None 162 | oneofContainer.Identifier.IsSome |> should be False 163 | oneofContainer.Text.IsSome |> should be False 164 | oneofContainer.ValueCase |> should be (equal ValueOneofCase.None) 165 | 166 | oneofContainer.Identifier <- Some 10 167 | oneofContainer.ClearValue() 168 | oneofContainer.ValueCase |> should be (equal ValueOneofCase.None) 169 | oneofContainer.Identifier.IsSome |> should be False 170 | oneofContainer.Text.IsSome |> should be False 171 | 172 | [] 173 | let ``Oneof properties serialization test``() = 174 | let oneofContainer = Sample.OneOfContainer() 175 | oneofContainer.Identifier <- Some 42 176 | oneofContainer.AnotherText <- "Some another text" 177 | 178 | let buffer = ZeroCopyBuffer 1000 179 | oneofContainer.Serialize buffer 180 | let oneofContainer' = Sample.OneOfContainer.Deserialize <| ZeroCopyBuffer buffer.AsArraySegment 181 | 182 | oneofContainer.Identifier |> should be (equal oneofContainer'.Identifier) 183 | oneofContainer.AnotherText |> should be (equal oneofContainer'.AnotherText) 184 | oneofContainer.ValueCase |> should be (equal oneofContainer'.ValueCase) 185 | 186 | [] 187 | let ``Map test``() = 188 | let mapContainer = Sample.MapContainer() 189 | let map = proto_concrete_map<_, _>() 190 | map.Add(1, "foo") 191 | map.Add(2, "bar") 192 | mapContainer.PrimitiveMap <- map 193 | 194 | let people = proto_concrete_map<_, _>() 195 | people.Add("Vasya", createPerson()) 196 | mapContainer.People <- people 197 | 198 | let switches = proto_concrete_map<_, _>() 199 | switches.Add(1, Sample.MapContainer.Switch.On) 200 | mapContainer.Switches <- switches 201 | 202 | let buffer = mapContainer.SerializedLength |> int |> ZeroCopyBuffer 203 | mapContainer.Serialize buffer 204 | 205 | let mapContainer' = Sample.MapContainer.Deserialize <| ZeroCopyBuffer buffer.AsArraySegment 206 | 207 | mapContainer'.PrimitiveMap |> should be (not' Null) 208 | mapContainer'.PrimitiveMap.[1] |> should be (equal "foo") 209 | mapContainer'.PrimitiveMap.[2] |> should be (equal "bar") 210 | 211 | mapContainer'.Switches |> should be (not' Null) 212 | mapContainer'.Switches |> should haveCount 1 213 | mapContainer'.Switches.[1] |> should be (equal Sample.MapContainer.Switch.On) 214 | 215 | mapContainer'.People |> should be (not' Null) 216 | mapContainer'.People |> should haveCount 1 217 | mapContainer'.People.["Vasya"].Name |> should be (equal "Name") -------------------------------------------------------------------------------- /src/ProtoTypes.Tests/paket.references: -------------------------------------------------------------------------------- 1 | FsCheck 2 | FsUnit 3 | NUnit 4 | Froto.Core -------------------------------------------------------------------------------- /src/ProtoTypes.Tests/proto/person.proto: -------------------------------------------------------------------------------- 1 | package ProtoTypes.Sample; 2 | 3 | message Person { 4 | 5 | enum Gender { 6 | MALE = 0; 7 | FEMALE = 1; 8 | } 9 | 10 | message IntContainer { 11 | required int32 value = 1; 12 | } 13 | 14 | message Address { 15 | required string address1 = 1; 16 | required int32 house_number = 2; 17 | repeated int32 whatever = 3; 18 | repeated IntContainer some_ints = 4; 19 | } 20 | 21 | required string name = 1; 22 | required int32 id = 2; 23 | required bool has_criminal_convictions = 3; 24 | required double weight = 4; 25 | required Gender person_gender = 5; 26 | optional string email = 6; 27 | optional Address person_address = 7; 28 | } 29 | 30 | message PrimitiveContainer { 31 | required double double_field = 1; 32 | required float float_field = 2; 33 | required int32 int32_field = 3; 34 | required int64 int64_field = 4; 35 | required uint32 uint32_field = 5; 36 | required uint64 uint64_field = 6; 37 | required sint32 sint32_field = 7; 38 | required sint64 sint64_field = 8; 39 | required fixed32 fixed32_field = 9; 40 | required fixed64 fixed64_field = 10; 41 | required sfixed32 sfixed32_field = 11; 42 | required sfixed64 sfixed64_field = 12; 43 | required bool bool_field = 13; 44 | required string string_field = 14; 45 | required bytes bytes_field = 15; 46 | } 47 | 48 | message OneOfContainer { 49 | oneof value { 50 | string text = 1; 51 | int32 identifier = 2; 52 | } 53 | 54 | required string another_text = 3; 55 | } 56 | 57 | message MapContainer { 58 | enum Switch { 59 | ON = 0; 60 | OFF = 1; 61 | } 62 | 63 | required int32 id = 1; 64 | map primitive_map = 2; 65 | map people = 3; 66 | map switches = 4; 67 | } -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Expr.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | open System 4 | open System.Collections 5 | open System.Collections.Generic 6 | 7 | open FSharp.Quotations 8 | open FSharp.Quotations.Patterns 9 | 10 | open ProviderImplementation.ProvidedTypes 11 | 12 | [] 13 | module Expr = 14 | 15 | let sequence expressions = 16 | if expressions |> Seq.isEmpty then Expr.Value(()) 17 | else expressions |> Seq.reduce (fun acc s -> Expr.Sequential(acc, s)) 18 | 19 | let getMethodDef = function 20 | | Call(_, m, _) -> 21 | if m.IsGenericMethod 22 | then m.GetGenericMethodDefinition() 23 | else m 24 | | x -> notsupportedf "Expression %A is not supported" x 25 | 26 | let private isGenerated (ty: Type) = 27 | ty :? ProvidedTypeDefinition || 28 | (ty.IsGenericType && ty.GetGenericArguments() |> Seq.exists (fun gt -> gt :? ProvidedTypeDefinition)) 29 | 30 | let makeGenericMethod (types: Type list) methodInfo = 31 | if types |> List.exists isGenerated 32 | then ProvidedTypeBuilder.MakeGenericMethod(methodInfo, types) 33 | else methodInfo.MakeGenericMethod(types |> Array.ofList) 34 | 35 | let makeGenericType (types: Type list) typeDef = 36 | if types |> List.exists isGenerated 37 | then ProvidedTypeBuilder.MakeGenericType(typeDef, types) 38 | else typeDef.MakeGenericType(types |> Array.ofList) 39 | 40 | let callStatic parameters staticMethod = Expr.Call(staticMethod, parameters) 41 | 42 | let callStaticGeneric types arguments = 43 | getMethodDef >> makeGenericMethod types >> callStatic arguments 44 | 45 | let rec private typeHierarchy (ty: Type) = seq { 46 | if notNull ty 47 | then 48 | yield ty 49 | yield! typeHierarchy ty.BaseType 50 | } 51 | 52 | /// Generates an expression that iterates over a given sequence using provided body expression 53 | let forLoop (sequence: Expr) (body: Expr -> Expr) = 54 | let elementType = 55 | typeHierarchy sequence.Type 56 | |> Seq.tryFind (fun ty -> ty.IsGenericType && ty.GetGenericTypeDefinition() = typedefof>) 57 | |> Option.map (fun ty -> ty.GetGenericArguments().[0]) 58 | |> Option.require "Given collection is not a seq<'T>" 59 | 60 | let iterVar = Var("x", elementType) 61 | let enumeratorVar = Var("enumerator", typedefof> |> makeGenericType [elementType]) 62 | let enumeratorExpr = Expr.Var enumeratorVar 63 | let moveNextMethod = typeof.GetMethod("MoveNext") 64 | let disposeMethod = typeof.GetMethod("Dispose") 65 | 66 | let whileLoop = 67 | Expr.WhileLoop( 68 | Expr.Call(enumeratorExpr, moveNextMethod, []), 69 | Expr.Let( 70 | iterVar, 71 | Expr.PropertyGet(enumeratorExpr, enumeratorVar.Type.GetProperty("Current")), 72 | body <| Expr.Var iterVar)) 73 | 74 | // Expr.TryFinally is not really supported by FSharp.TypeProviders.StarterPack 75 | // so, Expr.Sequential is used instead (Dispose() won't be called if exception is raised) 76 | Expr.Let( 77 | enumeratorVar, 78 | Expr.Call(sequence, sequence.Type.GetMethod("GetEnumerator"), []), 79 | Expr.Sequential(whileLoop, Expr.Call(enumeratorExpr, disposeMethod, []))) 80 | 81 | let equal (a: Expr) (b: Expr) = 82 | if a.Type = b.Type then 83 | callStaticGeneric [a.Type] [a; b] <@@ x = x @@> 84 | else 85 | invalidOp <| sprintf "Arguments should have the same type, but their types: %s and %s" a.Type.FullName b.Type.FullName 86 | 87 | let defaultOf ty = 88 | callStaticGeneric [ty] [] <@@ Unchecked.defaultof<_> @@> 89 | 90 | let apply lambda = Seq.fold (fun l arg -> Expr.Application(l, arg)) lambda 91 | 92 | let box expr = Expr.Coerce(expr, typeof) -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Message.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | open Froto.Core 4 | 5 | // Eventually, this class might be replaced by Froto.Core.Encoding.MessageBase, but so far 6 | // this interface looks simpler and satisfies all needs. 7 | 8 | /// Base class for types generated from proto messages. 9 | [] 10 | type Message() as this = 11 | 12 | let mutable size = lazy ( 13 | let buffer = NullWriteBuffer() 14 | this.Serialize buffer 15 | buffer.Length 16 | ) 17 | 18 | member this.SerializedLength = size.Value 19 | 20 | abstract Serialize: ZeroCopyBuffer -> unit 21 | 22 | abstract ReadFrom: ZeroCopyBuffer -> unit 23 | 24 | /// Simple implementation of Message class that does nothing useful 25 | /// Basically, this class is needed only for type inference within quotations, because it satisfies requirements 26 | /// to be inherited from Message and to have constructor without parameters 27 | type internal Dummy() = 28 | inherit Message() 29 | 30 | override this.Serialize(buffer) = () 31 | override this.ReadFrom(buffer) = () 32 | 33 | /// Simple type used to simplify serialization and deserialization of map values 34 | type internal MapItem<'Key, 'Value> 35 | ( keyReader: Reader<'Key>, 36 | valueReader: Reader<'Value>, 37 | keyWriter: Writer<'Key>, 38 | valueWriter: Writer<'Value> ) = 39 | inherit Message() 40 | 41 | member val Key = x with get, set 42 | member val Value = x with get, set 43 | 44 | override this.Serialize(buffer) = 45 | keyWriter 1 buffer this.Key 46 | valueWriter 2 buffer this.Value 47 | 48 | override this.ReadFrom(buffer) = 49 | for field in ZeroCopyBuffer.allFields buffer do 50 | if field.FieldNum = 1 then 51 | this.Key <- keyReader field 52 | elif field.FieldNum = 2 then 53 | this.Value <- valueReader field -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Naming.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | open System 4 | 5 | [] 6 | module internal Naming = 7 | 8 | let private withFirstChar f (input: string) = 9 | if String.IsNullOrEmpty input then input 10 | else 11 | let first = input.[0] |> f |> string 12 | if input.Length > 1 then first + input.[1..] 13 | else first 14 | 15 | /// Converts "name_like_that" to "NameLikeThat" 16 | let snakeToPascal (identifier: string) = 17 | identifier.Split('_') 18 | |> Seq.map (withFirstChar Char.ToUpper) 19 | |> String.concat String.Empty 20 | 21 | /// Converts "NameLikeThat" to "nameLikeThat" 22 | let pascalToCamel = withFirstChar Char.ToLower 23 | 24 | /// Converts "nameLikeThat" to "NameLikeThat" 25 | let camelToPascal = withFirstChar Char.ToUpper 26 | 27 | /// Converts "name_like_that" to "nameLikeThat" 28 | let snakeToCamel = snakeToPascal >> pascalToCamel 29 | 30 | /// Converts "NAME_LIKE_THAT" to "nameLikeThat" 31 | let upperSnakeToPascal (identifier: string) = 32 | snakeToPascal <| identifier.ToLower() 33 | -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Option.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | /// Some extensions over standard option API 4 | [] 5 | module Option = 6 | 7 | /// Invokes function if provided option is None, otherwise returns original value of the provided option 8 | let otherwise f opt = 9 | match opt with 10 | | None -> f() 11 | | x -> x 12 | 13 | /// If provided option is Some - it's value is returned, otherwise an exception with provided error message is thrown 14 | let require msg = function 15 | | Some(x) -> x 16 | | None -> failwith msg 17 | 18 | /// If given option has some value, this value is returned, otherwise 'alternative' is used 19 | let getOrElse alternative = function 20 | | Some(x) -> x 21 | | None -> alternative 22 | 23 | /// Unwraps nested option to plain option<'T> 24 | let unwrap = function 25 | | Some(Some(x)) -> Some x 26 | | _ -> None 27 | 28 | let some x = Some x -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Prelude.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | open System 4 | open System.IO 5 | 6 | open Printf 7 | 8 | [] 9 | module Prelude = 10 | 11 | /// Raises NotSupportedException with given string as message 12 | let notsupportedf fmt = ksprintf (NotSupportedException >> raise) fmt 13 | 14 | /// Concatenates two paths using System.IO.Path.Combine 15 | let () path1 path2 = Path.Combine(path1, path2) 16 | 17 | /// Concatenates two namespaces/class names and separates them with "." 18 | let (+.+) scope1 scope2 = (scope1 + "." + scope2).Trim('.') 19 | 20 | let x<'T> : 'T = Unchecked.defaultof<'T> 21 | 22 | let notNull x = not <| isNull x 23 | 24 | let create<'T when 'T: (new: unit -> 'T)>() = new 'T() -------------------------------------------------------------------------------- /src/ProtoTypes/Core/ResizeArray.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | 4 | [] 5 | module ResizeArray = 6 | 7 | let add<'T> (list: obj) (item: 'T) = 8 | let list = list :?> ResizeArray<'T> 9 | list.Add item 10 | 11 | let toList<'T> (xs: obj) = xs :?> ResizeArray<'T> |> List.ofSeq -------------------------------------------------------------------------------- /src/ProtoTypes/Core/Types.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Core 2 | 3 | open System 4 | open System.Collections.Generic 5 | 6 | open Froto.Core 7 | open Froto.Core.Encoding 8 | 9 | // scalar type aliases based on https://developers.google.com/protocol-buffers/docs/proto3#scalar 10 | type proto_double = float 11 | type proto_float = float32 12 | type proto_int32 = int 13 | type proto_int64 = int64 14 | type proto_uint32 = uint32 15 | type proto_uint64 = uint64 16 | type proto_sint32 = int 17 | type proto_sint64 = int64 18 | type proto_fixed32 = uint32 19 | type proto_fixed64 = uint64 20 | type proto_sfixed32 = int 21 | type proto_sfixed64 = int64 22 | type proto_bool = bool 23 | type proto_string = string 24 | type proto_bytes = ArraySegment 25 | type proto_map<'Key, 'Value> = IReadOnlyDictionary<'Key, 'Value> 26 | type proto_concrete_map<'Key, 'Value> = Dictionary<'Key, 'Value> 27 | 28 | type Writer<'T> = FieldNum -> ZeroCopyBuffer -> 'T -> unit 29 | type Reader<'T> = RawField -> 'T -------------------------------------------------------------------------------- /src/ProtoTypes/Core/ZeroCopyBuffer.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ProtoTypes.Core.ZeroCopyBuffer 3 | 4 | open Froto.Core 5 | 6 | /// Reads all fields from given instance of ZeroCopyBuffer 7 | let allFields (zcb: ZeroCopyBuffer) = seq { 8 | while (not zcb.IsEof) && zcb.Array.[int zcb.Position] > 7uy do 9 | yield WireFormat.decodeField zcb 10 | } -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/Codec.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System 4 | open System.Collections.Generic 5 | 6 | open Froto.Core 7 | open Froto.Core.Encoding 8 | 9 | open ProtoTypes.Core 10 | 11 | /// Contains helper functions to read/write values to/from ZeroCopyBuffer 12 | [] 13 | module Codec = 14 | 15 | let private write f (fieldNumber: FieldNum) (buffer: ZeroCopyBuffer) value = 16 | f fieldNumber value buffer |> ignore 17 | 18 | let writeDouble: Writer = write Serializer.dehydrateDouble 19 | let writeFloat: Writer = write Serializer.dehydrateSingle 20 | let writeInt32: Writer = write Serializer.dehydrateVarint 21 | let writeInt64: Writer = write Serializer.dehydrateVarint 22 | let writeUInt32: Writer = write Serializer.dehydrateVarint 23 | let writeUInt64: Writer = write Serializer.dehydrateVarint 24 | let writeSInt32: Writer = write Serializer.dehydrateSInt32 25 | let writeSInt64: Writer = write Serializer.dehydrateSInt64 26 | // TODO Maybe should be fixed in Froto? 27 | let writeFixed32: Writer = fun fieldNumber buffer value -> 28 | Serializer.dehydrateDefaultedFixed32 0u fieldNumber value buffer |> ignore 29 | 30 | let writeFixed64: Writer = fun fieldNumber buffer value -> 31 | Serializer.dehydrateDefaultedFixed64 0UL fieldNumber value buffer |> ignore 32 | 33 | let writeSFixed32: Writer = write Serializer.dehydrateFixed32 34 | let writeSFixed64: Writer = fun fieldNumber buffer value -> 35 | Serializer.dehydrateDefaultedFixed64 0L fieldNumber value buffer |> ignore 36 | 37 | let writeBool: Writer = write Serializer.dehydrateBool 38 | let writeString: Writer = write Serializer.dehydrateString 39 | let writeBytes: Writer = write Serializer.dehydrateBytes 40 | 41 | /// Serializes optional field using provided function to handle inner value if present 42 | let writeOptional (writeInner: Writer<'T>) position buffer value = 43 | match value with 44 | | Some(v) -> writeInner position buffer v 45 | | None -> () 46 | 47 | let writeEmbedded position buffer (value: Message) = 48 | buffer 49 | |> WireFormat.encodeTag position WireType.LengthDelimited 50 | |> WireFormat.encodeVarint (uint64 value.SerializedLength) 51 | |> value.Serialize 52 | 53 | /// Value is expected to be of type option<'T>. It's not possible 54 | /// to use this type directly in the signature because of type providers limitations. 55 | /// All optional non-generated types (i.e. primitive types and enums) should be serialized using 56 | /// more strongly-typed writeOptional function 57 | let writeOptionalEmbedded<'T when 'T :> Message> : Writer = 58 | fun position buffer value -> 59 | if value <> null 60 | then value :?> option<'T> |> Option.get |> writeEmbedded position buffer 61 | 62 | let writeRepeated (writeItem: Writer<'T>) position buffer value = 63 | value |> Seq.iter (writeItem position buffer) 64 | 65 | let writeRepeatedEmbedded<'T when 'T :> Message> : Writer = 66 | fun position buffer value -> 67 | value :?> list<'T> |> writeRepeated writeEmbedded position buffer 68 | 69 | let private writeMap writeKey writeValue convertValue : Writer> = 70 | fun position buffer value -> 71 | let item = new MapItem<_, _>(x, x, writeKey, writeValue) 72 | for pair in value do 73 | item.Key <- pair.Key 74 | item.Value <- convertValue pair.Value 75 | writeEmbedded position buffer item 76 | 77 | let writePrimitiveMap writeKey writeValue : Writer> = 78 | fun position buffer value -> writeMap writeKey writeValue id position buffer value 79 | 80 | let writeMessageMap<'Key, 'Value when 'Value :> Message> writeKey : Writer = 81 | fun position buffer value -> 82 | writeMap 83 | writeKey 84 | writeEmbedded 85 | (fun msg -> msg :> Message) 86 | position 87 | buffer 88 | (value :?> proto_map<'Key, 'Value>) 89 | 90 | let writeEnumMap<'Key> writeKey : Writer> = 91 | fun position buffer value -> 92 | writeMap writeKey writeInt32 id position buffer value 93 | 94 | let private readField<'T> f field = 95 | let result = ref Unchecked.defaultof<'T> 96 | f result field 97 | !result 98 | 99 | let deserialize<'T when 'T :> Message and 'T : (new: unit -> 'T)> buffer = 100 | let x = new 'T() 101 | x.ReadFrom buffer 102 | x 103 | 104 | let readDouble: Reader = readField Serializer.hydrateDouble 105 | let readFloat: Reader = readField Serializer.hydrateSingle 106 | let readInt32: Reader = readField Serializer.hydrateInt32 107 | let readInt64: Reader = readField Serializer.hydrateInt64 108 | let readUInt32: Reader = readField Serializer.hydrateUInt32 109 | let readUInt64: Reader = readField Serializer.hydrateUInt64 110 | let readSInt32: Reader = readField Serializer.hydrateSInt32 111 | let readSInt64: Reader = readField Serializer.hydrateSInt64 112 | let readFixed32: Reader = readField Serializer.hydrateFixed32 113 | let readFixed64: Reader = readField Serializer.hydrateFixed64 114 | let readSFixed32: Reader = readField Serializer.hydrateSFixed32 115 | let readSFixed64: Reader = readField Serializer.hydrateSFixed64 116 | let readBool: Reader = readField Serializer.hydrateBool 117 | let readString: Reader = readField Serializer.hydrateString 118 | let readBytes: Reader = readField Serializer.hydrateBytes >> proto_bytes 119 | 120 | let readEmbedded<'T when 'T :> Message and 'T : (new: unit -> 'T)> field = 121 | match field with 122 | | LengthDelimited(_, segment) -> ZeroCopyBuffer segment |> deserialize<'T> 123 | | _ -> failwithf "Invalid format of the field: %O" field 124 | 125 | let readMapElement<'Key, 'Value> (map: proto_concrete_map<_, _>) keyReader (valueReader: Reader<'Value>) field = 126 | match field with 127 | | LengthDelimited(_, segment) -> 128 | let item = MapItem(keyReader, valueReader, x, x) 129 | item.ReadFrom <| ZeroCopyBuffer segment 130 | (map :?> proto_concrete_map<'Key, 'Value>).Add(item.Key, item.Value) 131 | | _ -> failwithf "Invalid format of the field: %O" field 132 | 133 | let readMessageMapElement<'Key, 'Value when 'Value :> Message and 'Value : (new: unit -> 'Value)> (map: obj) keyReader field = 134 | readMapElement (map :?> proto_concrete_map<'Key, 'Value>) keyReader readEmbedded<'Value> field -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/Deserialization.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System 4 | open System.Collections.Generic 5 | open FSharp.Quotations 6 | 7 | open ProtoTypes.Core 8 | open ProviderImplementation.ProvidedTypes 9 | 10 | open Froto.Parser.Model 11 | open Froto.Core 12 | open Froto.Core.Encoding 13 | 14 | /// Contains an implementation of deserialization methods for types generated from ProtoBuf messages 15 | [] 16 | module Deserialization = 17 | 18 | let private primitiveReader = function 19 | | "double" -> <@@ Codec.readDouble @@> 20 | | "float" -> <@@ Codec.readFloat @@> 21 | | "int32" -> <@@ Codec.readInt32 @@> 22 | | "int64" -> <@@ Codec.readInt64 @@> 23 | | "uint32" -> <@@ Codec.readUInt32 @@> 24 | | "uint64" -> <@@ Codec.readUInt64 @@> 25 | | "sint32" -> <@@ Codec.readSInt32 @@> 26 | | "sint64" -> <@@ Codec.readSInt64 @@> 27 | | "fixed32" -> <@@ Codec.readFixed32 @@> 28 | | "fixed64" -> <@@ Codec.readFixed64 @@> 29 | | "sfixed32" -> <@@ Codec.readSFixed32 @@> 30 | | "sfixed64" -> <@@ Codec.readSFixed64 @@> 31 | | "bool" -> <@@ Codec.readBool @@> 32 | | "string" -> <@@ Codec.readString @@> 33 | | "bytes" -> <@@ Codec.readBytes @@> 34 | | x -> notsupportedf "Primitive type '%s' is not supported" x 35 | 36 | /// Creates quotation that converts RawField quotation to target property type 37 | let private deserializeField (property: PropertyDescriptor) (rawField: Expr) = 38 | match property.TypeKind with 39 | | Primitive -> Expr.Application(primitiveReader property.ProtobufType, rawField) 40 | | Enum -> <@@ Codec.readInt32 %%rawField @@> 41 | | Class -> Expr.callStaticGeneric [property.UnderlyingType] [rawField ] <@@ Codec.readEmbedded x @@> 42 | 43 | let readFrom (typeInfo: TypeDescriptor) this allFields = 44 | 45 | // 1. Declare local vars of type Dictionary<,> for all map fields 46 | // 2. Declare ResizeArray local variables for all repeated fields 47 | // 3. Read all fields from given buffer 48 | // 4. Iterate over fields read: if FieldNum matches field position - 49 | // set corresponding property or add value to the ResizeArray (for repeated fields) 50 | // 5. Convert ResizeArray to lists and set repeated fields 51 | // 6. Set properties corresponding to map fields using local Dictionary<,> variables 52 | 53 | try 54 | 55 | // for repeated rules - map from property to variable 56 | let resizeArrays = 57 | typeInfo.AllProperties 58 | |> Seq.filter (fun prop -> prop.Rule = Repeated) 59 | |> Seq.map (fun prop -> prop, Var(prop.ProvidedProperty.Name, Expr.makeGenericType [prop.UnderlyingType] typedefof>)) 60 | |> dict 61 | 62 | let dictionaries = 63 | typeInfo.Maps 64 | |> Seq.map (fun map -> 65 | map, 66 | Var(map.Property.Name, 67 | Expr.makeGenericType 68 | (map.Property.PropertyType.GenericTypeArguments |> List.ofArray) 69 | typedefof>)) 70 | |> dict 71 | 72 | let samePosition field idx = <@@ (%%field: RawField).FieldNum = idx @@> 73 | 74 | /// For required and optional fields - set the property directly; 75 | /// for repeated - add to corresponding ResizeArray 76 | let handleField (property: PropertyDescriptor) (field: Expr) = 77 | let value = deserializeField property field 78 | 79 | match property.Rule with 80 | | Repeated -> 81 | let list = Expr.Var(resizeArrays.[property]) 82 | match property.TypeKind with 83 | | Class -> 84 | Expr.callStaticGeneric 85 | [list.Type.GenericTypeArguments.[0]] 86 | [Expr.box list; value] 87 | <@@ ResizeArray.add x x @@> 88 | | _ -> 89 | let addMethod = list.Type.GetMethod("Add") 90 | Expr.Call(list, addMethod, [value]) 91 | | Optional -> 92 | let someValue = Expr.callStaticGeneric [value.Type] [value] <@@ Option.some x @@> 93 | Expr.PropertySet(this, property.ProvidedProperty, someValue) 94 | | Required -> 95 | Expr.PropertySet(this, property.ProvidedProperty, value) 96 | 97 | let handleMap map field = 98 | let dict = Expr.Var(dictionaries.[map]) 99 | let keyReader = primitiveReader map.KeyType 100 | 101 | let readMethod, args = 102 | match map.ValueTypeKind with 103 | | Primitive -> 104 | <@@ Codec.readMapElement x x x x @@>, 105 | [dict; keyReader; primitiveReader map.ValueType; field] 106 | | Enum -> 107 | <@@ Codec.readMapElement x x x x @@>, 108 | [dict; keyReader; <@@ Codec.readInt32 @@>; field] 109 | | Class -> 110 | <@@ Codec.readMessageMapElement<_, Dummy> x x x @@>, 111 | [ Expr.box dict; keyReader; field ] 112 | 113 | Expr.callStaticGeneric 114 | (dict.Type.GenericTypeArguments |> List.ofArray) 115 | args 116 | readMethod 117 | 118 | /// Converts ResizeArray to immutable list and sets corresponding repeated property 119 | let setRepeatedProperty property (resizeArrayVar: Var) = 120 | let itemTy = resizeArrayVar.Type.GenericTypeArguments.[0] 121 | let list = 122 | match property.TypeKind with 123 | | Class -> 124 | Expr.callStaticGeneric 125 | [itemTy] 126 | [Expr.box <| Expr.Var(resizeArrayVar)] 127 | <@@ ResizeArray.toList x @@> 128 | | _ -> 129 | Expr.callStaticGeneric [itemTy] [Expr.Var(resizeArrayVar)] <@@ List.ofSeq<_> x @@> 130 | 131 | Expr.PropertySet(this, property.ProvidedProperty, list) 132 | 133 | let setMapProperty map content = 134 | Expr.PropertySet(this, map.Property, Expr.Var(content)) 135 | 136 | let fieldLoop = Expr.forLoop <@@ ZeroCopyBuffer.allFields %%allFields @@> (fun field -> 137 | let maps = 138 | typeInfo.Maps 139 | |> Seq.fold 140 | (fun acc prop -> 141 | Expr.IfThenElse( 142 | samePosition field prop.Position, 143 | handleMap prop field, 144 | acc 145 | )) 146 | (Expr.Value(())) 147 | 148 | typeInfo.AllProperties 149 | |> Seq.fold 150 | (fun acc prop -> 151 | Expr.IfThenElse( 152 | samePosition field prop.Position, 153 | handleField prop field, 154 | acc)) 155 | maps) 156 | 157 | let setRepeatedFields = 158 | resizeArrays 159 | |> Seq.map (fun pair -> setRepeatedProperty pair.Key pair.Value) 160 | |> List.ofSeq 161 | 162 | let setMapFields = 163 | dictionaries 164 | |> Seq.map (fun pair -> setMapProperty pair.Key pair.Value) 165 | |> List.ofSeq 166 | 167 | let create ty = Expr.callStaticGeneric [ty] [] <@@ create<_>() @@> 168 | 169 | let body = fieldLoop :: setRepeatedFields @ setMapFields |> Expr.sequence 170 | 171 | let body = 172 | resizeArrays.Values 173 | |> Seq.fold (fun acc var -> Expr.Let(var, create var.Type, acc)) body 174 | 175 | dictionaries.Values 176 | |> Seq.fold (fun acc var -> Expr.Let(var, create var.Type, acc)) body 177 | 178 | with 179 | | ex -> 180 | printfn "Failed to generate Deserialize method for type %s. Details: %O" typeInfo.Type.Name ex 181 | reraise() -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/Model.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open ProviderImplementation.ProvidedTypes 4 | 5 | open Froto.Parser.Model 6 | open Froto.Core.Encoding 7 | 8 | type TypeKind = 9 | | Primitive 10 | | Class 11 | | Enum 12 | 13 | type PropertyDescriptor = 14 | { ProvidedProperty: ProvidedProperty; 15 | Position: FieldNum; 16 | ProtobufType: string; 17 | Rule: ProtoFieldRule; 18 | TypeKind: TypeKind } 19 | 20 | member this.UnderlyingType = 21 | if this.ProvidedProperty.PropertyType.IsGenericType 22 | then this.ProvidedProperty.PropertyType.GenericTypeArguments.[0] 23 | else this.ProvidedProperty.PropertyType 24 | 25 | type OneOfGroupDescriptor = 26 | { Properties: Map; 27 | CaseField: ProvidedField } 28 | 29 | type MapDescriptor = 30 | { KeyType: string; 31 | ValueType: string; 32 | ValueTypeKind: TypeKind; 33 | Position: FieldNum; 34 | Property: ProvidedProperty } 35 | 36 | type TypeDescriptor = 37 | { Type: ProvidedTypeDefinition; 38 | Properties: PropertyDescriptor list; 39 | OneOfGroups: OneOfGroupDescriptor list; 40 | Maps: MapDescriptor list } 41 | 42 | member this.AllProperties = 43 | this.OneOfGroups 44 | |> Seq.collect (fun group -> group.Properties |> Map.toSeq) 45 | |> Seq.map snd 46 | |> Seq.append this.Properties 47 | |> List.ofSeq -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/OneOf.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System 4 | open System.Reflection 5 | open FSharp.Quotations 6 | 7 | open Froto.Parser.Model 8 | open Froto.Parser.Ast 9 | 10 | open ProtoTypes.Core 11 | open ProviderImplementation.ProvidedTypes 12 | 13 | [] 14 | module OneOf = 15 | 16 | /// Generates members that represent "oneof" group. Similar to whan current version of C# code generator is doing 17 | /// https://developers.google.com/protocol-buffers/docs/reference/csharp-generated#oneof 18 | let generateOneOf scope (typesLookup: TypesLookup) (name: string) (members: POneOfStatement list) = 19 | 20 | let oneofCaseEnum = Provided.enum <| Naming.snakeToPascal name + "OneofCase" 21 | 22 | let numberedMembers = members |> List.mapi (fun i m -> i + 1, m) 23 | 24 | ("None", 0) :: (numberedMembers |> List.map (fun (i, TOneOfField(name, _, _, _)) -> Naming.snakeToPascal name, i)) 25 | |> Provided.addEnumValues oneofCaseEnum 26 | 27 | let caseField = ProvidedField(Naming.snakeToCamel name + "Case", typeof) 28 | let caseProperty = ProvidedProperty(Naming.camelToPascal caseField.Name, typeof) 29 | caseProperty.GetterCode <- fun args -> Expr.FieldGet(args.[0], caseField) 30 | 31 | let valueField = ProvidedField(Naming.snakeToCamel name, typeof) 32 | let properties = 33 | numberedMembers 34 | |> List.map (fun (i, TOneOfField(name, ptype, position, _)) -> 35 | let kind, propertyType = 36 | TypeResolver.resolvePType scope ptype typesLookup 37 | |> Option.map (fun (kind, ty) -> kind, Expr.makeGenericType [ty] typedefof>) 38 | |> Option.require (sprintf "Unable to find type %A" ptype) 39 | 40 | let property = ProvidedProperty(Naming.snakeToPascal name, propertyType) 41 | 42 | property.GetterCode <- fun args -> 43 | Expr.IfThenElse( 44 | Expr.equal (Expr.FieldGet(args.[0], caseField)) (Expr.Value(i)), 45 | Expr.Coerce(Expr.FieldGet(args.[0], valueField), propertyType), 46 | Expr.defaultOf propertyType) 47 | 48 | property.SetterCode <- fun args -> 49 | let value = args.[1] 50 | let case = 51 | Expr.IfThenElse( 52 | Expr.equal (Expr.box value) (Expr.Value(null)), 53 | Expr.Value(0), 54 | Expr.Value(i)) 55 | Expr.Sequential( 56 | Expr.FieldSet(args.[0], valueField, Expr.box value), 57 | Expr.FieldSet(args.[0], caseField, case)) 58 | 59 | let propertyInfo = 60 | { ProvidedProperty = property; 61 | Position = int position; 62 | Rule = Optional; 63 | ProtobufType = TypeResolver.ptypeToString ptype; 64 | TypeKind = kind } 65 | 66 | i, propertyInfo) 67 | 68 | let clearMethod = ProvidedMethod("Clear" + Naming.snakeToPascal name, [], typeof) 69 | clearMethod.InvokeCode <- fun args -> 70 | Expr.Sequential( 71 | Expr.FieldSet(args.[0], caseField, Expr.Value(0)), 72 | Expr.FieldSet(args.[0], valueField, Expr.Value(null)) 73 | ) 74 | 75 | let oneOfGroup = 76 | { Properties = properties |> Map.ofSeq; 77 | CaseField = caseField } 78 | 79 | let allMembers = 80 | [ valueField :> MemberInfo; 81 | oneofCaseEnum :> MemberInfo; 82 | caseField :> MemberInfo; 83 | caseProperty :> MemberInfo; 84 | clearMethod :> MemberInfo ] 85 | |> List.append (properties |> List.map (fun (_, p) -> p.ProvidedProperty :> MemberInfo)) 86 | 87 | oneOfGroup, allMembers -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/Provided.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System.Reflection 4 | open FSharp.Quotations 5 | 6 | open ProtoTypes.Core 7 | open ProviderImplementation.ProvidedTypes 8 | 9 | [] 10 | module internal Provided = 11 | 12 | let message name = ProvidedTypeDefinition(name, Some typeof, IsErased = false) 13 | 14 | let enum name = ProvidedTypeDefinition(name, Some typeof, IsErased = false) 15 | 16 | let addEnumValues (enum: ProvidedTypeDefinition) = 17 | Seq.map(fun (name, value) -> ProvidedLiteralField(name, typeof, value)) 18 | >> Seq.iter enum.AddMember 19 | 20 | let readOnlyProperty propertyType name = 21 | let field = ProvidedField(Naming.pascalToCamel name, propertyType) 22 | field.SetFieldAttributes(FieldAttributes.InitOnly ||| FieldAttributes.Private) 23 | let property = 24 | ProvidedProperty(name, propertyType, GetterCode = (fun args -> Expr.FieldGet(args.[0], field))) 25 | 26 | property, field 27 | 28 | let readWriteProperty propertyType name = 29 | let property, field = readOnlyProperty propertyType name 30 | property.SetterCode <- (fun args -> Expr.FieldSet(args.[0], field, args.[1])) 31 | 32 | property, field 33 | 34 | /// Creates an empty parameterless constructor 35 | let ctor () = ProvidedConstructor([], InvokeCode = (fun _ -> Expr.Value(()))) -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/Serialization.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System.Collections.Generic 4 | open Microsoft.FSharp.Quotations 5 | 6 | open ProtoTypes.Core 7 | open ProviderImplementation.ProvidedTypes 8 | 9 | open Froto.Parser.Model 10 | open Froto.Core 11 | open Froto.Core.Encoding 12 | 13 | /// Contains an implementation of serialization method for types generated from ProtoBuf messages 14 | [] 15 | module Serialization = 16 | 17 | let private primitiveWriter = function 18 | | "double" -> <@@ Codec.writeDouble @@> 19 | | "float" -> <@@ Codec.writeFloat @@> 20 | | "int32" -> <@@ Codec.writeInt32 @@> 21 | | "int64" -> <@@ Codec.writeInt64 @@> 22 | | "uint32" -> <@@ Codec.writeUInt32 @@> 23 | | "uint64" -> <@@ Codec.writeUInt64 @@> 24 | | "sint32" -> <@@ Codec.writeSInt32 @@> 25 | | "sint64" -> <@@ Codec.writeSInt64 @@> 26 | | "fixed32" -> <@@ Codec.writeFixed32 @@> 27 | | "fixed64" -> <@@ Codec.writeFixed64 @@> 28 | | "sfixed32" -> <@@ Codec.writeSFixed32 @@> 29 | | "sfixed64" -> <@@ Codec.writeSFixed64 @@> 30 | | "bool" -> <@@ Codec.writeBool @@> 31 | | "string" -> <@@ Codec.writeString @@> 32 | | "bytes" -> <@@ Codec.writeBytes @@> 33 | | x -> notsupportedf "Primitive type '%s' is not supported" x 34 | 35 | let private serializeMapExpr buffer this (map: MapDescriptor) = 36 | let keyWriter = primitiveWriter map.KeyType 37 | let keyType = map.Property.PropertyType.GenericTypeArguments.[0] 38 | let valueType = map.Property.PropertyType.GenericTypeArguments.[1] 39 | let positionExpr = Expr.Value(map.Position) 40 | let mapExpr = Expr.PropertyGet(this, map.Property) 41 | 42 | match map.ValueTypeKind with 43 | | Primitive -> 44 | Expr.callStaticGeneric 45 | [keyType; valueType] 46 | [keyWriter; primitiveWriter map.ValueType; positionExpr; buffer; mapExpr] 47 | <@@ Codec.writePrimitiveMap x x x x x @@> 48 | | Class -> 49 | Expr.callStaticGeneric 50 | [keyType; valueType] 51 | [keyWriter; positionExpr; buffer; Expr.box mapExpr] 52 | <@@ Codec.writeMessageMap x x x x @@> 53 | | Enum -> 54 | Expr.callStaticGeneric 55 | [keyType] 56 | [keyWriter; positionExpr; buffer; mapExpr] 57 | <@@ Codec.writeEnumMap x x x x @@> 58 | | _ -> notsupportedf "Serializing of a map with values of type %s is not supported" valueType.Name 59 | 60 | /// Creates an expression that serializes all given properties to the given instance of ZeroCopyBuffer 61 | let private serializeProperty buffer this prop = 62 | 63 | let value = Expr.PropertyGet(this, prop.ProvidedProperty) 64 | let position = prop.Position 65 | 66 | // writer is an expression that represents a function 'T -> unit for any primitive or enum field of type 'T. 67 | // For embedded messages, writer will have type Message -> unit. It's caused by the fact that it's not possible to pass 68 | // any generic arguments including option<'T> and 'T -> unit to other functions if 'T is generated by a type provider. 69 | let writer = 70 | match prop.TypeKind with 71 | | Primitive -> primitiveWriter prop.ProtobufType 72 | | Class -> <@@ Codec.writeEmbedded @@> 73 | | Enum -> <@@ Codec.writeInt32 @@> 74 | 75 | let write f value = Expr.callStaticGeneric [prop.UnderlyingType] [writer; value] f 76 | 77 | let callPrimitive writer rule = 78 | let args = [Expr.Value(position); buffer; value] 79 | match rule with 80 | | Required -> Expr.apply writer args 81 | | Optional -> 82 | Expr.callStaticGeneric 83 | [prop.UnderlyingType] 84 | (writer::args) 85 | <@@ Codec.writeOptional x x x x @@> 86 | | Repeated -> 87 | Expr.callStaticGeneric 88 | [prop.UnderlyingType] 89 | (writer::args) 90 | <@@ Codec.writeRepeated x x x x @@> 91 | try 92 | 93 | match prop.TypeKind, prop.Rule with 94 | | Class, Optional -> 95 | Expr.callStaticGeneric 96 | [prop.UnderlyingType] 97 | [Expr.Value(position); buffer; Expr.box value] 98 | <@@ Codec.writeOptionalEmbedded x x x @@> 99 | | Class, Repeated -> 100 | Expr.callStaticGeneric 101 | [prop.UnderlyingType] 102 | [Expr.Value(position); buffer; Expr.box value] 103 | <@@ Codec.writeRepeatedEmbedded x x x @@> 104 | | Class, Required -> 105 | <@@ Codec.writeEmbedded x x x @@> 106 | |> Expr.getMethodDef 107 | |> Expr.callStatic [Expr.Value(position); buffer; value] 108 | | Enum, rule -> callPrimitive <@@ Codec.writeInt32 @@> rule 109 | | Primitive, rule -> callPrimitive (primitiveWriter prop.ProtobufType) rule 110 | with 111 | | ex -> 112 | printfn "Failed to serialize property %s: %O. Error: %O" prop.ProvidedProperty.Name value.Type ex 113 | reraise() 114 | 115 | let serializeExpr (typeInfo: TypeDescriptor) buffer this = 116 | 117 | let properties = 118 | typeInfo.AllProperties 119 | |> List.sortBy (fun prop -> prop.Position) 120 | |> List.map (serializeProperty buffer this) 121 | |> Expr.sequence 122 | 123 | let maps = 124 | typeInfo.Maps 125 | |> List.sortBy (fun map -> map.Position) 126 | |> List.map (serializeMapExpr buffer this) 127 | |> Expr.sequence 128 | 129 | Expr.Sequential(properties, maps) -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/TypeGen.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System 4 | open System.Reflection 5 | open System.Collections.Generic 6 | 7 | open Microsoft.FSharp.Quotations 8 | open Microsoft.FSharp.Quotations.Patterns 9 | 10 | open ProtoTypes.Core 11 | open ProviderImplementation.ProvidedTypes 12 | 13 | open Froto.Parser.Model 14 | open Froto.Parser.Ast 15 | open Froto.Core 16 | open Froto.Core.Encoding 17 | 18 | [] 19 | module internal TypeGen = 20 | 21 | let private applyRule rule (fieldType: Type) = 22 | match rule with 23 | | Required -> fieldType 24 | | Optional -> Expr.makeGenericType [fieldType] typedefof> 25 | | Repeated -> Expr.makeGenericType [fieldType] typedefof> 26 | 27 | let private createProperty scope (lookup: TypesLookup) (field: ProtoField) = 28 | 29 | let typeKind, propertyType = 30 | match TypeResolver.resolve scope field.Type lookup with 31 | | Some(Enum, t) -> TypeKind.Enum, typeof 32 | | Some(kind, t) -> kind, t 33 | | None -> invalidOp <| sprintf "Unable to resolve type '%s'" field.Type 34 | 35 | let propertyType = applyRule field.Rule propertyType 36 | let propertyName = Naming.snakeToPascal field.Name 37 | let property, backingField = Provided.readWriteProperty propertyType propertyName 38 | let propertyInfo = 39 | { ProvidedProperty = property; 40 | TypeKind = typeKind; 41 | Position = field.Position; 42 | ProtobufType = field.Type; 43 | Rule = field.Rule } 44 | 45 | propertyInfo, backingField 46 | 47 | 48 | let private createSerializeMethod typeInfo = 49 | let serialize = 50 | ProvidedMethod( 51 | "Serialize", 52 | [ ProvidedParameter("buffer", typeof) ], 53 | typeof, 54 | InvokeCode = (fun args -> Serialization.serializeExpr typeInfo args.[1] args.[0])) 55 | 56 | serialize.SetMethodAttrs(MethodAttributes.Virtual ||| MethodAttributes.Public) 57 | 58 | serialize 59 | 60 | let private createReadFromMethod typeInfo = 61 | let readFrom = 62 | ProvidedMethod( 63 | "LoadFrom", 64 | [ProvidedParameter("buffer", typeof)], 65 | typeof, 66 | InvokeCode = (fun args -> Deserialization.readFrom typeInfo args.[0] args.[1])) 67 | 68 | readFrom.SetMethodAttrs(MethodAttributes.Virtual) 69 | 70 | readFrom 71 | 72 | let private createDeserializeMethod properties targetType = 73 | let deserializeMethod = 74 | ProvidedMethod( 75 | "Deserialize", 76 | [ProvidedParameter("buffer", typeof)], 77 | targetType, 78 | InvokeCode = 79 | (fun args -> Expr.callStaticGeneric [targetType] [args.[0]] <@@ Codec.deserialize x @@>)) 80 | 81 | deserializeMethod.SetMethodAttrs(MethodAttributes.Static ||| MethodAttributes.Public) 82 | 83 | deserializeMethod 84 | 85 | let private createEnum scope lookup (enum: ProtoEnum) = 86 | let _, providedEnum = 87 | TypeResolver.resolveNonScalar scope enum.Name lookup 88 | |> Option.require (sprintf "Enum '%s' is not defined" enum.Name) 89 | 90 | enum.Items 91 | |> Seq.map (fun item -> Naming.upperSnakeToPascal item.Name, item.Value) 92 | |> Provided.addEnumValues providedEnum 93 | 94 | providedEnum 95 | 96 | let private createMap scope typesLookup (name: string) (keyTy: PKeyType) (valueTy: PType) (position: FieldNum) = 97 | let keyTypeName = 98 | match keyTy with 99 | | TKInt32 -> TInt32 | TKInt64 -> TInt64 100 | | TKUInt32 -> TUInt32 | TKUInt64 -> TUInt64 101 | | TKSInt32 -> TSInt32 | TKSInt64 -> TSInt64 102 | | TKFixed32 -> TFixed32 | TKFixed64 -> TFixed64 103 | | TKSFixed32 -> TSFixed32 | TKSFixed64 -> TSFixed64 104 | | TKBool -> TBool 105 | | TKString -> TString 106 | |> TypeResolver.ptypeToString 107 | 108 | let valueTypeName = TypeResolver.ptypeToString valueTy 109 | let valueTypeKind, valueType = 110 | TypeResolver.resolve scope valueTypeName typesLookup 111 | |> Option.require (sprintf "Can't resolve type '%s'" valueTypeName) 112 | |> function 113 | | Enum, _ -> TypeKind.Enum, typeof 114 | | kind, ty -> kind, ty 115 | 116 | let mapType = 117 | Expr.makeGenericType 118 | [ TypeResolver.resolveScalar keyTypeName |> Option.require (sprintf "Can't resolve scalar type '%s'" keyTypeName); 119 | valueType] 120 | typedefof> 121 | 122 | let property, field = Provided.readWriteProperty mapType <| Naming.snakeToPascal name 123 | 124 | let descriptor = 125 | { KeyType = keyTypeName; 126 | ValueType = valueTypeName; 127 | ValueTypeKind = valueTypeKind; 128 | Position = position; 129 | Property = property } 130 | 131 | descriptor, field 132 | 133 | let rec createType scope (lookup: TypesLookup) (message: ProtoMessage) = 134 | try 135 | let _, providedType = 136 | TypeResolver.resolveNonScalar scope message.Name lookup 137 | |> Option.require (sprintf "Type '%s' is not defined" message.Name) 138 | 139 | let nestedScope = scope +.+ message.Name 140 | message.Enums |> Seq.map (createEnum nestedScope lookup) |> Seq.iter providedType.AddMember 141 | message.Messages |> Seq.map (createType nestedScope lookup) |> Seq.iter providedType.AddMember 142 | 143 | let properties = message.Fields |> List.map (createProperty nestedScope lookup) 144 | let propertiesInfo = properties |> List.map fst 145 | 146 | providedType.AddMembers(properties |> List.map snd) 147 | providedType.AddMembers(propertiesInfo |> List.map (fun p -> p.ProvidedProperty)) 148 | providedType.AddMember <| Provided.ctor() 149 | 150 | let oneOfGroups = 151 | message.Parts 152 | |> Seq.choose (fun x -> match x with | TOneOf(name, members) -> Some((name, members)) | _ -> None) 153 | |> Seq.map (fun (name, members) -> OneOf.generateOneOf nestedScope lookup name members) 154 | |> Seq.fold (fun all (info, members) -> providedType.AddMembers members; info::all) [] 155 | 156 | let maps = 157 | message.Parts 158 | |> Seq.choose (fun x -> 159 | match x with 160 | | TMap(name, keyTy, valueTy, position) -> Some <| createMap nestedScope lookup name keyTy valueTy (int position) 161 | | _ -> None) 162 | |> Seq.fold (fun all (descriptor, field) -> 163 | providedType.AddMember field 164 | providedType.AddMember descriptor.Property 165 | descriptor::all) 166 | [] 167 | 168 | let typeInfo = { Type = providedType; Properties = propertiesInfo; OneOfGroups = oneOfGroups; Maps = maps } 169 | 170 | let serializeMethod = createSerializeMethod typeInfo 171 | providedType.AddMember serializeMethod 172 | providedType.DefineMethodOverride(serializeMethod, typeof.GetMethod("Serialize")) 173 | 174 | let readFromMethod = createReadFromMethod typeInfo 175 | providedType.AddMember readFromMethod 176 | providedType.DefineMethodOverride(readFromMethod, typeof.GetMethod("ReadFrom")) 177 | 178 | providedType.AddMember <| createDeserializeMethod properties providedType 179 | 180 | providedType 181 | with 182 | | ex -> 183 | printfn "An error occurred while generating type for message %s: %O" message.Name ex 184 | reraise() 185 | 186 | /// For the given package e.g. "foo.bar.baz.abc" creates a hierarchy of nested types Foo.Bar.Baz.Abc 187 | /// and returns pair of the first and last types in the hirarchy, Foo and Abc in this case 188 | let createNamespaceContainer (package: string) = 189 | 190 | let rec loop names (current: ProvidedTypeDefinition) = 191 | match names with 192 | | [] -> current 193 | | x::xs -> 194 | let nested = ProvidedTypeDefinition(Naming.snakeToPascal x, Some typeof, IsErased = false) 195 | current.AddMember nested 196 | loop xs nested 197 | 198 | let rootName::rest = package.Split('.') |> List.ofSeq 199 | let root = ProvidedTypeDefinition(Naming.snakeToPascal rootName, Some typeof, IsErased = false) 200 | let deepest = loop rest root 201 | root, deepest -------------------------------------------------------------------------------- /src/ProtoTypes/Generation/TypeResolver.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes.Generation 2 | 3 | open System 4 | 5 | open ProtoTypes.Core 6 | open ProviderImplementation.ProvidedTypes 7 | 8 | open Froto.Parser.Model 9 | open Froto.Parser.Ast 10 | 11 | type TypesLookup = Map 12 | 13 | module internal TypeResolver = 14 | 15 | let private getShortName (fullName: string) = fullName.Split('.') |> Seq.last 16 | 17 | let rec private allScopes (scope: string) = seq{ 18 | yield scope 19 | let lowestScopePosition = scope.LastIndexOf(".") 20 | if lowestScopePosition > 0 21 | then yield! scope.Substring(0, lowestScopePosition) |> allScopes 22 | } 23 | 24 | let discoverTypes scope (messages: ProtoMessage seq): TypesLookup = 25 | 26 | let rec loop scope (message: ProtoMessage) = seq { 27 | let fullName = scope +.+ message.Name 28 | yield Class, fullName 29 | yield! message.Enums |> Seq.map (fun enum -> TypeKind.Enum, fullName +.+ enum.Name) 30 | yield! message.Messages |> Seq.collect (loop fullName) 31 | } 32 | 33 | messages 34 | |> Seq.collect (loop scope) 35 | |> Seq.map (fun (kind, fullName) -> 36 | let name = getShortName fullName 37 | let ty = 38 | match kind with 39 | | Class -> Provided.message name 40 | | Enum -> Provided.enum name 41 | | Primitive -> invalidOp <| sprintf "Primitive type '%s' does not require custom Type" fullName 42 | fullName, (kind, ty)) 43 | |> Map.ofSeq 44 | 45 | let resolveScalar = function 46 | | "double" -> Some typeof 47 | | "float" -> Some typeof 48 | | "int32" -> Some typeof 49 | | "int64" -> Some typeof 50 | | "uint32" -> Some typeof 51 | | "uint64" -> Some typeof 52 | | "sint32" -> Some typeof 53 | | "sint64" -> Some typeof 54 | | "fixed32" -> Some typeof 55 | | "fixed64" -> Some typeof 56 | | "sfixed32" -> Some typeof 57 | | "sfixed64" -> Some typeof 58 | | "bool" -> Some typeof 59 | | "string" -> Some typeof 60 | | "bytes" -> Some typeof 61 | | x -> None 62 | 63 | let ptypeToString = function 64 | | TDouble -> "double" | TFloat -> "float" 65 | | TInt32 -> "int32" | TInt64 -> "int64" | TUInt32 -> "uint32" | TUInt64 -> "uint64" | TSInt32 -> "sint32" | TSInt64 -> "sint64" 66 | | TFixed32 -> "fixed32"| TFixed64 -> "fixed64" | TSFixed32 -> "sfixed32" | TSFixed64 -> "sfixed64" 67 | | TBool -> "bool" 68 | | TString -> "string" | TBytes -> "bytes" 69 | | TIdent typeIdent -> typeIdent 70 | 71 | let resolveNonScalar scope targetType (lookup: TypesLookup) = 72 | allScopes scope 73 | |> Seq.map (fun s -> s +.+ targetType) 74 | |> Seq.map (fun tp -> lookup |> Map.tryFind tp) 75 | |> Seq.tryFind Option.isSome 76 | |> Option.unwrap 77 | 78 | let resolve scope targetType (lookup: TypesLookup) = 79 | let findInLookup () = 80 | resolveNonScalar scope targetType lookup 81 | |> Option.map (fun (kind, ty) -> kind, ty :> Type) 82 | 83 | resolveScalar targetType 84 | |> Option.map (fun t -> Primitive, t) 85 | |> Option.otherwise findInLookup 86 | 87 | let resolvePType scope targetType (lookup: TypesLookup) = 88 | resolve scope (ptypeToString targetType) lookup 89 | 90 | -------------------------------------------------------------------------------- /src/ProtoTypes/ProtoTypes.fs: -------------------------------------------------------------------------------- 1 | namespace ProtoTypes 2 | 3 | open System 4 | open System.IO 5 | open System.Reflection 6 | 7 | open ProtoTypes.Core 8 | open ProtoTypes.Generation 9 | open ProviderImplementation 10 | open ProviderImplementation.ProvidedTypes 11 | open ProviderImplementation.ProvidedTypesTesting 12 | 13 | open Microsoft.FSharp.Quotations 14 | open Microsoft.FSharp.Core.CompilerServices 15 | 16 | open Froto.Parser.Model 17 | 18 | [] 19 | type ProtocolBuffersTypeProviderCreator(config : TypeProviderConfig) as this= 20 | inherit TypeProviderForNamespaces() 21 | 22 | let ns = typeof.Namespace 23 | let asm = Assembly.LoadFrom config.RuntimeAssembly 24 | let tempAssembly = Path.ChangeExtension(Path.GetTempFileName(), ".dll") |> ProvidedAssembly 25 | 26 | let protobufProvider = 27 | ProvidedTypeDefinition( 28 | asm, ns, "ProtocolBuffersTypeProvider", Some typeof, 29 | IsErased = false, 30 | HideObjectMethods = true) 31 | 32 | let parameters = [ProvidedStaticParameter("pathToFile", typeof)] 33 | 34 | do 35 | protobufProvider.DefineStaticParameters(parameters, fun typeName args -> 36 | let provider = 37 | ProvidedTypeDefinition( 38 | asm, ns, typeName, Some typeof, 39 | HideObjectMethods = true, 40 | IsErased = false) 41 | 42 | let tempAssembly = Path.ChangeExtension(Path.GetTempFileName(), ".dll") |> ProvidedAssembly 43 | 44 | let pathToFile = args.[0] :?> string 45 | 46 | let protoLocation = 47 | if Path.IsPathRooted pathToFile then pathToFile 48 | else config.ResolutionFolder pathToFile 49 | 50 | let protoFile = ProtoFile.ParseFile protoLocation 51 | 52 | let rootScope = protoFile.Packages |> Seq.tryHead |> Option.getOrElse String.Empty 53 | 54 | let container = 55 | if String.IsNullOrEmpty rootScope 56 | then provider 57 | else 58 | let root, deepest = TypeGen.createNamespaceContainer rootScope 59 | provider.AddMember root 60 | deepest 61 | 62 | let lookup = TypeResolver.discoverTypes rootScope protoFile.Messages 63 | 64 | protoFile.Messages 65 | |> Seq.map (TypeGen.createType rootScope lookup) 66 | |> Seq.iter container.AddMember 67 | 68 | if config.IsHostedExecution then 69 | Testing.FormatProvidedType(container, true) 70 | |> printfn "%s" 71 | 72 | tempAssembly.AddTypes [provider] 73 | provider) 74 | 75 | tempAssembly.AddTypes [protobufProvider] 76 | this.AddNamespace(ns, [protobufProvider]) 77 | 78 | [] 79 | do() 80 | -------------------------------------------------------------------------------- /src/ProtoTypes/ProtoTypes.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 0a304644-d2f6-4ee2-8a7d-19335f93fe3e 9 | Library 10 | ProtoTypes 11 | ProtoTypes 12 | v4.5 13 | 4.4.0.0 14 | ProtoTypes 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | ..\..\build 23 | DEBUG;TRACE 24 | 3 25 | bin\Debug\ProtoTypes.XML 26 | Project 27 | 28 | 29 | 30 | 31 | 32 | 33 | pdbonly 34 | true 35 | true 36 | ..\..\build 37 | TRACE 38 | 3 39 | bin\Release\ProtoTypes.XML 40 | 41 | 42 | 11 43 | 44 | 45 | 46 | 47 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 48 | 49 | 50 | 51 | 52 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 53 | 54 | 55 | 56 | 57 | 64 | 65 | 66 | True 67 | external/ProvidedTypes.fsi 68 | 69 | 70 | True 71 | external/ProvidedTypes.fsi 72 | 73 | 74 | True 75 | external/ProvidedTypes.fs 76 | 77 | 78 | True 79 | external/ProvidedTypesTesting.fs 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | True 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ..\..\packages\FParsec\lib\net40-client\FParsec.dll 114 | True 115 | True 116 | 117 | 118 | ..\..\packages\FParsec\lib\net40-client\FParsecCS.dll 119 | True 120 | True 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | ..\..\packages\Froto.Core\lib\net45\Froto.Core.dll 130 | True 131 | True 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | ..\..\packages\Froto.Parser\lib\net45\Froto.Parser.dll 141 | True 142 | True 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/ProtoTypes/Script.fsx: -------------------------------------------------------------------------------- 1 | #I "../../build" 2 | 3 | #r "ProtoTypes.dll" 4 | #r "FParsecCS.dll" 5 | #r "FParsec.dll" 6 | #r "Froto.Parser.dll" 7 | #r "Froto.Core.dll" 8 | 9 | open Froto.Core 10 | open Froto.Core.Encoding 11 | 12 | [] 13 | let path = __SOURCE_DIRECTORY__ + "/../ProtoTypes.Tests/proto/person.proto" 14 | 15 | type ProtoBuf = ProtoTypes.ProtocolBuffersTypeProvider 16 | type Sample = ProtoBuf.ProtoTypes.Sample 17 | let address = Sample.Person.Address(Address1 = "Street", HouseNumber = 12, Whatever = [1; 2; 3], SomeInts = [Sample.Person.IntContainer(Value = 5); Sample.Person.IntContainer(Value = 7)]) 18 | let p = Sample.Person(Name = "Name", Id = 1, HasCriminalConvictions = false, Weight = 82.3, PersonGender = Sample.Person.Gender.Female, Email = Some "Email", PersonAddress = Some address) 19 | 20 | let buffer = ZeroCopyBuffer(1000) 21 | 22 | let container = Sample.MapContainer() 23 | let map = System.Collections.Generic.Dictionary<_, _>() 24 | map.Add(1, "ololo") 25 | container.PrimitiveMap <- map 26 | container.Serialize(buffer) 27 | -------------------------------------------------------------------------------- /src/ProtoTypes/paket.references: -------------------------------------------------------------------------------- 1 | Froto.Parser 2 | Froto.Core 3 | 4 | File:ProvidedTypes.fsi external 5 | File:ProvidedTypes.fs external 6 | File:ProvidedTypesTesting.fs external --------------------------------------------------------------------------------