{- | A __selective__ decode of an npm packument: pull __one version's__ pieces out of
the document bytes without materialising the other versions.

The whole-packument decode (@aeson@'s @eitherDecodeStrict@) builds a 'Value' for /every/
version -- and on a heavy packument (thousands of versions, multiple megabytes) that
decode dominates the serve-path cost. But the tarball gate consults a __single__
version: it needs that version's manifest object, its @time[version]@ publish stamp, and
the document's self-reported @name@ -- nothing of the other versions. This module walks
the registry's own JSON token stream (@aeson@'s @Data.Aeson.Decoding@, no new
dependency) and materialises a 'Value' only for those few pieces, __skipping every other
version's tokens without allocating them__. The win is on the /parse/, not the fetch:
the full bytes are still read (npm carries @time@ only in the full document), but they
are parsed selectively -- O(1 version) work and residency rather than O(N).

== Faithful to the whole-document decode

The skip is not a shortcut past validation. The walk consumes the __entire__ token
stream, so:

  * malformed JSON __anywhere__ surfaces as 'SelectiveUndecodable' -- the lexer reaches
    the offending bytes whether or not they sit in the requested version (matching
    @eitherDecodeStrict@ failing the whole body);
  * trailing non-whitespace after the top-level object is rejected likewise (the same
    end-of-input check @eitherDecodeStrict@ applies);
  * every value is depth-bounded at the same budget
    'Ecluse.Core.Security.checkNestingDepth' would apply to it, so a deeply-nested
    sub-tree __anywhere__ is a 'SelectiveTooDeeplyNested' breach, not a serve.

The two pieces it /does/ build -- the requested version object and the document @name@ --
are produced by the same @aeson@ 'Value' decoder the whole-document path uses, so
projecting them yields a byte-for-byte identical 'Ecluse.Core.Package.PackageDetails'
(the projection is "Ecluse.Core.Registry.Npm.Project.projectVersionEntry", run over the
same 'Value').

== What it deliberately does not re-validate

The selective walk reaches only the requested version's @time@ entry: a structurally
malformed-JSON one anywhere is still 'SelectiveUndecodable' (the lexer reaches it), but a
__schema-invalid__ sibling (a non-ISO @time@ string for /another/ version, a non-string
@dist-tags@ value) is __skipped unallocated__ and never inspected. The whole-document
decode degrades the same way: it drops a malformed @time@\/@dist-tags@ entry per-entry
(graceful per-entry degradation) rather than failing the document, so neither path
refuses a sound version over an unrelated sibling malformation. The two paths agree on
__what is served__ (the one sound version, identically projected) and differ only in
__tracking__: the whole-document projection records each dropped sibling as an
'Ecluse.Core.Package.InvalidEntry' for the serve-path log, while this walk, skipping the
siblings unallocated, cannot report them (the degenerate tracking a single-version read
inherently has). The requested version's /own/ schema-invalid stamp folds, on both paths,
to a version with no known publish time (the projecting caller's lenient parse), never a
document failure.
-}
module Ecluse.Core.Registry.Npm.SelectiveDecode (
    -- * The selective decode
    SelectedVersion (..),
    SelectiveError (..),
    selectVersionFromPackument,
) where

import Data.Aeson (Value)
import Data.Aeson.Decoding (toEitherValue)
import Data.Aeson.Decoding.ByteString (bsToTokens)
import Data.Aeson.Decoding.Tokens (TkArray (..), TkRecord (..), Tokens (..))
import Data.Aeson.Key qualified as Key
import Data.ByteString qualified as BS

import Ecluse.Core.Security (withinNestingBudget)
import Ecluse.Core.Version (Version, renderVersion)

{- | The pieces a selective decode pulls out of a packument for one requested version:
the document's self-reported @name@, the requested version's manifest object and publish
stamp (each as the raw 'Value' the same projection the whole-document path uses then
consumes), and the __raw__ number of entries in the @versions@ object.

Each value field is 'Nothing' when its key is absent from the document, so the caller
reproduces the whole-document outcome: an absent @name@ is the empty-name decode failure,
an absent version object is a genuine miss, an absent @time@ entry is a version with no
known publish stamp. The 'svVersionCount' is the count the caller bounds against
'Ecluse.Core.Security.maxVersionCount'.
-}
data SelectedVersion = SelectedVersion
    { SelectedVersion -> Maybe Value
svName :: Maybe Value
    -- ^ The top-level @name@ value, if the key was present (else 'Nothing').
    , SelectedVersion -> Maybe Value
svVersion :: Maybe Value
    -- ^ The requested version's object from @versions@, if that key was present.
    , SelectedVersion -> Maybe Value
svTime :: Maybe Value
    -- ^ The requested version's @time[version]@ value, if that key was present.
    , SelectedVersion -> Int
svVersionCount :: Int
    -- ^ The number of entries in the @versions@ object (@0@ when @versions@ is absent).
    }
    deriving stock (SelectedVersion -> SelectedVersion -> Bool
(SelectedVersion -> SelectedVersion -> Bool)
-> (SelectedVersion -> SelectedVersion -> Bool)
-> Eq SelectedVersion
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SelectedVersion -> SelectedVersion -> Bool
== :: SelectedVersion -> SelectedVersion -> Bool
$c/= :: SelectedVersion -> SelectedVersion -> Bool
/= :: SelectedVersion -> SelectedVersion -> Bool
Eq, Int -> SelectedVersion -> ShowS
[SelectedVersion] -> ShowS
SelectedVersion -> String
(Int -> SelectedVersion -> ShowS)
-> (SelectedVersion -> String)
-> ([SelectedVersion] -> ShowS)
-> Show SelectedVersion
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SelectedVersion -> ShowS
showsPrec :: Int -> SelectedVersion -> ShowS
$cshow :: SelectedVersion -> String
show :: SelectedVersion -> String
$cshowList :: [SelectedVersion] -> ShowS
showList :: [SelectedVersion] -> ShowS
Show)

{- | Why a selective decode could not yield a 'SelectedVersion' -- the two refusal causes
the whole-document decode would also raise, so the caller maps them onto the same
'Ecluse.Core.Registry.Metadata.MetadataError' the full path does.
-}
data SelectiveError
    = -- | The body was not a well-formed JSON object (or carried trailing non-whitespace).
      SelectiveUndecodable
    | -- | Some value nested deeper than the depth budget allowed.
      SelectiveTooDeeplyNested
    deriving stock (SelectiveError -> SelectiveError -> Bool
(SelectiveError -> SelectiveError -> Bool)
-> (SelectiveError -> SelectiveError -> Bool) -> Eq SelectiveError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SelectiveError -> SelectiveError -> Bool
== :: SelectiveError -> SelectiveError -> Bool
$c/= :: SelectiveError -> SelectiveError -> Bool
/= :: SelectiveError -> SelectiveError -> Bool
Eq, Int -> SelectiveError -> ShowS
[SelectiveError] -> ShowS
SelectiveError -> String
(Int -> SelectiveError -> ShowS)
-> (SelectiveError -> String)
-> ([SelectiveError] -> ShowS)
-> Show SelectiveError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SelectiveError -> ShowS
showsPrec :: Int -> SelectiveError -> ShowS
$cshow :: SelectiveError -> String
show :: SelectiveError -> String
$cshowList :: [SelectiveError] -> ShowS
showList :: [SelectiveError] -> ShowS
Show)

{- | Selectively decode a packument's bytes for one version: walk the token stream,
extracting the document @name@, the requested version's object and @time@ entry, and the
@versions@ count, while skipping every other version's tokens unallocated and bounding
every value at @maxDepth@ levels (the 'Ecluse.Core.Security.maxNestingDepth' budget, so
the depth bound matches 'Ecluse.Core.Security.checkNestingDepth' over the whole
document).

The body must be a well-formed JSON object with nothing but whitespace after it, or the
result is 'SelectiveUndecodable' -- exactly as @eitherDecodeStrict@ would fail it.
-}
selectVersionFromPackument :: Int -> Version -> ByteString -> Either SelectiveError SelectedVersion
selectVersionFromPackument :: Int
-> Version -> ByteString -> Either SelectiveError SelectedVersion
selectVersionFromPackument Int
maxDepth Version
version ByteString
body
    -- The top-level value is itself a container occupying one level, so a zero (or
    -- negative) budget refuses it before the walk -- mirroring @within cap@ requiring
    -- @cap >= 1@ for the document object.
    | Int
maxDepth Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
1 = SelectiveError -> Either SelectiveError SelectedVersion
forall a b. a -> Either a b
Left SelectiveError
SelectiveTooDeeplyNested
    | Bool
otherwise = case ByteString -> Tokens ByteString String
bsToTokens ByteString
body of
        TkRecordOpen TkRecord ByteString String
rec -> Int
-> Text
-> SelectedVersion
-> TkRecord ByteString String
-> Either SelectiveError SelectedVersion
walkTop (Int
maxDepth Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (Version -> Text
renderVersion Version
version) SelectedVersion
emptySelection TkRecord ByteString String
rec
        -- A well-formed non-object body decodes but never projects to a packument, and a
        -- malformed body never decodes; the whole-document path renders both as the same
        -- "unobtainable metadata", so neither is distinguished here.
        Tokens ByteString String
_ -> SelectiveError -> Either SelectiveError SelectedVersion
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable

-- The starting accumulator: nothing found, no versions counted.
emptySelection :: SelectedVersion
emptySelection :: SelectedVersion
emptySelection = Maybe Value -> Maybe Value -> Maybe Value -> Int -> SelectedVersion
SelectedVersion Maybe Value
forall a. Maybe a
Nothing Maybe Value
forall a. Maybe a
Nothing Maybe Value
forall a. Maybe a
Nothing Int
0

{- Walk the top-level packument record to its end, threading the accumulated selection.
@childBudget@ is the depth budget each top-level value sits at (one below the document
object's own budget). The requested version object and the @name@ value are materialised;
the @versions@ count is tallied; every other value is skipped unallocated. The trailing
bytes after the record must be whitespace only. -}
walkTop :: Int -> Text -> SelectedVersion -> TkRecord ByteString String -> Either SelectiveError SelectedVersion
walkTop :: Int
-> Text
-> SelectedVersion
-> TkRecord ByteString String
-> Either SelectiveError SelectedVersion
walkTop Int
childBudget Text
target = SelectedVersion
-> TkRecord ByteString String
-> Either SelectiveError SelectedVersion
go
  where
    go :: SelectedVersion
-> TkRecord ByteString String
-> Either SelectiveError SelectedVersion
go SelectedVersion
acc = \case
        TkRecordEnd ByteString
leftover
            | ByteString -> Bool
trailingWhitespace ByteString
leftover -> SelectedVersion -> Either SelectiveError SelectedVersion
forall a b. b -> Either a b
Right SelectedVersion
acc
            | Bool
otherwise -> SelectiveError -> Either SelectiveError SelectedVersion
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable
        TkRecordErr String
_ -> SelectiveError -> Either SelectiveError SelectedVersion
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable
        TkPair Key
key Tokens (TkRecord ByteString String) String
valueToks -> case Key -> Text
Key.toText Key
key of
            Text
"versions" -> Int
-> Tokens (TkRecord ByteString String) String
-> (TkRecord (TkRecord ByteString String) String
    -> Either SelectiveError SelectedVersion)
-> Either SelectiveError SelectedVersion
forall k a.
Int
-> Tokens k String
-> (TkRecord k String -> Either SelectiveError a)
-> Either SelectiveError a
withRecord Int
childBudget Tokens (TkRecord ByteString String) String
valueToks ((TkRecord (TkRecord ByteString String) String
  -> Either SelectiveError SelectedVersion)
 -> Either SelectiveError SelectedVersion)
-> (TkRecord (TkRecord ByteString String) String
    -> Either SelectiveError SelectedVersion)
-> Either SelectiveError SelectedVersion
forall a b. (a -> b) -> a -> b
$ \TkRecord (TkRecord ByteString String) String
versionsRec -> do
                (found, count, cont) <- Int
-> Text
-> TkRecord (TkRecord ByteString String) String
-> Either
     SelectiveError (Maybe Value, Int, TkRecord ByteString String)
forall k.
Int
-> Text
-> TkRecord k String
-> Either SelectiveError (Maybe Value, Int, k)
findInRecord (Int
childBudget Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Text
target TkRecord (TkRecord ByteString String) String
versionsRec
                go acc{svVersion = found, svVersionCount = svVersionCount acc + count} cont
            Text
"time" -> Int
-> Tokens (TkRecord ByteString String) String
-> (TkRecord (TkRecord ByteString String) String
    -> Either SelectiveError SelectedVersion)
-> Either SelectiveError SelectedVersion
forall k a.
Int
-> Tokens k String
-> (TkRecord k String -> Either SelectiveError a)
-> Either SelectiveError a
withRecord Int
childBudget Tokens (TkRecord ByteString String) String
valueToks ((TkRecord (TkRecord ByteString String) String
  -> Either SelectiveError SelectedVersion)
 -> Either SelectiveError SelectedVersion)
-> (TkRecord (TkRecord ByteString String) String
    -> Either SelectiveError SelectedVersion)
-> Either SelectiveError SelectedVersion
forall a b. (a -> b) -> a -> b
$ \TkRecord (TkRecord ByteString String) String
timeRec -> do
                (found, _count, cont) <- Int
-> Text
-> TkRecord (TkRecord ByteString String) String
-> Either
     SelectiveError (Maybe Value, Int, TkRecord ByteString String)
forall k.
Int
-> Text
-> TkRecord k String
-> Either SelectiveError (Maybe Value, Int, k)
findInRecord (Int
childBudget Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Text
target TkRecord (TkRecord ByteString String) String
timeRec
                go acc{svTime = found} cont
            Text
"name" -> do
                (nameValue, cont) <- Int
-> Tokens (TkRecord ByteString String) String
-> Either SelectiveError (Value, TkRecord ByteString String)
forall k.
Int -> Tokens k String -> Either SelectiveError (Value, k)
materialiseWithinBudget Int
childBudget Tokens (TkRecord ByteString String) String
valueToks
                go acc{svName = Just nameValue} cont
            Text
_ -> Int
-> Tokens (TkRecord ByteString String) String
-> Either SelectiveError (TkRecord ByteString String)
forall k. Int -> Tokens k String -> Either SelectiveError k
skipValue Int
childBudget Tokens (TkRecord ByteString String) String
valueToks Either SelectiveError (TkRecord ByteString String)
-> (TkRecord ByteString String
    -> Either SelectiveError SelectedVersion)
-> Either SelectiveError SelectedVersion
forall a b.
Either SelectiveError a
-> (a -> Either SelectiveError b) -> Either SelectiveError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= SelectedVersion
-> TkRecord ByteString String
-> Either SelectiveError SelectedVersion
go SelectedVersion
acc

{- Find one key in a record, materialising __only__ that key's value (a 'Value', the last
occurrence winning as @aeson@'s object decode does) and skipping every other entry's
tokens unallocated. Returns the found value (if any), the number of entries scanned, and
the record's continuation. @childBudget@ is the depth budget the record's values sit at;
each is depth-bounded there so a deeply-nested sibling still breaches. The scan runs to
the record's end (it never stops early), so a later duplicate key, a malformed entry, or
an over-deep sibling is still seen. -}
findInRecord :: Int -> Text -> TkRecord k String -> Either SelectiveError (Maybe Value, Int, k)
findInRecord :: forall k.
Int
-> Text
-> TkRecord k String
-> Either SelectiveError (Maybe Value, Int, k)
findInRecord Int
childBudget Text
target = Maybe Value
-> Int
-> TkRecord k String
-> Either SelectiveError (Maybe Value, Int, k)
forall {t} {c}.
Num t =>
Maybe Value
-> t
-> TkRecord c String
-> Either SelectiveError (Maybe Value, t, c)
go Maybe Value
forall a. Maybe a
Nothing Int
0
  where
    go :: Maybe Value
-> t
-> TkRecord c String
-> Either SelectiveError (Maybe Value, t, c)
go Maybe Value
found !t
count = \case
        TkRecordEnd c
cont -> (Maybe Value, t, c) -> Either SelectiveError (Maybe Value, t, c)
forall a b. b -> Either a b
Right (Maybe Value
found, t
count, c
cont)
        TkRecordErr String
_ -> SelectiveError -> Either SelectiveError (Maybe Value, t, c)
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable
        TkPair Key
key Tokens (TkRecord c String) String
valueToks
            | Key -> Text
Key.toText Key
key Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
target -> do
                (value, cont) <- Int
-> Tokens (TkRecord c String) String
-> Either SelectiveError (Value, TkRecord c String)
forall k.
Int -> Tokens k String -> Either SelectiveError (Value, k)
materialiseWithinBudget Int
childBudget Tokens (TkRecord c String) String
valueToks
                go (Just value) (count + 1) cont
            | Bool
otherwise -> Int
-> Tokens (TkRecord c String) String
-> Either SelectiveError (TkRecord c String)
forall k. Int -> Tokens k String -> Either SelectiveError k
skipValue Int
childBudget Tokens (TkRecord c String) String
valueToks Either SelectiveError (TkRecord c String)
-> (TkRecord c String -> Either SelectiveError (Maybe Value, t, c))
-> Either SelectiveError (Maybe Value, t, c)
forall a b.
Either SelectiveError a
-> (a -> Either SelectiveError b) -> Either SelectiveError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Maybe Value
-> t
-> TkRecord c String
-> Either SelectiveError (Maybe Value, t, c)
go Maybe Value
found (t
count t -> t -> t
forall a. Num a => a -> a -> a
+ t
1)

{- Materialise one value from its tokens -- the same 'Value' decode the whole-document
path uses -- bounded at @budget@: malformed tokens are 'SelectiveUndecodable', a value
past the depth budget is 'SelectiveTooDeeplyNested'. The single materialisation point of
the walk, so every built 'Value' passes the same depth gate. -}
materialiseWithinBudget :: Int -> Tokens k String -> Either SelectiveError (Value, k)
materialiseWithinBudget :: forall k.
Int -> Tokens k String -> Either SelectiveError (Value, k)
materialiseWithinBudget Int
budget Tokens k String
toks = case Tokens k String -> Either String (Value, k)
forall k e. Tokens k e -> Either e (Value, k)
toEitherValue Tokens k String
toks of
    Left String
_ -> SelectiveError -> Either SelectiveError (Value, k)
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable
    Right (Value
value, k
cont)
        | Int -> Value -> Bool
withinNestingBudget Int
budget Value
value -> (Value, k) -> Either SelectiveError (Value, k)
forall a b. b -> Either a b
Right (Value
value, k
cont)
        | Bool
otherwise -> SelectiveError -> Either SelectiveError (Value, k)
forall a b. a -> Either a b
Left SelectiveError
SelectiveTooDeeplyNested

{- Run @k@ on a record token, refusing a non-record (the @versions@\/@time@ value must be
an object, exactly as the whole-document decode reads each as a @Map@) and refusing the
container outright when the depth budget is already spent (a record is itself one level).
-}
withRecord :: Int -> Tokens k String -> (TkRecord k String -> Either SelectiveError a) -> Either SelectiveError a
withRecord :: forall k a.
Int
-> Tokens k String
-> (TkRecord k String -> Either SelectiveError a)
-> Either SelectiveError a
withRecord Int
budget Tokens k String
toks TkRecord k String -> Either SelectiveError a
k
    | Int
budget Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
1 = SelectiveError -> Either SelectiveError a
forall a b. a -> Either a b
Left SelectiveError
SelectiveTooDeeplyNested
    | Bool
otherwise = case Tokens k String
toks of
        TkRecordOpen TkRecord k String
rec -> TkRecord k String -> Either SelectiveError a
k TkRecord k String
rec
        TkErr String
_ -> SelectiveError -> Either SelectiveError a
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable
        Tokens k String
_ -> SelectiveError -> Either SelectiveError a
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable

{- Consume one value's tokens without allocating a 'Value', returning the continuation.
Bounds nesting at @budget@ levels exactly as 'Ecluse.Core.Security.withinNestingBudget'
does over a built 'Value' -- a value occupies one level (refused at @budget < 1@) and a
container's children are bounded one level deeper -- so skipping reproduces the depth check
the whole-document path runs. Malformed tokens are 'SelectiveUndecodable'. -}
skipValue :: Int -> Tokens k String -> Either SelectiveError k
skipValue :: forall k. Int -> Tokens k String -> Either SelectiveError k
skipValue Int
budget Tokens k String
toks
    | Int
budget Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
1 = SelectiveError -> Either SelectiveError k
forall a b. a -> Either a b
Left SelectiveError
SelectiveTooDeeplyNested
    | Bool
otherwise = case Tokens k String
toks of
        TkLit Lit
_ k
cont -> k -> Either SelectiveError k
forall a b. b -> Either a b
Right k
cont
        TkText Text
_ k
cont -> k -> Either SelectiveError k
forall a b. b -> Either a b
Right k
cont
        TkNumber Number
_ k
cont -> k -> Either SelectiveError k
forall a b. b -> Either a b
Right k
cont
        TkArrayOpen TkArray k String
arr -> Int -> TkArray k String -> Either SelectiveError k
forall k. Int -> TkArray k String -> Either SelectiveError k
skipArray (Int
budget Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) TkArray k String
arr
        TkRecordOpen TkRecord k String
rec -> Int -> TkRecord k String -> Either SelectiveError k
forall k. Int -> TkRecord k String -> Either SelectiveError k
skipRecord (Int
budget Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) TkRecord k String
rec
        TkErr String
_ -> SelectiveError -> Either SelectiveError k
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable

-- Skip an array's items (each at @budget@), returning the continuation after its end.
skipArray :: Int -> TkArray k String -> Either SelectiveError k
skipArray :: forall k. Int -> TkArray k String -> Either SelectiveError k
skipArray Int
budget = \case
    TkItem Tokens (TkArray k String) String
toks -> Int
-> Tokens (TkArray k String) String
-> Either SelectiveError (TkArray k String)
forall k. Int -> Tokens k String -> Either SelectiveError k
skipValue Int
budget Tokens (TkArray k String) String
toks Either SelectiveError (TkArray k String)
-> (TkArray k String -> Either SelectiveError k)
-> Either SelectiveError k
forall a b.
Either SelectiveError a
-> (a -> Either SelectiveError b) -> Either SelectiveError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> TkArray k String -> Either SelectiveError k
forall k. Int -> TkArray k String -> Either SelectiveError k
skipArray Int
budget
    TkArrayEnd k
cont -> k -> Either SelectiveError k
forall a b. b -> Either a b
Right k
cont
    TkArrayErr String
_ -> SelectiveError -> Either SelectiveError k
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable

-- Skip a record's values (each at @budget@), returning the continuation after its end.
skipRecord :: Int -> TkRecord k String -> Either SelectiveError k
skipRecord :: forall k. Int -> TkRecord k String -> Either SelectiveError k
skipRecord Int
budget = \case
    TkPair Key
_ Tokens (TkRecord k String) String
toks -> Int
-> Tokens (TkRecord k String) String
-> Either SelectiveError (TkRecord k String)
forall k. Int -> Tokens k String -> Either SelectiveError k
skipValue Int
budget Tokens (TkRecord k String) String
toks Either SelectiveError (TkRecord k String)
-> (TkRecord k String -> Either SelectiveError k)
-> Either SelectiveError k
forall a b.
Either SelectiveError a
-> (a -> Either SelectiveError b) -> Either SelectiveError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Int -> TkRecord k String -> Either SelectiveError k
forall k. Int -> TkRecord k String -> Either SelectiveError k
skipRecord Int
budget
    TkRecordEnd k
cont -> k -> Either SelectiveError k
forall a b. b -> Either a b
Right k
cont
    TkRecordErr String
_ -> SelectiveError -> Either SelectiveError k
forall a b. a -> Either a b
Left SelectiveError
SelectiveUndecodable

{- Whether the bytes after the top-level value are JSON whitespace only -- the
end-of-input check @eitherDecodeStrict@ applies, so a body with trailing non-whitespace is
refused identically (space, tab, newline, carriage return are the four JSON whitespace
bytes). -}
trailingWhitespace :: ByteString -> Bool
trailingWhitespace :: ByteString -> Bool
trailingWhitespace = (Word8 -> Bool) -> ByteString -> Bool
BS.all Word8 -> Bool
isJsonSpace
  where
    isJsonSpace :: Word8 -> Bool
    isJsonSpace :: Word8 -> Bool
isJsonSpace Word8
w = Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x20 Bool -> Bool -> Bool
|| Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x0a Bool -> Bool -> Bool
|| Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x0d Bool -> Bool -> Bool
|| Word8
w Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
0x09