packages feed

hydra-0.8.0: src/main/haskell/Hydra/Ext/Protobuf/Serde.hs

module Hydra.Ext.Protobuf.Serde (
  deprecatedOptionName,
  descriptionOptionName,
  writeProtoFile) where

import Hydra.Tools.Serialization
import Hydra.Tools.Formatting
import qualified Hydra.Ast as CT
import qualified Hydra.Ext.Protobuf.Proto3 as P3

import qualified Data.List as L
import qualified Data.Maybe as Y


deprecatedOptionName = "deprecated"
-- A special Protobuf option for descriptions (documentation)
descriptionOptionName = "_description"

excludeInternalOptions :: [P3.Option] -> [P3.Option]
excludeInternalOptions = L.filter (\opt -> L.head (P3.optionName opt) /= '_' )

protoBlock :: [CT.Expr] -> CT.Expr
protoBlock = brackets curlyBraces fullBlockStyle . doubleNewlineSep

semi :: CT.Expr -> CT.Expr
semi e = noSep [e, cst ";"]

optDesc :: Bool -> [P3.Option] -> CT.Expr -> CT.Expr
optDesc doubleNewline opts expr = if L.null descs
    then expr
    else sep [cst $ asComment (unValue $ P3.optionValue $ L.head descs), expr]
  where
    sep = if doubleNewline then doubleNewlineSep else newlineSep
    descs = L.filter (\(P3.Option name value) -> name == descriptionOptionName) opts
    asComment = L.intercalate "\n" . fmap (\s -> "// " ++ s) . lines
    unValue v = case v of
      P3.ValueBoolean b -> if b then "true" else "false"
      P3.ValueString s -> s

writeDefinition :: P3.Definition -> CT.Expr
writeDefinition def = case def of
  P3.DefinitionEnum enum -> writeEnumDefinition enum
  P3.DefinitionMessage msg -> writeMessageDefinition msg

writeEnumDefinition :: P3.EnumDefinition -> CT.Expr
writeEnumDefinition (P3.EnumDefinition name values options) = optDesc False options $ spaceSep [
  cst "enum",
  cst $ P3.unTypeName name,
  protoBlock (writeEnumValue <$> values)]

writeEnumValue :: P3.EnumValue -> CT.Expr
writeEnumValue (P3.EnumValue name number options) = optDesc False options $ semi $ spaceSep [
    cst $ P3.unEnumValueName name,
    cst "=",
    cst $ show number]

writeField :: P3.Field -> CT.Expr
writeField (P3.Field name jsonName typ num options) = optDesc False options $ case typ of
  P3.FieldTypeOneof fields -> spaceSep [
    cst "oneof",
    cst $ P3.unFieldName name,
    protoBlock (writeField <$> fields)]
  _ -> semi $ spaceSep $ Y.catMaybes [ -- TODO: jsonName
    Just $ writeFieldType typ,
    Just $ cst $ P3.unFieldName name,
    Just $ cst "=",
    Just $ cst $ show num,
    writeFieldOptions options]

writeFieldOption :: P3.Option -> CT.Expr
writeFieldOption (P3.Option name value) = spaceSep [cst name, cst "=", writeValue value]

writeFieldOptions :: [P3.Option] -> Y.Maybe CT.Expr
writeFieldOptions options0 = if L.null options
    then Nothing
    else Just $ bracketList inlineStyle (writeFieldOption <$> options)
  where
    options = excludeInternalOptions options0

writeFieldType :: P3.FieldType -> CT.Expr
writeFieldType ftyp = case ftyp of
  P3.FieldTypeMap st -> noSep [cst "map", angleBracesList inlineStyle [cst "string", writeSimpleType st]]
  P3.FieldTypeRepeated st -> spaceSep [cst "repeated", writeSimpleType st]
  P3.FieldTypeSimple st -> writeSimpleType st

writeFileOption :: P3.Option -> CT.Expr
writeFileOption (P3.Option name value) = semi $ spaceSep [cst "option", cst name, cst "=", writeValue value]

writeFileOptions :: [P3.Option] -> Y.Maybe CT.Expr
writeFileOptions options0 = if L.null options
    then Nothing
    else Just $ newlineSep $ writeFileOption <$> options
  where
    options = excludeInternalOptions options0

writeImport :: P3.FileReference -> CT.Expr
writeImport (P3.FileReference path) = semi $ spaceSep [cst "import", cst $ show path]

writeMessageDefinition :: P3.MessageDefinition -> CT.Expr
writeMessageDefinition (P3.MessageDefinition name fields options) = optDesc False options $ spaceSep [
  cst "message",
  cst $ P3.unTypeName name,
  protoBlock (writeField <$> fields)]

writeProtoFile :: P3.ProtoFile -> CT.Expr
writeProtoFile (P3.ProtoFile pkg imports defs options) = optDesc True options $ doubleNewlineSep
    $ Y.catMaybes [headerSec, importsSec, optionsSec, defsSec]
  where
    headerSec = Just $ newlineSep [
      semi $ cst "syntax = \"proto3\"",
      semi $ spaceSep [cst "package", cst (P3.unPackageName pkg)]]
    importsSec = if L.null imports
      then Nothing
      else Just $ newlineSep $ writeImport <$> imports
    optionsSec = writeFileOptions options1
    defsSec = if L.null defs
      then Nothing
      else Just $ doubleNewlineSep $ writeDefinition <$> defs
    options1 = L.filter (\(P3.Option name value) -> name /= descriptionOptionName) options

writeScalarType :: P3.ScalarType -> CT.Expr
writeScalarType sct = cst $ case sct of
  P3.ScalarTypeBool -> "bool"
  P3.ScalarTypeBytes -> "bytes"
  P3.ScalarTypeDouble -> "double"
  P3.ScalarTypeFixed32 -> "fixed32"
  P3.ScalarTypeFixed64 -> "fixed64"
  P3.ScalarTypeFloat -> "float"
  P3.ScalarTypeInt32 -> "int32"
  P3.ScalarTypeInt64 -> "int64"
  P3.ScalarTypeSfixed32 -> "sfixed32"
  P3.ScalarTypeSfixed64 -> "sfixed64"
  P3.ScalarTypeSint32 -> "sint32"
  P3.ScalarTypeSint64 -> "sint64"
  P3.ScalarTypeString -> "string"
  P3.ScalarTypeUint32 -> "uint32"
  P3.ScalarTypeUint64 -> "uint64"

writeSimpleType :: P3.SimpleType -> CT.Expr
writeSimpleType st = case st of
  P3.SimpleTypeReference name -> cst $ P3.unTypeName name
  P3.SimpleTypeScalar sct -> writeScalarType sct

writeValue :: P3.Value -> CT.Expr
writeValue v = cst $ case v of
  P3.ValueBoolean b -> if b then "true" else "false"
  P3.ValueString s -> show s