module Hackage.Security.TUF.Layout.Index (
    -- * Repository layout
    IndexLayout(..)
  , IndexFile(..)
  , hackageIndexLayout
    -- ** Utility
  , indexLayoutPkgMetadata
  , indexLayoutPkgCabal
  , indexLayoutPkgPrefs
  ) where

import MyPrelude
import Distribution.Package
import Distribution.Text

import Hackage.Security.TUF.Paths
import Hackage.Security.TUF.Signed
import Hackage.Security.TUF.Targets
import Hackage.Security.Util.Path
import Hackage.Security.Util.Pretty
import Hackage.Security.Util.Some

{-------------------------------------------------------------------------------
  Index layout
-------------------------------------------------------------------------------}

-- | Layout of the files within the index tarball
data IndexLayout = IndexLayout  {
      -- | Translate an 'IndexFile' to a path
      IndexLayout -> forall dec. IndexFile dec -> IndexPath
indexFileToPath :: forall dec. IndexFile dec -> IndexPath

      -- | Parse an 'FilePath'
    , IndexLayout -> IndexPath -> Maybe (Some IndexFile)
indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
    }

-- | Files that we might request from the index
--
-- The type index tells us the type of the decoded file, if any. For files for
-- which the library does not support decoding this will be @()@.
-- NOTE: Clients should NOT rely on this type index being @()@, or they might
-- break if we add support for parsing additional file formats in the future.
--
-- TODO: If we wanted to support legacy Hackage, we should also have a case for
-- the global preferred-versions file. But supporting legacy Hackage will
-- probably require more work anyway..
data IndexFile :: * -> * where
    -- Package-specific metadata (@targets.json@)
    IndexPkgMetadata :: PackageIdentifier -> IndexFile (Signed Targets)

    -- Cabal file for a package
    IndexPkgCabal :: PackageIdentifier -> IndexFile ()

    -- Preferred versions a package
    IndexPkgPrefs :: PackageName -> IndexFile ()
--TODO: ^^ older haddock doesn't support GADT doc comments :-(

deriving instance Show (IndexFile dec)

instance Pretty (IndexFile dec) where
  pretty :: IndexFile dec -> String
pretty (IndexPkgMetadata PackageIdentifier
pkgId) = String
"metadata for " forall a. [a] -> [a] -> [a]
++ forall a. Pretty a => a -> String
display PackageIdentifier
pkgId
  pretty (IndexPkgCabal    PackageIdentifier
pkgId) = String
".cabal for " forall a. [a] -> [a] -> [a]
++ forall a. Pretty a => a -> String
display PackageIdentifier
pkgId
  pretty (IndexPkgPrefs    PackageName
pkgNm) = String
"preferred-versions for " forall a. [a] -> [a] -> [a]
++ forall a. Pretty a => a -> String
display PackageName
pkgNm

instance SomeShow   IndexFile where someShow :: forall a. DictShow (IndexFile a)
someShow   = forall a. Show a => DictShow a
DictShow
instance SomePretty IndexFile where somePretty :: forall a. DictPretty (IndexFile a)
somePretty = forall a. Pretty a => DictPretty a
DictPretty

-- | The layout of the index as maintained on Hackage
hackageIndexLayout :: IndexLayout
hackageIndexLayout :: IndexLayout
hackageIndexLayout = IndexLayout {
      indexFileToPath :: forall dec. IndexFile dec -> IndexPath
indexFileToPath   = forall dec. IndexFile dec -> IndexPath
toPath
    , indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
indexFileFromPath = IndexPath -> Maybe (Some IndexFile)
fromPath
    }
  where
    toPath :: IndexFile dec -> IndexPath
    toPath :: forall dec. IndexFile dec -> IndexPath
toPath (IndexPkgCabal    PackageIdentifier
pkgId) = [String] -> IndexPath
fromFragments [
                                          forall a. Pretty a => a -> String
display (forall pkg. Package pkg => pkg -> PackageName
packageName    PackageIdentifier
pkgId)
                                        , forall a. Pretty a => a -> String
display (forall pkg. Package pkg => pkg -> Version
packageVersion PackageIdentifier
pkgId)
                                        , forall a. Pretty a => a -> String
display (forall pkg. Package pkg => pkg -> PackageName
packageName PackageIdentifier
pkgId) forall a. [a] -> [a] -> [a]
++ String
".cabal"
                                        ]
    toPath (IndexPkgMetadata PackageIdentifier
pkgId) = [String] -> IndexPath
fromFragments [
                                          forall a. Pretty a => a -> String
display (forall pkg. Package pkg => pkg -> PackageName
packageName    PackageIdentifier
pkgId)
                                        , forall a. Pretty a => a -> String
display (forall pkg. Package pkg => pkg -> Version
packageVersion PackageIdentifier
pkgId)
                                        , String
"package.json"
                                        ]
    toPath (IndexPkgPrefs    PackageName
pkgNm) = [String] -> IndexPath
fromFragments [
                                          forall a. Pretty a => a -> String
display PackageName
pkgNm
                                        , String
"preferred-versions"
                                        ]

    fromFragments :: [String] -> IndexPath
    fromFragments :: [String] -> IndexPath
fromFragments = forall root. Path Unrooted -> Path root
rootPath forall b c a. (b -> c) -> (a -> b) -> a -> c
. [String] -> Path Unrooted
joinFragments

    fromPath :: IndexPath -> Maybe (Some IndexFile)
    fromPath :: IndexPath -> Maybe (Some IndexFile)
fromPath IndexPath
fp = case Path Unrooted -> [String]
splitFragments (forall root. Path root -> Path Unrooted
unrootPath IndexPath
fp) of
      [String
pkg, String
version, String
_file] -> do
        PackageIdentifier
pkgId <- forall a. Parsec a => String -> Maybe a
simpleParse (String
pkg forall a. [a] -> [a] -> [a]
++ String
"-" forall a. [a] -> [a] -> [a]
++ String
version)
        case forall a. Path a -> String
takeExtension IndexPath
fp of
          String
".cabal"   -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. f a -> Some f
Some forall a b. (a -> b) -> a -> b
$ PackageIdentifier -> IndexFile ()
IndexPkgCabal    PackageIdentifier
pkgId
          String
".json"    -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. f a -> Some f
Some forall a b. (a -> b) -> a -> b
$ PackageIdentifier -> IndexFile (Signed Targets)
IndexPkgMetadata PackageIdentifier
pkgId
          String
_otherwise -> forall a. Maybe a
Nothing
      [String
pkg, String
"preferred-versions"] ->
        forall (f :: * -> *) a. f a -> Some f
Some forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageName -> IndexFile ()
IndexPkgPrefs forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall a. Parsec a => String -> Maybe a
simpleParse String
pkg
      [String]
_otherwise -> forall a. Maybe a
Nothing

{-------------------------------------------------------------------------------
  Utility
-------------------------------------------------------------------------------}

indexLayoutPkgMetadata :: IndexLayout -> PackageIdentifier -> IndexPath
indexLayoutPkgMetadata :: IndexLayout -> PackageIdentifier -> IndexPath
indexLayoutPkgMetadata IndexLayout{IndexPath -> Maybe (Some IndexFile)
forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexLayout -> IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: IndexLayout -> forall dec. IndexFile dec -> IndexPath
..} = forall dec. IndexFile dec -> IndexPath
indexFileToPath forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageIdentifier -> IndexFile (Signed Targets)
IndexPkgMetadata

indexLayoutPkgCabal :: IndexLayout -> PackageIdentifier -> IndexPath
indexLayoutPkgCabal :: IndexLayout -> PackageIdentifier -> IndexPath
indexLayoutPkgCabal IndexLayout{IndexPath -> Maybe (Some IndexFile)
forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexLayout -> IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: IndexLayout -> forall dec. IndexFile dec -> IndexPath
..} = forall dec. IndexFile dec -> IndexPath
indexFileToPath forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageIdentifier -> IndexFile ()
IndexPkgCabal

indexLayoutPkgPrefs :: IndexLayout -> PackageName -> IndexPath
indexLayoutPkgPrefs :: IndexLayout -> PackageName -> IndexPath
indexLayoutPkgPrefs IndexLayout{IndexPath -> Maybe (Some IndexFile)
forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: forall dec. IndexFile dec -> IndexPath
indexFileFromPath :: IndexLayout -> IndexPath -> Maybe (Some IndexFile)
indexFileToPath :: IndexLayout -> forall dec. IndexFile dec -> IndexPath
..} = forall dec. IndexFile dec -> IndexPath
indexFileToPath forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageName -> IndexFile ()
IndexPkgPrefs