packages feed

wireform-proto-0.1.0.0: src/Proto/IDL/Annotations.hs

{- | Custom annotation and option handling for protobuf definitions.

Protobuf allows custom options on messages, fields, enums, services, RPCs,
and files. This module provides utilities for querying and extracting
custom option values from parsed proto definitions.
-}
module Proto.IDL.Annotations (
  -- * Option querying
  lookupOption,
  lookupSimpleOption,
  lookupExtensionOption,
  hasOption,

  -- * Typed option extraction
  optionAsInt,
  optionAsFloat,
  optionAsBool,
  optionAsString,
  optionAsIdent,
  optionAsAggregate,

  -- * Bulk operations
  allOptions,
  extensionOptions,
  simpleOptions,

  -- * Custom annotation types
  Annotation (..),
  extractAnnotations,
  lookupAnnotation,
) where

import Data.Maybe (mapMaybe)
import Data.Text (Text)
import Proto.IDL.AST


-- | Look up an option by its full name (matching all name parts).
lookupOption :: Text -> [OptionDef] -> Maybe Constant
lookupOption name opts =
  case filter (matchesName name) opts of
    (o : _) -> Just (optValue o)
    [] -> Nothing


-- | Look up a simple (non-extension) option by name.
lookupSimpleOption :: Text -> [OptionDef] -> Maybe Constant
lookupSimpleOption name opts =
  case filter matchSimple opts of
    (o : _) -> Just (optValue o)
    [] -> Nothing
  where
    matchSimple o = case optNameParts (optName o) of
      [SimpleOption n] -> n == name
      _ -> False


-- | Look up an extension option by its fully qualified name.
lookupExtensionOption :: Text -> [OptionDef] -> Maybe Constant
lookupExtensionOption name opts =
  case filter matchExt opts of
    (o : _) -> Just (optValue o)
    [] -> Nothing
  where
    matchExt o = case optNameParts (optName o) of
      [ExtensionOption n] -> n == name
      _ -> False


-- | Check if an option with the given name exists.
hasOption :: Text -> [OptionDef] -> Bool
hasOption name opts = case lookupOption name opts of
  Just _ -> True
  Nothing -> False


matchesName :: Text -> OptionDef -> Bool
matchesName name o = case optNameParts (optName o) of
  [SimpleOption n] -> n == name
  [ExtensionOption n] -> n == name
  _ -> False


-- | Extract integer value from a constant.
optionAsInt :: Constant -> Maybe Integer
optionAsInt (CInt n) = Just n
optionAsInt _ = Nothing


-- | Extract float value from a constant.
optionAsFloat :: Constant -> Maybe Double
optionAsFloat (CFloat n) = Just n
optionAsFloat _ = Nothing


-- | Extract bool value from a constant.
optionAsBool :: Constant -> Maybe Bool
optionAsBool (CBool b) = Just b
optionAsBool _ = Nothing


-- | Extract string value from a constant.
optionAsString :: Constant -> Maybe Text
optionAsString (CString s) = Just s
optionAsString _ = Nothing


-- | Extract identifier value from a constant.
optionAsIdent :: Constant -> Maybe Text
optionAsIdent (CIdent i) = Just i
optionAsIdent _ = Nothing


-- | Extract aggregate value from a constant.
optionAsAggregate :: Constant -> Maybe [(Text, Constant)]
optionAsAggregate (CAggregate kvs) = Just kvs
optionAsAggregate _ = Nothing


-- | Collect all options from a list.
allOptions :: [OptionDef] -> [(OptionName, Constant)]
allOptions = fmap (\o -> (optName o, optValue o))


-- | Collect only extension options.
extensionOptions :: [OptionDef] -> [(Text, Constant)]
extensionOptions = mapMaybe go
  where
    go o = case optNameParts (optName o) of
      [ExtensionOption n] -> Just (n, optValue o)
      _ -> Nothing


-- | Collect only simple options.
simpleOptions :: [OptionDef] -> [(Text, Constant)]
simpleOptions = mapMaybe go
  where
    go o = case optNameParts (optName o) of
      [SimpleOption n] -> Just (n, optValue o)
      _ -> Nothing


-- | A resolved annotation (custom option with structured data).
data Annotation = Annotation
  { annotationName :: !Text
  , annotationValue :: !Constant
  }
  deriving stock (Show, Eq)


-- | Extract all custom annotations (extension options) from a list of options.
extractAnnotations :: [OptionDef] -> [Annotation]
extractAnnotations = mapMaybe go
  where
    go o = case optNameParts (optName o) of
      [ExtensionOption n] ->
        Just
          Annotation
            { annotationName = n
            , annotationValue = optValue o
            }
      _ -> Nothing


-- | Look up a specific annotation by name.
lookupAnnotation :: Text -> [Annotation] -> Maybe Constant
lookupAnnotation name anns =
  case filter (\a -> annotationName a == name) anns of
    (a : _) -> Just (annotationValue a)
    [] -> Nothing