git.iko.soy/typeable/hjsonpointer
on master help sign in
0.2
907156bIan Grant Jeffries
README.md+21−2
@@ -5,6 +5,25 @@
5# Example
5# Example
6
6
7```
7```
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 ->
8−λ jsonPointer "/foo" >>= resolvePointer (Object $ singleton "foo" $ String "bar")
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+```
9−Right (String "bar")
28+String "bar"
10```
29```
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 @@
1name: hjsonpointer
1name: hjsonpointer
2−version: 0.1.0.1
2+version: 0.2.0.0
3−synopsis: JSON Pointer library for Haskell
3+synopsis: JSON Pointer library
4homepage: https://github.com/seagreen/hjsonpointer
4homepage: https://github.com/seagreen/hjsonpointer
5license: MIT
5license: MIT
6license-file: MIT-LICENSE.txt
6license-file: MIT-LICENSE.txt
@@ -9,7 +9,8 @@ maintainer: ian@housejeffries.com
9category: Data
9category: Data
10build-type: Simple
10build-type: Simple
11cabal-version: >=1.10
11cabal-version: >=1.10
12−extra-source-files: README.md
12+extra-source-files: changelog.txt
13+ README.md
13
14
14library
15library
15 hs-source-dirs: src
16 hs-source-dirs: src
src/Data/JsonPointer.hs+28−26
@@ -11,67 +11,69 @@ import Text.Read (readMaybe)
11
11
12newtype JsonPointer = JsonPointer { _unJsonPointer :: [Text] } deriving (Eq, Show)
12newtype JsonPointer = JsonPointer { _unJsonPointer :: [Text] } deriving (Eq, Show)
13
13
14−data PointerErr
14+data PointerFormatError
15 -- | The Text to build a JSON Pointer must either be empty
15 -- | The Text to build a JSON Pointer must either be empty
16− -- or start with a '/'.
16+ -- or start with a "/".
17 = InvalidFirstChar
17 = InvalidFirstChar
18 | UnescapedTilde
18 | UnescapedTilde
19+ deriving (Eq, Show)
20+
21+data ResolutionError
19− | ObjectLookupFailed
22+ = ObjectLookupFailed
20 | ArrayIndexInvalid
23 | ArrayIndexInvalid
21 | ArrayElemNotFound
24 | ArrayElemNotFound
22− | UnindexableValue
25+ | ExpectedObjectOrArray
23 deriving (Eq, Show)
26 deriving (Eq, Show)
24
27
25-- | The Text to build a JSON Pointer must either be empty or start
28-- | 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
29+-- with a "/". If you're turning a URI Fragment into a JSON Pointer
27−-- you must drop the initial '#'.
30+-- you must drop the initial "#".
28−jsonPointer :: Text -> Either PointerErr JsonPointer
31+jsonPointer :: Text -> Either PointerFormatError JsonPointer
29jsonPointer t =
32jsonPointer t =
30 JsonPointer <$> (unescape =<< process (T.splitOn "/" t))
33 JsonPointer <$> (unescape =<< process (T.splitOn "/" t))
31 where
34 where
32− process ::[Text] -> Either PointerErr [Text]
35+ process ::[Text] -> Either PointerFormatError [Text]
33 process [] = Right []
36 process [] = Right []
34− process (x:xs) = do
37+ process (x:xs)
38+ -- This checks that the JsonPointer started with a "/":
35− unless (T.null x) $ Left InvalidFirstChar
39+ | (not . T.null $ x) = Left InvalidFirstChar
36− Right xs
40+ | otherwise = Right xs
37
41
38− unescape :: [Text] -> Either PointerErr [Text]
42+ unescape :: [Text] -> Either PointerFormatError [Text]
39 unescape xs = do
43 unescape xs = do
40 void $ mapM checkValid xs
44 void $ mapM checkValid xs
41 Right $ T.replace "~0" "~" . T.replace "~1" "/" <$> xs
45 Right $ T.replace "~0" "~" . T.replace "~1" "/" <$> xs
42
46
43− checkValid :: Text -> Either PointerErr ()
47+ checkValid :: Text -> Either PointerFormatError ()
44 checkValid x = do
48 checkValid x = do
45 let afterTildes = drop 1 $ T.splitOn "~" x
49 let afterTildes = drop 1 $ T.splitOn "~" x
46 if all (\y -> T.isPrefixOf "0" y || T.isPrefixOf "1" y) afterTildes
50 if all (\y -> T.isPrefixOf "0" y || T.isPrefixOf "1" y) afterTildes
47 then Right ()
51 then Right ()
48 else Left UnescapedTilde
52 else Left UnescapedTilde
49
53
50−resolvePointer :: Value -> JsonPointer -> Either PointerErr Value
54+resolvePointer :: JsonPointer -> Value -> Either ResolutionError Value
51−resolvePointer v p =
55+resolvePointer p v =
52 case _unJsonPointer p of
56 case _unJsonPointer p of
53 [] -> Right v
57 [] -> Right v
54− _ -> resolveRefTok v p >>= uncurry resolvePointer
58+ _ -> resolveRefTok p v >>= uncurry resolvePointer
55
59
56-- | For internal use and specialized applications that don't want to
60-- | For internal use and specialized applications that don't want to
57-- resolve the entire pointer at once.
61-- resolve the entire pointer at once.
58−resolveRefTok :: Value -> JsonPointer -> Either PointerErr (Value, JsonPointer)
62+resolveRefTok :: JsonPointer -> Value -> Either ResolutionError (JsonPointer, Value)
59−resolveRefTok v p = do
63+resolveRefTok p v = do
60 case _unJsonPointer p of
64 case _unJsonPointer p of
61− [] -> Right (v, p)
65+ [] -> Right (p, v)
62 (tok:ps) ->
66 (tok:ps) ->
63 case v of
67 case v of
64 Object h ->
68 Object h ->
65 case H.lookup tok h of
69 case H.lookup tok h of
66 Nothing -> Left ObjectLookupFailed
70 Nothing -> Left ObjectLookupFailed
67− Just vv -> Right (vv, JsonPointer ps)
71+ Just vv -> Right (JsonPointer ps, vv)
68 Array vs -> do
72 Array vs -> do
69 case readMaybe (T.unpack tok) of
73 case readMaybe (T.unpack tok) of
70 Nothing -> Left ArrayIndexInvalid
74 Nothing -> Left ArrayIndexInvalid
71 Just n -> do
75 Just n -> do
72− unless (n >= 0) $ Left ArrayIndexInvalid
76+ when (n < 0) $ Left ArrayIndexInvalid
73− unless (n < V.length vs) $ Left ArrayElemNotFound
77+ when (n >= V.length vs) $ Left ArrayElemNotFound
74− Right (vs V.! n, JsonPointer ps)
78+ Right (JsonPointer ps, vs V.! n)
75− vv -> do
79+ _ -> Left ExpectedObjectOrArray
76− unless (null ps) $ Left UnindexableValue
77− Right (vv, JsonPointer [])
tests/Unit.hs+13−6
@@ -1,5 +1,7 @@
1module Main where
1module Main where
2
2
3+import Control.Applicative
4+import Control.Arrow
3import Control.Monad
5import Control.Monad
4import Data.Aeson
6import Data.Aeson
5import qualified Data.HashMap.Strict as H
7import qualified Data.HashMap.Strict as H
@@ -38,11 +40,18 @@ specExample =
38 , ("m~n" , Number 8)
40 , ("m~n" , Number 8)
39 ]
41 ]
40
42
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+
41jsonString :: Assertion
52jsonString :: Assertion
42jsonString =
53jsonString =
43− void $ mapM
54+ void $ mapM resolvesTo
44− (\(a,expected) -> assertEqual ("Tried to resolve " <> show a) (Right expected)
45− $ jsonPointer a >>= resolvePointer specExample)
46 [ ("" , specExample)
55 [ ("" , specExample)
47 , ("/foo" , Array $ V.fromList ["bar", "baz"])
56 , ("/foo" , Array $ V.fromList ["bar", "baz"])
48 , ("/foo/0", String "bar")
57 , ("/foo/0", String "bar")
@@ -59,9 +68,7 @@ jsonString =
59
68
60uriFragment :: Assertion
69uriFragment :: Assertion
61uriFragment =
70uriFragment =
62− void $ mapM
71+ void $ mapM resolvesTo $ first decodeFragment <$>
63− (\(a,expected) -> assertEqual ("Tried to resolve " <> show a) (Right expected)
64− $ jsonPointer (decodeFragment a) >>= resolvePointer specExample)
65 [ ("#" , specExample)
72 [ ("#" , specExample)
66 , ("#/foo" , Array $ V.fromList ["bar", "baz"])
73 , ("#/foo" , Array $ V.fromList ["bar", "baz"])
67 , ("#/foo/0", String "bar")
74 , ("#/foo/0", String "bar")