git.iko.soy/typeable/hjsonpointer
on master help sign in
0.2

Fix mistake in resolveRefTok. Split errors over two types. Switch the order of pointer and value arguments for functions that take both. The pointer now comes first.

commit907156bdaf7d3ceaf33b9c7d8d0c15d5f09cb34a authorIan Grant Jeffries <ian@housejeffries.com> dateThu 26 Mar 2015, 19:41 (12 years ago) parentc1075bb
5 files changed+75−37
README.md+21−2 changelog.txt+9 hjsonpointer.cabal+4−3 src/Data/JsonPointer.hs+28−26 tests/Unit.hs+13−6
README.md+21−2
@@ -5,6 +5,25 @@
55# Example
66
77```
8−λ jsonPointer "/foo" >>= resolvePointer (Object $ singleton "foo" $ String "bar")
9−Right (String "bar")
8+{-# LANGUAGE OverloadedStrings #-}
9+
10+module Main where
11+
12+import Data.Aeson
13+import qualified Data.HashMap.Strict as H
14+import Data.JsonPointer
15+
16+main :: IO ()
17+main =
18+ case jsonPointer "/foo" of
19+ Left e -> print e
20+ Right pntr ->
21+ case resolvePointer pntr (Object $ H.singleton "foo" $ String "bar") of
22+ Left e2 -> print e2
23+ Right v -> print v
24+```
25+
26+Output:
27+```
28+String "bar"
1029```
changelog.txtnew file+9
@@ -0,0 +1,9 @@
1+# 0.2
2+
3+Fix mistake in resolveRefTok.
4+Split errors over two types.
5+Switch the order of pointer and value arguments for functions that take both. The pointer now comes first.
6+
7+# 0.1
8+
9+Initial implementation.
hjsonpointer.cabal+4−3
@@ -1,6 +1,6 @@
11name: hjsonpointer
2−version: 0.1.0.1
3−synopsis: JSON Pointer library for Haskell
2+version: 0.2.0.0
3+synopsis: JSON Pointer library
44homepage: https://github.com/seagreen/hjsonpointer
55license: MIT
66license-file: MIT-LICENSE.txt
@@ -9,7 +9,8 @@ maintainer: ian@housejeffries.com
99category: Data
1010build-type: Simple
1111cabal-version: >=1.10
12−extra-source-files: README.md
12+extra-source-files: changelog.txt
13+ README.md
1314
1415library
1516 hs-source-dirs: src
src/Data/JsonPointer.hs+28−26
@@ -11,67 +11,69 @@ import Text.Read (readMaybe)
1111
1212newtype JsonPointer = JsonPointer { _unJsonPointer :: [Text] } deriving (Eq, Show)
1313
14−data PointerErr
14+data PointerFormatError
1515 -- | The Text to build a JSON Pointer must either be empty
16− -- or start with a '/'.
16+ -- or start with a "/".
1717 = InvalidFirstChar
1818 | UnescapedTilde
19− | ObjectLookupFailed
19+ deriving (Eq, Show)
20+
21+data ResolutionError
22+ = ObjectLookupFailed
2023 | ArrayIndexInvalid
2124 | ArrayElemNotFound
22− | UnindexableValue
25+ | ExpectedObjectOrArray
2326 deriving (Eq, Show)
2427
2528-- | The Text to build a JSON Pointer must either be empty or start
26−-- with a '/'. If you're turning a URI Fragment into a JSON Pointer
27−-- you must drop the initial '#'.
28−jsonPointer :: Text -> Either PointerErr JsonPointer
29+-- with a "/". If you're turning a URI Fragment into a JSON Pointer
30+-- you must drop the initial "#".
31+jsonPointer :: Text -> Either PointerFormatError JsonPointer
2932jsonPointer t =
3033 JsonPointer <$> (unescape =<< process (T.splitOn "/" t))
3134 where
32− process ::[Text] -> Either PointerErr [Text]
35+ process ::[Text] -> Either PointerFormatError [Text]
3336 process [] = Right []
34− process (x:xs) = do
35− unless (T.null x) $ Left InvalidFirstChar
36− Right xs
37+ process (x:xs)
38+ -- This checks that the JsonPointer started with a "/":
39+ | (not . T.null $ x) = Left InvalidFirstChar
40+ | otherwise = Right xs
3741
38− unescape :: [Text] -> Either PointerErr [Text]
42+ unescape :: [Text] -> Either PointerFormatError [Text]
3943 unescape xs = do
4044 void $ mapM checkValid xs
4145 Right $ T.replace "~0" "~" . T.replace "~1" "/" <$> xs
4246
43− checkValid :: Text -> Either PointerErr ()
47+ checkValid :: Text -> Either PointerFormatError ()
4448 checkValid x = do
4549 let afterTildes = drop 1 $ T.splitOn "~" x
4650 if all (\y -> T.isPrefixOf "0" y || T.isPrefixOf "1" y) afterTildes
4751 then Right ()
4852 else Left UnescapedTilde
4953
50−resolvePointer :: Value -> JsonPointer -> Either PointerErr Value
51−resolvePointer v p =
54+resolvePointer :: JsonPointer -> Value -> Either ResolutionError Value
55+resolvePointer p v =
5256 case _unJsonPointer p of
5357 [] -> Right v
54− _ -> resolveRefTok v p >>= uncurry resolvePointer
58+ _ -> resolveRefTok p v >>= uncurry resolvePointer
5559
5660-- | For internal use and specialized applications that don't want to
5761-- resolve the entire pointer at once.
58−resolveRefTok :: Value -> JsonPointer -> Either PointerErr (Value, JsonPointer)
59−resolveRefTok v p = do
62+resolveRefTok :: JsonPointer -> Value -> Either ResolutionError (JsonPointer, Value)
63+resolveRefTok p v = do
6064 case _unJsonPointer p of
61− [] -> Right (v, p)
65+ [] -> Right (p, v)
6266 (tok:ps) ->
6367 case v of
6468 Object h ->
6569 case H.lookup tok h of
6670 Nothing -> Left ObjectLookupFailed
67− Just vv -> Right (vv, JsonPointer ps)
71+ Just vv -> Right (JsonPointer ps, vv)
6872 Array vs -> do
6973 case readMaybe (T.unpack tok) of
7074 Nothing -> Left ArrayIndexInvalid
7175 Just n -> do
72− unless (n >= 0) $ Left ArrayIndexInvalid
73− unless (n < V.length vs) $ Left ArrayElemNotFound
74− Right (vs V.! n, JsonPointer ps)
75− vv -> do
76− unless (null ps) $ Left UnindexableValue
77− Right (vv, JsonPointer [])
76+ when (n < 0) $ Left ArrayIndexInvalid
77+ when (n >= V.length vs) $ Left ArrayElemNotFound
78+ Right (JsonPointer ps, vs V.! n)
79+ _ -> Left ExpectedObjectOrArray
tests/Unit.hs+13−6
@@ -1,5 +1,7 @@
11module Main where
22
3+import Control.Applicative
4+import Control.Arrow
35import Control.Monad
46import Data.Aeson
57import qualified Data.HashMap.Strict as H
@@ -38,11 +40,18 @@ specExample =
3840 , ("m~n" , Number 8)
3941 ]
4042
43+resolvesTo :: (Text, Value) -> Assertion
44+resolvesTo (t, expected) =
45+ case jsonPointer t of
46+ Left e -> assertFailure (show e <> " error for pointer: " <> show t)
47+ Right p ->
48+ assertEqual ("Resolved value for pointer: " <> show t)
49+ (Right expected)
50+ $ resolvePointer p specExample
51+
4152jsonString :: Assertion
4253jsonString =
43− void $ mapM
44− (\(a,expected) -> assertEqual ("Tried to resolve " <> show a) (Right expected)
45− $ jsonPointer a >>= resolvePointer specExample)
54+ void $ mapM resolvesTo
4655 [ ("" , specExample)
4756 , ("/foo" , Array $ V.fromList ["bar", "baz"])
4857 , ("/foo/0", String "bar")
@@ -59,9 +68,7 @@ jsonString =
5968
6069uriFragment :: Assertion
6170uriFragment =
62− void $ mapM
63− (\(a,expected) -> assertEqual ("Tried to resolve " <> show a) (Right expected)
64− $ jsonPointer (decodeFragment a) >>= resolvePointer specExample)
71+ void $ mapM resolvesTo $ first decodeFragment <$>
6572 [ ("#" , specExample)
6673 , ("#/foo" , Array $ V.fromList ["bar", "baz"])
6774 , ("#/foo/0", String "bar")