packages feed

cicero-api-0.1.1.3: src/IOHK/Cicero/API/Action.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
module IOHK.Cicero.API.Action where

import Data.Coerce
import Data.Text
import Data.Aeson
import Data.Aeson.Encoding
import Data.Aeson.Types
import Data.Map
import Data.Time.LocalTime
import Data.UUID as UUID
import Data.String
import Servant.API
import Servant.API.Generic
import Servant.API.NamedRoutes

type API = NamedRoutes ActionRoutes

newtype ActionID = ActionID { uuid :: UUID } deriving newtype (ToHttpApiData, FromJSON, ToJSON, Eq, Ord)

actionIdFromString :: String -> Maybe ActionID
actionIdFromString = coerce . UUID.fromString

-- | Action routes in the Cicero API
data ActionRoutes mode = ActionRoutes
  { create :: mode :- ReqBody '[JSON] CreateActionV1 :> Post '[JSON] CreateActionResponseV2
  , get :: mode :- Capture "id" ActionID :> Get '[JSON] ActionV2
  } deriving stock Generic

data CreateActionV1 = CreateAction
  { names :: !ActionNamesV1
  , source :: !ActionSourceV1
  }

instance ToJSON CreateActionV1 where
  toJSON ca = object $ "source" .= ca.source : case ca.names of
    Only n -> [ "name" .= n ]
    AllNames -> []
  toEncoding ca = pairs $ "source" .= ca.source <> case ca.names of
    Only n -> "name" .= n
    AllNames -> mempty

data CreateActionResponseV2
  = -- | The specific action created
    --
    -- Returned if an 'Only' was set in 'names'
    CreateActionOnly !ActionV2
  | -- | All actions created
    --
    -- Returned if 'AllNames' was set in 'names'
    CreateActionAll !([ ActionV2 ])

instance FromJSON CreateActionResponseV2 where
  parseJSON v = prependFailure "parsing CreateActionResponseV2 failed, " $ case v of
    Object _ -> CreateActionOnly <$> parseJSON v
    Array _ -> CreateActionAll <$> parseJSON v
    _ -> typeMismatch "Object or Array" v

instance ToJSON CreateActionResponseV2 where
  toJSON (CreateActionOnly act) = toJSON act
  toJSON (CreateActionAll acts) = toJSON acts

  toEncoding (CreateActionOnly act) = toEncoding act
  toEncoding (CreateActionAll acts) = toEncoding acts

-- | The source of an action, as a [go-getter URL](https://github.com/hashicorp/go-getter#url-format)
newtype ActionSourceV1 = ActionSource { unActionSource :: Text } deriving newtype (IsString, FromJSON, ToJSON)

-- | The action names to add from a given 'ActionSourceV1'
data ActionNamesV1
  = -- | Just add the action with the given name
    Only !Text
  | -- | Add all names exported by the source
    AllNames

-- | A [CUE lang](https://cuelang.org/) value
--
-- For now just a wrapper around 'Text' that we hope parses
newtype CUE = CUE { expr :: Text } deriving newtype (FromJSON, ToJSON)

-- | An input to a job
data InputV2 = Input
  { -- | Negate the matching condition?
    not :: !Bool
  , -- | Can this input be missing?
    optional :: !Bool
  , -- | Which facts to match
    match :: !CUE
  }

instance FromJSON InputV2 where
  parseJSON = withObject "InputV2" \o -> Input
    <$> o .: "not"
    <*> o .: "optional"
    <*> o .: "match"

instance ToJSON InputV2 where
  toJSON i = object
    [ "not" .= i.not
    , "optional" .= i.optional
    , "match" .= i.match
    ]
  toEncoding i = pairs
    ( "not" .= i.not
   <> "optional" .= i.optional
   <> "match" .= i.match
    )

-- | An action
data ActionV2 = Action
  { id :: !ActionID
  , name :: !Text
  , source :: !ActionSourceV1
  , createdAt :: !ZonedTime
  , active :: !Bool
  , meta :: !(Maybe (Map Text Value))
  , inputs :: !(Map Text InputV2)
  }

instance FromJSON ActionV2 where
  parseJSON = withObject "ActionV2" \o -> Action
    <$> o .: "id"
    <*> o .: "name"
    <*> o .: "source"
    <*> o .: "created_at"
    <*> o .: "active"
    <*> o .: "meta"
    <*> o .: "inputs"

instance ToJSON ActionV2 where
  toJSON a = object
    [ "id" .= a.id
    , "name" .= a.name
    , "source" .= a.source
    , "created_at" .= a.createdAt
    , "active" .= a.active
    , "meta" .= a.meta
    , "inputs" .= a.inputs
    ]
  toEncoding a = pairs
    ( "id" .= a.id
   <> "name" .= a.name
   <> "source" .= a.source
   <> "created_at" .= a.createdAt
   <> "active" .= a.active
   <> "meta" .= a.meta
   <> "inputs" .= a.inputs
    )