packages feed

jsonpatch-0.2.0.0: src/Data/JSON/Patch/Apply.hs

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

import Data.JSON.Patch.Prelude

import Data.Aeson (Value (..))
import Data.JSON.Patch.Error
import Data.JSON.Patch.Type
import Data.JSON.Pointer
import Data.JSON.Pointer.Token
import Data.Vector qualified as V
import Optics.Core

applyPatches :: [Patch] -> Value -> Either PatchError Value
applyPatches ps v = foldM applyPatch v ps

applyPatch :: Value -> Patch -> Either PatchError Value
applyPatch val = \case
  Add op -> add op.value op.path val
  Remove op -> remove op.path val
  Replace op -> remove op.path val >>= add op.value op.path
  Move op -> get op.from val >>= \v -> remove op.from val >>= add v op.path
  Copy op -> get op.from val >>= \v -> add v op.path val
  Test op -> get op.path val >>= \v -> test v op.value op.path val

get :: Pointer -> Value -> Either PatchError Value
get p val = note (PointerNotFound p Nothing) $ val ^? pointerL p

add :: Value -> Pointer -> Value -> Either PatchError Value
add v p val = case splitPointer p of
  Nothing -> Right v
  Just (parent, t) -> do
    validateAdd parent t val
    Right $ val & atPointerL p ?~ v

remove :: Pointer -> Value -> Either PatchError Value
remove p val = do
  void $ get p val
  Right $ val & atPointerL p .~ Nothing

test :: Value -> Value -> Pointer -> Value -> Either PatchError Value
test v expected p val =
  note (TestFailed p v expected) $ val <$ guard (v == expected)

validateAdd :: Pointer -> Token -> Value -> Either PatchError ()
validateAdd parent t val = do
  target <- get parent val

  case (t, target) of
    (_, Object _) -> Right () -- everything works on objects
    (K _, v) -> Left $ InvalidObjectOperation parent v
    (N n, Array vec) -> do
      when (n < 0) $ Left $ IndexOutOfBounds parent n vec
      when (n > V.length vec) $ Left $ IndexOutOfBounds parent n vec
    (N _, v) -> Left $ InvalidArrayOperation parent v
    _ -> Right ()