@@ -0,0 +1,394 @@
17+, class ReadForeignFields
19+, class ReadForeignVariant
24+, class WriteForeignFields
26+, class WriteForeignVariant
33+import Control.Alt ((<|>))
34+import Control.Monad.Except (ExceptT(..), except, runExcept, runExceptT, withExcept)
35+import Data.Array.NonEmpty (NonEmptyArray, fromArray, toArray)
36+import Data.Bifunctor (lmap)
37+import Data.Either (Either(..), hush, note)
38+import Data.Identity (Identity(..))
39+import Data.List.NonEmpty (singleton)
40+import Data.Maybe (Maybe(..), maybe)
41+import Data.Nullable (Nullable, toMaybe, toNullable)
42+import Data.Symbol (class IsSymbol, reflectSymbol)
43+import Data.Traversable (sequence, traverse)
44+import Data.TraversableWithIndex (traverseWithIndex)
45+import Data.Variant (Variant, inj, on)
46+import Effect.Exception (message, try)
47+import Effect.Uncurried as EU
48+import Effect.Unsafe (unsafePerformEffect)
49+import Foreign (F, Foreign, ForeignError(..), MultipleErrors, fail, isNull, isUndefined, readArray, readBoolean, readChar, readInt, readNull, readNumber, readString, tagOf, unsafeFromForeign, unsafeToForeign)
50+import Foreign.Index (readProp)
51+import Foreign.Object (Object)
52+import Foreign.Object as Object
53+import Partial.Unsafe (unsafeCrashWith)
54+import Prim.Row as Row
55+import Prim.RowList (class RowToList, Cons, Nil, RowList)
57+import Record.Builder (Builder)
58+import Record.Builder as Builder
59+import Type.Prelude (Proxy(..))
61+-- | An alias for the Either result of decoding
62+type E a = Either MultipleErrors a
64+-- | Read a JSON string to a type `a` while returning a `MultipleErrors` if the
70+readJSON = runExcept <<< (readImpl <=< parseJSON)
72+-- | Read a JSON string to a type `a` using `F a`. Useful with record types.
77+readJSON' = readImpl <=< parseJSON
79+-- | Read a JSON string to a type `a` while returning `Nothing` if the parsing
85+readJSON_ = hush <<< readJSON
88+foreign import _unsafeStringify :: forall a. a -> String
90+unsafeStringify :: forall a. a -> String
91+unsafeStringify = _unsafeStringify
93+-- | Write a JSON string from a type `a`.
98+writeJSON = _unsafeStringify <<< writeImpl
106+-- | Read a Foreign value to a type
111+read = runExcept <<< readImpl
113+-- | Read a value of any type as Foreign to a type
114+readAsForeign :: forall a b
118+readAsForeign = read <<< unsafeToForeign
126+-- | Read a Foreign value to a type, as a Maybe of type
131+read_ = hush <<< read
133+foreign import _parseJSON :: EU.EffectFn1 String Foreign
135+parseJSON :: String -> F Foreign
139+ <<< lmap (pure <<< ForeignError <<< message)
142+ <<< EU.runEffectFn1 _parseJSON
144+ -- Nate Faubion: "It uses unsafePerformEffect because that’s the only way to catch exceptions and still use the builtin json decoder"
145+ runPure = unsafePerformEffect
147+foreign import _undefined :: Foreign
150+undefined = _undefined
152+-- | A class for reading foreign values to a type
153+class ReadForeign a where
154+ readImpl :: Foreign -> F a
156+instance readForeign :: ReadForeign Foreign where
159+instance readChar :: ReadForeign Char where
162+instance readNumber :: ReadForeign Number where
163+ readImpl = readNumber
165+instance readInt :: ReadForeign Int where
168+instance readString :: ReadForeign String where
169+ readImpl = readString
171+instance readBoolean :: ReadForeign Boolean where
172+ readImpl = readBoolean
174+instance readArray :: ReadForeign a => ReadForeign (Array a) where
175+ readImpl = traverseWithIndex readAtIdx <=< readArray
177+ readAtIdx i f = withExcept (map (ErrorAtIndex i)) (readImpl f)
179+instance readMaybe :: ReadForeign a => ReadForeign (Maybe a) where
180+ readImpl = readNullOrUndefined readImpl
182+ readNullOrUndefined _ value | isNull value || isUndefined value = pure Nothing
183+ readNullOrUndefined f value = Just <$> f value
185+instance readNullable :: ReadForeign a => ReadForeign (Nullable a) where
186+ readImpl o = withExcept (map reformat) $
187+ map toNullable <$> traverse readImpl =<< readNull o
189+ reformat error = case error of
190+ TypeMismatch inner other -> TypeMismatch ("Nullable " <> inner) other
193+instance readObject :: ReadForeign a => ReadForeign (Object.Object a) where
194+ readImpl = sequence <<< Object.mapWithKey (const readImpl) <=< readObject'
196+ readObject' :: Foreign -> F (Object Foreign)
198+ | tagOf value == "Object" = pure $ unsafeFromForeign value
199+ | otherwise = fail $ TypeMismatch "Object" (tagOf value)
202+instance readRecord ::
203+ ( RowToList fields fieldList
204+ , ReadForeignFields fieldList () fields
205+ ) => ReadForeign (Record fields) where
206+ readImpl o = flip Builder.build {} <$> getFields fieldListP o
208+ fieldListP = Proxy :: Proxy fieldList
210+-- | A class for reading foreign values from properties
211+class ReadForeignFields (xs :: RowList Type) (from :: Row Type) (to :: Row Type)
212+ | xs -> from to where
213+ getFields :: Proxy xs
215+ -> F (Builder (Record from) (Record to))
217+instance readFieldsCons ::
220+ , ReadForeignFields tail from from'
221+ , Row.Lacks name from'
222+ , Row.Cons name ty from' to
223+ ) => ReadForeignFields (Cons name ty tail) from to where
224+ getFields _ obj = (compose <$> first) `exceptTApply` rest
227+ value <- withExcept' (readImpl =<< readProp name obj)
228+ pure $ Builder.insert nameP value
229+ rest = getFields tailP obj
230+ nameP = Proxy :: Proxy name
231+ tailP = Proxy :: Proxy tail
232+ name = reflectSymbol nameP
233+ withExcept' = withExcept <<< map $ ErrorAtProperty name
235+exceptTApply :: forall a b e m. Semigroup e => Applicative m => ExceptT e m (a -> b) -> ExceptT e m a -> ExceptT e m b
236+exceptTApply fun a = ExceptT $ applyEither
240+applyEither :: forall e a b. Semigroup e => Either e (a -> b) -> Either e a -> Either e b
241+applyEither (Left e) (Right _) = Left e
242+applyEither (Left e1) (Left e2) = Left (e1 <> e2)
243+applyEither (Right _) (Left e) = Left e
244+applyEither (Right fun) (Right a) = Right (fun a)
246+instance readFieldsNil ::
247+ ReadForeignFields Nil () () where
251+instance readForeignVariant ::
252+ ( RowToList variants rl
253+ , ReadForeignVariant rl variants
254+ ) => ReadForeign (Variant variants) where
255+ readImpl o = readVariantImpl (Proxy :: Proxy rl) o
257+class ReadForeignVariant (xs :: RowList Type) (row :: Row Type)
259+ readVariantImpl :: Proxy xs
263+instance readVariantNil ::
264+ ReadForeignVariant Nil trash where
265+ readVariantImpl _ _ = fail $ ForeignError "Unable to match any variant member."
267+instance readVariantCons ::
270+ , Row.Cons name ty trash row
271+ , ReadForeignVariant tail row
272+ ) => ReadForeignVariant (Cons name ty tail) row where
273+ readVariantImpl _ o = do
274+ obj :: { type :: String, value :: Foreign } <- readImpl o
277+ value :: ty <- readImpl obj.value
278+ pure $ inj namep value
280+ (fail <<< ForeignError $ "Did not match variant tag " <> name)
281+ <|> readVariantImpl (Proxy :: Proxy tail) o
283+ namep = Proxy :: Proxy name
284+ name = reflectSymbol namep
286+-- -- | A class for writing a value into JSON
287+-- -- | need to do this intelligently using Foreign probably, because of null and undefined whatever
288+class WriteForeign a where
289+ writeImpl :: a -> Foreign
291+instance WriteForeign Foreign where
292+ writeImpl = identity
294+instance WriteForeign String where
295+ writeImpl = unsafeToForeign
297+instance WriteForeign Int where
298+ writeImpl = unsafeToForeign
300+instance WriteForeign Char where
301+ writeImpl = unsafeToForeign
303+instance WriteForeign Number where
304+ writeImpl = unsafeToForeign
306+instance WriteForeign Boolean where
307+ writeImpl = unsafeToForeign
309+instance WriteForeign a => WriteForeign (Array a) where
310+ writeImpl xs = unsafeToForeign $ writeImpl <$> xs
312+instance WriteForeign a => WriteForeign (Maybe a) where
313+ writeImpl = maybe undefined writeImpl
315+instance WriteForeign a => WriteForeign (Nullable a) where
316+ writeImpl = maybe (unsafeToForeign $ toNullable Nothing) writeImpl <<< toMaybe
318+instance WriteForeign a => WriteForeign (Object.Object a) where
319+ writeImpl = unsafeToForeign <<< Object.mapWithKey (const writeImpl)
323+ , WriteForeignFields rl row () to
324+ ) => WriteForeign (Record row) where
325+ writeImpl rec = unsafeToForeign $ Builder.build steps {}
327+ rlp = Proxy :: Proxy rl
328+ steps = writeImplFields rlp rec
330+class WriteForeignFields (rl :: RowList Type) row (from :: Row Type) (to :: Row Type)
331+ | rl -> row from to where
332+ writeImplFields :: forall g. g rl -> Record row -> Builder (Record from) (Record to)
337+ , WriteForeignFields tail row from from'
338+ , Row.Cons name ty whatever row
339+ , Row.Lacks name from'
340+ , Row.Cons name Foreign from' to
341+ ) => WriteForeignFields (Cons name ty tail) row from to where
342+ writeImplFields _ rec = result
344+ namep = Proxy :: Proxy name
345+ value = writeImpl $ get namep rec
346+ tailp = Proxy :: Proxy tail
347+ rest = writeImplFields tailp rec
348+ result = Builder.insert namep value <<< rest
350+ WriteForeignFields Nil row () () where
351+ writeImplFields _ _ = identity
355+ , WriteForeignVariant rl row
356+ ) => WriteForeign (Variant row) where
357+ writeImpl variant = writeVariantImpl (Proxy :: Proxy rl) variant
359+class WriteForeignVariant (rl :: RowList Type) (row :: Row Type)
361+ writeVariantImpl :: forall g. g rl -> Variant row -> Foreign
364+ WriteForeignVariant Nil () where
365+ writeVariantImpl _ _ =
366+ -- a PureScript-defined variant cannot reach this path, but a JavaScript FFI one could.
367+ unsafeCrashWith "Variant was not able to be writen row WriteForeign."
372+ , Row.Cons name ty subRow row
373+ , WriteForeignVariant tail subRow
374+ ) => WriteForeignVariant (Cons name ty tail) row where
375+ writeVariantImpl _ variant =
379+ (writeVariantImpl (Proxy :: Proxy tail))
382+ namep = Proxy :: Proxy name
383+ writeVariant value = unsafeToForeign
384+ { type: reflectSymbol namep
385+ , value: writeImpl value
388+instance ReadForeign a => ReadForeign (NonEmptyArray a) where
390+ raw :: Array a <- readImpl f
391+ except $ note (singleton $ ForeignError "Nonempty array expected, got empty array") $ fromArray raw
393+instance writeForeignNEArray :: WriteForeign a => WriteForeign (NonEmptyArray a) where
394+ writeImpl a = writeImpl <<< toArray $ a