module Ecluse.Core.Queue.Sqs (
SqsConfig (..),
SqsEndpoint (..),
defaultSqsConfig,
newSqsQueue,
encodeJob,
decodeJob,
parseHashAlg,
) where
import Amazonka qualified as AWS
import Amazonka.SQS.ChangeMessageVisibility qualified as SQS
import Amazonka.SQS.DeleteMessage qualified as SQS
import Amazonka.SQS.ReceiveMessage qualified as SQS
import Amazonka.SQS.SendMessage qualified as SQS
import Amazonka.SQS.Types qualified as SQS
import Control.Monad.Trans.Resource (runResourceT)
import Data.Aeson (
eitherDecodeStrict',
object,
withObject,
(.:),
(.:?),
(.=),
)
import Data.Aeson qualified as Aeson
import Data.Aeson.Types (Parser, parseEither)
import Lens.Micro ((?~), (^.))
import Ecluse.Core.Ecosystem (ecosystemName, parseEcosystem)
import Ecluse.Core.Package (
Hash,
HashAlg (Blake2b, MD5, SHA1, SHA256, SHA384, SHA512, SRI),
hashAlg,
hashValue,
mkHash,
mkPackageName,
mkScope,
pkgEcosystem,
pkgNamespace,
unScope,
unscopedName,
)
import Ecluse.Core.Package.Integrity (renderHashAlg)
import Ecluse.Core.Queue (
MirrorArtifact (MirrorArtifact, maFilename, maHashes, maSize),
MirrorJob (..),
MirrorQueue (..),
QueueMessage (..),
RemoteSpanContext (RemoteSpanContext, rscTraceparent, rscTracestate),
Seconds (..),
mkReceiptHandle,
unReceiptHandle,
)
import Ecluse.Core.Version (mkVersion, renderVersion)
data SqsEndpoint = SqsEndpoint
{ SqsEndpoint -> Bool
endpointSecure :: Bool
, SqsEndpoint -> Text
endpointHost :: Text
, SqsEndpoint -> Int
endpointPort :: Int
}
deriving stock (SqsEndpoint -> SqsEndpoint -> Bool
(SqsEndpoint -> SqsEndpoint -> Bool)
-> (SqsEndpoint -> SqsEndpoint -> Bool) -> Eq SqsEndpoint
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SqsEndpoint -> SqsEndpoint -> Bool
== :: SqsEndpoint -> SqsEndpoint -> Bool
$c/= :: SqsEndpoint -> SqsEndpoint -> Bool
/= :: SqsEndpoint -> SqsEndpoint -> Bool
Eq, Int -> SqsEndpoint -> ShowS
[SqsEndpoint] -> ShowS
SqsEndpoint -> String
(Int -> SqsEndpoint -> ShowS)
-> (SqsEndpoint -> String)
-> ([SqsEndpoint] -> ShowS)
-> Show SqsEndpoint
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SqsEndpoint -> ShowS
showsPrec :: Int -> SqsEndpoint -> ShowS
$cshow :: SqsEndpoint -> String
show :: SqsEndpoint -> String
$cshowList :: [SqsEndpoint] -> ShowS
showList :: [SqsEndpoint] -> ShowS
Show)
data SqsConfig = SqsConfig
{ SqsConfig -> Text
sqsQueueUrl :: Text
, SqsConfig -> Text
sqsRegion :: Text
, SqsConfig -> Maybe SqsEndpoint
sqsEndpoint :: Maybe SqsEndpoint
, SqsConfig -> Int
sqsBatchSize :: Int
, SqsConfig -> Int
sqsWaitSeconds :: Int
, SqsConfig -> Seconds
sqsVisibilityTimeout :: Seconds
}
deriving stock (SqsConfig -> SqsConfig -> Bool
(SqsConfig -> SqsConfig -> Bool)
-> (SqsConfig -> SqsConfig -> Bool) -> Eq SqsConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SqsConfig -> SqsConfig -> Bool
== :: SqsConfig -> SqsConfig -> Bool
$c/= :: SqsConfig -> SqsConfig -> Bool
/= :: SqsConfig -> SqsConfig -> Bool
Eq, Int -> SqsConfig -> ShowS
[SqsConfig] -> ShowS
SqsConfig -> String
(Int -> SqsConfig -> ShowS)
-> (SqsConfig -> String)
-> ([SqsConfig] -> ShowS)
-> Show SqsConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SqsConfig -> ShowS
showsPrec :: Int -> SqsConfig -> ShowS
$cshow :: SqsConfig -> String
show :: SqsConfig -> String
$cshowList :: [SqsConfig] -> ShowS
showList :: [SqsConfig] -> ShowS
Show)
defaultSqsConfig :: Text -> Text -> SqsConfig
defaultSqsConfig :: Text -> Text -> SqsConfig
defaultSqsConfig Text
queueUrl Text
region =
SqsConfig
{ sqsQueueUrl :: Text
sqsQueueUrl = Text
queueUrl
, sqsRegion :: Text
sqsRegion = Text
region
, sqsEndpoint :: Maybe SqsEndpoint
sqsEndpoint = Maybe SqsEndpoint
forall a. Maybe a
Nothing
, sqsBatchSize :: Int
sqsBatchSize = Int
10
, sqsWaitSeconds :: Int
sqsWaitSeconds = Int
20
, sqsVisibilityTimeout :: Seconds
sqsVisibilityTimeout = Int -> Seconds
Seconds Int
30
}
newSqsQueue :: SqsConfig -> IO MirrorQueue
newSqsQueue :: SqsConfig -> IO MirrorQueue
newSqsQueue SqsConfig
cfg = do
env <- SqsConfig -> IO Env
mkEnv SqsConfig
cfg
let run :: (AWS.AWSRequest a) => a -> IO (AWS.AWSResponse a)
run = ResourceT IO (AWSResponse a) -> IO (AWSResponse a)
forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT (ResourceT IO (AWSResponse a) -> IO (AWSResponse a))
-> (a -> ResourceT IO (AWSResponse a)) -> a -> IO (AWSResponse a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Env -> a -> ResourceT IO (AWSResponse a)
forall (m :: * -> *) a.
(MonadResource m, AWSRequest a) =>
Env -> a -> m (AWSResponse a)
AWS.send Env
env
queueUrl = SqsConfig -> Text
sqsQueueUrl SqsConfig
cfg
pure
MirrorQueue
{ enqueue = void . run . SQS.newSendMessage queueUrl . encodeJob
, receive = do
response <- run (receiveRequest cfg)
let messages = [Message] -> Maybe [Message] -> [Message]
forall a. a -> Maybe a -> a
fromMaybe [] (ReceiveMessageResponse
response ReceiveMessageResponse
-> Getting
(Maybe [Message]) ReceiveMessageResponse (Maybe [Message])
-> Maybe [Message]
forall s a. s -> Getting a s a -> a
^. Getting (Maybe [Message]) ReceiveMessageResponse (Maybe [Message])
Lens' ReceiveMessageResponse (Maybe [Message])
SQS.receiveMessageResponse_messages)
pure (mapMaybe toQueueMessage messages)
, ack = void . run . SQS.newDeleteMessage queueUrl . unReceiptHandle
, extendVisibility = \ReceiptHandle
receipt (Seconds Int
secs) ->
IO ChangeMessageVisibilityResponse -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO ChangeMessageVisibilityResponse -> IO ())
-> (ChangeMessageVisibility -> IO ChangeMessageVisibilityResponse)
-> ChangeMessageVisibility
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ChangeMessageVisibility -> IO (AWSResponse ChangeMessageVisibility)
ChangeMessageVisibility -> IO ChangeMessageVisibilityResponse
forall a. AWSRequest a => a -> IO (AWSResponse a)
run (ChangeMessageVisibility -> IO ())
-> ChangeMessageVisibility -> IO ()
forall a b. (a -> b) -> a -> b
$
Text -> Text -> Int -> ChangeMessageVisibility
SQS.newChangeMessageVisibility Text
queueUrl (ReceiptHandle -> Text
unReceiptHandle ReceiptHandle
receipt) Int
secs
}
mkEnv :: SqsConfig -> IO AWS.Env
mkEnv :: SqsConfig -> IO Env
mkEnv SqsConfig
cfg = case SqsConfig -> Maybe SqsEndpoint
sqsEndpoint SqsConfig
cfg of
Just SqsEndpoint
ep -> do
base <- Env -> Env
regioned (Env -> Env) -> IO Env -> IO Env
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (EnvNoAuth -> IO Env) -> IO Env
forall (m :: * -> *). MonadIO m => (EnvNoAuth -> m Env) -> m Env
AWS.newEnv EnvNoAuth -> IO Env
forall (m :: * -> *) (withAuth :: * -> *).
(MonadCatch m, MonadIO m, Foldable withAuth) =>
Env' withAuth -> m Env
AWS.discover
pure (configured ep base)
Maybe SqsEndpoint
Nothing -> Env -> Env
regioned (Env -> Env) -> IO Env -> IO Env
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (EnvNoAuth -> IO Env) -> IO Env
forall (m :: * -> *). MonadIO m => (EnvNoAuth -> m Env) -> m Env
AWS.newEnv EnvNoAuth -> IO Env
forall (m :: * -> *) (withAuth :: * -> *).
(MonadCatch m, MonadIO m, Foldable withAuth) =>
Env' withAuth -> m Env
AWS.discover
where
regioned :: AWS.Env -> AWS.Env
regioned :: Env -> Env
regioned Env
env = Env
env{AWS.region = AWS.Region' (sqsRegion cfg)}
configured :: SqsEndpoint -> AWS.Env -> AWS.Env
configured :: SqsEndpoint -> Env -> Env
configured SqsEndpoint
ep =
Service -> Env -> Env
forall (withAuth :: * -> *).
Service -> Env' withAuth -> Env' withAuth
AWS.configureService
( Bool -> ByteString -> Int -> Service -> Service
AWS.setEndpoint
(SqsEndpoint -> Bool
endpointSecure SqsEndpoint
ep)
(Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (SqsEndpoint -> Text
endpointHost SqsEndpoint
ep))
(SqsEndpoint -> Int
endpointPort SqsEndpoint
ep)
Service
SQS.defaultService
)
receiveRequest :: SqsConfig -> SQS.ReceiveMessage
receiveRequest :: SqsConfig -> ReceiveMessage
receiveRequest SqsConfig
cfg =
Text -> ReceiveMessage
SQS.newReceiveMessage (SqsConfig -> Text
sqsQueueUrl SqsConfig
cfg)
ReceiveMessage
-> (ReceiveMessage -> ReceiveMessage) -> ReceiveMessage
forall a b. a -> (a -> b) -> b
& (Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage
Lens' ReceiveMessage (Maybe Int)
SQS.receiveMessage_maxNumberOfMessages
((Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage)
-> Int -> ReceiveMessage -> ReceiveMessage
forall s t a b. ASetter s t a (Maybe b) -> b -> s -> t
?~ SqsConfig -> Int
sqsBatchSize SqsConfig
cfg
ReceiveMessage
-> (ReceiveMessage -> ReceiveMessage) -> ReceiveMessage
forall a b. a -> (a -> b) -> b
& (Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage
Lens' ReceiveMessage (Maybe Int)
SQS.receiveMessage_waitTimeSeconds
((Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage)
-> Int -> ReceiveMessage -> ReceiveMessage
forall s t a b. ASetter s t a (Maybe b) -> b -> s -> t
?~ SqsConfig -> Int
sqsWaitSeconds SqsConfig
cfg
ReceiveMessage
-> (ReceiveMessage -> ReceiveMessage) -> ReceiveMessage
forall a b. a -> (a -> b) -> b
& (Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage
Lens' ReceiveMessage (Maybe Int)
SQS.receiveMessage_visibilityTimeout
((Maybe Int -> Identity (Maybe Int))
-> ReceiveMessage -> Identity ReceiveMessage)
-> Int -> ReceiveMessage -> ReceiveMessage
forall s t a b. ASetter s t a (Maybe b) -> b -> s -> t
?~ Int
visibilitySeconds
where
Seconds Int
visibilitySeconds = SqsConfig -> Seconds
sqsVisibilityTimeout SqsConfig
cfg
toQueueMessage :: SQS.Message -> Maybe QueueMessage
toQueueMessage :: Message -> Maybe QueueMessage
toQueueMessage Message
message = do
body <- Message
message Message -> Getting (Maybe Text) Message (Maybe Text) -> Maybe Text
forall s a. s -> Getting a s a -> a
^. Getting (Maybe Text) Message (Maybe Text)
Lens' Message (Maybe Text)
SQS.message_body
receipt <- message ^. SQS.message_receiptHandle
job <- rightToMaybe (decodeJob body)
pure QueueMessage{msgJob = job, msgReceipt = mkReceiptHandle receipt}
encodeJob :: MirrorJob -> Text
encodeJob :: MirrorJob -> Text
encodeJob MirrorJob
job =
ByteString -> Text
forall a b. ConvertUtf8 a b => b -> a
decodeUtf8 (ByteString -> Text) -> (Value -> ByteString) -> Value -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode (Value -> Text) -> Value -> Text
forall a b. (a -> b) -> a -> b
$
[Pair] -> Value
object
[ Key
"ecosystem" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= Ecosystem -> Text
ecosystemName (PackageName -> Ecosystem
pkgEcosystem PackageName
name)
, Key
"scope" Key -> Maybe Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= (Scope -> Text
unScope (Scope -> Text) -> Maybe Scope -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PackageName -> Maybe Scope
pkgNamespace PackageName
name)
, Key
"name" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= PackageName -> Text
unscopedName PackageName
name
, Key
"version" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= Version -> Text
renderVersion (MirrorJob -> Version
jobVersion MirrorJob
job)
, Key
"artifactUrl" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= MirrorJob -> Text
jobArtifactUrl MirrorJob
job
, Key
"mirrorTarget" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= MirrorJob -> Text
jobMirrorTarget MirrorJob
job
, Key
"artifact" Key -> Value -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= MirrorArtifact -> Value
encodeArtifact (MirrorJob -> MirrorArtifact
jobArtifact MirrorJob
job)
, Key
"traceContext" Key -> Maybe Value -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= (RemoteSpanContext -> Value
encodeTraceContext (RemoteSpanContext -> Value)
-> Maybe RemoteSpanContext -> Maybe Value
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> MirrorJob -> Maybe RemoteSpanContext
jobTraceContext MirrorJob
job)
]
where
name :: PackageName
name = MirrorJob -> PackageName
jobPackage MirrorJob
job
encodeTraceContext :: RemoteSpanContext -> Aeson.Value
encodeTraceContext :: RemoteSpanContext -> Value
encodeTraceContext RemoteSpanContext
rsc =
[Pair] -> Value
object
[ Key
"traceparent" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= RemoteSpanContext -> Text
rscTraceparent RemoteSpanContext
rsc
, Key
"tracestate" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= RemoteSpanContext -> Text
rscTracestate RemoteSpanContext
rsc
]
encodeArtifact :: MirrorArtifact -> Aeson.Value
encodeArtifact :: MirrorArtifact -> Value
encodeArtifact MirrorArtifact
artifact =
[Pair] -> Value
object
[ Key
"filename" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= MirrorArtifact -> Text
maFilename MirrorArtifact
artifact
, Key
"hashes" Key -> [Value] -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= (Hash -> Value) -> [Hash] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map Hash -> Value
encodeHash (NonEmpty Hash -> [Hash]
forall a. NonEmpty a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList (MirrorArtifact -> NonEmpty Hash
maHashes MirrorArtifact
artifact))
, Key
"size" Key -> Maybe Int -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= MirrorArtifact -> Maybe Int
maSize MirrorArtifact
artifact
]
where
encodeHash :: Hash -> Aeson.Value
encodeHash :: Hash -> Value
encodeHash Hash
h = [Pair] -> Value
object [Key
"alg" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= HashAlg -> Text
renderHashAlg (Hash -> HashAlg
hashAlg Hash
h), Key
"value" Key -> Text -> Pair
forall v. ToJSON v => Key -> v -> Pair
forall e kv v. (KeyValue e kv, ToJSON v) => Key -> v -> kv
.= Hash -> Text
hashValue Hash
h]
decodeJob :: Text -> Either Text MirrorJob
decodeJob :: Text -> Either Text MirrorJob
decodeJob Text
body =
(String -> Text) -> Either String Value -> Either Text Value
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first String -> Text
forall a. ToText a => a -> Text
toText (ByteString -> Either String Value
forall a. FromJSON a => ByteString -> Either String a
eitherDecodeStrict' (Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 Text
body))
Either Text Value
-> (Value -> Either Text MirrorJob) -> Either Text MirrorJob
forall a b. Either Text a -> (a -> Either Text b) -> Either Text b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (String -> Text)
-> Either String MirrorJob -> Either Text MirrorJob
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first String -> Text
forall a. ToText a => a -> Text
toText (Either String MirrorJob -> Either Text MirrorJob)
-> (Value -> Either String MirrorJob)
-> Value
-> Either Text MirrorJob
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Value -> Parser MirrorJob) -> Value -> Either String MirrorJob
forall a b. (a -> Parser b) -> a -> Either String b
parseEither Value -> Parser MirrorJob
parseMirrorJob
parseMirrorJob :: Aeson.Value -> Parser MirrorJob
parseMirrorJob :: Value -> Parser MirrorJob
parseMirrorJob = String -> (Object -> Parser MirrorJob) -> Value -> Parser MirrorJob
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"MirrorJob" ((Object -> Parser MirrorJob) -> Value -> Parser MirrorJob)
-> (Object -> Parser MirrorJob) -> Value -> Parser MirrorJob
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
ecoName <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"ecosystem"
eco <- maybe (fail (unknownEcosystem ecoName)) pure (parseEcosystem ecoName)
scope <- o .:? "scope"
rawName <- o .: "name"
rawVersion <- o .: "version"
artifactUrl <- o .: "artifactUrl"
mirrorTarget <- o .: "mirrorTarget"
artifact <- o .: "artifact" >>= parseArtifact
traceContext <- o .:? "traceContext" >>= traverse parseTraceContext
pure
MirrorJob
{ jobPackage = mkPackageName eco (mkScope <$> scope) rawName
, jobVersion = mkVersion eco rawVersion
, jobArtifactUrl = artifactUrl
, jobMirrorTarget = mirrorTarget
, jobArtifact = artifact
, jobTraceContext = traceContext
}
where
unknownEcosystem :: Text -> a
unknownEcosystem Text
n = a
"unknown ecosystem " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> Text -> a
forall b a. (Show a, IsString b) => a -> b
show (Text
n :: Text)
parseTraceContext :: Aeson.Value -> Parser RemoteSpanContext
parseTraceContext :: Value -> Parser RemoteSpanContext
parseTraceContext = String
-> (Object -> Parser RemoteSpanContext)
-> Value
-> Parser RemoteSpanContext
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"RemoteSpanContext" ((Object -> Parser RemoteSpanContext)
-> Value -> Parser RemoteSpanContext)
-> (Object -> Parser RemoteSpanContext)
-> Value
-> Parser RemoteSpanContext
forall a b. (a -> b) -> a -> b
$ \Object
t ->
Text -> Text -> RemoteSpanContext
RemoteSpanContext (Text -> Text -> RemoteSpanContext)
-> Parser Text -> Parser (Text -> RemoteSpanContext)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
t Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"traceparent" Parser (Text -> RemoteSpanContext)
-> Parser Text -> Parser RemoteSpanContext
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
t Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"tracestate"
parseArtifact :: Aeson.Value -> Parser MirrorArtifact
parseArtifact :: Value -> Parser MirrorArtifact
parseArtifact = String
-> (Object -> Parser MirrorArtifact)
-> Value
-> Parser MirrorArtifact
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"MirrorArtifact" ((Object -> Parser MirrorArtifact)
-> Value -> Parser MirrorArtifact)
-> (Object -> Parser MirrorArtifact)
-> Value
-> Parser MirrorArtifact
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
filename <- Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"filename"
rawHashes <- o .: "hashes" >>= traverse parseHash
size <- o .:? "size"
case nonEmpty rawHashes of
Maybe (NonEmpty Hash)
Nothing -> String -> Parser MirrorArtifact
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"MirrorArtifact carries no integrity digest"
Just NonEmpty Hash
hashes ->
MirrorArtifact -> Parser MirrorArtifact
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure MirrorArtifact{maFilename :: Text
maFilename = Text
filename, maHashes :: NonEmpty Hash
maHashes = NonEmpty Hash
hashes, maSize :: Maybe Int
maSize = Maybe Int
size}
parseHash :: Aeson.Value -> Parser Hash
parseHash :: Value -> Parser Hash
parseHash = String -> (Object -> Parser Hash) -> Value -> Parser Hash
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Hash" ((Object -> Parser Hash) -> Value -> Parser Hash)
-> (Object -> Parser Hash) -> Value -> Parser Hash
forall a b. (a -> b) -> a -> b
$ \Object
h -> do
algName <- Object
h Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"alg"
alg <- maybe (fail (unknownAlg algName)) pure (parseHashAlg algName)
value <- h .: "value"
either (fail . toString) pure (mkHash alg value)
where
unknownAlg :: Text -> a
unknownAlg Text
n = a
"unknown hash algorithm " a -> a -> a
forall a. Semigroup a => a -> a -> a
<> Text -> a
forall b a. (Show a, IsString b) => a -> b
show (Text
n :: Text)
parseHashAlg :: Text -> Maybe HashAlg
parseHashAlg :: Text -> Maybe HashAlg
parseHashAlg = \case
Text
"sha1" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA1
Text
"sha256" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA256
Text
"sha384" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA384
Text
"sha512" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SHA512
Text
"md5" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
MD5
Text
"blake2b" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
Blake2b
Text
"sri" -> HashAlg -> Maybe HashAlg
forall a. a -> Maybe a
Just HashAlg
SRI
Text
_ -> Maybe HashAlg
forall a. Maybe a
Nothing