{- | The npm registry __wire__ JSON types and their lenient decoders.

This module is the npm protocol __boundary__: it models the JSON the registry
actually sends and parses it with deliberately forgiving 'FromJSON' instances.
It is the raw-wire layer of "parse, don't validate" -- it captures /what the
registry said/ as faithfully as the rules and serving need, and __nothing
more__. Projecting these wire types into the ecosystem-agnostic domain model
("Ecluse.Core.Package": @PackageDetails@ et al.) is a separate concern; keeping the
two apart is what keeps the lenient\/faithful handle clean.

The shapes here are reverse-engineered from live captures of
@registry.npmjs.org@; the authoritative reference (with real bodies) is
@docs\/research\/reverse-engineering\/npm.md@ (§4 full packument, §5 abbreviated,
§7 @dist@, §11 type model, §3 errors).

== Lenient on input

The public registry has drifted from its own spec and is inconsistent across
endpoints, so every decoder here is forgiving in five specific ways, matching
the documented reality:

* __Unknown keys are ignored.__ Manifests carry arbitrary author keys
  (@gitHead@, @exports@, tool-config blocks like @is-odd@'s @verb@) and
  registry bookkeeping (@_npmOperationalInternal@); a decoder must not choke on
  them. aeson's record decoders already ignore extra keys, so this falls out of
  using @(.:?)@\/@(.:)@ rather than enumerating the whole object.
* __String-or-object scalars.__ @license@, @bugs@, @repository@, and the
  @author@\/maintainer person fields each arrive as /either/ a bare string /or/
  an object, depending on the package's age and tooling. Each corresponding
  type ('License', 'Bugs', 'Repository', 'Person') therefore parses both shapes.
* __The bare-string error body.__ npm's per-version 404 is a bare JSON
  __string__ (@"version not found: ^3.0.0"@), not the documented
  @{error|message}@ object. 'ErrorResponse' tolerates both.
* __The string-or-boolean @deprecated@ flag.__ @deprecated@ is conventionally the
  deprecation message string, but some published versions carry a boolean instead
  (@true@ = deprecated without a message, @false@ = not deprecated). 'vmDeprecated'
  reads every form, so a boolean never fails the whole packument decode (a real
  packument such as react's mixes the string and boolean forms across versions).
* __Advisory @dist@ sub-fields degrade rather than deny.__ @fileCount@,
  @unpackedSize@, and @signatures@ are advisory -- they decide no rule and no
  serve -- so a hostile value in one (a fractional\/huge\/@Int@-overflowing number,
  a wrong-typed field, or a malformed\/non-array @signatures@) reads as
  absent\/empty rather than failing the version. One poisoned value therefore
  cannot deny the whole packument ('Dist').

== Faithful on the rule-decisive fields

The fields the rules engine and the serving path actually need are captured
precisely: the abbreviated-only 'vmHasInstallScript' flag, the 'vmDeprecated'
notice, the whole 'vmScripts' map (so the full form's install-script presence
can be /derived/ -- the full manifest has no @hasInstallScript@ key), the
'Dist' integrity triple (@tarball@\/@shasum@\/@integrity@), and the full
packument's 'pkmtTime' map (the source of truth for publish age, which the
abbreviated form drops).

Only the decode path (@FromJSON@) is modelled here.
-}
module Ecluse.Core.Registry.Npm.Wire (
    -- * Shared scalars
    Person (..),
    Repository (..),
    Bugs (..),
    License (..),

    -- * The @dist@ object
    Dist (..),
    Signature (..),

    -- * Per-version manifest
    VersionManifest (..),

    -- * Packuments
    Packument (..),
    AbbreviatedPackument (..),

    -- * Errors
    ErrorResponse (..),
    ErrorBody (..),
    errorMessage,
) where

import Data.Aeson (
    FromJSON (parseJSON),
    Object,
    Value (Array, Bool, Null, Number, Object, String),
    withObject,
    (.!=),
    (.:),
    (.:?),
 )
import Data.Aeson.Key (Key)
import Data.Aeson.Types (Parser, parseMaybe)
import Data.Map.Strict qualified as Map
import Data.Time (UTCTime)

{- | A person associated with a package -- an author, maintainer, contributor, or
the per-version publisher (@_npmUser@).

__Lenient:__ npm sends a person as /either/ an object @{name, email?, url?}@ /or/
a single packed string of the conventional form
@"Name \<email\> (url)"@. The packed form is captured __verbatim__ in
'personName' (with 'personEmail'\/'personUrl' left 'Nothing'); this wire layer
does not attempt to split it, leaving that to the domain projection if it is ever
needed. Distinct from "Ecluse.Core.Package"'s domain @Person@ -- this is the raw wire
shape.
-}
data Person = Person
    { Person -> Text
personName :: Text
    {- ^ The person's name. For the packed-string form, the entire string as
    sent (e.g. @"Mikeal Rogers \<mikeal\@example.com\>"@).
    -}
    , Person -> Maybe Text
personEmail :: Maybe Text
    -- ^ Their email address, if given as an object field.
    , Person -> Maybe Text
personUrl :: Maybe Text
    -- ^ A homepage \/ profile URL, if given as an object field.
    }
    deriving stock (Person -> Person -> Bool
(Person -> Person -> Bool)
-> (Person -> Person -> Bool) -> Eq Person
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Person -> Person -> Bool
== :: Person -> Person -> Bool
$c/= :: Person -> Person -> Bool
/= :: Person -> Person -> Bool
Eq, Eq Person
Eq Person =>
(Person -> Person -> Ordering)
-> (Person -> Person -> Bool)
-> (Person -> Person -> Bool)
-> (Person -> Person -> Bool)
-> (Person -> Person -> Bool)
-> (Person -> Person -> Person)
-> (Person -> Person -> Person)
-> Ord Person
Person -> Person -> Bool
Person -> Person -> Ordering
Person -> Person -> Person
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Person -> Person -> Ordering
compare :: Person -> Person -> Ordering
$c< :: Person -> Person -> Bool
< :: Person -> Person -> Bool
$c<= :: Person -> Person -> Bool
<= :: Person -> Person -> Bool
$c> :: Person -> Person -> Bool
> :: Person -> Person -> Bool
$c>= :: Person -> Person -> Bool
>= :: Person -> Person -> Bool
$cmax :: Person -> Person -> Person
max :: Person -> Person -> Person
$cmin :: Person -> Person -> Person
min :: Person -> Person -> Person
Ord, Int -> Person -> ShowS
[Person] -> ShowS
Person -> String
(Int -> Person -> ShowS)
-> (Person -> String) -> ([Person] -> ShowS) -> Show Person
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Person -> ShowS
showsPrec :: Int -> Person -> ShowS
$cshow :: Person -> String
show :: Person -> String
$cshowList :: [Person] -> ShowS
showList :: [Person] -> ShowS
Show)

instance FromJSON Person where
    parseJSON :: Value -> Parser Person
parseJSON = \case
        String Text
name -> Person -> Parser Person
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> Maybe Text -> Maybe Text -> Person
Person Text
name Maybe Text
forall a. Maybe a
Nothing Maybe Text
forall a. Maybe a
Nothing)
        Object Object
o ->
            Text -> Maybe Text -> Maybe Text -> Person
Person
                (Text -> Maybe Text -> Maybe Text -> Person)
-> Parser Text -> Parser (Maybe Text -> Maybe Text -> Person)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"name" Parser (Maybe Text) -> Text -> Parser Text
forall a. Parser (Maybe a) -> a -> Parser a
.!= Text
""
                Parser (Maybe Text -> Maybe Text -> Person)
-> Parser (Maybe Text) -> Parser (Maybe Text -> Person)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"email"
                Parser (Maybe Text -> Person)
-> Parser (Maybe Text) -> Parser Person
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"url"
        Value
other -> String -> Value -> Parser Person
forall a. String -> Value -> Parser a
typeMismatchOneOf String
"Person (object or string)" Value
other

{- | An SCM location for a package.

__Lenient:__ npm sends @repository@ as /either/ an object @{type?, url}@ /or/ a
bare string (a shorthand URL such as @"github:user\/repo"@). Both are captured;
the bare-string form fills 'repoUrl' and leaves 'repoType' 'Nothing'.
-}
data Repository = Repository
    { Repository -> Maybe Text
repoType :: Maybe Text
    -- ^ The SCM type (e.g. @"git"@), if given.
    , Repository -> Text
repoUrl :: Text
    -- ^ The repository URL, as sent.
    }
    deriving stock (Repository -> Repository -> Bool
(Repository -> Repository -> Bool)
-> (Repository -> Repository -> Bool) -> Eq Repository
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Repository -> Repository -> Bool
== :: Repository -> Repository -> Bool
$c/= :: Repository -> Repository -> Bool
/= :: Repository -> Repository -> Bool
Eq, Eq Repository
Eq Repository =>
(Repository -> Repository -> Ordering)
-> (Repository -> Repository -> Bool)
-> (Repository -> Repository -> Bool)
-> (Repository -> Repository -> Bool)
-> (Repository -> Repository -> Bool)
-> (Repository -> Repository -> Repository)
-> (Repository -> Repository -> Repository)
-> Ord Repository
Repository -> Repository -> Bool
Repository -> Repository -> Ordering
Repository -> Repository -> Repository
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Repository -> Repository -> Ordering
compare :: Repository -> Repository -> Ordering
$c< :: Repository -> Repository -> Bool
< :: Repository -> Repository -> Bool
$c<= :: Repository -> Repository -> Bool
<= :: Repository -> Repository -> Bool
$c> :: Repository -> Repository -> Bool
> :: Repository -> Repository -> Bool
$c>= :: Repository -> Repository -> Bool
>= :: Repository -> Repository -> Bool
$cmax :: Repository -> Repository -> Repository
max :: Repository -> Repository -> Repository
$cmin :: Repository -> Repository -> Repository
min :: Repository -> Repository -> Repository
Ord, Int -> Repository -> ShowS
[Repository] -> ShowS
Repository -> String
(Int -> Repository -> ShowS)
-> (Repository -> String)
-> ([Repository] -> ShowS)
-> Show Repository
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Repository -> ShowS
showsPrec :: Int -> Repository -> ShowS
$cshow :: Repository -> String
show :: Repository -> String
$cshowList :: [Repository] -> ShowS
showList :: [Repository] -> ShowS
Show)

instance FromJSON Repository where
    parseJSON :: Value -> Parser Repository
parseJSON = \case
        String Text
url -> Repository -> Parser Repository
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Text -> Text -> Repository
Repository Maybe Text
forall a. Maybe a
Nothing Text
url)
        Object Object
o ->
            Maybe Text -> Text -> Repository
Repository
                (Maybe Text -> Text -> Repository)
-> Parser (Maybe Text) -> Parser (Text -> Repository)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"type"
                Parser (Text -> Repository) -> Parser Text -> Parser Repository
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"url" Parser (Maybe Text) -> Text -> Parser Text
forall a. Parser (Maybe a) -> a -> Parser a
.!= Text
""
        Value
other -> String -> Value -> Parser Repository
forall a. String -> Value -> Parser a
typeMismatchOneOf String
"Repository (object or string)" Value
other

{- | The issue tracker for a package.

__Lenient:__ npm sends @bugs@ as /either/ an object @{url?, email?}@ /or/ a bare
string (just the tracker URL). The bare-string form fills 'bugsUrl'.
-}
data Bugs = Bugs
    { Bugs -> Maybe Text
bugsUrl :: Maybe Text
    -- ^ The issue-tracker URL, if given.
    , Bugs -> Maybe Text
bugsEmail :: Maybe Text
    -- ^ A contact email, if given as an object field.
    }
    deriving stock (Bugs -> Bugs -> Bool
(Bugs -> Bugs -> Bool) -> (Bugs -> Bugs -> Bool) -> Eq Bugs
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Bugs -> Bugs -> Bool
== :: Bugs -> Bugs -> Bool
$c/= :: Bugs -> Bugs -> Bool
/= :: Bugs -> Bugs -> Bool
Eq, Eq Bugs
Eq Bugs =>
(Bugs -> Bugs -> Ordering)
-> (Bugs -> Bugs -> Bool)
-> (Bugs -> Bugs -> Bool)
-> (Bugs -> Bugs -> Bool)
-> (Bugs -> Bugs -> Bool)
-> (Bugs -> Bugs -> Bugs)
-> (Bugs -> Bugs -> Bugs)
-> Ord Bugs
Bugs -> Bugs -> Bool
Bugs -> Bugs -> Ordering
Bugs -> Bugs -> Bugs
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Bugs -> Bugs -> Ordering
compare :: Bugs -> Bugs -> Ordering
$c< :: Bugs -> Bugs -> Bool
< :: Bugs -> Bugs -> Bool
$c<= :: Bugs -> Bugs -> Bool
<= :: Bugs -> Bugs -> Bool
$c> :: Bugs -> Bugs -> Bool
> :: Bugs -> Bugs -> Bool
$c>= :: Bugs -> Bugs -> Bool
>= :: Bugs -> Bugs -> Bool
$cmax :: Bugs -> Bugs -> Bugs
max :: Bugs -> Bugs -> Bugs
$cmin :: Bugs -> Bugs -> Bugs
min :: Bugs -> Bugs -> Bugs
Ord, Int -> Bugs -> ShowS
[Bugs] -> ShowS
Bugs -> String
(Int -> Bugs -> ShowS)
-> (Bugs -> String) -> ([Bugs] -> ShowS) -> Show Bugs
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Bugs -> ShowS
showsPrec :: Int -> Bugs -> ShowS
$cshow :: Bugs -> String
show :: Bugs -> String
$cshowList :: [Bugs] -> ShowS
showList :: [Bugs] -> ShowS
Show)

instance FromJSON Bugs where
    parseJSON :: Value -> Parser Bugs
parseJSON = \case
        String Text
url -> Bugs -> Parser Bugs
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Text -> Maybe Text -> Bugs
Bugs (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
url) Maybe Text
forall a. Maybe a
Nothing)
        Object Object
o ->
            Maybe Text -> Maybe Text -> Bugs
Bugs
                (Maybe Text -> Maybe Text -> Bugs)
-> Parser (Maybe Text) -> Parser (Maybe Text -> Bugs)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"url"
                Parser (Maybe Text -> Bugs) -> Parser (Maybe Text) -> Parser Bugs
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"email"
        Value
other -> String -> Value -> Parser Bugs
forall a. String -> Value -> Parser a
typeMismatchOneOf String
"Bugs (object or string)" Value
other

{- | A declared license.

__Lenient:__ modern packages send a bare SPDX __string__ (@"MIT"@); legacy
packages send an object @{type, url?}@. Both are preserved as a sum so the
distinction is not lost: 'LicenseSpdx' for the string, 'LicenseObject' for the
legacy object.
-}
data License
    = {- | An SPDX expression or identifier, sent as a bare string (@"MIT"@,
      @"Apache-2.0"@, @"(MIT OR Apache-2.0)"@). The modern form.
      -}
      LicenseSpdx Text
    | {- | The legacy object form @{type, url?}@: a license name plus an optional
      URL to the license text.
      -}
      LicenseObject Text (Maybe Text)
    deriving stock (License -> License -> Bool
(License -> License -> Bool)
-> (License -> License -> Bool) -> Eq License
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: License -> License -> Bool
== :: License -> License -> Bool
$c/= :: License -> License -> Bool
/= :: License -> License -> Bool
Eq, Eq License
Eq License =>
(License -> License -> Ordering)
-> (License -> License -> Bool)
-> (License -> License -> Bool)
-> (License -> License -> Bool)
-> (License -> License -> Bool)
-> (License -> License -> License)
-> (License -> License -> License)
-> Ord License
License -> License -> Bool
License -> License -> Ordering
License -> License -> License
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: License -> License -> Ordering
compare :: License -> License -> Ordering
$c< :: License -> License -> Bool
< :: License -> License -> Bool
$c<= :: License -> License -> Bool
<= :: License -> License -> Bool
$c> :: License -> License -> Bool
> :: License -> License -> Bool
$c>= :: License -> License -> Bool
>= :: License -> License -> Bool
$cmax :: License -> License -> License
max :: License -> License -> License
$cmin :: License -> License -> License
min :: License -> License -> License
Ord, Int -> License -> ShowS
[License] -> ShowS
License -> String
(Int -> License -> ShowS)
-> (License -> String) -> ([License] -> ShowS) -> Show License
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> License -> ShowS
showsPrec :: Int -> License -> ShowS
$cshow :: License -> String
show :: License -> String
$cshowList :: [License] -> ShowS
showList :: [License] -> ShowS
Show)

instance FromJSON License where
    parseJSON :: Value -> Parser License
parseJSON = \case
        String Text
spdx -> License -> Parser License
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> License
LicenseSpdx Text
spdx)
        Object Object
o ->
            Text -> Maybe Text -> License
LicenseObject
                (Text -> Maybe Text -> License)
-> Parser Text -> Parser (Maybe Text -> License)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"type" Parser (Maybe Text) -> Text -> Parser Text
forall a. Parser (Maybe a) -> a -> Parser a
.!= Text
""
                Parser (Maybe Text -> License)
-> Parser (Maybe Text) -> Parser License
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"url"
        Value
other -> String -> Value -> Parser License
forall a. String -> Value -> Parser a
typeMismatchOneOf String
"License (object or string)" Value
other

{- | One registry signature over a published artifact: an ECDSA signature and
the id of the key that produced it. Verifiable against npm's published public
keys (@GET \/-\/npm\/v1\/keys@) -- the basis of @npm audit signatures@.
-}
data Signature = Signature
    { Signature -> Text
sigSig :: Text
    -- ^ The base64-encoded signature value.
    , Signature -> Text
sigKeyid :: Text
    -- ^ The id of the signing key (e.g. @"SHA256:jl3bwswu80…"@).
    }
    deriving stock (Signature -> Signature -> Bool
(Signature -> Signature -> Bool)
-> (Signature -> Signature -> Bool) -> Eq Signature
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Signature -> Signature -> Bool
== :: Signature -> Signature -> Bool
$c/= :: Signature -> Signature -> Bool
/= :: Signature -> Signature -> Bool
Eq, Eq Signature
Eq Signature =>
(Signature -> Signature -> Ordering)
-> (Signature -> Signature -> Bool)
-> (Signature -> Signature -> Bool)
-> (Signature -> Signature -> Bool)
-> (Signature -> Signature -> Bool)
-> (Signature -> Signature -> Signature)
-> (Signature -> Signature -> Signature)
-> Ord Signature
Signature -> Signature -> Bool
Signature -> Signature -> Ordering
Signature -> Signature -> Signature
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Signature -> Signature -> Ordering
compare :: Signature -> Signature -> Ordering
$c< :: Signature -> Signature -> Bool
< :: Signature -> Signature -> Bool
$c<= :: Signature -> Signature -> Bool
<= :: Signature -> Signature -> Bool
$c> :: Signature -> Signature -> Bool
> :: Signature -> Signature -> Bool
$c>= :: Signature -> Signature -> Bool
>= :: Signature -> Signature -> Bool
$cmax :: Signature -> Signature -> Signature
max :: Signature -> Signature -> Signature
$cmin :: Signature -> Signature -> Signature
min :: Signature -> Signature -> Signature
Ord, Int -> Signature -> ShowS
[Signature] -> ShowS
Signature -> String
(Int -> Signature -> ShowS)
-> (Signature -> String)
-> ([Signature] -> ShowS)
-> Show Signature
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Signature -> ShowS
showsPrec :: Int -> Signature -> ShowS
$cshow :: Signature -> String
show :: Signature -> String
$cshowList :: [Signature] -> ShowS
showList :: [Signature] -> ShowS
Show)

instance FromJSON Signature where
    parseJSON :: Value -> Parser Signature
parseJSON = String -> (Object -> Parser Signature) -> Value -> Parser Signature
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Signature" ((Object -> Parser Signature) -> Value -> Parser Signature)
-> (Object -> Parser Signature) -> Value -> Parser Signature
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text -> Text -> Signature
Signature
            (Text -> Text -> Signature)
-> Parser Text -> Parser (Text -> Signature)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"sig"
            Parser (Text -> Signature) -> Parser Text -> Parser Signature
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"keyid"

{- | The @dist@ object: the artifact descriptor carried by every version
manifest (full and abbreviated). It is the gateway to the tarball bytes and the
integrity guarantee.

The integrity triple ('distTarball', 'distShasum', 'distIntegrity') is
rule-decisive and serving-decisive -- a client __fails the install__ if the
downloaded bytes do not match @integrity@\/@shasum@, so any mirror or URL rewrite
must preserve these byte-for-byte. Prefer 'distIntegrity' (SRI) over the legacy
SHA-1 'distShasum'.

The remaining sub-fields ('distFileCount', 'distUnpackedSize',
'distSignatures') are __advisory__ -- they inform reporting but decide no rule and
no serve -- and so are decoded __leniently__: a present-but-undecodable number
(fractional, huge, or 'Int'-overflowing) reads as absent ('Nothing'), a malformed
signature element is skipped rather than failing the array, and a
@signatures@ value that is not even an array reads as empty. A hostile value in
one version therefore degrades that field alone, never denying the whole
packument.
-}
data Dist = Dist
    { Dist -> Text
distTarball :: Text
    -- ^ Absolute URL of the @.tgz@ artifact. Always present.
    , Dist -> Maybe Text
distShasum :: Maybe Text
    -- ^ The tarball's SHA-1, hex-encoded (legacy integrity).
    , Dist -> Maybe Text
distIntegrity :: Maybe Text
    {- ^ The Subresource-Integrity string (@"\<alg\>-\<base64\>"@, e.g.
    @"sha512-…"@). The modern integrity check; prefer it over the shasum.
    -}
    , Dist -> Maybe Int
distFileCount :: Maybe Int
    -- ^ Number of files in the tarball, if reported.
    , Dist -> Maybe Int
distUnpackedSize :: Maybe Int
    -- ^ Unpacked size in bytes, if reported.
    , Dist -> [Signature]
distSignatures :: [Signature]
    -- ^ Registry ECDSA signatures; empty when none are present.
    }
    deriving stock (Dist -> Dist -> Bool
(Dist -> Dist -> Bool) -> (Dist -> Dist -> Bool) -> Eq Dist
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Dist -> Dist -> Bool
== :: Dist -> Dist -> Bool
$c/= :: Dist -> Dist -> Bool
/= :: Dist -> Dist -> Bool
Eq, Eq Dist
Eq Dist =>
(Dist -> Dist -> Ordering)
-> (Dist -> Dist -> Bool)
-> (Dist -> Dist -> Bool)
-> (Dist -> Dist -> Bool)
-> (Dist -> Dist -> Bool)
-> (Dist -> Dist -> Dist)
-> (Dist -> Dist -> Dist)
-> Ord Dist
Dist -> Dist -> Bool
Dist -> Dist -> Ordering
Dist -> Dist -> Dist
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Dist -> Dist -> Ordering
compare :: Dist -> Dist -> Ordering
$c< :: Dist -> Dist -> Bool
< :: Dist -> Dist -> Bool
$c<= :: Dist -> Dist -> Bool
<= :: Dist -> Dist -> Bool
$c> :: Dist -> Dist -> Bool
> :: Dist -> Dist -> Bool
$c>= :: Dist -> Dist -> Bool
>= :: Dist -> Dist -> Bool
$cmax :: Dist -> Dist -> Dist
max :: Dist -> Dist -> Dist
$cmin :: Dist -> Dist -> Dist
min :: Dist -> Dist -> Dist
Ord, Int -> Dist -> ShowS
[Dist] -> ShowS
Dist -> String
(Int -> Dist -> ShowS)
-> (Dist -> String) -> ([Dist] -> ShowS) -> Show Dist
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Dist -> ShowS
showsPrec :: Int -> Dist -> ShowS
$cshow :: Dist -> String
show :: Dist -> String
$cshowList :: [Dist] -> ShowS
showList :: [Dist] -> ShowS
Show)

instance FromJSON Dist where
    parseJSON :: Value -> Parser Dist
parseJSON = String -> (Object -> Parser Dist) -> Value -> Parser Dist
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Dist" ((Object -> Parser Dist) -> Value -> Parser Dist)
-> (Object -> Parser Dist) -> Value -> Parser Dist
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text
-> Maybe Text
-> Maybe Text
-> Maybe Int
-> Maybe Int
-> [Signature]
-> Dist
Dist
            (Text
 -> Maybe Text
 -> Maybe Text
 -> Maybe Int
 -> Maybe Int
 -> [Signature]
 -> Dist)
-> Parser Text
-> Parser
     (Maybe Text
      -> Maybe Text -> Maybe Int -> Maybe Int -> [Signature] -> Dist)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"tarball"
            Parser
  (Maybe Text
   -> Maybe Text -> Maybe Int -> Maybe Int -> [Signature] -> Dist)
-> Parser (Maybe Text)
-> Parser
     (Maybe Text -> Maybe Int -> Maybe Int -> [Signature] -> Dist)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"shasum"
            Parser
  (Maybe Text -> Maybe Int -> Maybe Int -> [Signature] -> Dist)
-> Parser (Maybe Text)
-> Parser (Maybe Int -> Maybe Int -> [Signature] -> Dist)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"integrity"
            Parser (Maybe Int -> Maybe Int -> [Signature] -> Dist)
-> Parser (Maybe Int) -> Parser (Maybe Int -> [Signature] -> Dist)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Parser (Maybe Int)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
lenientOptional Object
o Key
"fileCount"
            Parser (Maybe Int -> [Signature] -> Dist)
-> Parser (Maybe Int) -> Parser ([Signature] -> Dist)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Key -> Parser (Maybe Int)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
lenientOptional Object
o Key
"unpackedSize"
            Parser ([Signature] -> Dist) -> Parser [Signature] -> Parser Dist
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Parser [Signature]
lenientSignatures Object
o

{- Decode an optional field __leniently__: an absent, @null@, or
present-but-undecodable value all yield 'Nothing'. Where @(.:?)@ fails the whole
decode on a present-but-wrong value, this degrades a hostile value (wrong-typed,
fractional, or outside the target's range) to 'Nothing' instead. Reserved for the
advisory @dist@ sub-fields, so one poisoned value cannot deny the whole packument;
the load-bearing integrity fields keep @(.:?)@\/@(.:)@. -}
lenientOptional :: (FromJSON a) => Object -> Key -> Parser (Maybe a)
lenientOptional :: forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
lenientOptional Object
o Key
k = do
    mv <- Object
o Object -> Key -> Parser (Maybe Value)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
k -- Parser (Maybe Value): a present junk value still arrives here
    pure (mv >>= parseMaybe parseJSON) -- but a Value that will not decode becomes Nothing

{- Decode the advisory @signatures@ array __leniently__: skip any element that
does not parse as a 'Signature' rather than failing the array, and treat a
present-but-non-array value (or absence\/@null@) as no signatures. The 'Signature'
instance itself stays strict; only its aggregation here tolerates a malformed
entry, so one bad signature cannot deny the version. -}
lenientSignatures :: Object -> Parser [Signature]
lenientSignatures :: Object -> Parser [Signature]
lenientSignatures Object
o = do
    mv <- Object
o Object -> Key -> Parser (Maybe Value)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"signatures"
    pure $ case mv of
        Just (Array Array
xs) -> (Value -> Maybe Signature) -> [Value] -> [Signature]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ((Value -> Parser Signature) -> Value -> Maybe Signature
forall a b. (a -> Parser b) -> a -> Maybe b
parseMaybe Value -> Parser Signature
forall a. FromJSON a => Value -> Parser a
parseJSON) (Array -> [Value]
forall a. Vector a -> [a]
forall (t :: * -> *) a. Foldable t => t a -> [a]
toList Array
xs)
        Maybe Value
_ -> []

{- | A single version's manifest -- the per-version object that is essentially
the package's @package.json@ at publish time plus registry-injected fields. It
appears three ways on the wire and this one type decodes all of them: embedded in
a full 'Packument' (@versions[v]@), embedded in an 'AbbreviatedPackument' (a
trimmed subset of the same shape), and standalone (@GET \/{pkg}\/{version}@).

Only the fields Écluse's rules and serving need are modelled; everything else is
ignored (see the module header). The two rule-decisive optionals deserve note:

* 'vmHasInstallScript' is __abbreviated-only__ -- the registry sets it when the
  version declares @preinstall@\/@install@\/@postinstall@ scripts. It is the
  cleanest install-script signal, but it is __absent from the full manifest__.
* 'vmScripts' is therefore captured whole so that, when only the full form is
  available, install-script presence can be /derived/
  (@scripts@ has any of @preinstall@\/@install@\/@postinstall@). That derivation
  is a domain-projection concern, not this layer's.

The publish timestamp is __not__ here -- it lives in the packument's
'pkmtTime' map, not the manifest (see §8 of the protocol reference).
-}
data VersionManifest = VersionManifest
    { VersionManifest -> Text
vmName :: Text
    -- ^ The package name, possibly scoped (@"\@scope\/name"@), verbatim.
    , VersionManifest -> Text
vmVersion :: Text
    -- ^ The exact version string (e.g. @"1.2.3"@), kept opaque at this layer.
    , VersionManifest -> Dist
vmDist :: Dist
    -- ^ The artifact descriptor (always present).
    , VersionManifest -> Maybe Text
vmDeprecated :: Maybe Text
    {- ^ The deprecation message when the version is deprecated, else 'Nothing'.
    npm sends @deprecated@ as the message string, or as a boolean (@true@ =
    deprecated with no message, captured as @""@; @false@ = not deprecated); an
    absent, @null@, @false@, or otherwise-shaped value reads as 'Nothing'.
    -}
    , VersionManifest -> Maybe Bool
vmHasInstallScript :: Maybe Bool
    {- ^ Whether the version declares install scripts. Present in the
    __abbreviated__ form only; 'Nothing' in the full form (derive from
    'vmScripts' there).
    -}
    , VersionManifest -> Map Text Text
vmScripts :: Map Text Text
    {- ^ The @scripts@ map (lifecycle name to command), empty when absent. The
    source for deriving install-script presence from the full form.
    -}
    , VersionManifest -> Maybe License
vmLicense :: Maybe License
    {- ^ The declared license, if any (string or legacy object; see 'License').

    The manifest's dependency maps and maintainer list are __deliberately not
    parsed__: no rule or serve path consults them, the raw document relays them
    to the client untouched, and a heavy packument carries thousands of
    per-version entries of pure parse cost (architect ruling, 2026-07-02 --
    including that a malformed entry there may degrade rather than deny). Restore
    them from history if a dependency-reading rule ever lands.
    -}
    }
    deriving stock (VersionManifest -> VersionManifest -> Bool
(VersionManifest -> VersionManifest -> Bool)
-> (VersionManifest -> VersionManifest -> Bool)
-> Eq VersionManifest
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: VersionManifest -> VersionManifest -> Bool
== :: VersionManifest -> VersionManifest -> Bool
$c/= :: VersionManifest -> VersionManifest -> Bool
/= :: VersionManifest -> VersionManifest -> Bool
Eq, Int -> VersionManifest -> ShowS
[VersionManifest] -> ShowS
VersionManifest -> String
(Int -> VersionManifest -> ShowS)
-> (VersionManifest -> String)
-> ([VersionManifest] -> ShowS)
-> Show VersionManifest
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> VersionManifest -> ShowS
showsPrec :: Int -> VersionManifest -> ShowS
$cshow :: VersionManifest -> String
show :: VersionManifest -> String
$cshowList :: [VersionManifest] -> ShowS
showList :: [VersionManifest] -> ShowS
Show)

instance FromJSON VersionManifest where
    parseJSON :: Value -> Parser VersionManifest
parseJSON = String
-> (Object -> Parser VersionManifest)
-> Value
-> Parser VersionManifest
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"VersionManifest" ((Object -> Parser VersionManifest)
 -> Value -> Parser VersionManifest)
-> (Object -> Parser VersionManifest)
-> Value
-> Parser VersionManifest
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text
-> Text
-> Dist
-> Maybe Text
-> Maybe Bool
-> Map Text Text
-> Maybe License
-> VersionManifest
VersionManifest
            (Text
 -> Text
 -> Dist
 -> Maybe Text
 -> Maybe Bool
 -> Map Text Text
 -> Maybe License
 -> VersionManifest)
-> Parser Text
-> Parser
     (Text
      -> Dist
      -> Maybe Text
      -> Maybe Bool
      -> Map Text Text
      -> Maybe License
      -> VersionManifest)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
            Parser
  (Text
   -> Dist
   -> Maybe Text
   -> Maybe Bool
   -> Map Text Text
   -> Maybe License
   -> VersionManifest)
-> Parser Text
-> Parser
     (Dist
      -> Maybe Text
      -> Maybe Bool
      -> Map Text Text
      -> Maybe License
      -> VersionManifest)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"version"
            Parser
  (Dist
   -> Maybe Text
   -> Maybe Bool
   -> Map Text Text
   -> Maybe License
   -> VersionManifest)
-> Parser Dist
-> Parser
     (Maybe Text
      -> Maybe Bool -> Map Text Text -> Maybe License -> VersionManifest)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser Dist
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"dist"
            Parser
  (Maybe Text
   -> Maybe Bool -> Map Text Text -> Maybe License -> VersionManifest)
-> Parser (Maybe Text)
-> Parser
     (Maybe Bool -> Map Text Text -> Maybe License -> VersionManifest)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (Maybe Value -> Maybe Text
deprecatedNotice (Maybe Value -> Maybe Text)
-> Parser (Maybe Value) -> Parser (Maybe Text)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Value)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"deprecated")
            Parser
  (Maybe Bool -> Map Text Text -> Maybe License -> VersionManifest)
-> Parser (Maybe Bool)
-> Parser (Map Text Text -> Maybe License -> VersionManifest)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Bool)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"hasInstallScript"
            Parser (Map Text Text -> Maybe License -> VersionManifest)
-> Parser (Map Text Text)
-> Parser (Maybe License -> VersionManifest)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe (Map Text Text))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"scripts" Parser (Maybe (Map Text Text))
-> Map Text Text -> Parser (Map Text Text)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Text
forall a. Monoid a => a
mempty
            Parser (Maybe License -> VersionManifest)
-> Parser (Maybe License) -> Parser VersionManifest
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe License)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"license"

{- Decode the @deprecated@ field leniently (see the module header's "string-or-boolean
@deprecated@ flag"): a string is the deprecation message, the boolean @true@ a
deprecated version with no message ('Just' @""@), and @false@, @null@, absence, or any
other shape a non-deprecated one ('Nothing'). Total, so a boolean never fails the decode. -}
deprecatedNotice :: Maybe Value -> Maybe Text
deprecatedNotice :: Maybe Value -> Maybe Text
deprecatedNotice = \case
    Just (String Text
message) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
message
    Just (Bool Bool
True) -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
""
    Maybe Value
_ -> Maybe Text
forall a. Maybe a
Nothing

{- Decode the @versions@ map __element-wise leniently__: read it as a raw map of
version key to 'Value', then keep only the entries that parse as a 'VersionManifest',
dropping any that do not. A version whose manifest is missing or malformed in a
required field (no @dist@\/@tarball@, an unusable @name@\/@version@) is __dropped__
rather than failing the whole packument, so one poisoned version cannot deny the
others. An absent @versions@ is the empty map; a @versions@ that is not an object at
all still fails the decode (the document is not a usable packument). -}
lenientVersionMap :: Object -> Parser (Map Text VersionManifest)
lenientVersionMap :: Object -> Parser (Map Text VersionManifest)
lenientVersionMap Object
o = do
    raw <- Object
o Object -> Key -> Parser (Maybe (Map Text Value))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"versions" Parser (Maybe (Map Text Value))
-> Map Text Value -> Parser (Map Text Value)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Value
forall a. Monoid a => a
mempty -- Map Text Value: each version object kept raw
    pure (Map.mapMaybe (parseMaybe parseJSON) raw) -- drop the entries that will not decode

{- | The __full__ packument: @GET \/{pkg}@ with @Accept: application\/json@ (or
no @Accept@). One document describing the package and __every__ published
version.

The field that earns the full form its place in the pipeline is 'pkmtTime': the
map of publish timestamps (@created@, @modified@, and one per version), the
source of truth for publish age that age-based rules need. The abbreviated form
(§5) drops it, keeping only a top-level @modified@. Package-level
@description@\/@license@\/@author@ are hoisted from the @latest@ version for
convenience; the authoritative copy is the per-version one in 'pkmtVersions'.

@_attachments@ is intentionally not modelled -- it is populated only on the
publish document, not on reads.
-}
data Packument = Packument
    { Packument -> Text
pkmtName :: Text
    -- ^ The package name (may be @"\@scope\/name"@).
    , Packument -> Map Text Text
pkmtDistTags :: Map Text Text
    -- ^ The @dist-tags@ map (tag to version); __always__ includes @"latest"@.
    , Packument -> Map Text VersionManifest
pkmtVersions :: Map Text VersionManifest
    {- ^ Every published version that decodes, keyed by its exact version string. A
    version whose manifest is malformed in a required field is __dropped__ (see
    'lenientVersionMap'), so one poisoned version never denies the rest.
    -}
    , Packument -> Map Text UTCTime
pkmtTime :: Map Text UTCTime
    {- ^ Publish timestamps: @"created"@, @"modified"@, and one entry per
    version key. The source of truth for publish age.
    -}
    , Packument -> [Person]
pkmtMaintainers :: [Person]
    -- ^ Current package maintainers; empty when absent.
    , Packument -> Maybe Text
pkmtDescription :: Maybe Text
    -- ^ Package description (hoisted from @latest@).
    , Packument -> Maybe Text
pkmtHomepage :: Maybe Text
    -- ^ Homepage URL, if given.
    , Packument -> Maybe Repository
pkmtRepository :: Maybe Repository
    -- ^ SCM location, if given (string or object; see 'Repository').
    , Packument -> Maybe Bugs
pkmtBugs :: Maybe Bugs
    -- ^ Issue tracker, if given (string or object; see 'Bugs').
    , Packument -> Maybe License
pkmtLicense :: Maybe License
    -- ^ Package-level license, if given (string or object; see 'License').
    , Packument -> [Text]
pkmtKeywords :: [Text]
    -- ^ Search keywords; empty when absent.
    }
    deriving stock (Packument -> Packument -> Bool
(Packument -> Packument -> Bool)
-> (Packument -> Packument -> Bool) -> Eq Packument
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Packument -> Packument -> Bool
== :: Packument -> Packument -> Bool
$c/= :: Packument -> Packument -> Bool
/= :: Packument -> Packument -> Bool
Eq, Int -> Packument -> ShowS
[Packument] -> ShowS
Packument -> String
(Int -> Packument -> ShowS)
-> (Packument -> String)
-> ([Packument] -> ShowS)
-> Show Packument
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Packument -> ShowS
showsPrec :: Int -> Packument -> ShowS
$cshow :: Packument -> String
show :: Packument -> String
$cshowList :: [Packument] -> ShowS
showList :: [Packument] -> ShowS
Show)

instance FromJSON Packument where
    parseJSON :: Value -> Parser Packument
parseJSON = String -> (Object -> Parser Packument) -> Value -> Parser Packument
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Packument" ((Object -> Parser Packument) -> Value -> Parser Packument)
-> (Object -> Parser Packument) -> Value -> Parser Packument
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text
-> Map Text Text
-> Map Text VersionManifest
-> Map Text UTCTime
-> [Person]
-> Maybe Text
-> Maybe Text
-> Maybe Repository
-> Maybe Bugs
-> Maybe License
-> [Text]
-> Packument
Packument
            (Text
 -> Map Text Text
 -> Map Text VersionManifest
 -> Map Text UTCTime
 -> [Person]
 -> Maybe Text
 -> Maybe Text
 -> Maybe Repository
 -> Maybe Bugs
 -> Maybe License
 -> [Text]
 -> Packument)
-> Parser Text
-> Parser
     (Map Text Text
      -> Map Text VersionManifest
      -> Map Text UTCTime
      -> [Person]
      -> Maybe Text
      -> Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
            Parser
  (Map Text Text
   -> Map Text VersionManifest
   -> Map Text UTCTime
   -> [Person]
   -> Maybe Text
   -> Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser (Map Text Text)
-> Parser
     (Map Text VersionManifest
      -> Map Text UTCTime
      -> [Person]
      -> Maybe Text
      -> Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe (Map Text Text))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"dist-tags" Parser (Maybe (Map Text Text))
-> Map Text Text -> Parser (Map Text Text)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Text
forall a. Monoid a => a
mempty
            Parser
  (Map Text VersionManifest
   -> Map Text UTCTime
   -> [Person]
   -> Maybe Text
   -> Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser (Map Text VersionManifest)
-> Parser
     (Map Text UTCTime
      -> [Person]
      -> Maybe Text
      -> Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Parser (Map Text VersionManifest)
lenientVersionMap Object
o
            Parser
  (Map Text UTCTime
   -> [Person]
   -> Maybe Text
   -> Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser (Map Text UTCTime)
-> Parser
     ([Person]
      -> Maybe Text
      -> Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe (Map Text UTCTime))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"time" Parser (Maybe (Map Text UTCTime))
-> Map Text UTCTime -> Parser (Map Text UTCTime)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text UTCTime
forall a. Monoid a => a
mempty
            Parser
  ([Person]
   -> Maybe Text
   -> Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser [Person]
-> Parser
     (Maybe Text
      -> Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe [Person])
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"maintainers" Parser (Maybe [Person]) -> [Person] -> Parser [Person]
forall a. Parser (Maybe a) -> a -> Parser a
.!= []
            Parser
  (Maybe Text
   -> Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser (Maybe Text)
-> Parser
     (Maybe Text
      -> Maybe Repository
      -> Maybe Bugs
      -> Maybe License
      -> [Text]
      -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"description"
            Parser
  (Maybe Text
   -> Maybe Repository
   -> Maybe Bugs
   -> Maybe License
   -> [Text]
   -> Packument)
-> Parser (Maybe Text)
-> Parser
     (Maybe Repository
      -> Maybe Bugs -> Maybe License -> [Text] -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"homepage"
            Parser
  (Maybe Repository
   -> Maybe Bugs -> Maybe License -> [Text] -> Packument)
-> Parser (Maybe Repository)
-> Parser (Maybe Bugs -> Maybe License -> [Text] -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Repository)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"repository"
            Parser (Maybe Bugs -> Maybe License -> [Text] -> Packument)
-> Parser (Maybe Bugs)
-> Parser (Maybe License -> [Text] -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Bugs)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"bugs"
            Parser (Maybe License -> [Text] -> Packument)
-> Parser (Maybe License) -> Parser ([Text] -> Packument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe License)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"license"
            Parser ([Text] -> Packument) -> Parser [Text] -> Parser Packument
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe [Text])
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"keywords" Parser (Maybe [Text]) -> [Text] -> Parser [Text]
forall a. Parser (Maybe a) -> a -> Parser a
.!= []

{- | The __abbreviated__ packument: @GET \/{pkg}@ with
@Accept: application\/vnd.npm.install-v1+json@. The install-optimised view and
the one the proxy treats as primary.

It carries exactly four top-level fields. Notably the full @time@ map is dropped
(only a top-level 'apkmtModified' remains), so publish-age rules need the full
'Packument'. Its 'apkmtVersions' manifests are the trimmed subset of
'VersionManifest' -- the same type, with the install-only fields populated
(including the abbreviated-only 'vmHasInstallScript').
-}
data AbbreviatedPackument = AbbreviatedPackument
    { AbbreviatedPackument -> Text
apkmtName :: Text
    -- ^ The package name.
    , AbbreviatedPackument -> UTCTime
apkmtModified :: UTCTime
    {- ^ Equivalent to the full form's @time.modified@; the only timestamp the
    abbreviated form carries.
    -}
    , AbbreviatedPackument -> Map Text Text
apkmtDistTags :: Map Text Text
    -- ^ The @dist-tags@ map (tag to version), as in the full form.
    , AbbreviatedPackument -> Map Text VersionManifest
apkmtVersions :: Map Text VersionManifest
    {- ^ Every published version that decodes (abbreviated subset of fields), keyed
    by exact version string. As in the full form, a version whose manifest is
    malformed in a required field is __dropped__ (see 'lenientVersionMap').
    -}
    }
    deriving stock (AbbreviatedPackument -> AbbreviatedPackument -> Bool
(AbbreviatedPackument -> AbbreviatedPackument -> Bool)
-> (AbbreviatedPackument -> AbbreviatedPackument -> Bool)
-> Eq AbbreviatedPackument
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AbbreviatedPackument -> AbbreviatedPackument -> Bool
== :: AbbreviatedPackument -> AbbreviatedPackument -> Bool
$c/= :: AbbreviatedPackument -> AbbreviatedPackument -> Bool
/= :: AbbreviatedPackument -> AbbreviatedPackument -> Bool
Eq, Int -> AbbreviatedPackument -> ShowS
[AbbreviatedPackument] -> ShowS
AbbreviatedPackument -> String
(Int -> AbbreviatedPackument -> ShowS)
-> (AbbreviatedPackument -> String)
-> ([AbbreviatedPackument] -> ShowS)
-> Show AbbreviatedPackument
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AbbreviatedPackument -> ShowS
showsPrec :: Int -> AbbreviatedPackument -> ShowS
$cshow :: AbbreviatedPackument -> String
show :: AbbreviatedPackument -> String
$cshowList :: [AbbreviatedPackument] -> ShowS
showList :: [AbbreviatedPackument] -> ShowS
Show)

instance FromJSON AbbreviatedPackument where
    parseJSON :: Value -> Parser AbbreviatedPackument
parseJSON = String
-> (Object -> Parser AbbreviatedPackument)
-> Value
-> Parser AbbreviatedPackument
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"AbbreviatedPackument" ((Object -> Parser AbbreviatedPackument)
 -> Value -> Parser AbbreviatedPackument)
-> (Object -> Parser AbbreviatedPackument)
-> Value
-> Parser AbbreviatedPackument
forall a b. (a -> b) -> a -> b
$ \Object
o ->
        Text
-> UTCTime
-> Map Text Text
-> Map Text VersionManifest
-> AbbreviatedPackument
AbbreviatedPackument
            (Text
 -> UTCTime
 -> Map Text Text
 -> Map Text VersionManifest
 -> AbbreviatedPackument)
-> Parser Text
-> Parser
     (UTCTime
      -> Map Text Text
      -> Map Text VersionManifest
      -> AbbreviatedPackument)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser Text
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"name"
            Parser
  (UTCTime
   -> Map Text Text
   -> Map Text VersionManifest
   -> AbbreviatedPackument)
-> Parser UTCTime
-> Parser
     (Map Text Text -> Map Text VersionManifest -> AbbreviatedPackument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser UTCTime
forall a. FromJSON a => Object -> Key -> Parser a
.: Key
"modified"
            Parser
  (Map Text Text -> Map Text VersionManifest -> AbbreviatedPackument)
-> Parser (Map Text Text)
-> Parser (Map Text VersionManifest -> AbbreviatedPackument)
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe (Map Text Text))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"dist-tags" Parser (Maybe (Map Text Text))
-> Map Text Text -> Parser (Map Text Text)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Text
forall a. Monoid a => a
mempty
            Parser (Map Text VersionManifest -> AbbreviatedPackument)
-> Parser (Map Text VersionManifest) -> Parser AbbreviatedPackument
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object -> Parser (Map Text VersionManifest)
lenientVersionMap Object
o

{- | An npm error body.

__Lenient:__ the documented shape is an object @{ message?, error?, ok?: false
}@ and clients "should check for @message@, then @error@". But the registry is
inconsistent -- its per-version 404 is a bare JSON __string__
(@"version not found: ^3.0.0"@), not an object. This type tolerates both: the
object form keeps its fields in an 'ErrorBody', and a bare string is captured
whole as 'ErrorString'. Read the human-facing reason via 'errorMessage', which
applies npm's "@message@, then @error@" precedence across both shapes.
-}
data ErrorResponse
    = -- | The documented object form @{ message?, error? }@.
      ErrorObject ErrorBody
    | -- | A bare JSON string body (npm's per-version 404), captured whole.
      ErrorString Text
    deriving stock (ErrorResponse -> ErrorResponse -> Bool
(ErrorResponse -> ErrorResponse -> Bool)
-> (ErrorResponse -> ErrorResponse -> Bool) -> Eq ErrorResponse
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ErrorResponse -> ErrorResponse -> Bool
== :: ErrorResponse -> ErrorResponse -> Bool
$c/= :: ErrorResponse -> ErrorResponse -> Bool
/= :: ErrorResponse -> ErrorResponse -> Bool
Eq, Int -> ErrorResponse -> ShowS
[ErrorResponse] -> ShowS
ErrorResponse -> String
(Int -> ErrorResponse -> ShowS)
-> (ErrorResponse -> String)
-> ([ErrorResponse] -> ShowS)
-> Show ErrorResponse
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ErrorResponse -> ShowS
showsPrec :: Int -> ErrorResponse -> ShowS
$cshow :: ErrorResponse -> String
show :: ErrorResponse -> String
$cshowList :: [ErrorResponse] -> ShowS
showList :: [ErrorResponse] -> ShowS
Show)

{- | The fields of npm's object-form error body. A product type (not inline
constructor fields on 'ErrorResponse') so its selectors are __total__ -- there is
no @ErrorString@ case for them to be partial over.
-}
data ErrorBody = ErrorBody
    { ErrorBody -> Maybe Text
errMessage :: Maybe Text
    -- ^ The @message@ field -- the preferred human-facing reason.
    , ErrorBody -> Maybe Text
errError :: Maybe Text
    -- ^ The @error@ field -- the fallback reason.
    }
    deriving stock (ErrorBody -> ErrorBody -> Bool
(ErrorBody -> ErrorBody -> Bool)
-> (ErrorBody -> ErrorBody -> Bool) -> Eq ErrorBody
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ErrorBody -> ErrorBody -> Bool
== :: ErrorBody -> ErrorBody -> Bool
$c/= :: ErrorBody -> ErrorBody -> Bool
/= :: ErrorBody -> ErrorBody -> Bool
Eq, Int -> ErrorBody -> ShowS
[ErrorBody] -> ShowS
ErrorBody -> String
(Int -> ErrorBody -> ShowS)
-> (ErrorBody -> String)
-> ([ErrorBody] -> ShowS)
-> Show ErrorBody
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ErrorBody -> ShowS
showsPrec :: Int -> ErrorBody -> ShowS
$cshow :: ErrorBody -> String
show :: ErrorBody -> String
$cshowList :: [ErrorBody] -> ShowS
showList :: [ErrorBody] -> ShowS
Show)

instance FromJSON ErrorResponse where
    parseJSON :: Value -> Parser ErrorResponse
parseJSON = \case
        String Text
msg -> ErrorResponse -> Parser ErrorResponse
forall a. a -> Parser a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> ErrorResponse
ErrorString Text
msg)
        Object Object
o ->
            (ErrorBody -> ErrorResponse)
-> Parser ErrorBody -> Parser ErrorResponse
forall a b. (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ErrorBody -> ErrorResponse
ErrorObject (Parser ErrorBody -> Parser ErrorResponse)
-> Parser ErrorBody -> Parser ErrorResponse
forall a b. (a -> b) -> a -> b
$
                Maybe Text -> Maybe Text -> ErrorBody
ErrorBody
                    (Maybe Text -> Maybe Text -> ErrorBody)
-> Parser (Maybe Text) -> Parser (Maybe Text -> ErrorBody)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"message"
                    Parser (Maybe Text -> ErrorBody)
-> Parser (Maybe Text) -> Parser ErrorBody
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"error"
        Value
other -> String -> Value -> Parser ErrorResponse
forall a. String -> Value -> Parser a
typeMismatchOneOf String
"ErrorResponse (object or string)" Value
other

{- | The human-facing reason carried by an error body, applying npm's documented
precedence: prefer @message@, then @error@, and for the bare-string form the
string itself. 'Nothing' only when an object form carried neither field. Total.
-}
errorMessage :: ErrorResponse -> Maybe Text
errorMessage :: ErrorResponse -> Maybe Text
errorMessage = \case
    ErrorString Text
msg -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
msg
    ErrorObject ErrorBody
body -> ErrorBody -> Maybe Text
errMessage ErrorBody
body Maybe Text -> Maybe Text -> Maybe Text
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> ErrorBody -> Maybe Text
errError ErrorBody
body

{- Fail a lenient string-or-object decoder with a descriptive message,
naming the accepted shapes and reporting what was actually found. A small wrapper
that keeps the @other ->@ branch of each tolerant instance to one readable line.
-}
typeMismatchOneOf :: String -> Value -> Parser a
typeMismatchOneOf :: forall a. String -> Value -> Parser a
typeMismatchOneOf String
expected Value
actual =
    String -> Parser a
forall a. String -> Parser a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String
"expected " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
expected String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
", but encountered " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Value -> String
valueKind Value
actual)

-- A short, human description of a JSON value's kind, for parse-error messages.
valueKind :: Value -> String
valueKind :: Value -> String
valueKind = \case
    Object{} -> String
"an object"
    String{} -> String
"a string"
    Array{} -> String
"an array"
    Number{} -> String
"a number"
    Bool{} -> String
"a boolean"
    Value
Null -> String
"null"