Skip to content

Instantly share code, notes, and snippets.

@object
Created May 24, 2021 19:13
Show Gist options
  • Save object/dbfc9069725504ced1069eebe5be405c to your computer and use it in GitHub Desktop.
Save object/dbfc9069725504ced1069eebe5be405c to your computer and use it in GitHub Desktop.
Protobuf serialization in F#
module ProtobufSerializationTests
open System
open System.IO
open ProtoBuf
open Xunit
module ProtoBufUtils =
[<ProtoContract>]
type DateTimeOffsetSurrogate() =
[<ProtoMember(1)>]
member val DateTimeString = "" with get, set
static member public op_Implicit(value : DateTimeOffset) : DateTimeOffsetSurrogate =
DateTimeOffsetSurrogate(DateTimeString = value.ToString("o"))
static member public op_Implicit(value : DateTimeOffsetSurrogate) : DateTimeOffset =
DateTimeOffset.Parse(value.DateTimeString)
let initProtoBuf () =
ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typedefof<DateTimeOffset>, false).SetSurrogate(typedefof<DateTimeOffsetSurrogate>)
[<AllowNullLiteral>]
type IProtoBufSerializable = interface end
[<AllowNullLiteral>]
type IProtoBufSerializableEvent =
inherit IProtoBufSerializable
abstract member Timestamp : DateTimeOffset
[<ProtoContract;CLIMutable;NoComparison>]
type ReceivedTranscodingVersion =
{ [<ProtoMember(1)>] Version : int
[<ProtoMember(2)>] Timestamp : DateTimeOffset }
interface IProtoBufSerializableEvent with
member this.Timestamp = this.Timestamp
static member FromDomainWithTimestamp (version, timestamp) =
{ Version = version
Timestamp = timestamp }
static member FromDomain version =
ReceivedTranscodingVersion.FromDomainWithTimestamp (version, DateTimeOffset.Now)
member this.ToDomain() =
this.Version
[<ProtoContract;CLIMutable;NoComparison>]
type AssignedProgramId =
{ [<ProtoMember(1)>] ProgramId : string
[<ProtoMember(2)>] Timestamp : DateTimeOffset }
interface IProtoBufSerializableEvent with
member this.Timestamp = this.Timestamp
static member FromDomainWithTimestamp (progId, timestamp) =
{ ProgramId = progId
Timestamp = timestamp }
static member FromDomain piProgId =
AssignedProgramId.FromDomainWithTimestamp (piProgId, DateTimeOffset.Now)
member this.ToDomain() =
this.ProgramId
let [<Literal>] AssignedProgramIdManifest = "AssignedProgramId"
let [<Literal>] ReceivedTranscodingVersionManifest = "ReceivedTranscodingVersion"
type ProtobufSerializer () =
member this.ToBinary(o:obj) =
use stream = new MemoryStream()
Serializer.Serialize(stream, o)
stream.ToArray()
member this.FromBinary(bytes:byte[], manifest:string) =
use stream = new MemoryStream(bytes)
let typ =
match manifest with
| AssignedProgramIdManifest -> typedefof<AssignedProgramId>
| ReceivedTranscodingVersionManifest -> typedefof<ReceivedTranscodingVersion>
| _ -> failwith $"Unknown manifest: %s{manifest}"
Serializer.Deserialize(typ, stream)
member this.Manifest(o:obj) =
match o with
| :? AssignedProgramId -> AssignedProgramIdManifest
| :? ReceivedTranscodingVersion -> ReceivedTranscodingVersionManifest
| _ -> failwith $"Unknown type : %s{o.GetType().FullName}"
let testSerialization<'DomainType,'PersistentType> (serializer : ProtobufSerializer) (domainMsg : 'DomainType) fromDomain toDomain (manifest : string) =
let input = fromDomain domainMsg
let bytes = serializer.ToBinary(input)
let deserialized = serializer.FromBinary(bytes, manifest) :?> 'PersistentType
Assert.Equal(input, deserialized)
Assert.Equal(domainMsg, toDomain deserialized)
[<Fact>]
let ``Test round trip serialization`` () =
ProtoBufUtils.initProtoBuf ()
let serializer = ProtobufSerializer()
let versionMessage = 0
testSerialization serializer versionMessage ReceivedTranscodingVersion.FromDomain (fun x -> x.ToDomain()) ReceivedTranscodingVersionManifest
let msg = "abcd"
testSerialization serializer msg AssignedProgramId.FromDomain (fun x -> x.ToDomain()) AssignedProgramIdManifest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment