Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions src/Foreign.purs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Foreign
, ForeignError(..)
, MultipleErrors(..)
, F
, FT
, renderForeignError
, unsafeToForeign
, unsafeFromForeign
Expand All @@ -29,7 +30,7 @@ module Foreign

import Prelude

import Control.Monad.Except (Except, throwError, mapExcept)
import Control.Monad.Except (Except, ExceptT, mapExceptT, throwError)
import Data.Either (Either(..), either)
import Data.Int as Int
import Data.List.NonEmpty (NonEmptyList)
Expand Down Expand Up @@ -81,6 +82,7 @@ renderForeignError (TypeMismatch exp act) = "Type mismatch: expected " <> exp <>
-- | The `Alt` instance for `Except` allows us to accumulate errors,
-- | unlike `Either`, which preserves only the last error.
type F = Except MultipleErrors
type FT = ExceptT MultipleErrors

-- | Coerce any value to the a `Foreign` value.
-- |
Expand All @@ -105,7 +107,7 @@ 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 :: forall m a. Monad m => String -> Foreign -> FT m a
unsafeReadTagged tag value
| tagOf value == tag = pure (unsafeFromForeign value)
| otherwise = fail $ TypeMismatch tag (tagOf value)
Expand All @@ -120,52 +122,52 @@ foreign import isUndefined :: Foreign -> Boolean
foreign import isArray :: Foreign -> Boolean

-- | Attempt to coerce a foreign value to a `String`.
readString :: Foreign -> F String
readString :: forall m. Monad m => Foreign -> FT m String
readString = unsafeReadTagged "String"

-- | Attempt to coerce a foreign value to a `Char`.
readChar :: Foreign -> F Char
readChar value = mapExcept (either (const error) fromString) (readString value)
readChar :: forall m. Monad m => Foreign -> FT m Char
readChar value = mapExceptT (map $ either (const error) fromString) (readString value)
where
fromString = maybe error pure <<< toChar
error = Left $ NEL.singleton $ TypeMismatch "Char" (tagOf value)

-- | Attempt to coerce a foreign value to a `Boolean`.
readBoolean :: Foreign -> F Boolean
readBoolean :: forall m. Monad m => Foreign -> FT m Boolean
readBoolean = unsafeReadTagged "Boolean"

-- | Attempt to coerce a foreign value to a `Number`.
readNumber :: Foreign -> F Number
readNumber :: forall m. Monad m => Foreign -> FT m Number
readNumber = unsafeReadTagged "Number"

-- | Attempt to coerce a foreign value to an `Int`.
readInt :: Foreign -> F Int
readInt value = mapExcept (either (const error) fromNumber) (readNumber value)
readInt :: forall m. Monad m => Foreign -> FT m Int
readInt value = mapExceptT (map $ either (const error) fromNumber) (readNumber value)
where
fromNumber = maybe error pure <<< Int.fromNumber
error = Left $ NEL.singleton $ TypeMismatch "Int" (tagOf value)

-- | Attempt to coerce a foreign value to an array.
readArray :: Foreign -> F (Array Foreign)
readArray :: forall m. Monad m => Foreign -> FT m (Array Foreign)
readArray value
| isArray value = pure $ unsafeFromForeign value
| otherwise = fail $ TypeMismatch "array" (tagOf value)

readNull :: Foreign -> F (Maybe Foreign)
readNull :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
readNull value
| isNull value = pure Nothing
| otherwise = pure (Just value)

readUndefined :: Foreign -> F (Maybe Foreign)
readUndefined :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
readUndefined value
| isUndefined value = pure Nothing
| otherwise = pure (Just value)

readNullOrUndefined :: Foreign -> F (Maybe Foreign)
readNullOrUndefined :: forall m. Monad m => Foreign -> FT m (Maybe Foreign)
readNullOrUndefined value
| isNull value || isUndefined value = pure Nothing
| otherwise = pure (Just value)

-- | Throws a failure error in `F`.
fail :: forall a. ForeignError -> F a
-- | Throws a failure error in `FT`.
fail :: forall m a. Monad m => ForeignError -> FT m a
fail = throwError <<< NEL.singleton
25 changes: 12 additions & 13 deletions src/Foreign/Index.purs
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,36 @@ import Prelude

import Control.Monad.Except.Trans (ExceptT)

import Foreign (Foreign, F, ForeignError(..), typeOf, isUndefined, isNull, fail)
import Foreign (Foreign, FT, ForeignError(..), typeOf, isUndefined, isNull, fail)
import Data.Function.Uncurried (Fn2, runFn2, Fn4, runFn4)
import Data.Identity (Identity)
import Data.List.NonEmpty (NonEmptyList)

-- | This type class identifies types that act like _property indices_.
-- |
-- | The canonical instances are for `String`s and `Int`s.
class Index i where
index :: Foreign -> i -> F Foreign
class Index i m | i -> m where
index :: Foreign -> i -> FT m Foreign
hasProperty :: i -> Foreign -> Boolean
hasOwnProperty :: i -> Foreign -> Boolean
errorAt :: i -> ForeignError -> ForeignError

class Indexable a where
ix :: forall i. Index i => a -> i -> F Foreign
class Indexable a m | a -> m where
ix :: forall i. Index i m => a -> i -> FT m Foreign

infixl 9 ix as !

foreign import unsafeReadPropImpl :: forall r k. Fn4 r (Foreign -> r) k Foreign r

unsafeReadProp :: forall k. k -> Foreign -> F Foreign
unsafeReadProp :: forall k m. Monad m => k -> Foreign -> FT m Foreign
unsafeReadProp k value =
runFn4 unsafeReadPropImpl (fail (TypeMismatch "object" (typeOf value))) pure k value

-- | Attempt to read a value from a foreign value property
readProp :: String -> Foreign -> F Foreign
readProp :: forall m. Monad m => String -> Foreign -> FT m Foreign
readProp = unsafeReadProp

-- | Attempt to read a value from a foreign value at the specified numeric index
readIndex :: Int -> Foreign -> F Foreign
readIndex :: forall m. Monad m => Int -> Foreign -> FT m Foreign
readIndex = unsafeReadProp

foreign import unsafeHasOwnProperty :: forall k. Fn2 k Foreign Boolean
Expand All @@ -66,20 +65,20 @@ hasPropertyImpl _ value | isUndefined value = false
hasPropertyImpl p value | typeOf value == "object" || typeOf value == "function" = runFn2 unsafeHasProperty p value
hasPropertyImpl _ value = false

instance indexString :: Index String where
instance indexString :: Monad m => Index String m where
index = flip readProp
hasProperty = hasPropertyImpl
hasOwnProperty = hasOwnPropertyImpl
errorAt = ErrorAtProperty

instance indexInt :: Index Int where
instance indexInt :: Monad m => Index Int m where
index = flip readIndex
hasProperty = hasPropertyImpl
hasOwnProperty = hasOwnPropertyImpl
errorAt = ErrorAtIndex

instance indexableForeign :: Indexable Foreign where
instance indexableForeign :: Monad m => Indexable Foreign m where
ix = index

instance indexableExceptT :: Indexable (ExceptT (NonEmptyList ForeignError) Identity Foreign) where
instance indexableExceptT :: Monad m => Indexable (ExceptT (NonEmptyList ForeignError) m Foreign) m where
ix f i = flip index i =<< f
4 changes: 2 additions & 2 deletions src/Foreign/Keys.purs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ module Foreign.Keys

import Prelude

import Foreign (F, Foreign, ForeignError(..), typeOf, isUndefined, isNull, fail)
import Foreign (FT, 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 :: forall m. Monad m => Foreign -> FT m (Array String)
keys value
| isNull value = fail $ TypeMismatch "object" "null"
| isUndefined value = fail $ TypeMismatch "object" "undefined"
Expand Down