wireform-proto-0.1.0.0: src/Proto/IDL/Descriptor.hs
{- | Convert wireform's AST types to descriptor.proto types and back.
This enables bundling schema metadata with generated types:
the parsed 'ProtoFile' is converted to a 'R.FileDescriptorProto',
serialized, and embedded in the generated code.
The reverse direction ('fileDescriptorToAST') is used by the protoc
plugin, which receives 'R.FileDescriptorProto' from protoc in
'CodeGeneratorRequest.proto_file' and needs wireform's 'ProtoFile' to
drive code generation.
All descriptor shapes here are the generated reflection types from
'Proto.Google.Protobuf.Reflection.Descriptor' (full @descriptor.proto@),
so the plugin path matches what @protoc@ emits without a decode/encode
narrowing step.
-}
module Proto.IDL.Descriptor (
astToFileDescriptor,
astToDescriptor,
astToFieldDescriptor,
astToEnumDescriptor,
astToServiceDescriptor,
serializeFileDescriptor,
fileDescriptorToAST,
descriptorToMessage,
fieldDescriptorToField,
enumDescriptorToEnum,
serviceDescriptorToService,
) where
import Data.ByteString (ByteString)
import Data.List (find, partition)
import Data.Maybe (fromMaybe, isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Read as TR
import qualified Data.Vector as V
import Proto (encodeMessage)
import Proto.Google.Protobuf.Reflection.Descriptor as R
import Proto.IDL.AST
-- | Convert a parsed ProtoFile to a FileDescriptorProto.
astToFileDescriptor :: FilePath -> ProtoFile -> R.FileDescriptorProto
astToFileDescriptor path pf =
R.defaultFileDescriptorProto
{ R.fileDescriptorProtoName = Just (T.pack path)
, R.fileDescriptorProtoPackage = protoPackage pf
, R.fileDescriptorProtoDependency = V.fromList (fmap importPath (protoImports pf))
, R.fileDescriptorProtoMessageType = V.fromList (concatMap topMessages (protoTopLevels pf))
, R.fileDescriptorProtoEnumType = V.fromList (concatMap topEnums (protoTopLevels pf))
, R.fileDescriptorProtoService = V.fromList (concatMap topServices (protoTopLevels pf))
, R.fileDescriptorProtoSyntax = Just (syntaxStr (protoSyntax pf))
, R.fileDescriptorProtoEdition = astEditionToReflect (protoSyntax pf)
}
where
topMessages (TLMessage msg) = [astToDescriptor msg]
topMessages _ = []
topEnums (TLEnum ed) = [astToEnumDescriptor ed]
topEnums _ = []
topServices (TLService svc) = [astToServiceDescriptor svc]
topServices _ = []
syntaxStr Proto2 = "proto2"
syntaxStr Proto3 = "proto3"
syntaxStr (Editions _) = "editions"
-- | Convert a MessageDef to a DescriptorProto.
astToDescriptor :: MessageDef -> R.DescriptorProto
astToDescriptor msg =
R.defaultDescriptorProto
{ R.descriptorProtoName = Just (msgName msg)
, R.descriptorProtoField = V.fromList (concatMap extractField (msgElements msg))
, R.descriptorProtoNestedType = V.fromList (concatMap extractNested (msgElements msg))
, R.descriptorProtoEnumType = V.fromList (concatMap extractEnum (msgElements msg))
, R.descriptorProtoOneofDecl = V.fromList (concatMap extractOneof (msgElements msg))
}
where
extractField (MEField fd) = [astToFieldDescriptor fd]
extractField (MEMapField mf) = [mapToFieldDescriptor mf]
extractField (MEOneof od) = fmap oneofFieldToFDP (oneofFields od)
extractField _ = []
extractNested (MEMessage inner) = [astToDescriptor inner]
-- Emit the synthetic map-entry DescriptorProto so the round-trip
-- through FileDescriptorProto preserves the map field type info.
extractNested (MEMapField mf) = [mapEntryDescriptor mf]
extractNested _ = []
extractEnum (MEEnum ed) = [astToEnumDescriptor ed]
extractEnum _ = []
extractOneof (MEOneof od) =
[ R.defaultOneofDescriptorProto{R.oneofDescriptorProtoName = Just (oneofName od)}
]
extractOneof _ = []
-- | Convert a FieldDef to a FieldDescriptorProto.
astToFieldDescriptor :: FieldDef -> R.FieldDescriptorProto
astToFieldDescriptor fd =
let (ty, mTn) = fieldTypeToReflect (fieldType fd)
in R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just (fieldName fd)
, R.fieldDescriptorProtoNumber = Just (fromIntegral (unFieldNumber (fieldNumber fd)))
, R.fieldDescriptorProtoLabel = labelToReflect (fieldLabel fd)
, R.fieldDescriptorProtoType = Just ty
, R.fieldDescriptorProtoTypeName = mTn
}
mapToFieldDescriptor :: MapField -> R.FieldDescriptorProto
mapToFieldDescriptor mf =
R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just (mapFieldName mf)
, R.fieldDescriptorProtoNumber = Just (fromIntegral (unFieldNumber (mapFieldNum mf)))
, R.fieldDescriptorProtoLabel = Just R.FieldDescriptorProto'Label'LabelRepeated
, R.fieldDescriptorProtoType = Just R.FieldDescriptorProto'Type'TypeMessage
, R.fieldDescriptorProtoTypeName = Just (mapEntryName (mapFieldName mf))
}
-- | Build the synthetic map-entry DescriptorProto that encodes a map
-- field's key/value types. The entry name matches 'mapToFieldDescriptor'
-- so the round-trip through 'FileDescriptorProto' is lossless.
mapEntryDescriptor :: MapField -> R.DescriptorProto
mapEntryDescriptor mf =
R.defaultDescriptorProto
{ R.descriptorProtoName = Just (mapEntryName (mapFieldName mf))
, R.descriptorProtoField = V.fromList [keyField, valField]
, R.descriptorProtoOptions =
Just R.defaultMessageOptions{R.messageOptionsMapEntry = Just True}
}
where
keyField =
R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just "key"
, R.fieldDescriptorProtoNumber = Just 1
, R.fieldDescriptorProtoLabel = Just R.FieldDescriptorProto'Label'LabelOptional
, R.fieldDescriptorProtoType = Just (scalarToFieldType (mapKeyType mf))
}
valField = case mapValueType mf of
FTScalar st ->
R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just "value"
, R.fieldDescriptorProtoNumber = Just 2
, R.fieldDescriptorProtoLabel = Just R.FieldDescriptorProto'Label'LabelOptional
, R.fieldDescriptorProtoType = Just (scalarToFieldType st)
}
FTNamed n ->
R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just "value"
, R.fieldDescriptorProtoNumber = Just 2
, R.fieldDescriptorProtoLabel = Just R.FieldDescriptorProto'Label'LabelOptional
, R.fieldDescriptorProtoType = Just R.FieldDescriptorProto'Type'TypeMessage
, R.fieldDescriptorProtoTypeName = Just n
}
mapEntryName :: Text -> Text
mapEntryName fieldName = fieldName <> "Entry"
oneofFieldToFDP :: OneofField -> R.FieldDescriptorProto
oneofFieldToFDP of' =
let (ty, mTn) = fieldTypeToReflect (oneofFieldType of')
in R.defaultFieldDescriptorProto
{ R.fieldDescriptorProtoName = Just (oneofFieldName of')
, R.fieldDescriptorProtoNumber = Just (fromIntegral (unFieldNumber (oneofFieldNumber of')))
, R.fieldDescriptorProtoLabel = Just R.FieldDescriptorProto'Label'LabelOptional
, R.fieldDescriptorProtoType = Just ty
, R.fieldDescriptorProtoTypeName = mTn
}
-- | Convert an EnumDef to an EnumDescriptorProto.
astToEnumDescriptor :: EnumDef -> R.EnumDescriptorProto
astToEnumDescriptor ed =
R.defaultEnumDescriptorProto
{ R.enumDescriptorProtoName = Just (enumName ed)
, R.enumDescriptorProtoValue = V.fromList (fmap toEVDP (enumValues ed))
}
where
toEVDP ev =
R.defaultEnumValueDescriptorProto
{ R.enumValueDescriptorProtoName = Just (evName ev)
, R.enumValueDescriptorProtoNumber = Just (fromIntegral (evNumber ev))
}
-- | Convert a ServiceDef to a ServiceDescriptorProto.
astToServiceDescriptor :: ServiceDef -> R.ServiceDescriptorProto
astToServiceDescriptor svc =
R.defaultServiceDescriptorProto
{ R.serviceDescriptorProtoName = Just (svcName svc)
, R.serviceDescriptorProtoMethod = V.fromList (fmap toMDP (svcRpcs svc))
}
where
toMDP rpc =
R.defaultMethodDescriptorProto
{ R.methodDescriptorProtoName = Just (rpcName rpc)
, R.methodDescriptorProtoInputType = Just (rpcInput rpc)
, R.methodDescriptorProtoOutputType = Just (rpcOutput rpc)
, R.methodDescriptorProtoClientStreaming = Just (rpcInputStr rpc == Streaming)
, R.methodDescriptorProtoServerStreaming = Just (rpcOutputStr rpc == Streaming)
}
-- | Serialize a ProtoFile's schema to bytes (as a FileDescriptorProto).
serializeFileDescriptor :: FilePath -> ProtoFile -> ByteString
serializeFileDescriptor path pf = encodeMessage (astToFileDescriptor path pf)
labelToReflect :: Maybe FieldLabel -> Maybe R.FieldDescriptorProto'Label
labelToReflect Nothing = Just R.FieldDescriptorProto'Label'LabelOptional
labelToReflect (Just Optional) = Just R.FieldDescriptorProto'Label'LabelOptional
labelToReflect (Just Required) = Just R.FieldDescriptorProto'Label'LabelRequired
labelToReflect (Just Repeated) = Just R.FieldDescriptorProto'Label'LabelRepeated
fieldTypeToReflect :: FieldType -> (R.FieldDescriptorProto'Type, Maybe Text)
fieldTypeToReflect (FTScalar s) = (scalarToFieldType s, Nothing)
fieldTypeToReflect (FTNamed n) = (R.FieldDescriptorProto'Type'TypeMessage, Just n)
scalarToFieldType :: ScalarType -> R.FieldDescriptorProto'Type
scalarToFieldType = \case
SDouble -> R.FieldDescriptorProto'Type'TypeDouble
SFloat -> R.FieldDescriptorProto'Type'TypeFloat
SInt64 -> R.FieldDescriptorProto'Type'TypeInt64
SUInt64 -> R.FieldDescriptorProto'Type'TypeUint64
SInt32 -> R.FieldDescriptorProto'Type'TypeInt32
SFixed64 -> R.FieldDescriptorProto'Type'TypeFixed64
SFixed32 -> R.FieldDescriptorProto'Type'TypeFixed32
SBool -> R.FieldDescriptorProto'Type'TypeBool
SString -> R.FieldDescriptorProto'Type'TypeString
SBytes -> R.FieldDescriptorProto'Type'TypeBytes
SUInt32 -> R.FieldDescriptorProto'Type'TypeUint32
SSFixed32 -> R.FieldDescriptorProto'Type'TypeSfixed32
SSFixed64 -> R.FieldDescriptorProto'Type'TypeSfixed64
SSInt32 -> R.FieldDescriptorProto'Type'TypeSint32
SSInt64 -> R.FieldDescriptorProto'Type'TypeSint64
astEditionToReflect :: Syntax -> Maybe R.Edition
astEditionToReflect (Editions (Edition t))
| Right (n, rest) <- TR.decimal t, T.null rest =
case fromProtoEnumEdition (fromIntegral (n :: Integer)) of
Just ed -> Just ed
Nothing
| t == "2023" -> Just R.Edition'Edition2023
| t == "2024" -> Just R.Edition'Edition2024
| otherwise -> Nothing
| t == "2023" = Just R.Edition'Edition2023
| t == "2024" = Just R.Edition'Edition2024
| otherwise = Nothing
astEditionToReflect _ = Nothing
-- ---------------------------------------------------------------------------
-- Reverse conversion: FileDescriptorProto -> ProtoFile
-- ---------------------------------------------------------------------------
-- | Convert a 'FileDescriptorProto' (as received from protoc) to a 'ProtoFile'.
fileDescriptorToAST :: R.FileDescriptorProto -> ProtoFile
fileDescriptorToAST fdp =
ProtoFile
{ protoSyntax = parseSyntax (R.fileDescriptorProtoSyntax fdp) (R.fileDescriptorProtoEdition fdp)
, protoPackage =
case R.fileDescriptorProtoPackage fdp of
Nothing -> Nothing
Just p | T.null p -> Nothing
Just p -> Just p
, protoImports = fmap (ImportDef () Nothing) (V.toList (R.fileDescriptorProtoDependency fdp))
, protoOptions = []
, protoTopLevels =
fmap (TLMessage . descriptorToMessage) (V.toList (R.fileDescriptorProtoMessageType fdp))
<> fmap (TLEnum . enumDescriptorToEnum) (V.toList (R.fileDescriptorProtoEnumType fdp))
<> fmap (TLService . serviceDescriptorToService) (V.toList (R.fileDescriptorProtoService fdp))
, protoSource = Nothing
}
parseSyntax :: Maybe Text -> Maybe R.Edition -> Syntax
parseSyntax mSyn mEd
| Just "proto2" <- mSyn = Proto2
| Just "editions" <- mSyn, Just ed <- mEd = Editions (Edition (reflectEditionToAstText ed))
| Just "editions" <- mSyn = Editions (Edition "")
| otherwise = Proto3
reflectEditionToAstText :: R.Edition -> Text
reflectEditionToAstText = \case
R.Edition'Edition2023 -> "2023"
R.Edition'Edition2024 -> "2024"
R.Edition'EditionProto2 -> "proto2"
R.Edition'EditionProto3 -> "proto3"
e -> T.pack (show (toProtoEnumEdition e))
-- | Convert a 'DescriptorProto' to a 'MessageDef'.
--
-- Nested descriptors with @options.map_entry = true@ are treated as
-- map-entry stubs emitted by 'astToDescriptor'. They are paired back
-- with the corresponding repeated-message field to reconstruct the
-- original 'MEMapField' element.
descriptorToMessage :: R.DescriptorProto -> MessageDef
descriptorToMessage dp =
MessageDef
{ msgExt = ()
, msgDoc = Nothing
, msgName = fromMaybe "" (R.descriptorProtoName dp)
, msgElements = fieldElems <> nestedElems <> enumElems
}
where
oneofNames =
V.toList
(fmap (fromMaybe "" . R.oneofDescriptorProtoName) (R.descriptorProtoOneofDecl dp))
allFields = V.toList (R.descriptorProtoField dp)
nestedTypes = V.toList (R.descriptorProtoNestedType dp)
-- Separate map-entry stubs from real nested messages.
(mapEntries, realNested) = partition isMapEntry nestedTypes
isMapEntry n =
maybe False (fromMaybe False . R.messageOptionsMapEntry) (R.descriptorProtoOptions n)
-- Map entry names → MapField (if key/value fields are decodable).
mapEntryMap :: [(Text, MessageElement)]
mapEntryMap = concatMap entryToMapElem mapEntries
entryToMapElem n =
let entryName = fromMaybe "" (R.descriptorProtoName n)
fields = V.toList (R.descriptorProtoField n)
mkeyF = find (\f -> R.fieldDescriptorProtoNumber f == Just 1) fields
mvalF = find (\f -> R.fieldDescriptorProtoNumber f == Just 2) fields
in case (mkeyF, mvalF) of
(Just kf, Just vf) ->
case R.fieldDescriptorProtoType kf of
Just kt
| Just ks <- fieldTypeToScalar kt ->
-- Find the repeated field whose typeName matches this entry.
let vft = typeEnumToFieldType
(R.fieldDescriptorProtoType vf)
(fromMaybe "" (R.fieldDescriptorProtoTypeName vf))
matchingField = find
(\f -> R.fieldDescriptorProtoType f
== Just R.FieldDescriptorProto'Type'TypeMessage
&& maybe False (\tn -> tn == entryName || T.isSuffixOf ("." <> entryName) tn)
(R.fieldDescriptorProtoTypeName f))
allFields
in case matchingField of
Just mf ->
[ ( fromMaybe "" (R.fieldDescriptorProtoName mf)
, MEMapField MapField
{ mapExt = ()
, mapDoc = Nothing
, mapKeyType = ks
, mapValueType = vft
, mapFieldName = fromMaybe "" (R.fieldDescriptorProtoName mf)
, mapFieldNum = FieldNumber (fromIntegral (fromMaybe 0 (R.fieldDescriptorProtoNumber mf)))
, mapOptions = []
}
)
]
Nothing -> []
_ -> []
_ -> []
mapFieldNames :: [Text]
mapFieldNames = fmap fst mapEntryMap
-- Fields that are NOT the repeated-message backing a map entry.
(oneofFields', regularFields) = partitionOneofFields oneofNames
(filter (\f -> fromMaybe "" (R.fieldDescriptorProtoName f) `notElem` mapFieldNames) allFields)
fieldElems =
fmap (MEField . fieldDescriptorToField) regularFields
<> fmap snd mapEntryMap
nestedElems = fmap (MEMessage . descriptorToMessage) realNested
enumElems =
fmap (MEEnum . enumDescriptorToEnum) (V.toList (R.descriptorProtoEnumType dp))
<> buildOneofDefs oneofNames oneofFields'
partitionOneofFields ::
[Text] ->
[R.FieldDescriptorProto] ->
([[R.FieldDescriptorProto]], [R.FieldDescriptorProto])
partitionOneofFields oneofNames fields =
let regular = filter (isNothing . R.fieldDescriptorProtoOneofIndex) fields
grouped =
fmap
( \(i, _name) ->
filter
(\f -> R.fieldDescriptorProtoOneofIndex f == Just (fromIntegral (i :: Int)))
fields
)
(zip [(0 :: Int) ..] oneofNames)
in (grouped, regular)
buildOneofDefs :: [Text] -> [[R.FieldDescriptorProto]] -> [MessageElement]
buildOneofDefs = zipWith mkOneof
where
mkOneof name flds =
MEOneof
( OneofDef
{ oneofExt = ()
, oneofDoc = Nothing
, oneofName = name
, oneofFields = fmap mkOneofField flds
, oneofOptions = []
}
)
mkOneofField f =
OneofField
{ oneofFieldExt = ()
, oneofFieldDoc = Nothing
, oneofFieldType =
typeEnumToFieldType
(R.fieldDescriptorProtoType f)
(fromMaybe "" (R.fieldDescriptorProtoTypeName f))
, oneofFieldName = fromMaybe "" (R.fieldDescriptorProtoName f)
, oneofFieldNumber =
FieldNumber (fromIntegral (fromMaybe 0 (R.fieldDescriptorProtoNumber f)))
, oneofFieldOptions = []
}
-- | Convert a 'FieldDescriptorProto' to a 'FieldDef'.
fieldDescriptorToField :: R.FieldDescriptorProto -> FieldDef
fieldDescriptorToField f =
FieldDef
{ fieldExt = ()
, fieldDoc = Nothing
, fieldLabel = labelEnumToMaybeLabel (R.fieldDescriptorProtoLabel f)
, fieldType =
typeEnumToFieldType
(R.fieldDescriptorProtoType f)
(fromMaybe "" (R.fieldDescriptorProtoTypeName f))
, fieldName = fromMaybe "" (R.fieldDescriptorProtoName f)
, fieldNumber =
FieldNumber (fromIntegral (fromMaybe 0 (R.fieldDescriptorProtoNumber f)))
, fieldOptions = jsonNameOptMaybe (R.fieldDescriptorProtoJsonName f)
}
jsonNameOptMaybe :: Maybe Text -> [OptionDef]
jsonNameOptMaybe Nothing = []
jsonNameOptMaybe (Just t)
| T.null t = []
| otherwise = [OptionDef () (OptionName [SimpleOption "json_name"]) (CString t)]
-- | Convert an 'EnumDescriptorProto' to an 'EnumDef'.
enumDescriptorToEnum :: R.EnumDescriptorProto -> EnumDef
enumDescriptorToEnum e =
EnumDef
{ enumExt = ()
, enumDoc = Nothing
, enumName = fromMaybe "" (R.enumDescriptorProtoName e)
, enumValues =
fmap
( \v ->
EnumValue
()
Nothing
(fromMaybe "" (R.enumValueDescriptorProtoName v))
(fromIntegral (fromMaybe 0 (R.enumValueDescriptorProtoNumber v)))
[]
)
(V.toList (R.enumDescriptorProtoValue e))
, enumOptions = []
}
-- | Convert a 'ServiceDescriptorProto' to a 'ServiceDef'.
serviceDescriptorToService :: R.ServiceDescriptorProto -> ServiceDef
serviceDescriptorToService s =
ServiceDef
{ svcExt = ()
, svcDoc = Nothing
, svcName = fromMaybe "" (R.serviceDescriptorProtoName s)
, svcRpcs = fmap methodToRpc (V.toList (R.serviceDescriptorProtoMethod s))
, svcOptions = []
}
where
methodToRpc m =
RpcDef
{ rpcExt = ()
, rpcDoc = Nothing
, rpcName = fromMaybe "" (R.methodDescriptorProtoName m)
, rpcInput = fromMaybe "" (R.methodDescriptorProtoInputType m)
, rpcInputStr =
if fromMaybe False (R.methodDescriptorProtoClientStreaming m)
then Streaming
else NoStream
, rpcOutput = fromMaybe "" (R.methodDescriptorProtoOutputType m)
, rpcOutputStr =
if fromMaybe False (R.methodDescriptorProtoServerStreaming m)
then Streaming
else NoStream
, rpcOptions = []
}
labelEnumToMaybeLabel :: Maybe R.FieldDescriptorProto'Label -> Maybe FieldLabel
labelEnumToMaybeLabel Nothing = Nothing
labelEnumToMaybeLabel (Just R.FieldDescriptorProto'Label'LabelOptional) = Just Optional
labelEnumToMaybeLabel (Just R.FieldDescriptorProto'Label'LabelRequired) = Just Required
labelEnumToMaybeLabel (Just R.FieldDescriptorProto'Label'LabelRepeated) = Just Repeated
typeEnumToFieldType :: Maybe R.FieldDescriptorProto'Type -> Text -> FieldType
typeEnumToFieldType mTy typeName = case mTy of
Just R.FieldDescriptorProto'Type'TypeDouble -> FTScalar SDouble
Just R.FieldDescriptorProto'Type'TypeFloat -> FTScalar SFloat
Just R.FieldDescriptorProto'Type'TypeInt64 -> FTScalar SInt64
Just R.FieldDescriptorProto'Type'TypeUint64 -> FTScalar SUInt64
Just R.FieldDescriptorProto'Type'TypeInt32 -> FTScalar SInt32
Just R.FieldDescriptorProto'Type'TypeFixed64 -> FTScalar SFixed64
Just R.FieldDescriptorProto'Type'TypeFixed32 -> FTScalar SFixed32
Just R.FieldDescriptorProto'Type'TypeBool -> FTScalar SBool
Just R.FieldDescriptorProto'Type'TypeString -> FTScalar SString
Just R.FieldDescriptorProto'Type'TypeGroup -> FTNamed (stripLeadingDot typeName)
Just R.FieldDescriptorProto'Type'TypeMessage -> FTNamed (stripLeadingDot typeName)
Just R.FieldDescriptorProto'Type'TypeBytes -> FTScalar SBytes
Just R.FieldDescriptorProto'Type'TypeUint32 -> FTScalar SUInt32
Just R.FieldDescriptorProto'Type'TypeEnum -> FTNamed (stripLeadingDot typeName)
Just R.FieldDescriptorProto'Type'TypeSfixed32 -> FTScalar SSFixed32
Just R.FieldDescriptorProto'Type'TypeSfixed64 -> FTScalar SSFixed64
Just R.FieldDescriptorProto'Type'TypeSint32 -> FTScalar SSInt32
Just R.FieldDescriptorProto'Type'TypeSint64 -> FTScalar SSInt64
Nothing -> FTNamed (stripLeadingDot typeName)
stripLeadingDot :: Text -> Text
stripLeadingDot t = fromMaybe t (T.stripPrefix "." t)
-- | Invert 'scalarToFieldType': map a 'FieldDescriptorProto'Type' back to a
-- 'ScalarType'. Returns 'Nothing' for TYPE_MESSAGE, TYPE_ENUM, and TYPE_GROUP
-- (which are not scalar).
fieldTypeToScalar :: R.FieldDescriptorProto'Type -> Maybe ScalarType
fieldTypeToScalar = \case
R.FieldDescriptorProto'Type'TypeDouble -> Just SDouble
R.FieldDescriptorProto'Type'TypeFloat -> Just SFloat
R.FieldDescriptorProto'Type'TypeInt64 -> Just SInt64
R.FieldDescriptorProto'Type'TypeUint64 -> Just SUInt64
R.FieldDescriptorProto'Type'TypeInt32 -> Just SInt32
R.FieldDescriptorProto'Type'TypeFixed64 -> Just SFixed64
R.FieldDescriptorProto'Type'TypeFixed32 -> Just SFixed32
R.FieldDescriptorProto'Type'TypeBool -> Just SBool
R.FieldDescriptorProto'Type'TypeString -> Just SString
R.FieldDescriptorProto'Type'TypeBytes -> Just SBytes
R.FieldDescriptorProto'Type'TypeUint32 -> Just SUInt32
R.FieldDescriptorProto'Type'TypeSfixed32 -> Just SSFixed32
R.FieldDescriptorProto'Type'TypeSfixed64 -> Just SSFixed64
R.FieldDescriptorProto'Type'TypeSint32 -> Just SSInt32
R.FieldDescriptorProto'Type'TypeSint64 -> Just SSInt64
_ -> Nothing