diff --git a/.travis.yml b/.travis.yml index fe7482b..d1e8b83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ after_success: - >- test $TRAVIS_TAG && echo $GITHUB_TOKEN | pulp login && - echo y | pulp publish --no-push + echo y | pulp publish --no-push \ No newline at end of file diff --git a/bower.json b/bower.json index 21ac983..4d780f8 100644 --- a/bower.json +++ b/bower.json @@ -16,13 +16,13 @@ }, "license": "MIT", "dependencies": { - "purescript-generics": "^4.0.0", - "purescript-argonaut-core": "^3.1.0", - "purescript-argonaut-codecs": "^3.0.0", - "purescript-generics-rep": "^5.1.0" + "purescript-argonaut-core": "^4.0.1", + "purescript-argonaut-codecs": "^4.0.1", + "purescript-generics-rep": "^6.0.0", + "purescript-record": "^1.0.0" }, "devDependencies": { - "purescript-assert": "^3.0.0", - "purescript-console": "^3.0.0" + "purescript-assert": "^4.0.0", + "purescript-console": "^4.1.0" } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 90bef43..049d550 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "test": "pulp test" }, "devDependencies": { - "pulp": "^11.0.0", + "pulp": "^12.2.0", "purescript-psa": "^0.5.0", - "purescript": "^0.11.1", + "purescript": "^0.12.0", "rimraf": "^2.6.1" } -} +} \ No newline at end of file diff --git a/src/Data/Argonaut/Decode/Generic.purs b/src/Data/Argonaut/Decode/Generic.purs deleted file mode 100644 index 5addf86..0000000 --- a/src/Data/Argonaut/Decode/Generic.purs +++ /dev/null @@ -1,58 +0,0 @@ -module Data.Argonaut.Decode.Generic ( - gDecodeJson, - gDecodeJson' -) where - -import Prelude - -import Data.Argonaut.Core (Json, toArray, toBoolean, toNumber, toObject, toString) -import Data.Array (zipWithA) -import Data.Either (Either(..)) -import Data.Foldable (find) -import Data.Generic (class Generic, GenericSpine(..), GenericSignature(..), fromSpine, toSignature) -import Data.Int (fromNumber) -import Data.Maybe (maybe, Maybe(..)) -import Data.String (toChar) -import Data.StrMap as SM -import Data.Traversable (traverse, for) -import Type.Proxy (Proxy(..)) - --- | Decode `Json` representation of a value which has a `Generic` type. -gDecodeJson :: forall a. Generic a => Json -> Either String a -gDecodeJson - = maybe (Left "fromSpine failed") Right - <<< fromSpine - <=< gDecodeJson' (toSignature (Proxy :: Proxy a)) - --- | Decode `Json` representation of a `GenericSpine`. -gDecodeJson' :: GenericSignature -> Json -> Either String GenericSpine -gDecodeJson' signature json = case signature of - SigNumber -> SNumber <$> mFail "Expected a number" (toNumber json) - SigInt -> SInt <$> mFail "Expected an integer number" (fromNumber =<< toNumber json) - SigString -> SString <$> mFail "Expected a string" (toString json) - SigChar -> SChar <$> mFail "Expected a char" (toChar =<< toString json) - SigBoolean -> SBoolean <$> mFail "Expected a boolean" (toBoolean json) - SigUnit -> pure SUnit - SigArray thunk -> do - jArr <- mFail "Expected an array" $ toArray json - SArray <$> traverse (map const <<< gDecodeJson' (thunk unit)) jArr - SigRecord props -> do - jObj <- mFail "Expected an object" $ toObject json - SRecord <$> for props \({recLabel: lbl, recValue: val}) -> do - pf <- mFail ("'" <> lbl <> "' property missing") (SM.lookup lbl jObj) - sp <- gDecodeJson' (val unit) pf - pure { recLabel: lbl, recValue: const sp } - SigProd typeConstr alts -> do - let decodingErr msg = "When decoding a " <> typeConstr <> ": " <> msg - jObj <- mFail (decodingErr "expected an object") (toObject json) - tagJson <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) - tag <- mFail (decodingErr "'tag' property is not a string") (toString tagJson) - case find ((tag == _) <<< _.sigConstructor) alts of - Nothing -> Left (decodingErr ("'" <> tag <> "' isn't a valid constructor")) - Just { sigValues: sigValues } -> do - vals <- mFail (decodingErr "'values' array is missing") (toArray =<< SM.lookup "values" jObj) - sps <- zipWithA (\k -> gDecodeJson' (k unit)) sigValues vals - pure (SProd tag (const <$> sps)) - where - mFail :: forall a. String -> Maybe a -> Either String a - mFail msg = maybe (Left msg) Right diff --git a/src/Data/Argonaut/Decode/Generic/Rep.purs b/src/Data/Argonaut/Decode/Generic/Rep.purs index dc4c736..1783782 100644 --- a/src/Data/Argonaut/Decode/Generic/Rep.purs +++ b/src/Data/Argonaut/Decode/Generic/Rep.purs @@ -1,11 +1,11 @@ module Data.Argonaut.Decode.Generic.Rep ( class DecodeRep, class DecodeRepArgs, - class DecodeRepFields, + class DecodeRepRowList, class DecodeLiteral, decodeRep, decodeRepArgs, - decodeRepFields, + decodeRepRowList, genericDecodeJson, decodeLiteralSum, decodeLiteralSumWithTransform, @@ -21,10 +21,16 @@ import Data.Array (uncons) import Data.Bifunctor (lmap) import Data.Either (Either(..)) import Data.Generic.Rep as Rep -import Data.Maybe (Maybe, maybe) -import Data.StrMap as SM +import Data.Maybe (Maybe(..), maybe) import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) +import Foreign.Object as FO import Partial.Unsafe (unsafeCrashWith) +import Prim.Row as Row +import Prim.RowList (class RowToList, Cons, Nil, kind RowList) +import Prim.TypeError (class Fail, Text) +import Record.Builder (Builder) +import Record.Builder as Builder +import Type.Data.RowList (RLProxy(..)) class DecodeRep r where decodeRep :: Json -> Either String r @@ -40,11 +46,11 @@ instance decodeRepConstructor :: (IsSymbol name, DecodeRepArgs a) => DecodeRep ( let name = reflectSymbol (SProxy :: SProxy name) let decodingErr msg = "When decoding a " <> name <> ": " <> msg jObj <- mFail (decodingErr "expected an object") (toObject j) - jTag <- mFail (decodingErr "'tag' property is missing") (SM.lookup "tag" jObj) + jTag <- mFail (decodingErr "'tag' property is missing") (FO.lookup "tag" jObj) tag <- mFail (decodingErr "'tag' property is not a string") (toString jTag) when (tag /= name) $ Left $ decodingErr "'tag' property has an incorrect value" - jValues <- mFail (decodingErr "'values' property is missing") (SM.lookup "values" jObj) + jValues <- mFail (decodingErr "'values' property is missing") (FO.lookup "values" jObj) values <- mFail (decodingErr "'values' property is not an array") (toArray jValues) {init, rest} <- lmap decodingErr $ decodeRepArgs values when (rest /= []) $ @@ -63,28 +69,52 @@ instance decodeRepArgsProduct :: (DecodeRepArgs a, DecodeRepArgs b) => DecodeRep {init: b, rest: js''} <- decodeRepArgs js' pure {init: Rep.Product a b, rest: js''} -instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where +instance decodeRepRecordArgument :: + ( RowToList row rl + , DecodeRepRowList rl () row + ) => DecodeRepArgs (Rep.Argument (Record row)) where decodeRepArgs js = do - {head, tail} <- mFail "too few values were present" (uncons js) - {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head - -instance decodeRepArgsRec :: (DecodeRepFields fields) => DecodeRepArgs (Rep.Rec fields) where + {head, tail} <- mFail "to few values were present" (uncons js) + obj <- mFail "no json object" (toObject head) + steps <- decodeRepRowList rlp obj + let arg = Rep.Argument $ Builder.build steps {} + pure {init: arg, rest: tail} + where + rlp :: RLProxy rl + rlp = RLProxy + +else instance decodeRepArgsArgument :: (DecodeJson a) => DecodeRepArgs (Rep.Argument a) where decodeRepArgs js = do {head, tail} <- mFail "too few values were present" (uncons js) - jObj <- mFail "record is not encoded as an object" (toObject head) - {init: _, rest: tail} <<< Rep.Rec <$> decodeRepFields jObj - -class DecodeRepFields r where - decodeRepFields :: SM.StrMap Json -> Either String r + {init: _, rest: tail} <<< Rep.Argument <$> decodeJson head -instance decodeRepFieldsProduct :: (DecodeRepFields a, DecodeRepFields b) => DecodeRepFields (Rep.Product a b) where - decodeRepFields js = Rep.Product <$> decodeRepFields js <*> decodeRepFields js -instance decodeRepFieldsField :: (IsSymbol field, DecodeJson a) => DecodeRepFields (Rep.Field field a) where - decodeRepFields js = do - let name = reflectSymbol (SProxy :: SProxy field) - value <- mFail ("the field '" <> name <> "' is not present") (SM.lookup name js) - Rep.Field <$> decodeJson value +class DecodeRepRowList (rl :: RowList) (from :: #Type) (to :: #Type) | rl -> from to where + decodeRepRowList :: forall g . g rl -> FO.Object Json -> Either String (Builder (Record from) (Record to)) + +instance decodeRepRowListNil :: DecodeRepRowList Nil () () where + decodeRepRowList _ _ = pure identity + +instance decodeRepRowListCons :: + ( DecodeJson ty + , IsSymbol name + , DecodeRepRowList tail from from' + , Row.Lacks name from' + , Row.Cons name ty from' to + ) => DecodeRepRowList (Cons name ty tail) from to where + decodeRepRowList _ obj = do + value :: ty <- (error $ FO.lookup name obj) >>= decodeJson + rest <- decodeRepRowList tailp obj + let + first :: Builder (Record from') (Record to) + first = Builder.insert namep value + pure $ first <<< rest + where + namep = SProxy :: SProxy name + tailp = RLProxy :: RLProxy tail + name = reflectSymbol namep + error Nothing = Left ("error while decoding field " <> name) + error (Just a) = Right a -- | Decode `Json` representation of a value which has a `Generic` type. genericDecodeJson :: forall a r. Rep.Generic a r => DecodeRep r => Json -> Either String a @@ -95,7 +125,7 @@ mFail msg = maybe (Left msg) Right -- | A function for decoding `Generic` sum types using string literal representations decodeLiteralSum :: forall a r. Rep.Generic a r => DecodeLiteral r => Json -> Either String a -decodeLiteralSum = decodeLiteralSumWithTransform id +decodeLiteralSum = decodeLiteralSumWithTransform identity -- | A function for decoding `Generic` sum types using string literal representations -- | Takes a function for transforming the tag name in encoding @@ -117,7 +147,9 @@ instance decodeLiteralConstructor :: (IsSymbol name) => DecodeLiteral (Rep.Const Left $ decodingErr "string literal " <> tag <> " had an incorrect value." pure $ Rep.Constructor (Rep.NoArguments) -type FailMessage = """`decodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type.""" + +type FailMessage = + Text "`decodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type." instance decodeLiteralConstructorCannotTakeProduct :: Fail FailMessage diff --git a/src/Data/Argonaut/Encode/Generic.purs b/src/Data/Argonaut/Encode/Generic.purs deleted file mode 100644 index 6a6db07..0000000 --- a/src/Data/Argonaut/Encode/Generic.purs +++ /dev/null @@ -1,38 +0,0 @@ -module Data.Argonaut.Encode.Generic ( - gEncodeJson, - gEncodeJson' -) where - -import Prelude - -import Data.Argonaut.Encode.Class (encodeJson) -import Data.Argonaut.Core (Json(), jsonNull, fromBoolean, fromNumber, fromString, fromArray, fromObject) -import Data.Int (toNumber) -import Data.Foldable (foldr) -import Data.Generic (class Generic, GenericSpine(..), toSpine) -import Data.String (singleton) -import Data.StrMap as SM - --- | Encode any `Generic` data structure into `Json`. -gEncodeJson :: forall a. Generic a => a -> Json -gEncodeJson = gEncodeJson' <<< toSpine - --- | Encode `GenericSpine` into `Json`. -gEncodeJson' :: GenericSpine -> Json -gEncodeJson' = case _ of - SInt x -> fromNumber $ toNumber x - SString x -> fromString x - SChar x -> fromString $ singleton x - SNumber x -> fromNumber x - SBoolean x -> fromBoolean x - SArray thunks -> fromArray (gEncodeJson' <<< (unit # _) <$> thunks) - SUnit -> jsonNull - SProd constr args -> - fromObject - $ SM.insert "tag" (encodeJson constr) - $ SM.singleton "values" (encodeJson (gEncodeJson' <<< (unit # _) <$> args)) - SRecord fields -> - fromObject $ foldr addField SM.empty fields - where - addField field = - SM.insert field.recLabel (gEncodeJson' $ field.recValue unit) diff --git a/src/Data/Argonaut/Encode/Generic/Rep.purs b/src/Data/Argonaut/Encode/Generic/Rep.purs index 48c47ee..3573868 100644 --- a/src/Data/Argonaut/Encode/Generic/Rep.purs +++ b/src/Data/Argonaut/Encode/Generic/Rep.purs @@ -2,10 +2,12 @@ module Data.Argonaut.Encode.Generic.Rep ( class EncodeRep, class EncodeRepArgs, class EncodeRepFields, + class EncodeRepRowList, class EncodeLiteral, encodeRep, encodeRepArgs, encodeRepFields, + encodeRepRowList, genericEncodeJson, encodeLiteralSum, encodeLiteralSumWithTransform, @@ -17,9 +19,14 @@ import Prelude import Data.Argonaut.Core (Json, fromArray, fromObject, fromString) import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson) import Data.Generic.Rep as Rep -import Data.StrMap as SM import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) +import Foreign.Object as FO import Partial.Unsafe (unsafeCrashWith) +import Prim.Row as Row +import Prim.RowList (class RowToList, Cons, Nil, kind RowList) +import Prim.TypeError (class Fail, Text) +import Record (get) +import Type.Data.RowList (RLProxy(..)) class EncodeRep r where encodeRep :: r -> Json @@ -34,9 +41,9 @@ instance encodeRepSum :: (EncodeRep a, EncodeRep b) => EncodeRep (Rep.Sum a b) w instance encodeRepConstructor :: (IsSymbol name, EncodeRepArgs a) => EncodeRep (Rep.Constructor name a) where encodeRep (Rep.Constructor a) = fromObject - $ SM.insert "tag" (fromString (reflectSymbol (SProxy :: SProxy name))) - $ SM.insert "values" (fromArray (encodeRepArgs a)) - $ SM.empty + $ FO.insert "tag" (fromString (reflectSymbol (SProxy :: SProxy name))) + $ FO.insert "values" (fromArray (encodeRepArgs a)) + $ FO.empty class EncodeRepArgs r where encodeRepArgs :: r -> Array Json @@ -47,23 +54,36 @@ instance encodeRepArgsNoArguments :: EncodeRepArgs Rep.NoArguments where instance encodeRepArgsProduct :: (EncodeRepArgs a, EncodeRepArgs b) => EncodeRepArgs (Rep.Product a b) where encodeRepArgs (Rep.Product a b) = encodeRepArgs a <> encodeRepArgs b -instance encodeRepArgsArgument :: (EncodeJson a) => EncodeRepArgs (Rep.Argument a) where - encodeRepArgs (Rep.Argument a) = [encodeJson a] +instance encodeRepRecordArgument :: (RowToList row rl, EncodeRepRowList rl row) => EncodeRepArgs (Rep.Argument (Record row)) where + encodeRepArgs (Rep.Argument rec) = [ fromObject (encodeRepRowList rlp rec FO.empty) ] + where rlp = RLProxy :: RLProxy rl -instance encodeRepArgsRec :: (EncodeRepFields fields) => EncodeRepArgs (Rep.Rec fields) where - encodeRepArgs (Rep.Rec fields) = [fromObject $ encodeRepFields fields] +else instance encodeRepArgsArgument :: (EncodeJson a) => EncodeRepArgs (Rep.Argument a) where + encodeRepArgs (Rep.Argument a) = [encodeJson a] class EncodeRepFields r where - encodeRepFields :: r -> SM.StrMap Json + encodeRepFields :: r -> FO.Object Json instance encodeRepFieldsProduct :: (EncodeRepFields a, EncodeRepFields b) => EncodeRepFields (Rep.Product a b) where encodeRepFields (Rep.Product a b) = - SM.union (encodeRepFields a) (encodeRepFields b) + FO.union (encodeRepFields a) (encodeRepFields b) + + +class EncodeRepRowList (rl :: RowList) (row :: #Type) | rl -> row where + encodeRepRowList :: forall g . g rl -> Record row -> (FO.Object Json -> FO.Object Json) + +instance encodeRepRowListNil :: EncodeRepRowList Nil row where + encodeRepRowList _ _ = \obj -> obj + +instance encodeRepRowListCons :: (EncodeJson ty, IsSymbol name, EncodeRepRowList tail row, Row.Cons name ty ignore row) => EncodeRepRowList (Cons name ty tail) row where + encodeRepRowList _ rec = \obj -> FO.insert (reflectSymbol namep) (encodeJson value) (cont obj) + where + namep = SProxy :: SProxy name + value = get namep rec + tailp = RLProxy :: RLProxy tail + cont = encodeRepRowList tailp rec + -instance encodeRepFieldsField :: (IsSymbol field, EncodeJson a) => EncodeRepFields (Rep.Field field a) where - encodeRepFields (Rep.Field a) = - SM.singleton (reflectSymbol (SProxy :: SProxy field)) - (encodeJson a) -- | Encode any `Generic` data structure into `Json`. genericEncodeJson :: forall a r. Rep.Generic a r => EncodeRep r => a -> Json @@ -71,7 +91,7 @@ genericEncodeJson = encodeRep <<< Rep.from -- | A function for encoding `Generic` sum types using string literal representations encodeLiteralSum :: forall a r. Rep.Generic a r => EncodeLiteral r => a -> Json -encodeLiteralSum = encodeLiteralSumWithTransform id +encodeLiteralSum = encodeLiteralSumWithTransform identity -- | A function for encoding `Generic` sum types using string literal representations -- | Takes a function for transforming the tag name in encoding @@ -88,7 +108,8 @@ instance encodeLiteralSumInst :: (EncodeLiteral a, EncodeLiteral b) => EncodeLit instance encodeLiteralConstructor :: (IsSymbol name) => EncodeLiteral (Rep.Constructor name (Rep.NoArguments)) where encodeLiteral tagNameTransform _ = fromString <<< tagNameTransform $ reflectSymbol (SProxy :: SProxy name) -type FailMessage = """`encodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type.""" +type FailMessage = + Text """`encodeLiteralSum` can only be used with sum types, where all of the constructors are nullary. This is because a string literal cannot be encoded into a product type.""" instance encodeLiteralConstructorCannotBeProduct :: Fail FailMessage diff --git a/test/Main.purs b/test/Main.purs index bbcd087..defe6c3 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -4,8 +4,8 @@ module Test.Main import Prelude -import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE, log) +import Effect (Effect) +import Effect.Console (log) import Data.Argonaut.Core (stringify) import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson) import Data.Argonaut.Decode.Generic.Rep (class DecodeLiteral, decodeLiteralSumWithTransform, genericDecodeJson) @@ -17,7 +17,7 @@ import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) import Data.String (toLower, toUpper) import Partial.Unsafe (unsafePartial) -import Test.Assert (ASSERT, assert) +import Test.Assert (assert) data Example = Either (Either String Example) @@ -43,23 +43,23 @@ derive instance genericLiteralStringExample :: Generic LiteralStringExample _ instance showLiteralStringExample :: Show LiteralStringExample where show a = genericShow a instance encodeJsonLiteralStringExample :: EncodeJson LiteralStringExample where - encodeJson a = encodeLiteralSumWithTransform id a + encodeJson a = encodeLiteralSumWithTransform identity a instance decodeJsonLiteralStringExample :: DecodeJson LiteralStringExample where - decodeJson a = decodeLiteralSumWithTransform id a + decodeJson a = decodeLiteralSumWithTransform identity a -main :: forall eff. Eff (assert :: ASSERT, console :: CONSOLE | eff) Unit +main :: Effect Unit main = do example $ Either $ Left "foo" example $ Either $ Right $ Either $ Left "foo" example $ Record {foo: 42, bar: "bar"} example $ Product 1 2 $ Either $ Left "foo" example $ Frikandel - testLiteralSumWithTransform id Frikandel "\"Frikandel\"" + testLiteralSumWithTransform identity Frikandel "\"Frikandel\"" testLiteralSumWithTransform toUpper Frikandel "\"FRIKANDEL\"" testLiteralSumWithTransform toLower Frikandel "\"frikandel\"" where - example :: forall a. Show a => Eq a => EncodeJson a => DecodeJson a => a -> Eff _ Unit + example :: forall a. Show a => Eq a => EncodeJson a => DecodeJson a => a -> Effect Unit example original = do let json = encodeJson original let parsed = decodeJson json @@ -77,7 +77,7 @@ main = do => (String -> String) -> a -> String - -> Eff _ Unit + -> Effect Unit testLiteralSumWithTransform tagNameTransform original string = do let json = encodeLiteralSumWithTransform tagNameTransform original let parsed = decodeLiteralSumWithTransform tagNameTransform json