hercules-ci-api-0.8.0.0: src/Hercules/API/Attribute.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE StandaloneDeriving #-}
module Hercules.API.Attribute
( AttributeType (..),
Attribute (..),
AttributePath (..),
attributePathFromString,
attributePathToString,
)
where
import Control.Applicative (Alternative ((<|>)))
import Control.Lens (at, (%~))
import qualified Data.Aeson as A
import Data.Aeson.Lens
import Data.Function ((&))
import Data.Proxy (Proxy (Proxy))
import Data.Swagger (ToParamSchema (..))
import qualified Data.Text as T
import Hercules.API.Prelude
import Servant.API (FromHttpApiData (..), ToHttpApiData (..))
import Prelude ()
data AttributeType
= Regular
| MustFail
| MayFail
| DependenciesOnly
| Effect
deriving (Generic, Show, Eq, NFData, ToJSON, FromJSON, ToSchema)
-- | An arbitrary ordering
deriving instance Ord AttributeType
data Attribute a = Attribute
{ path :: [Text],
value :: a,
typ :: AttributeType
}
deriving (Generic, Show, Eq, NFData, ToJSON)
instance FromJSON a => FromJSON (Attribute a) where
parseJSON v = A.parseJSON (fixup v)
where
fixup :: A.Value -> A.Value
fixup = _Object . at "typ" %~ (<|> Just (A.String "Regular"))
deriving instance ToSchema a => ToSchema (Attribute a)
deriving instance Functor Attribute
deriving instance Foldable Attribute
deriving instance Traversable Attribute
newtype AttributePath = AttributePath {fromAttributePath :: [Text]}
deriving (Generic, Eq, NFData)
instance FromHttpApiData AttributePath where
parseUrlPiece = Right . AttributePath . attributePathFromString
parseQueryParam = parseUrlPiece
instance ToParamSchema AttributePath where
toParamSchema _ = toParamSchema (Proxy :: Proxy Text)
instance ToHttpApiData AttributePath where
toUrlPiece = toUrlPiece . attributePathToString . fromAttributePath
----------------------------------------
attributePathToString :: [Text] -> Text
attributePathToString l =
l & map stringToNixIdentifier & T.intercalate "."
stringToNixIdentifier :: Text -> Text
stringToNixIdentifier s =
if isNixSimpleId s
then s
else nixQuote s
isNixSimpleId :: Text -> Bool
isNixSimpleId t | T.null t = False
isNixSimpleId t
| not
( let h = T.head t
in (h >= 'a' && h <= 'z')
|| (h >= 'A' && h <= 'Z')
|| h == '_'
) =
False
isNixSimpleId t =
T.all
( \c ->
(c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_'
|| c == '\''
|| c == '-'
)
t
nixQuote :: Text -> Text
nixQuote s =
"\""
<> T.replace "\"" "\\\"" (T.replace "\\" "\\\\" s)
<> "\""
----------------------------------------
attributePathFromString :: Text -> [Text]
attributePathFromString "" = []
attributePathFromString t = t & T.unpack & attributePathFromStringImpl [] "" & reverse & fmap T.pack
attributePathFromStringImpl :: [String] -> String -> [Char] -> [String]
attributePathFromStringImpl = \accPath accAttr chars -> case chars of
[] -> reverse accAttr : accPath
('.' : cs) -> attributePathFromStringImpl (reverse accAttr : accPath) "" cs
('\"' : cs) -> attributePathFromStringElement accPath "" cs
(c : cs) -> attributePathFromStringImpl accPath (c : accAttr) cs
attributePathFromStringElement :: [String] -> String -> [Char] -> [String]
attributePathFromStringElement = \accPath accAttr chars -> case chars of
('"' : cs) -> attributePathFromStringImpl accPath accAttr cs
('\\' : c : cs) -> attributePathFromStringElement (accPath) (c : accAttr) cs
[] -> reverse accAttr : accPath
(c : cs) -> attributePathFromStringElement accPath (c : accAttr) cs