-- |
--
-- Module      : Data.JSON.Patch.Apply.AsValue
-- Copyright   : (c) 2025 Patrick Brisbin
-- License     : AGPL-3
-- Maintainer  : pbrisbin@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Data.JSON.Patch.Apply.AsValue
  ( PatchError (..)
  , patchAsValue
  ) where

import Data.JSON.Patch.Prelude

import Data.Aeson (FromJSON, Result (..), ToJSON (..), Value (..), fromJSON)
import Data.Aeson.Optics (AsValue (..))
import Data.JSON.Patch.Apply
import Optics.Core

-- | A polymorphic version of 'patchValue'
--
-- The @patch@ input uses 'AsValue' from @aeson-optics@, meaning you can supply
-- a variety of types such as 'ByteString' or 'Value' and it will be parsed into
-- @['Patches']@ (capturing failure as a 'PatchError').
--
-- The @v@ input can be any domain type with JSON instances. We don't use
-- 'AsValue' here as well, even though it provides the same functionality,
-- because it's unlikely your types will have this instance.
--
-- @
-- data Person = Person
--   { name :: Text
--   , age :: Int
--   }
--   deriving stock Generic
--   deriving anyclass (FromJSON, ToJSON)
--
-- patchPersonR :: PersonId -> Handler Person
-- patchPersonR id = do
--   person <- runDB $ get id           -- Person "pat" 19
--   bytes <- getRequestBody            -- "[{op:replace, path:/age, value:21}]"
--
--   case patchAsValue bytes person of
--     Left err -> sendResponse 400 $ displayException err
--     Right updated -> do
--       runDB $ update id updated
--       sendResponse 200 updated       -- Person "pat" 21
-- @
--
-- If the patch creates a value that can't parse back to your domain type, that
-- will also be normalized to 'PatchError'.
patchAsValue
  :: (AsValue patch, FromJSON v, ToJSON v) => patch -> v -> Either PatchError v
patchAsValue :: forall patch v.
(AsValue patch, FromJSON v, ToJSON v) =>
patch -> v -> Either PatchError v
patchAsValue patch
p v
target = do
  Value
pVal <- PatchError -> Maybe Value -> Either PatchError Value
forall e a. e -> Maybe a -> Either e a
note (Value -> String -> PatchError
ParseError Value
Null String
"not JSON") (Maybe Value -> Either PatchError Value)
-> Maybe Value -> Either PatchError Value
forall a b. (a -> b) -> a -> b
$ patch
p patch -> Optic' A_Prism NoIx patch Value -> Maybe Value
forall k s (is :: IxList) a.
Is k An_AffineFold =>
s -> Optic' k is s a -> Maybe a
^? Optic' A_Prism NoIx patch Value
forall t. AsValue t => Prism' t Value
_Value
  [Patch]
patches <- Value -> Either PatchError [Patch]
forall a. FromJSON a => Value -> Either PatchError a
fromJSONEither Value
pVal
  Value
result <- [Patch] -> Value -> Either PatchError Value
patchValue [Patch]
patches (Value -> Either PatchError Value)
-> Value -> Either PatchError Value
forall a b. (a -> b) -> a -> b
$ v -> Value
forall a. ToJSON a => a -> Value
toJSON v
target
  Value -> Either PatchError v
forall a. FromJSON a => Value -> Either PatchError a
fromJSONEither Value
result

fromJSONEither :: FromJSON a => Value -> Either PatchError a
fromJSONEither :: forall a. FromJSON a => Value -> Either PatchError a
fromJSONEither Value
v = case Value -> Result a
forall a. FromJSON a => Value -> Result a
fromJSON Value
v of
  Error String
e -> PatchError -> Either PatchError a
forall a b. a -> Either a b
Left (PatchError -> Either PatchError a)
-> PatchError -> Either PatchError a
forall a b. (a -> b) -> a -> b
$ Value -> String -> PatchError
ParseError Value
v String
e
  Success a
a -> a -> Either PatchError a
forall a b. b -> Either a b
Right a
a