prometheus-2.2.4: src/System/Metrics/Prometheus/MetricId.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Metrics.Prometheus.MetricId where
import Data.Bifunctor (first)
import Data.Char (isDigit)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Monoid (Monoid)
import Data.Semigroup (Semigroup)
import Data.String (IsString (..))
import Data.Text (Text)
import qualified Data.Text as Text
import Prelude hiding (null)
-- | Construct with 'makeName' to ensure that names use only valid characters
newtype Name = Name {unName :: Text} deriving (Show, Eq, Ord, Monoid, Semigroup)
instance IsString Name where
fromString = makeName . Text.pack
newtype Labels = Labels {unLabels :: Map Text Text} deriving (Show, Eq, Ord, Monoid, Semigroup)
data MetricId = MetricId
{ name :: Name
, labels :: Labels
}
deriving (Eq, Ord, Show)
addLabel :: Text -> Text -> Labels -> Labels
addLabel key val = Labels . Map.insert (makeValid key) val . unLabels
fromList :: [(Text, Text)] -> Labels
fromList = Labels . Map.fromList . map (first makeValid)
toList :: Labels -> [(Text, Text)]
toList = Map.toList . unLabels
null :: Labels -> Bool
null = Map.null . unLabels
-- | Make the input match the regex @[a-zA-Z_][a-zA-Z0-9_]@ which
-- defines valid metric and label names, according to
-- <https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels>
-- Replace invalid characters with @_@ and add a leading @_@ if the
-- first character is only valid as a later character.
makeValid :: Text -> Text
makeValid "" = "_"
makeValid txt = prefix_ <> Text.map (\c -> if allowedChar c then c else '_') txt
where
prefix_ = if isDigit (Text.head txt) then "_" else ""
allowedChar :: Char -> Bool
allowedChar c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || isDigit c || c == '_'
-- | Construct a 'Name', replacing disallowed characters.
makeName :: Text -> Name
makeName = Name . makeValid