wireform-proto-0.2.0.0: src/Proto/Google/Protobuf/Compiler/Plugin.hs
{-# LANGUAGE BangPatterns #-}
-- | Haskell types for @google/protobuf/compiler/plugin.proto@.
--
-- These types define the interface between protoc and code generator plugins.
-- A plugin reads a 'CodeGeneratorRequest' from stdin and writes a
-- 'CodeGeneratorResponse' to stdout.
module Proto.Google.Protobuf.Compiler.Plugin
( -- * Request
CodeGeneratorRequest (..)
, defaultCodeGeneratorRequest
-- * Response
, CodeGeneratorResponse (..)
, defaultCodeGeneratorResponse
, CodeGeneratorResponseFile (..)
, defaultCodeGeneratorResponseFile
-- * Plugin entry point
, pluginMain
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import System.IO (stdin, stdout, hSetBinaryMode)
import Proto.Encode
import Proto.Decode
import Proto.Internal.Wire (Tag(..))
import Proto.Google.Protobuf.Descriptor (FileDescriptorProto)
data CodeGeneratorRequest = CodeGeneratorRequest
{ cgrFileToGenerate :: !(V.Vector Text)
, cgrParameter :: !Text
, cgrProtoFile :: !(V.Vector FileDescriptorProto)
} deriving stock (Show, Eq, Generic)
deriving anyclass NFData
defaultCodeGeneratorRequest :: CodeGeneratorRequest
defaultCodeGeneratorRequest = CodeGeneratorRequest V.empty "" V.empty
instance MessageEncode CodeGeneratorRequest where
buildMessage cgr =
V.foldl' (\a f -> a <> encodeFieldString 1 f) mempty (cgrFileToGenerate cgr) <>
(if cgrParameter cgr == "" then mempty else encodeFieldString 2 (cgrParameter cgr)) <>
V.foldl' (\a p -> a <> encodeFieldMessage 15 p) mempty (cgrProtoFile cgr)
instance MessageDecode CodeGeneratorRequest where
messageDecoder = loop defaultCodeGeneratorRequest
where
loop !r = do
mt <- getTagOrU
case mt of
UNothing -> pure r
UJust (Tag 1 _) -> do v <- decodeFieldString; loop r { cgrFileToGenerate = V.snoc (cgrFileToGenerate r) v }
UJust (Tag 2 _) -> do v <- decodeFieldString; loop r { cgrParameter = v }
UJust (Tag 15 _) -> do v <- decodeFieldMessage; loop r { cgrProtoFile = V.snoc (cgrProtoFile r) v }
UJust (Tag _ wt) -> skipField wt >> loop r
data CodeGeneratorResponse = CodeGeneratorResponse
{ cgrsError :: !Text
, cgrsFile :: !(V.Vector CodeGeneratorResponseFile)
, cgrsSupportedFeatures :: !Int
} deriving stock (Show, Eq, Generic)
deriving anyclass NFData
defaultCodeGeneratorResponse :: CodeGeneratorResponse
defaultCodeGeneratorResponse = CodeGeneratorResponse "" V.empty 0
instance MessageEncode CodeGeneratorResponse where
buildMessage cgr =
(if cgrsError cgr == "" then mempty else encodeFieldString 1 (cgrsError cgr)) <>
(if cgrsSupportedFeatures cgr == 0 then mempty
else encodeFieldVarint 2 (fromIntegral (cgrsSupportedFeatures cgr))) <>
V.foldl' (\a f -> a <> encodeFieldMessage 15 f) mempty (cgrsFile cgr)
instance MessageDecode CodeGeneratorResponse where
messageDecoder = loop defaultCodeGeneratorResponse
where
loop !r = do
mt <- getTagOrU
case mt of
UNothing -> pure r
UJust (Tag 1 _) -> do v <- decodeFieldString; loop r { cgrsError = v }
UJust (Tag 2 _) -> do v <- getVarint; loop r { cgrsSupportedFeatures = fromIntegral v }
UJust (Tag 15 _) -> do v <- decodeFieldMessage; loop r { cgrsFile = V.snoc (cgrsFile r) v }
UJust (Tag _ wt) -> skipField wt >> loop r
data CodeGeneratorResponseFile = CodeGeneratorResponseFile
{ cgrfName :: !Text
, cgrfContent :: !Text
} deriving stock (Show, Eq, Generic)
deriving anyclass NFData
defaultCodeGeneratorResponseFile :: CodeGeneratorResponseFile
defaultCodeGeneratorResponseFile = CodeGeneratorResponseFile "" ""
instance MessageEncode CodeGeneratorResponseFile where
buildMessage f =
(if cgrfName f == "" then mempty else encodeFieldString 1 (cgrfName f)) <>
(if cgrfContent f == "" then mempty else encodeFieldString 15 (cgrfContent f))
instance MessageDecode CodeGeneratorResponseFile where
messageDecoder = loop defaultCodeGeneratorResponseFile
where
loop !f = do
mt <- getTagOrU
case mt of
UNothing -> pure f
UJust (Tag 1 _) -> do v <- decodeFieldString; loop f { cgrfName = v }
UJust (Tag 15 _) -> do v <- decodeFieldString; loop f { cgrfContent = v }
UJust (Tag _ wt) -> skipField wt >> loop f
-- | Entry point for a protoc plugin.
-- Reads a CodeGeneratorRequest from stdin, applies the handler,
-- and writes the CodeGeneratorResponse to stdout.
pluginMain :: (CodeGeneratorRequest -> IO CodeGeneratorResponse) -> IO ()
pluginMain handler = do
hSetBinaryMode stdin True
hSetBinaryMode stdout True
input <- BS.hGetContents stdin
case decodeMessage input of
Left err -> do
let resp = defaultCodeGeneratorResponse { cgrsError = T.pack (show err) }
BS.hPut stdout (encodeMessage resp)
Right req -> do
resp <- handler req
BS.hPut stdout (encodeMessage resp)