-- | -- = Tutorial -- -- >>> :set -XOverloadedStrings -- -- This module contains a worked example of encoding and decoding messages, -- and exporting a corresponding .proto file from Haskell types. -- -- == Setup -- -- If you are using "GHC.Generics", you should enable the generic deriving -- extension, and import the main module: -- -- > {-# LANGUAGE DeriveGeneric #-} -- -- > import Proto3.Suite -- > import GHC.Generics -- {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverloadedStrings #-} module Proto3.Suite.Tutorial where import Data.Int (Int32) import Data.Word (Word32) import GHC.Exts (Proxy#, proxy#) import GHC.Generics import Proto3.Suite (Enumerated, Nested, NestedVec, PackedVec, Message, Named, Finite, DotProtoDefinition, enum, message, packageFromDefs, toProtoFileDef) import Proto3.Wire.Class (ProtoEnum) -- | -- == Defining Message Types -- -- Define messages using Haskell record types. You can use any 'MessageField' types -- in your records, and the correct serializer and deserializer will be generated -- for you. -- -- Make sure to derive a 'Generic' instance for your type, and then derive instances -- for 'Message' and 'Named' using the default (empty) instances: -- -- > instance Message Foo -- > instance Named Foo -- -- == Encoding Messages -- -- Now we can encode a value of type 'Foo' using 'Proto3.Suite.toLazyByteString'. -- -- For example: -- -- >>> Proto3.Suite.toLazyByteString (Foo 42 (Proto3.Suite.PackedVec (pure 123))) -- "\b*\DC2\SOH{" -- -- We can also decode messages using `fromByteString`: -- -- >>> Proto3.Suite.fromByteString "\b*\DC2\SOH{" :: Either [Proto3.Suite..Decode.Parser.ParseError] Foo -- Right (Foo {fooX = 42, fooY = PackedVec {packedvec = [123]}}) data Foo = Foo { fooX :: Word32 , fooY :: PackedVec Int32 } deriving (Show, Eq, Generic) instance Message Foo instance Named Foo -- | -- == Nested Messages -- -- Messages can contain other messages, by using the 'Nested' constructor, and -- lists of nested messages using the 'NestedVec constructor'. data Bar = Bar { barShape :: Enumerated Shape , barFoo :: Nested Foo , foos :: NestedVec Foo } deriving (Eq, Generic) instance Message Bar instance Named Bar -- | -- == Enumerations -- -- Enumerated types can be used by deriving the 'Bounded', 'Enum', 'ProtoEnum', -- 'Finite', and 'Named' classes. Each of these instances are implied by -- a 'Generic' instance, so can be derived as follows: -- -- > data Shape -- > = Circle -- > | Square -- > | Triangle -- > deriving (Bounded, Eq, Enum, Finite, Generic, Named, Ord, ProtoEnum) data Shape = Circle | Square | Triangle deriving (Bounded, Eq, Enum, Finite, Generic, Named, Ord, ProtoEnum) -- | -- == Generating a .proto file -- -- We can generate a .proto file for the 'Foo' and 'Bar' data types by -- using the 'toProtoFileDef' function. We have to provide a package name, and -- explicitly list the message and enumeration types as a 'DotProto' value. -- -- >>> putStrLn protoFile -- syntax = "proto3"; -- package examplePackageName; -- enum Shape { -- Circle = 0; -- Square = 1; -- Triangle = 2; -- } -- message Foo { -- uint32 fooX = 1; -- repeated int32 fooY = 2 [packed=true]; -- } -- message Bar { -- Shape barShape = 1; -- Foo barFoo = 2; -- repeated Foo foos = 3 [packed=false]; -- } protoFile :: String protoFile = toProtoFileDef $ packageFromDefs "examplePackageName" ([ enum (proxy# :: Proxy# Shape) , message (proxy# :: Proxy# Foo) , message (proxy# :: Proxy# Bar) ] :: [DotProtoDefinition])