|
1 | | -# A Roslyn PostProcessor for protoc generated files to add Guid support |
| 1 | +# Porticle.Grpc.TypeMapper |
2 | 2 |
|
3 | | -This library allows you to map string and StringValue to Guid and Guid?. |
| 3 | +A Roslyn-based post-processor for protoc-generated files that adds automatic mappings for `Guid`, `Guid?`, `string?` or nullable enums. By simply adding this package and adding |
| 4 | +comments to |
| 5 | +your `.proto` file. |
| 6 | + |
| 7 | +## Build State |
| 8 | + |
| 9 | +[](https://github.com/Machibuse/Porticle.CLDR/actions/workflows/release.yaml) |
| 10 | + |
| 11 | +## Nuget |
| 12 | + |
| 13 | +[](https://www.nuget.org/packages/Porticle.Grpc.TypeMapper/) [](https://www.nuget.org/packages/Porticle.CLDR.Units/) |
| 14 | + |
| 15 | +## Overview |
| 16 | + |
| 17 | +This library adds automatic conversion for: |
| 18 | + |
| 19 | +- Protobuf string to C# Guid |
| 20 | +- Protobuf google.Protobuf.StringValue to C# Guid? |
| 21 | +- Protobuf google.Protobuf.StringValue to C# string? |
| 22 | +- Protobuf optional enum to C# nullable enum |
| 23 | + |
| 24 | +This Library adds a Roslyn Postprocessing zu the c# files generated by the protoc compiler. |
| 25 | +Enabling seamless integration of Guid, Guid?, string? And nullable Enums in your gRPC services without manual conversion. |
| 26 | +Code. |
| 27 | + |
| 28 | +## Installation |
| 29 | + |
| 30 | +### Install the package via NuGet: |
| 31 | + |
| 32 | +```powershell |
| 33 | +dotnet add package Porticle.Grpc.TypeMapper |
| 34 | +``` |
| 35 | + |
| 36 | +After installing the Package, this Post build step ist dynamically added to your build. |
| 37 | + |
| 38 | +```msbuild |
| 39 | +
|
| 40 | +<Project> |
| 41 | + <Target Name="RunProtoPostProcessing" AfterTargets="Protobuf_Compile"> |
| 42 | + <ItemGroup> |
| 43 | + <_FilesToPostProcess Include="$(MSBuildProjectDirectory)\%(Protobuf_Compile.OutputDir)\%(Protobuf_Compile.Filename)"/> |
| 44 | + </ItemGroup> |
| 45 | + <Message Text="Proto Postprocessing" Importance="high"/> |
| 46 | + <Exec Command="dotnet "$(MSBuildThisFileDirectory)..\tools\$(TargetFramework)\Porticle.Grpc.TypeMapper.dll" -- %(_FilesToPostProcess.Identity)" Condition="'@(_FilesToPostProcess)' != ''"/> |
| 47 | + </Target> |
| 48 | +</Project> |
| 49 | +``` |
| 50 | + |
| 51 | +Don't wonder ist you cant se it in your csproj file. It is dynamically added when your build is processed. |
| 52 | + |
| 53 | +## Usage |
| 54 | + |
| 55 | +There are three things you can do in your .proto files: |
| 56 | + |
| 57 | +- Add `// [GrpcGuid]` as comment to a string field - Converts the corresponding c# string property to Guid |
| 58 | +- Add `// [GrpcGuid]` as comment to a StringValue field - Converts the corresponding c# string property to Guid? |
| 59 | +- Add `// [NullableString]` as comment to a StringValue field - Converts the corresponding c# string property to string? |
| 60 | +- Add `// [NullableEnum]` as comment to a optional enum field - Converts the corresponding optional proto enum to a C# nullable Enum |
| 61 | + |
| 62 | +First an Example of a default .proto file |
| 63 | + |
| 64 | +### Without TypeMapper |
| 65 | + |
| 66 | +```protobuf |
| 67 | +syntax = "proto3"; |
| 68 | +
|
| 69 | +import "google/protobuf/wrappers.proto"; |
| 70 | +
|
| 71 | +enum TestEnum { |
| 72 | + FOO = 0; |
| 73 | + BAR = 1; |
| 74 | +} |
| 75 | +
|
| 76 | +message User { |
| 77 | + // Guid of the user object |
| 78 | + string id = 1; |
| 79 | +
|
| 80 | + // Optional parent UserId |
| 81 | + google.protobuf.StringValue optional_parent_user_id = 2; |
| 82 | +
|
| 83 | + // Optional description |
| 84 | + google.protobuf.StringValue description = 3; |
| 85 | +
|
| 86 | + // List of roles |
| 87 | + repeated string role_ids = 4; |
| 88 | +
|
| 89 | + // Simple Enum |
| 90 | + optional TestEnum foo_bar = 5; |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +Will result in protoc generated code like this, everything is a string or a simple enum |
| 95 | + |
| 96 | +```csharp |
| 97 | +/// <summary>Guid of the user object</summary> |
| 98 | +public string Id { |
| 99 | + get { return id_; } |
| 100 | + set { id_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } |
| 101 | +} |
| 102 | + |
| 103 | +/// <summary>Optional Guid of the parent UserId</summary> |
| 104 | +public string OptionalParentUserId { |
| 105 | + get { return optionalParentUserId_; } |
| 106 | + set { optionalParentUserId_ = value; } |
| 107 | +} |
| 108 | + |
| 109 | +/// <summary>Optional description string</summary> |
| 110 | +public string Description { |
| 111 | + get { return description_; } |
| 112 | + set { description_ = value; } |
| 113 | +} |
| 114 | + |
| 115 | +/// <summary>List of roles</summary> |
| 116 | +public pbc::RepeatedField<string> RoleIds { |
| 117 | + get { return roleIds_; } |
| 118 | +} |
| 119 | + |
| 120 | +public global::Porticle.Grpc.UnitTests.TestEnum FooBar { |
| 121 | + get { |
| 122 | + if ((_hasBits0 & 1) != 0) |
| 123 | + return fooBar_; |
| 124 | + else |
| 125 | + return FooBarDefaultValue; |
| 126 | + } |
| 127 | + set { |
| 128 | + _hasBits0 |= 1; |
| 129 | + fooBar_ = value; |
| 130 | + } |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +### Now a sample with TypeMapper enabled |
| 135 | + |
| 136 | +```protobuf |
| 137 | +syntax = "proto3"; |
| 138 | +
|
| 139 | +import "google/protobuf/wrappers.proto"; |
| 140 | +
|
| 141 | +enum TestEnum { |
| 142 | + FOO = 0; |
| 143 | + BAR = 1; |
| 144 | +} |
| 145 | +
|
| 146 | +message User { |
| 147 | + // [GrpcGuid] Guid of the user object |
| 148 | + string id = 1; |
| 149 | +
|
| 150 | + // [GrpcGuid] Optional Guid of the parent UserId |
| 151 | + google.protobuf.StringValue optional_parent_user_id = 2; |
| 152 | +
|
| 153 | + // [NullableString] Optional description string |
| 154 | + google.protobuf.StringValue description = 3; |
| 155 | +
|
| 156 | + // [GrpcGuid] List of roles |
| 157 | + repeated string role_ids = 4; |
| 158 | +
|
| 159 | + // [NullableEnum] Simple Enum |
| 160 | + optional TestEnum foo_bar = 5; |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +Will result in generated code like this, using string? Guid and Guid? and a nullable Enum |
| 165 | + |
| 166 | +```csharp |
| 167 | +/// <summary>[GrpcGuid] Guid of the user object</summary> |
| 168 | +public global::System.Guid Id { |
| 169 | + get |
| 170 | + { |
| 171 | + // Parse the internal string as Guid and return it |
| 172 | + return global::System.Guid.Parse(id_); |
| 173 | + } |
| 174 | + set |
| 175 | + { |
| 176 | + // Set the internal string fron the given guid |
| 177 | + id_ = (value).ToString("D"); |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +/// <summary>[GrpcGuid] Optional Guid of the parent UserId</summary> |
| 182 | +public global::System.Guid? OptionalParentUserId { |
| 183 | + get { |
| 184 | + // return null wehn corresponding string is null |
| 185 | + if(optionalParentUserId_==null) return default; |
| 186 | + // return a Guid instead of the string |
| 187 | + return global::System.Guid.Parse(optionalParentUserId_); |
| 188 | + } |
| 189 | + set |
| 190 | + { |
| 191 | + // sets the internal string from the given guid |
| 192 | + optionalParentUserId_ = (value)?.ToString("D"); |
| 193 | + } |
| 194 | +} |
| 195 | + |
| 196 | + |
| 197 | +// enable nullable for this property and return string? instead of string |
| 198 | +#nullable enable |
| 199 | +/// <summary>[NullableString] Optional description string</summary> |
| 200 | +public string? Description { |
| 201 | + get { return description_; } |
| 202 | + set { description_ = value; } |
| 203 | +} |
| 204 | +#nullable disable |
| 205 | + |
| 206 | +/// <summary>[GrpcGuid] List of roles</summary> |
| 207 | +public IList<Guid> RoleIds { |
| 208 | + get |
| 209 | + { |
| 210 | + // returns a wrapper that converts a list of strings to a list of guids |
| 211 | + return new RepeatedFieldGuidWrapper(roleIds_); |
| 212 | + } |
| 213 | +} |
| 214 | + |
| 215 | +public global::Porticle.Grpc.UnitTests.TestEnum? FooBar { |
| 216 | + get { |
| 217 | + if ((_hasBits0 & 1) != 0) |
| 218 | + { |
| 219 | + return fooBar_; |
| 220 | + } |
| 221 | + else |
| 222 | + { |
| 223 | + // return null instead of default value when Has-Flag is false |
| 224 | + return null; |
| 225 | + } |
| 226 | + } |
| 227 | + set { |
| 228 | + if(value==null) { |
| 229 | + // Set hasflag to false when null is assigend |
| 230 | + _hasBits0 &=~1; |
| 231 | + fooBar_ = FooBarDefaultValue; |
| 232 | + } |
| 233 | + else |
| 234 | + { |
| 235 | + _hasBits0 |= 1; |
| 236 | + fooBar_ = value.Value; |
| 237 | + } |
| 238 | + } |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +## What is currently not Possible? |
| 243 | + |
| 244 | +- Mapping `repeated google.protobuf.StringValue` to `List<Guid?>` or `List<string?>` because grpc internally uses `RepeatedField<string>` instead of `RepeatedField<StringValue>`. |
| 245 | + This may be a bug in protoc compiler, because it is also not possible to add `null` to `repeated google.protobuf.StringValue` because there is a not null check in the Add |
| 246 | + function in `RepeatedField<T>` |
| 247 | + |
| 248 | +- This Tool actually don't works when protoc / Grpc.Tools is compiled with GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE |
0 commit comments