├── .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 | | [](https://ci.appveyor.com/project/takemyoxygen/prototypes) | [](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