|
| 1 | +module Data.Argonaut.Decode.Generic.Rep ( |
| 2 | + class DecodeRep, |
| 3 | + class DecodeRepArgs, |
| 4 | + class DecodeRepFields, |
| 5 | + decodeRep, |
| 6 | + decodeRepArgs, |
| 7 | + decodeRepFields, |
| 8 | + genericDecodeJson |
| 9 | +) where |
| 10 | + |
| 11 | +import Prelude |
| 12 | + |
| 13 | +import Control.Alt ((<|>)) |
| 14 | +import Data.Argonaut.Core (Json, toArray, toObject, toString) |
| 15 | +import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) |
| 16 | +import Data.Array (uncons) |
| 17 | +import Data.Bifunctor (lmap) |
| 18 | +import Data.Either (Either(..)) |
| 19 | +import Data.Generic.Rep as Rep |
| 20 | +import Data.Maybe (Maybe, maybe) |
| 21 | +import Data.StrMap as SM |
| 22 | +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) |
| 23 | + |
| 24 | +class DecodeRep r where |
| 25 | + decodeRep :: Json -> Either String r |
| 26 | + |
| 27 | +instance decodeRepNoConstructors :: DecodeRep Rep.NoConstructors where |
| 28 | + decodeRep _ = Left "Cannot decode empty data type" |
| 29 | + |
| 30 | +instance decodeRepSum :: (DecodeRep a, DecodeRep b) => DecodeRep (Rep.Sum a b) where |
| 31 | + decodeRep j = Rep.Inl <$> decodeRep j <|> Rep.Inr <$> decodeRep j |
| 32 | + |
| 33 | +instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep (Rep.Constructor name a) where |
| 34 | + decodeRep j = do |
| 35 | + let name = reflectSymbol (SProxy :: SProxy name) |
| 36 | + let decodingErr msg = "When decoding a " <> name <> ": " <> msg |
| 37 | + jObj <- mFail (decodingErr "expected an object") (toObject j) |
| 38 | + jTag <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) |
| 39 | + tag <- mFail (decodingErr "'tag' property is not a string") (toString jTag) |
| 40 | + when (tag /= name) $ |
| 41 | + Left $ decodingErr "'tag' property has an incorrect value" |
| 42 | + jValues <- mFail (decodingErr "'values' property is missing") (SM.lookup "values" jObj) |
| 43 | + values <- mFail (decodingErr "'values' property is not an array") (toArray jValues) |
| 44 | + {init, rest} <- lmap decodingErr $ decodeRepArgs values |
| 45 | + when (rest /= []) $ |
| 46 | + Left $ decodingErr "'values' property had too many values" |
| 47 | + pure $ Rep.Constructor init |
| 48 | + |
| 49 | +class DecodeRepArgs r where |
| 50 | + decodeRepArgs :: Array Json -> Either String {init :: r, rest :: Array Json} |
| 51 | + |
| 52 | +instance decodeRepArgsNoArguments :: DecodeRepArgs Rep.NoArguments where |
| 53 | + decodeRepArgs js = Right {init: Rep.NoArguments, rest: js} |
| 54 | + |
| 55 | +instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRepArgs (Rep.Product a b) where |
| 56 | + decodeRepArgs js = do |
| 57 | + {init: a, rest: js'} <- decodeRepArgs js |
| 58 | + {init: b, rest: js''} <- decodeRepArgs js' |
| 59 | + pure {init: Rep.Product a b, rest: js''} |
| 60 | + |
| 61 | +instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where |
| 62 | + decodeRepArgs js = do |
| 63 | + {head, tail} <- mFail "too few values were present" (uncons js) |
| 64 | + {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head |
| 65 | + |
| 66 | +instance decodeRepArgsRec :: (DecodeRepFields fields) => DecodeRepArgs (Rep.Rec fields) where |
| 67 | + decodeRepArgs js = do |
| 68 | + {head, tail} <- mFail "too few values were present" (uncons js) |
| 69 | + jObj <- mFail "record is not encoded as an object" (toObject head) |
| 70 | + {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj |
| 71 | + |
| 72 | +class DecodeRepFields r where |
| 73 | + decodeRepFields :: SM.StrMap Json -> Either String r |
| 74 | + |
| 75 | +instance decodeRepFieldsProduct :: (DecodeRepFields a, DecodeRepFields b) => DecodeRepFields (Rep.Product a b) where |
| 76 | + decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js |
| 77 | + |
| 78 | +instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFields (Rep.Field field a) where |
| 79 | + decodeRepFields js = do |
| 80 | + let name = reflectSymbol (SProxy :: SProxy field) |
| 81 | + value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) |
| 82 | + Rep.Field <$> decodeJson value |
| 83 | + |
| 84 | +-- | Decode `Json` representation of a value which has a `Generic` type. |
| 85 | +genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a |
| 86 | +genericDecodeJson = map Rep.to <<< decodeRep |
| 87 | + |
| 88 | +mFail :: forall a. String -> Maybe a -> Either String a |
| 89 | +mFail msg = maybe (Left msg) Right |
0 commit comments