module Ecluse.Core.Server.Metadata (
ManifestCaching (..),
newMetadataClient,
newNpmMetadataClient,
) where
import Data.Map.Strict qualified as Map
import Network.HTTP.Client qualified as HTTP
import UnliftIO.Exception (throwIO, try, tryAny)
import Ecluse.Core.Package (InvalidEntry, PackageDetails, PackageInfo (infoInvalidEntries, infoVersions), PackageName)
import Ecluse.Core.Registry.Metadata (
Manifest (Manifest, manifestDigest, manifestInfo, manifestRaw),
MetadataClient (..),
MetadataError (MetadataBoundExceeded, MetadataNameMismatch, MetadataUndecodable),
)
import Ecluse.Core.Registry.Npm (NpmClientConfig)
import Ecluse.Core.Registry.Npm.Metadata (fetchNpmManifest, fetchNpmVersion)
import Ecluse.Core.Server.Cache (
CacheEntry (CacheEntry, entryDigest, entryInfo, entryRaw),
MetadataCache,
Source,
cachedMetadata,
cachedVersion,
resolveMetadata,
resolveVersion,
)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (MetricsPort (..), timedSeconds)
import Ecluse.Core.Telemetry.Span (TracingPort)
import Ecluse.Core.Version (Version, renderVersion)
data ManifestCaching
=
Uncached
|
Cached MetadataCache Source
newMetadataClient ::
MetricsPort ->
Metric.Upstream ->
ManifestCaching ->
(PackageName -> MetadataError -> IO ()) ->
(PackageName -> [InvalidEntry] -> IO ()) ->
(PackageName -> IO ()) ->
(PackageName -> IO (Either MetadataError Manifest)) ->
(PackageName -> Version -> IO (Either MetadataError (Maybe PackageDetails))) ->
MetadataClient
newMetadataClient :: MetricsPort
-> Upstream
-> ManifestCaching
-> (PackageName -> MetadataError -> IO ())
-> (PackageName -> [InvalidEntry] -> IO ())
-> (PackageName -> IO ())
-> (PackageName -> IO (Either MetadataError Manifest))
-> (PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails)))
-> MetadataClient
newMetadataClient MetricsPort
metrics Upstream
upstream ManifestCaching
caching PackageName -> MetadataError -> IO ()
logFailure PackageName -> [InvalidEntry] -> IO ()
logInvalid PackageName -> IO ()
logFetch PackageName -> IO (Either MetadataError Manifest)
rawFetch PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails))
rawFetchVersion =
MetadataClient
{ fetchFullManifest :: PackageName -> IO (Either MetadataError Manifest)
fetchFullManifest = IO CacheEntry -> IO (Either MetadataError Manifest)
foldManifestCarrier (IO CacheEntry -> IO (Either MetadataError Manifest))
-> (PackageName -> IO CacheEntry)
-> PackageName
-> IO (Either MetadataError Manifest)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageName -> IO CacheEntry
resolveEntry
, fetchVersionMetadata :: PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails))
fetchVersionMetadata = PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails))
resolveVersionHybrid
}
where
resolveEntry :: PackageName -> IO CacheEntry
resolveEntry :: PackageName -> IO CacheEntry
resolveEntry PackageName
name = case ManifestCaching
caching of
ManifestCaching
Uncached -> PackageName -> IO CacheEntry
manifestLeader PackageName
name
Cached MetadataCache
cache Source
source -> MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> IO CacheEntry
-> IO CacheEntry
resolveMetadata MetricsPort
metrics MetadataCache
cache Source
source PackageName
name (PackageName -> IO CacheEntry
manifestLeader PackageName
name)
manifestLeader :: PackageName -> IO CacheEntry
manifestLeader :: PackageName -> IO CacheEntry
manifestLeader PackageName
name = do
PackageName -> IO ()
logFetch PackageName
name
MetricsPort -> Upstream -> IO CacheEntry -> IO CacheEntry
forall a. MetricsPort -> Upstream -> IO a -> IO a
recordedFetch MetricsPort
metrics Upstream
upstream (IO CacheEntry -> IO CacheEntry) -> IO CacheEntry -> IO CacheEntry
forall a b. (a -> b) -> a -> b
$
PackageName -> IO (Either MetadataError Manifest)
rawFetch PackageName
name IO (Either MetadataError Manifest)
-> (Either MetadataError Manifest -> IO CacheEntry)
-> IO CacheEntry
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right Manifest
manifest -> do
let invalid :: [InvalidEntry]
invalid = PackageInfo -> [InvalidEntry]
infoInvalidEntries (Manifest -> PackageInfo
manifestInfo Manifest
manifest)
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([InvalidEntry] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [InvalidEntry]
invalid) (PackageName -> [InvalidEntry] -> IO ()
logInvalid PackageName
name [InvalidEntry]
invalid)
CacheEntry -> IO CacheEntry
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (PackageInfo -> Value -> ContentDigest -> CacheEntry
CacheEntry (Manifest -> PackageInfo
manifestInfo Manifest
manifest) (Manifest -> Value
manifestRaw Manifest
manifest) (Manifest -> ContentDigest
manifestDigest Manifest
manifest))
Left MetadataError
err -> PackageName -> MetadataError -> IO ()
logFailure PackageName
name MetadataError
err IO () -> IO CacheEntry -> IO CacheEntry
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ManifestFetchFailed -> IO CacheEntry
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (MetadataError -> ManifestFetchFailed
ManifestFetchFailed MetadataError
err)
resolveVersionHybrid :: PackageName -> Version -> IO (Either MetadataError (Maybe PackageDetails))
resolveVersionHybrid :: PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails))
resolveVersionHybrid PackageName
name Version
version = case ManifestCaching
caching of
ManifestCaching
Uncached -> IO (Maybe PackageDetails)
-> IO (Either MetadataError (Maybe PackageDetails))
foldVersionCarrier (PackageName -> Version -> IO (Maybe PackageDetails)
versionLeader PackageName
name Version
version)
Cached MetadataCache
cache Source
source -> do
cached <- MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe (Maybe PackageDetails))
cachedVersion MetadataCache
cache Source
source PackageName
name Version
version
case cached of
Just Maybe PackageDetails
details -> Either MetadataError (Maybe PackageDetails)
-> IO (Either MetadataError (Maybe PackageDetails))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe PackageDetails -> Either MetadataError (Maybe PackageDetails)
forall a b. b -> Either a b
Right Maybe PackageDetails
details)
Maybe (Maybe PackageDetails)
Nothing -> do
warm <- MetadataCache -> Source -> PackageName -> IO (Maybe CacheEntry)
cachedMetadata MetadataCache
cache Source
source PackageName
name
case warm of
Just CacheEntry
entry -> Either MetadataError (Maybe PackageDetails)
-> IO (Either MetadataError (Maybe PackageDetails))
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe PackageDetails -> Either MetadataError (Maybe PackageDetails)
forall a b. b -> Either a b
Right (Version -> PackageInfo -> Maybe PackageDetails
selectVersion Version
version (CacheEntry -> PackageInfo
entryInfo CacheEntry
entry)))
Maybe CacheEntry
Nothing -> IO (Maybe PackageDetails)
-> IO (Either MetadataError (Maybe PackageDetails))
foldVersionCarrier (MetricsPort
-> MetadataCache
-> Source
-> PackageName
-> Version
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
resolveVersion MetricsPort
metrics MetadataCache
cache Source
source PackageName
name Version
version (PackageName -> Version -> IO (Maybe PackageDetails)
versionLeader PackageName
name Version
version))
versionLeader :: PackageName -> Version -> IO (Maybe PackageDetails)
versionLeader :: PackageName -> Version -> IO (Maybe PackageDetails)
versionLeader PackageName
name Version
version = do
PackageName -> IO ()
logFetch PackageName
name
MetricsPort
-> Upstream
-> IO (Maybe PackageDetails)
-> IO (Maybe PackageDetails)
forall a. MetricsPort -> Upstream -> IO a -> IO a
recordedFetch MetricsPort
metrics Upstream
upstream (IO (Maybe PackageDetails) -> IO (Maybe PackageDetails))
-> IO (Maybe PackageDetails) -> IO (Maybe PackageDetails)
forall a b. (a -> b) -> a -> b
$
PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails))
rawFetchVersion PackageName
name Version
version IO (Either MetadataError (Maybe PackageDetails))
-> (Either MetadataError (Maybe PackageDetails)
-> IO (Maybe PackageDetails))
-> IO (Maybe PackageDetails)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Right Maybe PackageDetails
details -> Maybe PackageDetails -> IO (Maybe PackageDetails)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe PackageDetails
details
Left MetadataError
err -> PackageName -> MetadataError -> IO ()
logFailure PackageName
name MetadataError
err IO () -> IO (Maybe PackageDetails) -> IO (Maybe PackageDetails)
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> VersionFetchFailed -> IO (Maybe PackageDetails)
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (MetadataError -> VersionFetchFailed
VersionFetchFailed MetadataError
err)
newNpmMetadataClient ::
TracingPort ->
MetricsPort ->
Metric.Upstream ->
ManifestCaching ->
(PackageName -> MetadataError -> IO ()) ->
(PackageName -> [InvalidEntry] -> IO ()) ->
(PackageName -> IO ()) ->
NpmClientConfig ->
MetadataClient
newNpmMetadataClient :: TracingPort
-> MetricsPort
-> Upstream
-> ManifestCaching
-> (PackageName -> MetadataError -> IO ())
-> (PackageName -> [InvalidEntry] -> IO ())
-> (PackageName -> IO ())
-> NpmClientConfig
-> MetadataClient
newNpmMetadataClient TracingPort
tracing MetricsPort
metrics Upstream
upstream ManifestCaching
caching PackageName -> MetadataError -> IO ()
logFailure PackageName -> [InvalidEntry] -> IO ()
logInvalid PackageName -> IO ()
logFetch NpmClientConfig
config =
MetricsPort
-> Upstream
-> ManifestCaching
-> (PackageName -> MetadataError -> IO ())
-> (PackageName -> [InvalidEntry] -> IO ())
-> (PackageName -> IO ())
-> (PackageName -> IO (Either MetadataError Manifest))
-> (PackageName
-> Version -> IO (Either MetadataError (Maybe PackageDetails)))
-> MetadataClient
newMetadataClient MetricsPort
metrics Upstream
upstream ManifestCaching
caching PackageName -> MetadataError -> IO ()
logFailure PackageName -> [InvalidEntry] -> IO ()
logInvalid PackageName -> IO ()
logFetch (TracingPort
-> NpmClientConfig
-> PackageName
-> IO (Either MetadataError Manifest)
fetchNpmManifest TracingPort
tracing NpmClientConfig
config) (TracingPort
-> NpmClientConfig
-> PackageName
-> Version
-> IO (Either MetadataError (Maybe PackageDetails))
fetchNpmVersion TracingPort
tracing NpmClientConfig
config)
selectVersion :: Version -> PackageInfo -> Maybe PackageDetails
selectVersion :: Version -> PackageInfo -> Maybe PackageDetails
selectVersion Version
version PackageInfo
info = Text -> Map Text PackageDetails -> Maybe PackageDetails
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Version -> Text
renderVersion Version
version) (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
newtype ManifestFetchFailed = ManifestFetchFailed MetadataError
deriving stock (Int -> ManifestFetchFailed -> ShowS
[ManifestFetchFailed] -> ShowS
ManifestFetchFailed -> String
(Int -> ManifestFetchFailed -> ShowS)
-> (ManifestFetchFailed -> String)
-> ([ManifestFetchFailed] -> ShowS)
-> Show ManifestFetchFailed
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ManifestFetchFailed -> ShowS
showsPrec :: Int -> ManifestFetchFailed -> ShowS
$cshow :: ManifestFetchFailed -> String
show :: ManifestFetchFailed -> String
$cshowList :: [ManifestFetchFailed] -> ShowS
showList :: [ManifestFetchFailed] -> ShowS
Show)
instance Exception ManifestFetchFailed
foldManifestCarrier :: IO CacheEntry -> IO (Either MetadataError Manifest)
foldManifestCarrier :: IO CacheEntry -> IO (Either MetadataError Manifest)
foldManifestCarrier IO CacheEntry
resolve = do
outcome <- IO CacheEntry -> IO (Either ManifestFetchFailed CacheEntry)
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> m (Either e a)
try IO CacheEntry
resolve
pure $ case outcome of
Right CacheEntry
entry ->
Manifest -> Either MetadataError Manifest
forall a b. b -> Either a b
Right
Manifest
{ manifestInfo :: PackageInfo
manifestInfo = CacheEntry -> PackageInfo
entryInfo CacheEntry
entry
, manifestRaw :: Value
manifestRaw = CacheEntry -> Value
entryRaw CacheEntry
entry
, manifestDigest :: ContentDigest
manifestDigest = CacheEntry -> ContentDigest
entryDigest CacheEntry
entry
}
Left (ManifestFetchFailed MetadataError
err) -> MetadataError -> Either MetadataError Manifest
forall a b. a -> Either a b
Left MetadataError
err
newtype VersionFetchFailed = VersionFetchFailed MetadataError
deriving stock (Int -> VersionFetchFailed -> ShowS
[VersionFetchFailed] -> ShowS
VersionFetchFailed -> String
(Int -> VersionFetchFailed -> ShowS)
-> (VersionFetchFailed -> String)
-> ([VersionFetchFailed] -> ShowS)
-> Show VersionFetchFailed
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> VersionFetchFailed -> ShowS
showsPrec :: Int -> VersionFetchFailed -> ShowS
$cshow :: VersionFetchFailed -> String
show :: VersionFetchFailed -> String
$cshowList :: [VersionFetchFailed] -> ShowS
showList :: [VersionFetchFailed] -> ShowS
Show)
instance Exception VersionFetchFailed
foldVersionCarrier :: IO (Maybe PackageDetails) -> IO (Either MetadataError (Maybe PackageDetails))
foldVersionCarrier :: IO (Maybe PackageDetails)
-> IO (Either MetadataError (Maybe PackageDetails))
foldVersionCarrier IO (Maybe PackageDetails)
resolve = do
outcome <- IO (Maybe PackageDetails)
-> IO (Either VersionFetchFailed (Maybe PackageDetails))
forall (m :: * -> *) e a.
(MonadUnliftIO m, Exception e) =>
m a -> m (Either e a)
try IO (Maybe PackageDetails)
resolve
pure $ case outcome of
Right Maybe PackageDetails
details -> Maybe PackageDetails -> Either MetadataError (Maybe PackageDetails)
forall a b. b -> Either a b
Right Maybe PackageDetails
details
Left (VersionFetchFailed MetadataError
err) -> MetadataError -> Either MetadataError (Maybe PackageDetails)
forall a b. a -> Either a b
Left MetadataError
err
recordedFetch :: MetricsPort -> Metric.Upstream -> IO a -> IO a
recordedFetch :: forall a. MetricsPort -> Upstream -> IO a -> IO a
recordedFetch MetricsPort
metrics Upstream
upstream IO a
action = do
(result, seconds) <- IO (Either SomeException a) -> IO (Either SomeException a, Double)
forall (m :: * -> *) a. MonadIO m => m a -> m (a, Double)
timedSeconds (IO a -> IO (Either SomeException a)
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either SomeException a)
tryAny IO a
action)
case result of
Right a
entry -> do
MetricsPort -> Upstream -> StatusClass -> Double -> IO ()
mpUpstreamFetch MetricsPort
metrics Upstream
upstream StatusClass
Metric.Status2xx Double
seconds
a -> IO a
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
entry
Left SomeException
err -> do
MetricsPort -> Upstream -> Cause -> IO ()
mpUpstreamFetchError MetricsPort
metrics Upstream
upstream (SomeException -> Cause
fetchCause SomeException
err)
SomeException -> IO a
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO SomeException
err
fetchCause :: SomeException -> Metric.Cause
fetchCause :: SomeException -> Cause
fetchCause SomeException
err
| Just (ManifestFetchFailed MetadataError
me) <- SomeException -> Maybe ManifestFetchFailed
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
err = MetadataError -> Cause
metadataErrorCause MetadataError
me
| Just (VersionFetchFailed MetadataError
me) <- SomeException -> Maybe VersionFetchFailed
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
err = MetadataError -> Cause
metadataErrorCause MetadataError
me
| Just (HttpException
_ :: HTTP.HttpException) <- SomeException -> Maybe HttpException
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
err = Cause
Metric.Connection
| Bool
otherwise = Cause
Metric.OtherCause
metadataErrorCause :: MetadataError -> Metric.Cause
metadataErrorCause :: MetadataError -> Cause
metadataErrorCause = \case
MetadataError
MetadataUndecodable -> Cause
Metric.Decode
MetadataNameMismatch Text
_ -> Cause
Metric.Decode
MetadataBoundExceeded LimitError
_ -> Cause
Metric.OtherCause