packages feed

nvfetcher-0.7.0.0: src/NvFetcher/Types.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}

-- | Copyright: (c) 2021-2022 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
--
-- Types used in this program.
module NvFetcher.Types
  ( -- * Common types
    Version (..),
    Checksum (..),
    ContainerDigest (..),
    Branch (..),
    NixExpr,
    VersionChange (..),
    WithPackageKey (..),

    -- * Nvchecker types
    VersionSortMethod (..),
    ListOptions (..),
    VersionSource (..),
    NvcheckerResult (..),
    NvcheckerRaw (..),
    CheckVersion (..),
    NvcheckerOptions (..),
    UseStaleVersion (..),

    -- * Nix fetcher types
    RunFetch (..),
    ForceFetch (..),
    NixFetcher (..),
    FetchResult,
    FetchStatus (..),

    -- * ExtractSrc Types
    ExtractSrcQ (..),

    -- * FetchRustGitDeps types
    FetchRustGitDepsQ (..),

    -- * GetGitCommitDate types
    DateFormat (..),
    GetGitCommitDate (..),

    -- * Core types
    Core (..),

    -- * Package types
    PackageName,
    PackageFetcher,
    PackageExtractSrc (..),
    PackageCargoLockFiles (..),
    PackagePassthru (..),
    Package (..),
    PackageKey (..),
    PackageResult (..),
  )
where

import qualified Data.Aeson as A
import Data.Coerce (coerce)
import Data.Default
import Data.HashMap.Strict (HashMap)
import qualified Data.List.NonEmpty as NE
import Data.Maybe (fromMaybe, isNothing)
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as T
import Development.Shake
import Development.Shake.Classes
import GHC.Generics (Generic)
import Prettyprinter

--------------------------------------------------------------------------------

-- | Package version
newtype Version = Version Text
  deriving newtype (Eq, Show, Ord, IsString, Semigroup, Monoid, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Check sum, sha256, sri or base32, etc.
newtype Checksum = Checksum Text
  deriving newtype (Show, Eq, Ord, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Digest of a (Docker) container
newtype ContainerDigest = ContainerDigest Text
  deriving newtype (Show, Eq, Ord, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Git branch ('Nothing': master)
newtype Branch = Branch (Maybe Text)
  deriving newtype (Show, Eq, Ord, Default, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Version change of a package
--
-- >>> VersionChange "foo" Nothing "2.3.3"
-- foo: ∅ → 2.3.3
--
-- >>> VersionChange "bar" (Just "2.2.2") "2.3.3"
-- bar: 2.2.2 → 2.3.3
data VersionChange = VersionChange
  { vcName :: PackageName,
    vcOld :: Maybe Version,
    vcNew :: Version
  }
  deriving (Eq)

instance Show VersionChange where
  show VersionChange {..} =
    T.unpack $ vcName <> ": " <> fromMaybe "∅" (coerce vcOld) <> " → " <> coerce vcNew

-- | Nix expression
type NixExpr = Text

--------------------------------------------------------------------------------

data VersionSortMethod = ParseVersion | Vercmp
  deriving (Typeable, Eq, Ord, Enum, Generic, Hashable, Binary, NFData)

instance Show VersionSortMethod where
  show = \case
    ParseVersion -> "parse_version"
    Vercmp -> "vercmp"

instance Pretty VersionSortMethod where
  pretty ParseVersion = "ParseVersion"
  pretty Vercmp = "Vercmp"

instance Default VersionSortMethod where
  def = ParseVersion

-- | Filter-like configuration for some version sources.
-- See <https://nvchecker.readthedocs.io/en/latest/usage.html#list-options> for details.
data ListOptions = ListOptions
  { _includeRegex :: Maybe Text,
    _excludeRegex :: Maybe Text,
    _sortVersionKey :: Maybe VersionSortMethod,
    _ignored :: Maybe Text
  }
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData, Default)

isEmptyListOptions :: ListOptions -> Bool
isEmptyListOptions ListOptions {..} =
  isNothing _includeRegex
    && isNothing _excludeRegex
    && isNothing _sortVersionKey
    && isNothing _includeRegex

instance Pretty ListOptions where
  pretty ListOptions {..} =
    "ListOptions"
      <> indent
        2
        ( vsep $
            concat
              [ ppField "includeRegex" _includeRegex,
                ppField "excludeRegex" _excludeRegex,
                ppField "sortVersionKey" _sortVersionKey,
                ppField "ignored" _includeRegex
              ]
        )

-- | Configuration available for evey version sourece.
-- See <https://nvchecker.readthedocs.io/en/latest/usage.html#global-options> for details.
data NvcheckerOptions = NvcheckerOptions
  { _stripPrefix :: Maybe Text,
    _fromPattern :: Maybe Text,
    _toPattern :: Maybe Text
  }
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData, Default)

isEmptyNvcheckerOptions :: NvcheckerOptions -> Bool
isEmptyNvcheckerOptions NvcheckerOptions {..} =
  isNothing _stripPrefix
    && isNothing _fromPattern
    && isNothing _toPattern

instance Pretty NvcheckerOptions where
  pretty NvcheckerOptions {..} =
    "NvcheckerOptions"
      <> line
      <> indent
        2
        ( vsep $
            concat
              [ ppField "stripPrefix" _stripPrefix,
                ppField "fromPattern" _fromPattern,
                ppField "toPattern" _toPattern
              ]
        )

ppField :: Pretty a => Doc ann -> Maybe a -> [Doc ann]
ppField _ Nothing = []
ppField s (Just x) = [s <> colon <+> pretty x]

-- | Upstream version source for nvchecker to check
data VersionSource
  = GitHubRelease {_owner :: Text, _repo :: Text}
  | GitHubTag {_owner :: Text, _repo :: Text, _listOptions :: ListOptions}
  | Git {_vurl :: Text, _vbranch :: Branch}
  | Pypi {_pypi :: Text}
  | ArchLinux {_archpkg :: Text}
  | Aur {_aur :: Text}
  | Manual {_manual :: Text}
  | Repology {_repology :: Text, _repo :: Text}
  | Webpage {_vurl :: Text, _regex :: Text, _listOptions :: ListOptions}
  | HttpHeader {_vurl :: Text, _regex :: Text, _listOptions :: ListOptions}
  | OpenVsx {_ovPublisher :: Text, _ovExtName :: Text}
  | VscodeMarketplace {_vsmPublisher :: Text, _vsmExtName :: Text}
  | Cmd {_vcmd :: Text}
  | Container {_vcontainer :: Text, _listOptions :: ListOptions}
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData)

instance Pretty VersionSource where
  pretty GitHubRelease {..} =
    "CheckGitHubRelease"
      <> line
      <> indent
        2
        ( vsep
            [ "owner" <> colon <+> pretty _owner,
              "repo" <> colon <+> pretty _repo
            ]
        )
  pretty GitHubTag {..} =
    "CheckGitHubTag"
      <> line
      <> indent
        2
        ( vsep $
            [ "owner" <> colon <+> pretty _owner,
              "repo" <> colon <+> pretty _repo
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty Git {..} =
    "CheckGit"
      <> line
      <> indent
        2
        ( vsep
            [ "url" <> colon <+> pretty _vurl,
              "branch" <> colon <+> pretty _vbranch
            ]
        )
  pretty Pypi {..} =
    "CheckPypi" <> colon <+> pretty _pypi
  pretty ArchLinux {..} =
    "CheckArchLinux" <> colon <+> pretty _archpkg
  pretty Aur {..} =
    "CheckAur" <> colon <+> pretty _aur
  pretty Manual {..} =
    "CheckManual" <> colon <+> pretty _manual
  pretty Repology {..} =
    "CheckRepology"
      <> line
      <> indent
        2
        ( vsep
            [ "repology" <> colon <+> pretty _repology,
              "repo" <> colon <+> pretty _repo
            ]
        )
  pretty Webpage {..} =
    "CheckWebpage"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _vurl,
              "regex" <> colon <+> pretty _regex
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty HttpHeader {..} =
    "CheckHttpHeader"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _vurl,
              "regex" <> colon <+> pretty _regex
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty OpenVsx {..} =
    "CheckOpenVsx"
      <> line
      <> indent
        2
        ( vsep
            [ "publisher" <> colon <+> pretty _ovPublisher,
              "extName" <> colon <+> pretty _ovExtName
            ]
        )
  pretty VscodeMarketplace {..} =
    "CheckVscodeMarketplace"
      <> line
      <> indent
        2
        ( vsep
            [ "publisher" <> colon <+> pretty _vsmPublisher,
              "extName" <> colon <+> pretty _vsmExtName
            ]
        )
  pretty Cmd {..} =
    "CheckCmd" <> colon <+> pretty _vcmd
  pretty Container {..} =
    "CheckContainer" <> colon <+> pretty _vcontainer

-- | The input of nvchecker
data CheckVersion = CheckVersion VersionSource NvcheckerOptions
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData)

instance Pretty CheckVersion where
  pretty (CheckVersion v n) = align (vsep $ [pretty v] <> [pretty n | not $ isEmptyNvcheckerOptions n])

-- | The result of nvchecker rule
data NvcheckerResult = NvcheckerResult
  { nvNow :: Version,
    -- | last result of this nvchecker rule
    -- TODO: consider removing this field
    nvOld :: Maybe Version,
    -- | stale means even 'nvNow' comes from json file (last run)
    -- and we actually didn't run nvchecker this time. 'nvOld' will be 'Nothing' in this case.
    nvStale :: Bool
  }
  deriving (Show, Typeable, Eq, Generic, Hashable, Binary, NFData)

-- | Parsed JSON output from nvchecker
data NvcheckerRaw = NvcheckerSuccess Version | NvcheckerError Text
  deriving (Show, Typeable, Eq, Generic)

instance A.FromJSON NvcheckerRaw where
  parseJSON = A.withObject "NvcheckerRaw" $ \o -> do
    mVersion <- o A..:? "version"
    case mVersion of
      Just version -> pure $ NvcheckerSuccess version
      _ -> NvcheckerError <$> o A..: "error"

type instance RuleResult CheckVersion = NvcheckerResult

--------------------------------------------------------------------------------

-- | Whether to cache the fetched sha256
--
-- @ForceFetch@ indicates @alwaysRerun@ the fetcher rule
data ForceFetch = ForceFetch | NoForceFetch
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

instance Pretty ForceFetch where
  pretty ForceFetch = "ForceFetch"
  pretty NoForceFetch = "NoForceFetch"

instance Default ForceFetch where
  def = NoForceFetch

-- | The input of prefetch rule
data RunFetch = RunFetch ForceFetch (NixFetcher Fresh)
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

-- Prefetch rule never throws exceptions
type instance RuleResult RunFetch = Maybe (NixFetcher Fetched)

-- | If the package is prefetched, then we can obtain the SHA256
data NixFetcher (k :: FetchStatus)
  = FetchGit
      { _furl :: Text,
        _rev :: Version,
        _deepClone :: Bool,
        _fetchSubmodules :: Bool,
        _leaveDotGit :: Bool,
        _sparseCheckout :: [Text],
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchGitHub
      { _fowner :: Text,
        _frepo :: Text,
        _rev :: Version,
        _deepClone :: Bool,
        _fetchSubmodules :: Bool,
        _leaveDotGit :: Bool,
        _sparseCheckout :: [Text],
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchUrl
      { _furl :: Text,
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchTarball
      { _furl :: Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchDocker
      { _imageName :: Text,
        _imageTag :: Text,
        _imageDigest :: FetchResult ContainerDigest k,
        _sha256 :: FetchResult Checksum k,
        _fos :: Maybe Text,
        _farch :: Maybe Text,
        _finalImageName :: Maybe Text,
        _finalImageTag :: Maybe Text,
        _tlsVerify :: Maybe Bool
      }
  deriving (Typeable, Generic)

class (c (FetchResult Checksum k), c (FetchResult ContainerDigest k)) => ForFetchResult c k

instance (c (FetchResult Checksum k), c (FetchResult ContainerDigest k)) => ForFetchResult c k

deriving instance Show `ForFetchResult` k => Show (NixFetcher k)

deriving instance Eq `ForFetchResult` k => Eq (NixFetcher k)

deriving instance Ord `ForFetchResult` k => Ord (NixFetcher k)

deriving instance Hashable `ForFetchResult` k => Hashable (NixFetcher k)

deriving instance Binary `ForFetchResult` k => Binary (NixFetcher k)

deriving instance NFData `ForFetchResult` k => NFData (NixFetcher k)

-- | Fetch status
data FetchStatus = Fresh | Fetched

-- | Prefetched fetchers hold hashes
type family FetchResult a (k :: FetchStatus) where
  FetchResult _ Fresh = ()
  FetchResult a Fetched = a

instance A.ToJSON (NixFetcher Fetched) where
  toJSON FetchGit {..} =
    A.object
      [ "url" A..= _furl,
        "rev" A..= _rev,
        "deepClone" A..= _deepClone,
        "fetchSubmodules" A..= _fetchSubmodules,
        "leaveDotGit" A..= _leaveDotGit,
        "sparseCheckout" A..= _sparseCheckout,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "git"
      ]
  toJSON FetchGitHub {..} =
    A.object
      [ "owner" A..= _fowner,
        "repo" A..= _frepo,
        "rev" A..= _rev,
        "deepClone" A..= _deepClone,
        "fetchSubmodules" A..= _fetchSubmodules,
        "leaveDotGit" A..= _leaveDotGit,
        "sparseCheckout" A..= _sparseCheckout,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "github"
      ]
  toJSON FetchUrl {..} =
    A.object
      [ "url" A..= _furl,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "url"
      ]
  toJSON FetchTarball {..} =
    A.object
      [ "url" A..= _furl,
        "sha256" A..= _sha256,
        "type" A..= A.String "tarball"
      ]
  toJSON FetchDocker {..} =
    A.object
      [ "imageName" A..= _imageName,
        "imageTag" A..= _imageTag,
        "imageDigest" A..= _imageDigest,
        "sha256" A..= _sha256,
        "os" A..= _fos,
        "arch" A..= _farch,
        "finalImageName" A..= _finalImageName,
        "finalImageTag" A..= _finalImageTag,
        "tlsVerify" A..= _tlsVerify
      ]

instance Pretty (NixFetcher k) where
  pretty FetchGit {..} =
    "FetchGit"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _furl,
              "rev" <> colon <+> pretty _rev,
              "deepClone" <> colon <+> pretty _deepClone,
              "fetchSubmodules" <> colon <+> pretty _fetchSubmodules,
              "leaveDotGit" <> colon <+> pretty _leaveDotGit,
              "sparseCheckout" <> colon <+> pretty _sparseCheckout
            ]
              <> ppField "name" _name
        )
  pretty FetchGitHub {..} =
    "FetchGitHub"
      <> line
      <> indent
        2
        ( vsep $
            [ "owner" <> colon <+> pretty _fowner,
              "repo" <> colon <+> pretty _frepo,
              "rev" <> colon <+> pretty _rev,
              "deepClone" <> colon <+> pretty _deepClone,
              "fetchSubmodules" <> colon <+> pretty _fetchSubmodules,
              "leaveDotGit" <> colon <+> pretty _leaveDotGit,
              "sparseCheckout" <> colon <+> pretty _sparseCheckout
            ]
              <> ppField "name" _name
        )
  pretty FetchUrl {..} =
    "FetchUrl"
      <> line
      <> indent
        2
        ( vsep $
            ["url" <> colon <+> pretty _furl]
              <> ppField "name" _name
        )
  pretty FetchTarball {..} =
    "FetchTarball" <> colon <+> pretty _furl
  pretty FetchDocker {..} =
    "FetchDocker"
      <> line
      <> indent
        2
        ( vsep $
            [ "imageName" <> colon <+> pretty _imageName,
              "imageTag" <> colon <+> pretty _finalImageTag
            ]
              <> ppField "os" _fos
              <> ppField "arch" _farch
              <> ppField "finalImageName" _finalImageName
              <> ppField "finalImageTag" _finalImageTag
              <> ppField "tlsVerify" _tlsVerify
        )

--------------------------------------------------------------------------------

-- | Extract file contents from package source
-- e.g. @Cargo.lock@
data ExtractSrcQ = ExtractSrcQ (NixFetcher Fetched) (NE.NonEmpty FilePath)
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

type instance RuleResult ExtractSrcQ = HashMap FilePath Text

instance Pretty ExtractSrcQ where
  pretty (ExtractSrcQ f n) =
    "ExtractSrc"
      <> line
      <> indent
        2
        ( vsep
            [ "fetcher" <> colon <+> pretty f,
              "files" <> colon <+> pretty n
            ]
        )

--------------------------------------------------------------------------------

-- | Fetch @outputHashes@ for git dependencies in @Cargo.lock@.
-- See <https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/rust.section.md#importing-a-cargolock-file> for details.
-- We need fetched source and the file path to @Cargo.lock@.
data FetchRustGitDepsQ = FetchRustGitDepsQ (NixFetcher Fetched) FilePath
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

-- | @outputHashes@, a mapping from nameVer -> output hash
type instance RuleResult FetchRustGitDepsQ = HashMap Text Checksum

instance Pretty FetchRustGitDepsQ where
  pretty (FetchRustGitDepsQ f n) =
    "FetchRustGitDeps"
      <> line
      <> indent
        2
        ( vsep
            [ "fetcher" <> colon <+> pretty f,
              "cargoLock" <> colon <+> pretty n
            ]
        )

--------------------------------------------------------------------------------

-- | @strftime@ format
--
-- Nothing defaults to @%Y-%m-%d@
newtype DateFormat = DateFormat (Maybe Text)
  deriving newtype (Show, Eq, Ord, Default, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Get the commit date by using shallow clone
--
-- @_gformat@ is in.
-- Note: Requires git >= 2.5
data GetGitCommitDate = GetGitCommitDate {_gurl :: Text, _grev :: Text, _gformat :: DateFormat}
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

type instance RuleResult GetGitCommitDate = Text

instance Pretty GetGitCommitDate where
  pretty GetGitCommitDate {..} =
    "GetGitCommitDate"
      <> line
      <> indent
        2
        ( vsep
            [ "url" <> colon <+> pretty _gurl,
              "rev" <> colon <+> pretty _grev,
              "format" <> colon <+> pretty _gformat
            ]
        )

--------------------------------------------------------------------------------

-- | Package name, used in generating nix expr
type PackageName = Text

-- | How to create package source fetcher given a version
type PackageFetcher = Version -> NixFetcher Fresh

newtype PackageExtractSrc = PackageExtractSrc (NE.NonEmpty FilePath)

newtype PackageCargoLockFiles = PackageCargoLockFiles (NE.NonEmpty FilePath)

newtype PackagePassthru = PackagePassthru (HashMap Text Text)
  deriving newtype (Semigroup, Monoid)

-- | Using stale value indicates that we will /NOT/ check for new versions if
-- there is a known version recovered from json file or last use of the rule.
-- Normally you don't want a stale version
-- unless you need pin a package.
data UseStaleVersion
  = -- | Specified in configuration file
    PermanentStale
  | -- | Specified by @--filter@ command
    TemporaryStale
  | NoStale
  deriving stock (Eq, Show, Ord, Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | A package is defined with:
--
-- 1. its name
-- 2. how to track its version
-- 3. how to fetch it as we have the version
-- 4. optional file paths to extract (dump to build dir)
-- 5. optional @Cargo.lock@ path (if it's a rust package)
-- 6. an optional pass through map
-- 7. if the package version was pinned
-- 8. optional git date format (if the version source is git)
-- 9. whether to always fetch a package regardless of the version changing
-- /INVARIANT: 'Version' passed to 'PackageFetcher' MUST be used textually,/
-- /i.e. can only be concatenated with other strings,/
-- /in case we can't check the equality between fetcher functions./
data Package = Package
  { _pname :: PackageName,
    _pversion :: CheckVersion,
    _pfetcher :: PackageFetcher,
    _pextract :: Maybe PackageExtractSrc,
    _pcargo :: Maybe PackageCargoLockFiles,
    _ppassthru :: PackagePassthru,
    _ppinned :: UseStaleVersion,
    _pgitdateformat :: DateFormat,
    _pforcefetch :: ForceFetch
  }

-- | Package key is the name of a package.
-- We use this type to index packages.
newtype PackageKey = PackageKey PackageName
  deriving newtype (Eq, Show, Ord, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

--------------------------------------------------------------------------------

-- | The key type of nvfetcher rule. See "NvFetcher.Core"
data Core = Core
  deriving (Eq, Show, Ord, Typeable, Generic, Hashable, Binary, NFData)

-- If prefetch fails, we don't want to fail the whole build
type instance RuleResult Core = Maybe PackageResult

-- | Decorate a rule's key with 'PackageKey'
newtype WithPackageKey k = WithPackageKey (k, PackageKey)
  deriving newtype (Eq, Hashable, Binary, NFData)

instance Show k => Show (WithPackageKey k) where
  show (WithPackageKey (k, n)) = show k <> " (" <> show n <> ")"

type instance RuleResult (WithPackageKey k) = RuleResult k

-- | Result type of 'Core'
data PackageResult = PackageResult
  { _prname :: PackageName,
    _prversion :: NvcheckerResult,
    _prfetched :: NixFetcher 'Fetched,
    _prpassthru :: Maybe (HashMap Text Text),
    -- | extracted file name -> file path in build dir
    _prextract :: Maybe (HashMap FilePath NixExpr),
    -- | cargo lock file path in build dir -> (file path in nix, git dependencies)
    _prcargolock :: Maybe (HashMap FilePath (NixExpr, HashMap Text Checksum)),
    _prpinned :: UseStaleVersion,
    _prgitdate :: Maybe Text
  }
  deriving (Show, Typeable, Generic, NFData)

instance A.ToJSON PackageResult where
  toJSON PackageResult {..} =
    A.object
      [ "name" A..= _prname,
        "version" A..= nvNow _prversion,
        "src" A..= _prfetched,
        "extract" A..= _prextract,
        "passthru" A..= _prpassthru,
        "cargoLocks" A..= _prcargolock,
        "pinned" A..= case _prpinned of
          PermanentStale -> True
          _ -> False,
        "date" A..= _prgitdate
      ]