From c6f9159b7c55b38d63593c53198ee10557248ae3 Mon Sep 17 00:00:00 2001 From: Brian Sermons Date: Fri, 8 Jul 2016 05:44:15 -0500 Subject: [PATCH 1/8] Update TypeMismatch for multiple possible types. --- src/Data/Foreign.purs | 16 ++++++++++------ src/Data/Foreign/Index.purs | 2 +- src/Data/Foreign/Keys.purs | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index 898b445..3d228d3 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -46,16 +46,20 @@ foreign import data Foreign :: * -- | A type for runtime type errors data ForeignError - = TypeMismatch String String + = TypeMismatch (Array String) String | ErrorAtIndex Int ForeignError | ErrorAtProperty String ForeignError | JSONError String instance showForeignError :: Show ForeignError where - show (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> ", found " <> act show (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e show (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e show (JSONError s) = "JSON error: " <> s + show (TypeMismatch exps act) = "Type mismatch: expected " <> to_s exps <> ", found " <> act + where + to_s [] = "???" + to_s [typ] = typ + to_s typs = "one of " <> show typs derive instance eqForeignError :: Eq ForeignError derive instance ordForeignError :: Ord ForeignError @@ -88,7 +92,7 @@ foreign import tagOf :: Foreign -> String -- | value. unsafeReadTagged :: forall a. String -> Foreign -> F a unsafeReadTagged tag value | tagOf value == tag = pure (unsafeFromForeign value) -unsafeReadTagged tag value = Left (TypeMismatch tag (tagOf value)) +unsafeReadTagged tag value = Left (TypeMismatch [tag] (tagOf value)) -- | Test whether a foreign value is null foreign import isNull :: Foreign -> Boolean @@ -111,7 +115,7 @@ readChar value = either (const error) fromString (readString value) fromString = maybe error pure <<< toChar error :: F Char - error = Left $ TypeMismatch "Char" (tagOf value) + error = Left $ TypeMismatch ["Char"] (tagOf value) -- | Attempt to coerce a foreign value to a `Boolean`. readBoolean :: Foreign -> F Boolean @@ -129,12 +133,12 @@ readInt value = either (const error) fromNumber (readNumber value) fromNumber = maybe error pure <<< Int.fromNumber error :: F Int - error = Left $ TypeMismatch "Int" (tagOf value) + error = Left $ TypeMismatch ["Int"] (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) readArray value | isArray value = pure $ unsafeFromForeign value -readArray value = Left (TypeMismatch "array" (tagOf value)) +readArray value = Left (TypeMismatch ["array"] (tagOf value)) -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Index.purs b/src/Data/Foreign/Index.purs index 3016415..6117d96 100644 --- a/src/Data/Foreign/Index.purs +++ b/src/Data/Foreign/Index.purs @@ -31,7 +31,7 @@ infixl 9 ix as ! foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign (F Foreign) unsafeReadProp :: forall k. k -> Foreign -> F Foreign -unsafeReadProp k value = runFn4 unsafeReadPropImpl (Left (TypeMismatch "object" (typeOf value))) pure k value +unsafeReadProp k value = runFn4 unsafeReadPropImpl (Left (TypeMismatch ["object"] (typeOf value))) pure k value -- | Attempt to read a value from a foreign value property prop :: String -> Foreign -> F Foreign diff --git a/src/Data/Foreign/Keys.purs b/src/Data/Foreign/Keys.purs index 652361c..13c73f5 100644 --- a/src/Data/Foreign/Keys.purs +++ b/src/Data/Foreign/Keys.purs @@ -14,7 +14,7 @@ foreign import unsafeKeys :: Foreign -> Array String -- | Get an array of the properties defined on a foreign value keys :: Foreign -> F (Array String) -keys value | isNull value = Left $ TypeMismatch "object" "null" -keys value | isUndefined value = Left $ TypeMismatch "object" "undefined" +keys value | isNull value = Left $ TypeMismatch ["object"] "null" +keys value | isUndefined value = Left $ TypeMismatch ["object"] "undefined" keys value | typeOf value == "object" = Right $ unsafeKeys value -keys value = Left $ TypeMismatch "object" (typeOf value) +keys value = Left $ TypeMismatch ["object"] (typeOf value) From cb753a9d9ef11cd2c4a742376c0886771d27be6a Mon Sep 17 00:00:00 2001 From: Brian Sermons Date: Mon, 11 Jul 2016 09:41:52 -0500 Subject: [PATCH 2/8] Fix ForeignError Show instance. --- src/Data/Foreign.purs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index 3d228d3..e9012a6 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -52,18 +52,24 @@ data ForeignError | JSONError String instance showForeignError :: Show ForeignError where - show (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e - show (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e - show (JSONError s) = "JSON error: " <> s - show (TypeMismatch exps act) = "Type mismatch: expected " <> to_s exps <> ", found " <> act + show (ErrorAtIndex i e) = "(ErrorAtIndex " <> show i <> " " <> show e <> ")" + show (ErrorAtProperty prop e) = "(ErrorAtProperty " <> show prop <> " " <> show e <> ")" + show (JSONError s) = "(JSONError " <> show s <> ")" + show (TypeMismatch exps act) = "(TypeMismatch " <> show exps <> " " <> show act <> ")" + +derive instance eqForeignError :: Eq ForeignError +derive instance ordForeignError :: Ord ForeignError + +renderForeignError :: ForeignError -> String +renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e +renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e +renderForeignError (JSONError s) = "JSON error: " <> s +renderForeignError (TypeMismatch exps act) = "Type mismatch: expected " <> to_s exps <> ", found " <> act where to_s [] = "???" to_s [typ] = typ to_s typs = "one of " <> show typs -derive instance eqForeignError :: Eq ForeignError -derive instance ordForeignError :: Ord ForeignError - -- | An error monad, used in this library to encode possible failure when -- | dealing with foreign data. type F = Either ForeignError From ca0d74da53933fe491c47b4fd4bf0867eeb73acb Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 10 Oct 2016 21:29:32 +0100 Subject: [PATCH 3/8] Use NonEmpty for type mismatch options --- src/Data/Foreign.purs | 20 ++++++++++---------- src/Data/Foreign/Index.purs | 3 ++- src/Data/Foreign/Keys.purs | 7 ++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index e9012a6..6d3e33a 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -30,7 +30,8 @@ import Data.Either (Either(..), either) import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int import Data.Maybe (maybe) -import Data.String (toChar) +import Data.NonEmpty as NE +import Data.String (toChar, joinWith) -- | A type for _foreign data_. -- | @@ -46,7 +47,7 @@ foreign import data Foreign :: * -- | A type for runtime type errors data ForeignError - = TypeMismatch (Array String) String + = TypeMismatch (NE.NonEmpty Array String) String | ErrorAtIndex Int ForeignError | ErrorAtProperty String ForeignError | JSONError String @@ -65,10 +66,9 @@ renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ": renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e renderForeignError (JSONError s) = "JSON error: " <> s renderForeignError (TypeMismatch exps act) = "Type mismatch: expected " <> to_s exps <> ", found " <> act - where - to_s [] = "???" - to_s [typ] = typ - to_s typs = "one of " <> show typs + where + to_s (NE.NonEmpty typ []) = typ + to_s typs = "one of " <> joinWith ", " (NE.oneOf typs) -- | An error monad, used in this library to encode possible failure when -- | dealing with foreign data. @@ -98,7 +98,7 @@ foreign import tagOf :: Foreign -> String -- | value. unsafeReadTagged :: forall a. String -> Foreign -> F a unsafeReadTagged tag value | tagOf value == tag = pure (unsafeFromForeign value) -unsafeReadTagged tag value = Left (TypeMismatch [tag] (tagOf value)) +unsafeReadTagged tag value = Left (TypeMismatch (NE.singleton tag) (tagOf value)) -- | Test whether a foreign value is null foreign import isNull :: Foreign -> Boolean @@ -121,7 +121,7 @@ readChar value = either (const error) fromString (readString value) fromString = maybe error pure <<< toChar error :: F Char - error = Left $ TypeMismatch ["Char"] (tagOf value) + error = Left $ TypeMismatch (NE.singleton "Char") (tagOf value) -- | Attempt to coerce a foreign value to a `Boolean`. readBoolean :: Foreign -> F Boolean @@ -139,12 +139,12 @@ readInt value = either (const error) fromNumber (readNumber value) fromNumber = maybe error pure <<< Int.fromNumber error :: F Int - error = Left $ TypeMismatch ["Int"] (tagOf value) + error = Left $ TypeMismatch (NE.singleton "Int") (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) readArray value | isArray value = pure $ unsafeFromForeign value -readArray value = Left (TypeMismatch ["array"] (tagOf value)) +readArray value = Left (TypeMismatch (NE.singleton "array") (tagOf value)) -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Index.purs b/src/Data/Foreign/Index.purs index 6117d96..194d198 100644 --- a/src/Data/Foreign/Index.purs +++ b/src/Data/Foreign/Index.purs @@ -16,6 +16,7 @@ import Prelude import Data.Either (Either(..)) import Data.Foreign (Foreign, F, ForeignError(..), typeOf, isUndefined, isNull) import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4) +import Data.NonEmpty as NE -- | This type class identifies types that act like _property indices_. -- | @@ -31,7 +32,7 @@ infixl 9 ix as ! foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign (F Foreign) unsafeReadProp :: forall k. k -> Foreign -> F Foreign -unsafeReadProp k value = runFn4 unsafeReadPropImpl (Left (TypeMismatch ["object"] (typeOf value))) pure k value +unsafeReadProp k value = runFn4 unsafeReadPropImpl (Left (TypeMismatch (NE.singleton "object") (typeOf value))) pure k value -- | Attempt to read a value from a foreign value property prop :: String -> Foreign -> F Foreign diff --git a/src/Data/Foreign/Keys.purs b/src/Data/Foreign/Keys.purs index 13c73f5..be3f978 100644 --- a/src/Data/Foreign/Keys.purs +++ b/src/Data/Foreign/Keys.purs @@ -9,12 +9,13 @@ import Prelude import Data.Either (Either(..)) import Data.Foreign (F, Foreign, ForeignError(..), typeOf, isUndefined, isNull) +import Data.NonEmpty as NE foreign import unsafeKeys :: Foreign -> Array String -- | Get an array of the properties defined on a foreign value keys :: Foreign -> F (Array String) -keys value | isNull value = Left $ TypeMismatch ["object"] "null" -keys value | isUndefined value = Left $ TypeMismatch ["object"] "undefined" +keys value | isNull value = Left $ TypeMismatch (NE.singleton "object") "null" +keys value | isUndefined value = Left $ TypeMismatch (NE.singleton "object") "undefined" keys value | typeOf value == "object" = Right $ unsafeKeys value -keys value = Left $ TypeMismatch ["object"] (typeOf value) +keys value = Left $ TypeMismatch (NE.singleton "object") (typeOf value) From d5203793f84366db34672ecb5e041daaad53bb41 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 10 Oct 2016 21:30:46 +0100 Subject: [PATCH 4/8] Add ForeignError --- src/Data/Foreign.purs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index 6d3e33a..e7ed3ee 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -47,12 +47,14 @@ foreign import data Foreign :: * -- | A type for runtime type errors data ForeignError - = TypeMismatch (NE.NonEmpty Array String) String + = ForeignError String + | TypeMismatch (NE.NonEmpty Array String) String | ErrorAtIndex Int ForeignError | ErrorAtProperty String ForeignError | JSONError String instance showForeignError :: Show ForeignError where + show (ForeignError msg) = "(ForeignError " <> msg <> ")" show (ErrorAtIndex i e) = "(ErrorAtIndex " <> show i <> " " <> show e <> ")" show (ErrorAtProperty prop e) = "(ErrorAtProperty " <> show prop <> " " <> show e <> ")" show (JSONError s) = "(JSONError " <> show s <> ")" @@ -62,6 +64,7 @@ derive instance eqForeignError :: Eq ForeignError derive instance ordForeignError :: Ord ForeignError renderForeignError :: ForeignError -> String +renderForeignError (ForeignError msg) = msg renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e renderForeignError (JSONError s) = "JSON error: " <> s From a72a8ce06adcbe7920350f618cb18bd62735e051 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Mon, 10 Oct 2016 21:34:02 +0100 Subject: [PATCH 5/8] Tweak a few style things --- src/Data/Foreign.purs | 18 +++++++++--------- src/Data/Foreign/Keys.purs | 9 +++++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index e7ed3ee..1341f5f 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -68,10 +68,10 @@ renderForeignError (ForeignError msg) = msg renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e renderForeignError (JSONError s) = "JSON error: " <> s -renderForeignError (TypeMismatch exps act) = "Type mismatch: expected " <> to_s exps <> ", found " <> act +renderForeignError (TypeMismatch exps act) = "Type mismatch: expected " <> listTypes exps <> ", found " <> act where - to_s (NE.NonEmpty typ []) = typ - to_s typs = "one of " <> joinWith ", " (NE.oneOf typs) + listTypes (NE.NonEmpty typ []) = typ + listTypes typs = "one of " <> joinWith ", " (NE.oneOf typs) -- | An error monad, used in this library to encode possible failure when -- | dealing with foreign data. @@ -100,8 +100,9 @@ foreign import tagOf :: Foreign -> String -- | Unsafely coerce a `Foreign` value when the value has a particular `tagOf` -- | value. unsafeReadTagged :: forall a. String -> Foreign -> F a -unsafeReadTagged tag value | tagOf value == tag = pure (unsafeFromForeign value) -unsafeReadTagged tag value = Left (TypeMismatch (NE.singleton tag) (tagOf value)) +unsafeReadTagged tag value + | tagOf value == tag = pure (unsafeFromForeign value) + | otherwise = Left (TypeMismatch (NE.singleton tag) (tagOf value)) -- | Test whether a foreign value is null foreign import isNull :: Foreign -> Boolean @@ -122,7 +123,6 @@ readChar value = either (const error) fromString (readString value) where fromString :: String -> F Char fromString = maybe error pure <<< toChar - error :: F Char error = Left $ TypeMismatch (NE.singleton "Char") (tagOf value) @@ -140,14 +140,14 @@ readInt value = either (const error) fromNumber (readNumber value) where fromNumber :: Number -> F Int fromNumber = maybe error pure <<< Int.fromNumber - error :: F Int error = Left $ TypeMismatch (NE.singleton "Int") (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) -readArray value | isArray value = pure $ unsafeFromForeign value -readArray value = Left (TypeMismatch (NE.singleton "array") (tagOf value)) +readArray value + | isArray value = pure $ unsafeFromForeign value + | otherwise = Left (TypeMismatch (NE.singleton "array") (tagOf value)) -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Keys.purs b/src/Data/Foreign/Keys.purs index be3f978..5aa5fbf 100644 --- a/src/Data/Foreign/Keys.purs +++ b/src/Data/Foreign/Keys.purs @@ -15,7 +15,8 @@ foreign import unsafeKeys :: Foreign -> Array String -- | Get an array of the properties defined on a foreign value keys :: Foreign -> F (Array String) -keys value | isNull value = Left $ TypeMismatch (NE.singleton "object") "null" -keys value | isUndefined value = Left $ TypeMismatch (NE.singleton "object") "undefined" -keys value | typeOf value == "object" = Right $ unsafeKeys value -keys value = Left $ TypeMismatch (NE.singleton "object") (typeOf value) +keys value + | isNull value = Left $ TypeMismatch (NE.singleton "object") "null" + | isUndefined value = Left $ TypeMismatch (NE.singleton "object") "undefined" + | typeOf value == "object" = Right $ unsafeKeys value + | otherwise = Left $ TypeMismatch (NE.singleton "object") (typeOf value) From 8de0a35198d49c79f07f871b3cbba0951d4bfe94 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Tue, 11 Oct 2016 11:26:24 +0100 Subject: [PATCH 6/8] NonEmpty errors --- bower.json | 1 + src/Data/Foreign.purs | 25 ++++++++++++------------- src/Data/Foreign/Class.purs | 16 ++++++++++------ src/Data/Foreign/Index.purs | 5 +++-- src/Data/Foreign/Keys.purs | 6 +++--- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/bower.json b/bower.json index be2bd5a..7b91d8b 100644 --- a/bower.json +++ b/bower.json @@ -26,6 +26,7 @@ "purescript-foldable-traversable": "^2.0.0", "purescript-functions": "^2.0.0", "purescript-integers": "^2.0.0", + "purescript-lists": "^2.0.0", "purescript-strings": "^2.0.0" }, "devDependencies": { diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index 1341f5f..e4bc774 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -29,9 +29,10 @@ import Prelude import Data.Either (Either(..), either) import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int +import Data.List (List) import Data.Maybe (maybe) import Data.NonEmpty as NE -import Data.String (toChar, joinWith) +import Data.String (toChar) -- | A type for _foreign data_. -- | @@ -48,7 +49,7 @@ foreign import data Foreign :: * -- | A type for runtime type errors data ForeignError = ForeignError String - | TypeMismatch (NE.NonEmpty Array String) String + | TypeMismatch String String | ErrorAtIndex Int ForeignError | ErrorAtProperty String ForeignError | JSONError String @@ -68,20 +69,18 @@ renderForeignError (ForeignError msg) = msg renderForeignError (ErrorAtIndex i e) = "Error at array index " <> show i <> ": " <> show e renderForeignError (ErrorAtProperty prop e) = "Error at property " <> show prop <> ": " <> show e renderForeignError (JSONError s) = "JSON error: " <> s -renderForeignError (TypeMismatch exps act) = "Type mismatch: expected " <> listTypes exps <> ", found " <> act - where - listTypes (NE.NonEmpty typ []) = typ - listTypes typs = "one of " <> joinWith ", " (NE.oneOf typs) +renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> ", found " <> act --- | An error monad, used in this library to encode possible failure when +-- | An error monad, used in this library to encode possible failures when -- | dealing with foreign data. -type F = Either ForeignError +type F = Either (NE.NonEmpty List ForeignError) foreign import parseJSONImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r -- | Attempt to parse a JSON string, returning the result as foreign data. parseJSON :: String -> F Foreign -parseJSON json = runFn3 parseJSONImpl (Left <<< JSONError) Right json +parseJSON json = + runFn3 parseJSONImpl (Left <<< NE.singleton <<< JSONError) Right json -- | Coerce any value to the a `Foreign` value. foreign import toForeign :: forall a. a -> Foreign @@ -102,7 +101,7 @@ foreign import tagOf :: Foreign -> String unsafeReadTagged :: forall a. String -> Foreign -> F a unsafeReadTagged tag value | tagOf value == tag = pure (unsafeFromForeign value) - | otherwise = Left (TypeMismatch (NE.singleton tag) (tagOf value)) + | otherwise = Left (NE.singleton (TypeMismatch tag (tagOf value))) -- | Test whether a foreign value is null foreign import isNull :: Foreign -> Boolean @@ -124,7 +123,7 @@ readChar value = either (const error) fromString (readString value) fromString :: String -> F Char fromString = maybe error pure <<< toChar error :: F Char - error = Left $ TypeMismatch (NE.singleton "Char") (tagOf value) + error = Left $ NE.singleton $ TypeMismatch "Char" (tagOf value) -- | Attempt to coerce a foreign value to a `Boolean`. readBoolean :: Foreign -> F Boolean @@ -141,13 +140,13 @@ readInt value = either (const error) fromNumber (readNumber value) fromNumber :: Number -> F Int fromNumber = maybe error pure <<< Int.fromNumber error :: F Int - error = Left $ TypeMismatch (NE.singleton "Int") (tagOf value) + error = Left $ NE.singleton $ TypeMismatch "Int" (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) readArray value | isArray value = pure $ unsafeFromForeign value - | otherwise = Left (TypeMismatch (NE.singleton "array") (tagOf value)) + | otherwise = Left $ NE.singleton $ TypeMismatch "array" (tagOf value) -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Class.purs b/src/Data/Foreign/Class.purs index 674a36d..5a2b492 100644 --- a/src/Data/Foreign/Class.purs +++ b/src/Data/Foreign/Class.purs @@ -16,15 +16,19 @@ module Data.Foreign.Class import Prelude import Control.Alt ((<|>)) + import Data.Array (range, zipWith, length) -import Data.Either (Either(..), either) +import Data.Bifunctor (lmap) +import Data.Either (Either(..)) import Data.Foreign (F, Foreign, ForeignError(..), Prop(..), parseJSON, readArray, readInt, readNumber, readBoolean, readChar, readString, toForeign) import Data.Foreign.Index (class Index, errorAt, (!)) import Data.Foreign.Null (Null(..), readNull, writeNull) import Data.Foreign.NullOrUndefined (NullOrUndefined(..), readNullOrUndefined) import Data.Foreign.Undefined (Undefined(..), readUndefined, writeUndefined) -import Data.Traversable (sequence) +import Data.List (List) import Data.Maybe (maybe) +import Data.NonEmpty as NE +import Data.Traversable (sequence) -- | A type class instance for this class can be written for a type if it -- | is possible to attempt to _safely_ coerce a `Foreign` value to that @@ -60,7 +64,7 @@ instance arrayIsForeign :: IsForeign a => IsForeign (Array a) where readElements arr = sequence (zipWith readElement (range zero (length arr)) arr) readElement :: Int -> Foreign -> F a - readElement i value = readWith (ErrorAtIndex i) value + readElement i value = readWith (map (ErrorAtIndex i)) value instance nullIsForeign :: IsForeign a => IsForeign (Null a) where read = readNull read @@ -76,12 +80,12 @@ readJSON :: forall a. IsForeign a => String -> F a readJSON json = parseJSON json >>= read -- | Attempt to read a foreign value, handling errors using the specified function -readWith :: forall a e. IsForeign a => (ForeignError -> e) -> Foreign -> Either e a -readWith f value = either (Left <<< f) Right (read value) +readWith :: forall a e. IsForeign a => (NE.NonEmpty List ForeignError -> e) -> Foreign -> Either e a +readWith f = lmap f <<< read -- | Attempt to read a property of a foreign value at the specified index readProp :: forall a i. (IsForeign a, Index i) => i -> Foreign -> F a -readProp prop value = value ! prop >>= readWith (errorAt prop) +readProp prop value = value ! prop >>= readWith (map (errorAt prop)) -- | A type class to convert to a `Foreign` value. -- | diff --git a/src/Data/Foreign/Index.purs b/src/Data/Foreign/Index.purs index 194d198..7aadad2 100644 --- a/src/Data/Foreign/Index.purs +++ b/src/Data/Foreign/Index.purs @@ -29,10 +29,11 @@ class Index i where infixl 9 ix as ! -foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign (F Foreign) +foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign r unsafeReadProp :: forall k. k -> Foreign -> F Foreign -unsafeReadProp k value = runFn4 unsafeReadPropImpl (Left (TypeMismatch (NE.singleton "object") (typeOf value))) pure k value +unsafeReadProp k value = + runFn4 unsafeReadPropImpl (Left (NE.singleton (TypeMismatch "object" (typeOf value)))) Right k value -- | Attempt to read a value from a foreign value property prop :: String -> Foreign -> F Foreign diff --git a/src/Data/Foreign/Keys.purs b/src/Data/Foreign/Keys.purs index 5aa5fbf..8708887 100644 --- a/src/Data/Foreign/Keys.purs +++ b/src/Data/Foreign/Keys.purs @@ -16,7 +16,7 @@ foreign import unsafeKeys :: Foreign -> Array String -- | Get an array of the properties defined on a foreign value keys :: Foreign -> F (Array String) keys value - | isNull value = Left $ TypeMismatch (NE.singleton "object") "null" - | isUndefined value = Left $ TypeMismatch (NE.singleton "object") "undefined" + | isNull value = Left $ NE.singleton $ TypeMismatch "object" "null" + | isUndefined value = Left $ NE.singleton $ TypeMismatch "object" "undefined" | typeOf value == "object" = Right $ unsafeKeys value - | otherwise = Left $ TypeMismatch (NE.singleton "object") (typeOf value) + | otherwise = Left $ NE.singleton $ TypeMismatch "object" (typeOf value) From 2a7a4bf00e9096a98014a344e6667c8b66cb8288 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Tue, 11 Oct 2016 20:26:54 +0100 Subject: [PATCH 7/8] Use `Except` for error handling --- bower.json | 3 +- examples/Applicative.purs | 3 +- examples/Complex.purs | 3 +- examples/Either.purs | 11 ++++---- examples/JSONArrays.purs | 5 ++-- examples/JSONSimpleTypes.purs | 7 +++-- examples/MaybeNullable.purs | 5 ++-- examples/Nested.purs | 3 +- examples/Objects.purs | 3 +- examples/ParseErrors.purs | 9 +++--- examples/Union.purs | 5 ++-- src/Data/Foreign.purs | 52 ++++++++++++++++++++++++----------- src/Data/Foreign/Class.purs | 9 +++--- src/Data/Foreign/Index.purs | 6 ++-- src/Data/Foreign/Keys.purs | 12 ++++---- 15 files changed, 81 insertions(+), 55 deletions(-) diff --git a/bower.json b/bower.json index 7b91d8b..81af8c8 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,8 @@ "purescript-functions": "^2.0.0", "purescript-integers": "^2.0.0", "purescript-lists": "^2.0.0", - "purescript-strings": "^2.0.0" + "purescript-strings": "^2.0.0", + "purescript-transformers": "^2.0.0" }, "devDependencies": { "purescript-console": "^2.0.0" diff --git a/examples/Applicative.purs b/examples/Applicative.purs index 5e18965..18cbc72 100755 --- a/examples/Applicative.purs +++ b/examples/Applicative.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (class IsForeign, readJSON, readProp) @@ -19,5 +20,5 @@ instance pointIsForeign :: IsForeign Point where <*> readProp "z" value main :: Eff (console :: CONSOLE) Unit -main = logShow $ +main = logShow $ runExcept $ readJSON """{ "x": 1, "y": 2, "z": 3 }""" :: F Point diff --git a/examples/Complex.purs b/examples/Complex.purs index 448845a..588f23b 100755 --- a/examples/Complex.purs +++ b/examples/Complex.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (class IsForeign, readJSON, readProp) @@ -52,4 +53,4 @@ instance listItemIsForeign :: IsForeign ListItem where main :: Eff (console :: CONSOLE) Unit main = do let json = """{"foo":"hello","bar":true,"baz":1,"list":[{"x":1,"y":2},{"x":3,"y":4,"z":999}]}""" - logShow $ readJSON json :: F SomeObject + logShow $ runExcept $ readJSON json :: F SomeObject diff --git a/examples/Either.purs b/examples/Either.purs index 31d96aa..05ac773 100644 --- a/examples/Either.purs +++ b/examples/Either.purs @@ -1,14 +1,15 @@ module Examples.Either where import Prelude + import Control.Monad.Eff (Eff) -import Data.Either (Either) +import Control.Monad.Eff.Console (logShow, CONSOLE) +import Control.Monad.Except (runExcept) +import Data.Either (Either) import Data.Foreign (F, parseJSON) import Data.Foreign.Class (class IsForeign, readEitherR, readProp) -import Control.Monad.Eff.Console (logShow, CONSOLE) - data Point = Point Number Number Number instance showPoint :: Show Point where @@ -23,10 +24,10 @@ type Response = Either (Array String) Point main :: forall eff. Eff (console :: CONSOLE | eff) Unit main = do - logShow do + logShow $ runExcept do json <- parseJSON """{ "x":1, "y": 2, "z": 3}""" readEitherR json :: F Response - logShow do + logShow $ runExcept do json <- parseJSON """["Invalid parse", "Not a valid y point"]""" readEitherR json :: F Response diff --git a/examples/JSONArrays.purs b/examples/JSONArrays.purs index 3491540..fbc7645 100755 --- a/examples/JSONArrays.purs +++ b/examples/JSONArrays.purs @@ -4,11 +4,12 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (readJSON) main :: Eff (console :: CONSOLE) Unit main = do - logShow $ readJSON """["hello", "world"]""" :: F (Array String) - logShow $ readJSON """[1, 2, 3, 4]""" :: F (Array Number) + logShow $ runExcept $ readJSON """["hello", "world"]""" :: F (Array String) + logShow $ runExcept $ readJSON """[1, 2, 3, 4]""" :: F (Array Number) diff --git a/examples/JSONSimpleTypes.purs b/examples/JSONSimpleTypes.purs index 6ef0558..faa2350 100755 --- a/examples/JSONSimpleTypes.purs +++ b/examples/JSONSimpleTypes.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F, unsafeFromForeign) import Data.Foreign.Class (readJSON, write) @@ -12,9 +13,9 @@ import Data.Foreign.Class (readJSON, write) -- out of the box. main :: Eff (console :: CONSOLE) Unit main = do - logShow $ readJSON "\"a JSON string\"" :: F String - logShow $ readJSON "42" :: F Number - logShow $ readJSON "true" :: F Boolean + logShow $ runExcept $ readJSON "\"a JSON string\"" :: F String + logShow $ runExcept $ readJSON "42" :: F Number + logShow $ runExcept $ readJSON "true" :: F Boolean log $ unsafeFromForeign $ write "a JSON string" log $ unsafeFromForeign $ write 42.0 log $ unsafeFromForeign $ write true diff --git a/examples/MaybeNullable.purs b/examples/MaybeNullable.purs index 76038b1..8d53fce 100755 --- a/examples/MaybeNullable.purs +++ b/examples/MaybeNullable.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F, unsafeFromForeign) import Data.Foreign.Class (readJSON, write) @@ -14,7 +15,7 @@ import Data.Maybe (Maybe(..)) -- using Maybe types. main :: Eff (console :: CONSOLE) Unit main = do - logShow $ unNull <$> readJSON "null" :: F (Null Boolean) - logShow $ unNull <$> readJSON "true" :: F (Null Boolean) + logShow $ unNull <$> runExcept (readJSON "null" :: F (Null Boolean)) + logShow $ unNull <$> runExcept (readJSON "true" :: F (Null Boolean)) log $ unsafeFromForeign $ write $ Null Nothing :: Null Boolean log $ unsafeFromForeign $ write $ Null $ Just true diff --git a/examples/Nested.purs b/examples/Nested.purs index f187883..dd1a707 100755 --- a/examples/Nested.purs +++ b/examples/Nested.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (class IsForeign, readJSON, readProp) @@ -32,4 +33,4 @@ instance fooIsForeign :: IsForeign Foo where main :: Eff (console :: CONSOLE) Unit main = do - logShow $ readJSON """{ "foo": { "bar": "bar", "baz": 1 } }""" :: F Foo + logShow $ runExcept $ readJSON """{ "foo": { "bar": "bar", "baz": 1 } }""" :: F Foo diff --git a/examples/Objects.purs b/examples/Objects.purs index 7745ddf..eb5336a 100755 --- a/examples/Objects.purs +++ b/examples/Objects.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, log, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F, writeObject, unsafeFromForeign) import Data.Foreign.Class (class AsForeign, class IsForeign, (.=), readJSON, readProp, write) @@ -31,5 +32,5 @@ instance pointIsForeign :: IsForeign Point where main :: Eff (console :: CONSOLE) Unit main = do - logShow $ readJSON """{ "x": 1, "y": 2 }""" :: F Point + logShow $ runExcept $ readJSON """{ "x": 1, "y": 2 }""" :: F Point log $ unsafeFromForeign $ write $ Point { x: 1.0, y: 2.0 } diff --git a/examples/ParseErrors.purs b/examples/ParseErrors.purs index 057bda4..4ffbc30 100755 --- a/examples/ParseErrors.purs +++ b/examples/ParseErrors.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (class IsForeign, readJSON, readProp) @@ -26,15 +27,15 @@ main = do -- When trying to parse invalid JSON we catch an exception from -- `JSON.parse` and pass it on. - logShow $ readJSON "not even JSON" :: F String + logShow $ runExcept $ readJSON "not even JSON" :: F String -- When attempting to coerce one type to another we get an error. - logShow $ readJSON "26" :: F Boolean + logShow $ runExcept $ readJSON "26" :: F Boolean -- When parsing fails in an array, we're told at which index the value that -- failed to parse was, along with the reason the value didn't parse. - logShow $ readJSON "[1, true, 3]" :: F (Array Boolean) + logShow $ runExcept $ readJSON "[1, true, 3]" :: F (Array Boolean) -- When parsing fails in an object, we're the name of the property which -- failed to parse was, along with the reason the value didn't parse. - logShow $ readJSON """{ "x": 1, "y": false }""" :: F Point + logShow $ runExcept $ readJSON """{ "x": 1, "y": false }""" :: F Point diff --git a/examples/Union.purs b/examples/Union.purs index 4a7fe38..e65e066 100755 --- a/examples/Union.purs +++ b/examples/Union.purs @@ -4,6 +4,7 @@ import Prelude import Control.Monad.Eff (Eff) import Control.Monad.Eff.Console (CONSOLE, logShow) +import Control.Monad.Except (runExcept) import Data.Foreign (F) import Data.Foreign.Class (class IsForeign, readJSON, readProp) @@ -25,7 +26,7 @@ instance stringListIsForeign :: IsForeign StringList where main :: Eff (console :: CONSOLE) Unit main = do - logShow $ readJSON """ + logShow $ runExcept $ readJSON """ { "nil": false , "head": "Hello" , "tail": @@ -37,7 +38,7 @@ main = do } """ :: F StringList - logShow $ readJSON """ + logShow $ runExcept $ readJSON """ { "nil": false , "head": "Hello" , "tail": diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index e4bc774..52a87e5 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -4,6 +4,7 @@ module Data.Foreign ( Foreign , ForeignError(..) + , MultipleErrors(..) , Prop(..) , F , parseJSON @@ -21,16 +22,21 @@ module Data.Foreign , readNumber , readInt , readArray + , fail , writeObject ) where import Prelude +import Control.Monad.Except (Except, throwError, mapExcept) + import Data.Either (Either(..), either) import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int import Data.List (List) import Data.Maybe (maybe) +import Data.Newtype (class Newtype) +import Data.NonEmpty ((:|)) import Data.NonEmpty as NE import Data.String (toChar) @@ -46,7 +52,7 @@ import Data.String (toChar) -- | - To integrate with external JavaScript libraries. foreign import data Foreign :: * --- | A type for runtime type errors +-- | A type for foreign type errors data ForeignError = ForeignError String | TypeMismatch String String @@ -54,6 +60,9 @@ data ForeignError | ErrorAtProperty String ForeignError | JSONError String +derive instance eqForeignError :: Eq ForeignError +derive instance ordForeignError :: Ord ForeignError + instance showForeignError :: Show ForeignError where show (ForeignError msg) = "(ForeignError " <> msg <> ")" show (ErrorAtIndex i e) = "(ErrorAtIndex " <> show i <> " " <> show e <> ")" @@ -61,8 +70,20 @@ instance showForeignError :: Show ForeignError where show (JSONError s) = "(JSONError " <> show s <> ")" show (TypeMismatch exps act) = "(TypeMismatch " <> show exps <> " " <> show act <> ")" -derive instance eqForeignError :: Eq ForeignError -derive instance ordForeignError :: Ord ForeignError +-- | A type for accumulating multiple `ForeignError`s. +newtype MultipleErrors a = MultipleErrors (NE.NonEmpty List a) + +derive instance newtypeMultipleErrors :: Newtype (MultipleErrors a) _ +derive newtype instance eqMultipleErrors :: Eq a => Eq (MultipleErrors a) +derive newtype instance ordMultipleErrors :: Ord a => Ord (MultipleErrors a) +derive newtype instance functorMultipleErrors :: Functor MultipleErrors + +instance showMultipleErrors :: Show a => Show (MultipleErrors a) where + show (MultipleErrors errs) = "(MultipleErrors " <> show errs <> ")" + +instance semigroupMultipleErrors :: Semigroup (MultipleErrors a) where + append (MultipleErrors (e :| es)) (MultipleErrors es') = + MultipleErrors (e :| es <> NE.oneOf es') renderForeignError :: ForeignError -> String renderForeignError (ForeignError msg) = msg @@ -73,14 +94,13 @@ renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> -- | An error monad, used in this library to encode possible failures when -- | dealing with foreign data. -type F = Either (NE.NonEmpty List ForeignError) +type F a = Except (MultipleErrors ForeignError) a foreign import parseJSONImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r -- | Attempt to parse a JSON string, returning the result as foreign data. parseJSON :: String -> F Foreign -parseJSON json = - runFn3 parseJSONImpl (Left <<< NE.singleton <<< JSONError) Right json +parseJSON json = runFn3 parseJSONImpl (fail <<< JSONError) pure json -- | Coerce any value to the a `Foreign` value. foreign import toForeign :: forall a. a -> Foreign @@ -101,7 +121,7 @@ foreign import tagOf :: Foreign -> String unsafeReadTagged :: forall a. String -> Foreign -> F a unsafeReadTagged tag value | tagOf value == tag = pure (unsafeFromForeign value) - | otherwise = Left (NE.singleton (TypeMismatch tag (tagOf value))) + | otherwise = fail $ TypeMismatch tag (tagOf value) -- | Test whether a foreign value is null foreign import isNull :: Foreign -> Boolean @@ -118,12 +138,10 @@ readString = unsafeReadTagged "String" -- | Attempt to coerce a foreign value to a `Char`. readChar :: Foreign -> F Char -readChar value = either (const error) fromString (readString value) +readChar value = mapExcept (either (const error) fromString) (readString value) where - fromString :: String -> F Char fromString = maybe error pure <<< toChar - error :: F Char - error = Left $ NE.singleton $ TypeMismatch "Char" (tagOf value) + error = Left $ MultipleErrors $ NE.singleton $ TypeMismatch "Char" (tagOf value) -- | Attempt to coerce a foreign value to a `Boolean`. readBoolean :: Foreign -> F Boolean @@ -135,18 +153,20 @@ readNumber = unsafeReadTagged "Number" -- | Attempt to coerce a foreign value to an `Int`. readInt :: Foreign -> F Int -readInt value = either (const error) fromNumber (readNumber value) +readInt value = mapExcept (either (const error) fromNumber) (readNumber value) where - fromNumber :: Number -> F Int fromNumber = maybe error pure <<< Int.fromNumber - error :: F Int - error = Left $ NE.singleton $ TypeMismatch "Int" (tagOf value) + error = Left $ MultipleErrors $ NE.singleton $ TypeMismatch "Int" (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) readArray value | isArray value = pure $ unsafeFromForeign value - | otherwise = Left $ NE.singleton $ TypeMismatch "array" (tagOf value) + | otherwise = fail $ TypeMismatch "array" (tagOf value) + +-- | Throws a failure error in `F`. +fail :: forall a. ForeignError -> F a +fail = throwError <<< MultipleErrors <<< NE.singleton -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Class.purs b/src/Data/Foreign/Class.purs index 5a2b492..d635b97 100644 --- a/src/Data/Foreign/Class.purs +++ b/src/Data/Foreign/Class.purs @@ -16,18 +16,17 @@ module Data.Foreign.Class import Prelude import Control.Alt ((<|>)) +import Control.Monad.Except (Except, mapExcept) import Data.Array (range, zipWith, length) import Data.Bifunctor (lmap) import Data.Either (Either(..)) -import Data.Foreign (F, Foreign, ForeignError(..), Prop(..), parseJSON, readArray, readInt, readNumber, readBoolean, readChar, readString, toForeign) +import Data.Foreign (F, Foreign, MultipleErrors, ForeignError(..), Prop(..), toForeign, parseJSON, readArray, readInt, readNumber, readBoolean, readChar, readString) import Data.Foreign.Index (class Index, errorAt, (!)) import Data.Foreign.Null (Null(..), readNull, writeNull) import Data.Foreign.NullOrUndefined (NullOrUndefined(..), readNullOrUndefined) import Data.Foreign.Undefined (Undefined(..), readUndefined, writeUndefined) -import Data.List (List) import Data.Maybe (maybe) -import Data.NonEmpty as NE import Data.Traversable (sequence) -- | A type class instance for this class can be written for a type if it @@ -80,8 +79,8 @@ readJSON :: forall a. IsForeign a => String -> F a readJSON json = parseJSON json >>= read -- | Attempt to read a foreign value, handling errors using the specified function -readWith :: forall a e. IsForeign a => (NE.NonEmpty List ForeignError -> e) -> Foreign -> Either e a -readWith f = lmap f <<< read +readWith :: forall a e. IsForeign a => (MultipleErrors ForeignError -> e) -> Foreign -> Except e a +readWith f = mapExcept (lmap f) <<< read -- | Attempt to read a property of a foreign value at the specified index readProp :: forall a i. (IsForeign a, Index i) => i -> Foreign -> F a diff --git a/src/Data/Foreign/Index.purs b/src/Data/Foreign/Index.purs index 7aadad2..4fa3e67 100644 --- a/src/Data/Foreign/Index.purs +++ b/src/Data/Foreign/Index.purs @@ -13,10 +13,8 @@ module Data.Foreign.Index import Prelude -import Data.Either (Either(..)) -import Data.Foreign (Foreign, F, ForeignError(..), typeOf, isUndefined, isNull) +import Data.Foreign (Foreign, F, ForeignError(..), typeOf, isUndefined, isNull, fail) import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4) -import Data.NonEmpty as NE -- | This type class identifies types that act like _property indices_. -- | @@ -33,7 +31,7 @@ foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign unsafeReadProp :: forall k. k -> Foreign -> F Foreign unsafeReadProp k value = - runFn4 unsafeReadPropImpl (Left (NE.singleton (TypeMismatch "object" (typeOf value)))) Right k value + runFn4 unsafeReadPropImpl (fail (TypeMismatch "object" (typeOf value))) pure k value -- | Attempt to read a value from a foreign value property prop :: String -> Foreign -> F Foreign diff --git a/src/Data/Foreign/Keys.purs b/src/Data/Foreign/Keys.purs index 8708887..4bb9967 100644 --- a/src/Data/Foreign/Keys.purs +++ b/src/Data/Foreign/Keys.purs @@ -7,16 +7,14 @@ module Data.Foreign.Keys import Prelude -import Data.Either (Either(..)) -import Data.Foreign (F, Foreign, ForeignError(..), typeOf, isUndefined, isNull) -import Data.NonEmpty as NE +import Data.Foreign (F, Foreign, ForeignError(..), typeOf, isUndefined, isNull, fail) foreign import unsafeKeys :: Foreign -> Array String -- | Get an array of the properties defined on a foreign value keys :: Foreign -> F (Array String) keys value - | isNull value = Left $ NE.singleton $ TypeMismatch "object" "null" - | isUndefined value = Left $ NE.singleton $ TypeMismatch "object" "undefined" - | typeOf value == "object" = Right $ unsafeKeys value - | otherwise = Left $ NE.singleton $ TypeMismatch "object" (typeOf value) + | isNull value = fail $ TypeMismatch "object" "null" + | isUndefined value = fail $ TypeMismatch "object" "undefined" + | typeOf value == "object" = pure $ unsafeKeys value + | otherwise = fail $ TypeMismatch "object" (typeOf value) From 910caaeb0694a59fd52aee0b84f43eaf8d3b5d89 Mon Sep 17 00:00:00 2001 From: Gary Burgess Date: Thu, 13 Oct 2016 19:10:16 +0100 Subject: [PATCH 8/8] Use NonEmptyList to capture errors --- bower.json | 2 +- src/Data/Foreign.purs | 28 +++++++--------------------- src/Data/Foreign/Class.purs | 2 +- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/bower.json b/bower.json index 81af8c8..a2bdd3d 100644 --- a/bower.json +++ b/bower.json @@ -26,7 +26,7 @@ "purescript-foldable-traversable": "^2.0.0", "purescript-functions": "^2.0.0", "purescript-integers": "^2.0.0", - "purescript-lists": "^2.0.0", + "purescript-lists": "^2.1.0", "purescript-strings": "^2.0.0", "purescript-transformers": "^2.0.0" }, diff --git a/src/Data/Foreign.purs b/src/Data/Foreign.purs index 52a87e5..43f835f 100644 --- a/src/Data/Foreign.purs +++ b/src/Data/Foreign.purs @@ -33,11 +33,9 @@ import Control.Monad.Except (Except, throwError, mapExcept) import Data.Either (Either(..), either) import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int -import Data.List (List) +import Data.List.NonEmpty (NonEmptyList) +import Data.List.NonEmpty as NEL import Data.Maybe (maybe) -import Data.Newtype (class Newtype) -import Data.NonEmpty ((:|)) -import Data.NonEmpty as NE import Data.String (toChar) -- | A type for _foreign data_. @@ -71,19 +69,7 @@ instance showForeignError :: Show ForeignError where show (TypeMismatch exps act) = "(TypeMismatch " <> show exps <> " " <> show act <> ")" -- | A type for accumulating multiple `ForeignError`s. -newtype MultipleErrors a = MultipleErrors (NE.NonEmpty List a) - -derive instance newtypeMultipleErrors :: Newtype (MultipleErrors a) _ -derive newtype instance eqMultipleErrors :: Eq a => Eq (MultipleErrors a) -derive newtype instance ordMultipleErrors :: Ord a => Ord (MultipleErrors a) -derive newtype instance functorMultipleErrors :: Functor MultipleErrors - -instance showMultipleErrors :: Show a => Show (MultipleErrors a) where - show (MultipleErrors errs) = "(MultipleErrors " <> show errs <> ")" - -instance semigroupMultipleErrors :: Semigroup (MultipleErrors a) where - append (MultipleErrors (e :| es)) (MultipleErrors es') = - MultipleErrors (e :| es <> NE.oneOf es') +type MultipleErrors = NonEmptyList ForeignError renderForeignError :: ForeignError -> String renderForeignError (ForeignError msg) = msg @@ -94,7 +80,7 @@ renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <> -- | An error monad, used in this library to encode possible failures when -- | dealing with foreign data. -type F a = Except (MultipleErrors ForeignError) a +type F a = Except MultipleErrors a foreign import parseJSONImpl :: forall r. Fn3 (String -> r) (Foreign -> r) String r @@ -141,7 +127,7 @@ readChar :: Foreign -> F Char readChar value = mapExcept (either (const error) fromString) (readString value) where fromString = maybe error pure <<< toChar - error = Left $ MultipleErrors $ NE.singleton $ TypeMismatch "Char" (tagOf value) + error = Left $ NEL.singleton $ TypeMismatch "Char" (tagOf value) -- | Attempt to coerce a foreign value to a `Boolean`. readBoolean :: Foreign -> F Boolean @@ -156,7 +142,7 @@ readInt :: Foreign -> F Int readInt value = mapExcept (either (const error) fromNumber) (readNumber value) where fromNumber = maybe error pure <<< Int.fromNumber - error = Left $ MultipleErrors $ NE.singleton $ TypeMismatch "Int" (tagOf value) + error = Left $ NEL.singleton $ TypeMismatch "Int" (tagOf value) -- | Attempt to coerce a foreign value to an array. readArray :: Foreign -> F (Array Foreign) @@ -166,7 +152,7 @@ readArray value -- | Throws a failure error in `F`. fail :: forall a. ForeignError -> F a -fail = throwError <<< MultipleErrors <<< NE.singleton +fail = throwError <<< NEL.singleton -- | A key/value pair for an object to be written as a `Foreign` value. newtype Prop = Prop { key :: String, value :: Foreign } diff --git a/src/Data/Foreign/Class.purs b/src/Data/Foreign/Class.purs index d635b97..d74caf3 100644 --- a/src/Data/Foreign/Class.purs +++ b/src/Data/Foreign/Class.purs @@ -79,7 +79,7 @@ readJSON :: forall a. IsForeign a => String -> F a readJSON json = parseJSON json >>= read -- | Attempt to read a foreign value, handling errors using the specified function -readWith :: forall a e. IsForeign a => (MultipleErrors ForeignError -> e) -> Foreign -> Except e a +readWith :: forall a e. IsForeign a => (MultipleErrors -> e) -> Foreign -> Except e a readWith f = mapExcept (lmap f) <<< read -- | Attempt to read a property of a foreign value at the specified index