-- SPDX-FileCopyrightText: 2026 Alexandra de Wit
--
-- SPDX-License-Identifier: MIT

{- | 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@ (§6 manifest, §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), and the
'Dist' integrity triple (@tarball@\/@shasum@\/@integrity@).

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 (..),

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

import Data.Aeson (
    FromJSON (parseJSON),
    Object,
    Value (Array, Bool, Object, String),
    withObject,
    (.!=),
    (.:),
    (.:?),
 )
import Data.Aeson.Types (Parser, parseMaybe)

import Ecluse.Core.Json.Lenient (lenientOptional, typeMismatchOneOf)

{- | 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 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's @versions[v]@ object, embedded in an abbreviated packument's
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 @time@ 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

{- | 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'.
-}
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