GithubHelp home page GithubHelp logo

purescript-foreign-generic's Introduction

purescript-foreign-generic

Build Status

Generic deriving for purescript-foreign.

Example Usage

First, define some data type and derive Generic:

> import Prelude
> import Data.Generic.Rep (class Generic)
> import Data.Show.Generic (genericShow)

> newtype MyRecord = MyRecord { a :: Int }
> derive instance genericMyRecord :: Generic MyRecord _
> instance showMyRecord :: Show MyRecord where show = genericShow

To encode JSON, use genericEncodeJSON:

> import Foreign.Generic (defaultOptions, genericEncodeJSON)

> opts = defaultOptions { unwrapSingleConstructors = true }

> genericEncodeJSON opts (MyRecord { a: 1 })
"{\"a\":1}"

And to decode JSON, use genericDecodeJSON:

> import Control.Monad.Except (runExcept)
> import Foreign.Generic (genericDecodeJSON)

> runExcept (genericDecodeJSON opts "{\"a\":1}" :: _ MyRecord)
(Right (MyRecord { a: 1 }))

Badly formed JSON will result in a useful error, which can be inspected or pretty-printed:

> import Data.Bifunctor (lmap)
> import Foreign (renderForeignError)

> lmap (map renderForeignError) $ runExcept (genericDecodeJSON opts "{\"a\":\"abc\"}" :: _ MyRecord)
(Left
  (NonEmptyList
    (NonEmpty
      "Error at array index 0: (ErrorAtProperty \"a\" (TypeMismatch \"Int\" \"String\"))"
      Nil)))

purescript-foreign-generic's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

purescript-foreign-generic's Issues

De/Encode instance for Data.Map

I'm wondering what the preferred way of writing a de/encode instance for Data.Map would be?

I have an initial implementation in a fork here ( rubenpieters@e2f24ed ).

Some choices I made:

  • Convert the Data.Map from/to a Data.StrMap, then encode/decode it similarly to Data.StrMap's instance
  • All keys are first converted to Strings, so I introduced an EncodeKey/DecodeKey class which can encode or decode keys to/from String respectively

Also in the initial implementation only the first decoding error is reported, I think the current StrMap doesn't report all decoding errors either. But that can definitely be fixed.

There are a lot of possibilities, so maybe it would be interesting to discuss the preferred way of doing it first.

readProp >>= decode helper?

Currently, readProp k f >>= decode (which seems common usage?) fails with, for example:

Type mismatch: expected String, found Null

If you have a complex object you're decoding, for instance, it's not a particularly helpful message. Since we have the key :: String available, maybe it's reasonable to have a helper that will include the key provided in the error message?

My hacky version I wrote to help me debug something:

readDecodeProp ::
  forall a.
  Decode a =>
  String ->
  Foreign ->
  ExceptT (NonEmptyList ForeignError) Identity a
readDecodeProp k f =
  withExceptT annotate $ readProp k f >>= decode
  where
    annotate list = list <> (NEL.singleton $ ForeignError $ "Failed at key: " <> k)

Just an idea. And maybe there's already a helper or better way of writing this that already exists?

Add Tuple handling

I have a piece of data that's represented in JS-land as:

var business = {
  name: "O'Brien's Irish Pub & Restaurant",
  category: ["Irish Pub", "irish-pub"]
};

Which I'd like to represent in PureScript as:

newtype Business = Business {
  name :: String,
  category :: Tuple String String
}

Seems like a common enough use case to add support for Tuple and be able to use generics for such data structure?

RowToList?

Should we make a Decode instance for Records now that RowToList and RowList is available?

Generic for newtype not found when in a Sum type

I have these data structures:

import Data.Generic.Rep (class Generic)

opts :: Options
opts = defaultOptions { unwrapSingleConstructors = true }

data Sum
  = Nullary
  | Unary Int
  | UnaryNewType WrappedInt

newtype WrappedInt
  = WrappedInt Int

derive instance sumGeneric :: Generic Sum _

derive instance wrappedGeneric :: Generic WrappedInt _

If I do

genericEncodeJSON opts (WrappedInt 8)

I corretcly get a 8 but If I do

genericEncodeJSON opts (UnaryNewType (WrappedInt 8))

I get an error about not being able to find the generic instance that was used in the first example:

  No type class instance was found for                                                                                                      
                                           
    Foreign.Generic.Class.Encode WrappedInt
                                           

while applying a function genericEncodeJSON
  of type Generic t0 t1 => GenericEncode t1 => { fieldTransform :: String -> String 
                                               , sumEncoding :: SumEncoding         
                                               , unwrapSingleArguments :: Boolean   
                                               , unwrapSingleConstructors :: Boolean
                                               }                                    
                                               -> t0 -> String                      
  to argument opts
while inferring the type of genericEncodeJSON opts
in value declaration main

What am I doing wrong here?

generic{De,En}code without requirement that every type has a Encode instance

Previously it was possible to derive Generic instances of all my types and use toForeignGeneric to serialize the values. Now it seems that genericEncode/genericDecode require that every single type has a Decode/Encode instance, in addition to a Generic instance.

Even though we have dozens of types, this would be somewhat bearable, but we also use Maybe and other basic types which don't have Decode/Encode instances (why? is it intentional?). So now I need to write newtype wrappers around all the basic types so that I can attach Decode/Encode instances to them… and that's where I gave up.

Is there a reason why the new code requires Decode/Encode instances all the way down instead of relying on Generic alone?

Trying to upgrade to 0.15.4

Hello,
I'm trying to upgrade this library to PS version 0.15.4. My current code is here:
master...garganscript:purescript-foreign-generic:master

However I'm getting this error:

Error found:
in module Test.Types
at test/Types.purs:101:14 - 101:42 (line 101, column 14 - line 101, column 42)

  No type class instance was found for
                                              
    Foreign.Generic.Class.EncodeWithOptions a2
                                              
  The following instance partially overlaps the above constraint, which means the rest of its instance chain will not be considered:

    Foreign.Generic.Class.encodeWithOptionsRecord


while solving type class constraint
                                                       
  Foreign.Generic.Class.GenericEncodeArgs (Argument a2)
                                                       
while applying a function genericEncode
  of type Generic t0 t1 => GenericEncode t1 => { fieldTransform :: String -> String 
                                               , sumEncoding :: SumEncoding         
                                               , unwrapSingleArguments :: Boolean   
                                               , unwrapSingleConstructors :: Boolean
                                               }                                    
                                               -> t0 -> Foreign                     
  to argument defaultOptions
while inferring the type of genericEncode defaultOptions
in value declaration encodeTree

where a2 is a rigid type variable
        bound at (line 0, column 0 - line 0, column 0)
      t0 is an unknown type
      t1 is an unknown type

See https://github.com/purescript/documentation/blob/master/errors/NoInstanceFound.md for more information,
or to contribute content related to this error.


[error] Failed to build.

Any help appreciated.

should maybeAsNull be the default?

because I've just realised it breaks for Maybe (Maybe a) - Nothing and Just Nothing both get mapped to null. This behaviour is potentially useful in some cases, but I think it should probably be opt-in, with a warning in the docs.

readGeneric returns TypeMismatch instead of ErrorAtProperty for missing properties

I'm not sure if this is intentional but after I switch from implementing a custom read to using the readGeneric my error message for missing properties changed from

Left {
  value0:
   ErrorAtProperty {
     value0: 'y',
     value1: TypeMismatch { value0: 'Number', value1: 'Undefined' } } }

to

Left {
  value0: TypeMismatch { value0: 'Number', value1: 'Undefined' } }

I don't know if the ErrorAtProperty value is created when using readProp as I see readGeneric uses specific readXYZ functions based on the spine data. In any event, this makes parsing JS data significantly harder as you don't know where the error occurred and you're told it's a type mismatch when it's really a missing property.

Here is the test case

module Test where

import Prelude
import Data.Foreign
import Data.Foreign.Class
import Data.Foreign.Generic
import Data.Generic

parseOptions :: Options
parseOptions = defaultOptions { unwrapNewtypes = true }

data Vector2 = Vector2 { x :: Number, y :: Number }

derive instance genericVector :: Generic Vector2

-- This version produces a TypeMismatch error when one of the fields is missing
instance vector2IsForeign :: IsForeign Vector2 where
  read = readGeneric parseOptions

-- You can test this version to see a ErrorAtProperty error
-- instance vector2IsForeign :: IsForeign Vector2 where
--   read value = do
--     x <- readProp "x" value
--     y <- readProp "y" value
--     return $ Vector2 { x, y }

parseVector2 :: Foreign -> F Vector2
parseVector2 = read
"use strict";

var Test = require('Test');

console.log(Test.parseVector2({x: 0, y: 0})); // should parse fine and give Right value
console.log(Test.parseVector2({x: 0})); // should error with missing y
console.log(Test.parseVector2({y: 0})); // should error with missing x

Generics not working with nested data types

I am trying to use generics to implement my api library (the following code mocks api request and response). But I always get this error

(NonEmptyList (NonEmpty (ErrorAtProperty "contents" (TypeMismatch "String" "Undefined")) ((ErrorAtProperty "contents" (TypeMismatch "String" "Undefined")) : Nil)))

I understand that when we decode json and if it has nested types then it needs data in certain format. But I cannot provide that from js. What is the best way to decode the json to a type in purescript.

GenericsExample.purs

module GenericsExample where

import Prelude
import Control.Monad.Except

import Data.Either (Either(..))
import Data.Foreign (ForeignError(..))
import Data.Foreign.Class (class Decode, class Encode)
import Data.Foreign.Generic (decodeJSON, defaultOptions, encodeJSON, genericDecode, genericEncode, genericEncodeJSON, genericDecodeJSON)
import Data.Generic (toSignature)
import Data.Generic (class Generic) as G
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Data.Foreign.Generic.Class


type Headers = Array (Array String)
data METHOD = POST | GET | PUT | DELETE
data STATUS = SUCCESS | FAILURE
type StatusCode = Int
type URL = String

newtype Request = Request {
	method :: METHOD,
	url :: String,
	payload :: String,
	headers :: Headers
}

class Requestables a where
	toRequest :: Encode a => METHOD -> URL -> Headers -> a -> Request

class Responsables a where
	fromResponse :: Decode a => String -> a

class ApiRequest a b where
	callApi ::  Requestables a => Responsables b => METHOD -> URL -> Headers -> a -> b

newtype RegisterRequest = RegisterRequest {
  merchantId ::String,
  merchantChannelId ::String,
  mcc ::String,
  merchantCustomerId ::String,
  deviceId ::String,
  os ::String,
  version ::String,
  appVersion ::String,
  packageName ::String,
  manufacturer ::String,
  model ::String,
  merchantRequestId ::String,
  customerMobileNumber ::String,
  customerEmail ::String,
  udfParameters ::String,
  checksum ::String,
  merchantChecksum ::String
}

newtype RegisterResponsePayload = RegisterResponsePayload{
	status:: String,
  merchantId:: String,
  merchantCustomerId:: String,
  merchantChannelId:: String,
  merchantRequestId:: String,
  customerVpa::String,
  customerMobileNumber::String,
  bankCode :: String,
  maskedAccountNumber :: String,
  bankAccountUniqueId:: String,
  customResponse :: String,
  checksum :: String,
  sessionToken :: String
}


newtype RegisterResponse = RegisterResponse {
  statusCode :: String,
	payload :: RegisterResponsePayload,
	status :: String
}

newtype ErrorPayload = ErrorPayload {
  error :: Boolean,
  errorCode :: String,
  errorDescription :: String
}

type ErrorResponse = {
  statusCode :: String,
  status :: String,
  payload :: ErrorPayload
}

derive instance genericRegisterRequest :: Generic RegisterRequest _
instance registerRequest :: Requestables RegisterRequest where
	toRequest method url headers request = Request { method : method , url : url , headers : headers, payload : payload} where 
		payload = genericEncodeJSON (defaultOptions { unwrapSingleConstructors = true }) request


data ResponsePayload a = ResponsePayload a | ERR ErrorResponse

instance decodeResponsePayloadRegisterResponse :: Decode (ResponsePayload RegisterResponse) where
	decode x = genericDecode (defaultOptions { unwrapSingleConstructors = true }) x

instance decodeRegisterResponse :: Decode RegisterResponse where
	decode x = genericDecode (defaultOptions { unwrapSingleConstructors = true }) x

instance decodeErrorPayload :: Decode ErrorPayload where
	decode x = genericDecode (defaultOptions { unwrapSingleConstructors = true }) x

instance decodeRegisterResponsePayload :: Decode RegisterResponsePayload where
	decode x = genericDecode (defaultOptions { unwrapSingleConstructors = true }) x

instance encodeResponsePayloadRegisterResponse :: Encode (ResponsePayload RegisterResponse) where
	encode x = genericEncode (defaultOptions { unwrapSingleConstructors = true }) x

instance encodeRegisterResponse :: Encode RegisterResponse where
	encode x = genericEncode (defaultOptions { unwrapSingleConstructors = true }) x

instance encodeRegisterRequest :: Encode RegisterRequest where
	encode x = genericEncode (defaultOptions { unwrapSingleConstructors = true }) x

instance encodeErrorPayload :: Encode ErrorPayload where
	encode x = genericEncode (defaultOptions { unwrapSingleConstructors = true }) x

instance encodeRegisterResponsePayload :: Encode RegisterResponsePayload where
	encode x = genericEncode (defaultOptions { unwrapSingleConstructors = true }) x

derive instance genericErrorPayload :: Generic ErrorPayload _
derive instance genericRegisterResponsePayload :: Generic RegisterResponsePayload _
derive instance genericRegisterResponse :: Generic RegisterResponse _
derive instance genericResponseRegisterResponse :: Generic (ResponsePayload RegisterResponse) _
	
instance responsablesType :: Responsables (ResponsePayload RegisterResponse) where
	fromResponse response = case runExcept (decodeJSON response) of
																			Right x -> x
																			Left x -> ERR { status : "200", statusCode : "HIHELLO", payload : ErrorPayload {error : true, errorCode : "-50", errorDescription : show x}}

foreign import callApi' :: Request -> String

instance apiRequestRegister :: ApiRequest RegisterRequest (ResponsePayload RegisterResponse) where
	callApi method url headers request = (fromResponse <<< callApi' <<< (toRequest method url headers)) request

test :: RegisterRequest -> ResponsePayload RegisterResponse
test payload = callApi POST "abcdef" [["a", "b"]] payload



main = test $ RegisterRequest { 
						merchantId :"a",
						merchantChannelId :"a",
						mcc :"a",
						merchantCustomerId :"a",
						deviceId :"a",
						os :"a",
						version :"a",
						appVersion :"a",
						packageName :"a",
						manufacturer :"a",
						model :"a",
						merchantRequestId :"a",
						customerMobileNumber :"a",
						customerEmail :"a",
						udfParameters :"a",
						checksum :"a",
						merchantChecksum :"a" 
					}

GenericsExample.js

exports["callApi'"] = function(a) {
	var v = JSON.stringify({
		status : "b",
		statusCode : "b",
		payload : {
			status: "a",
		  merchantId: "a",
		  merchantCustomerId: "a",
		  merchantChannelId: "a",
		  merchantRequestId: "a",
		  customerVpa: "a",
		  customerMobileNumber: "a",
		  bankCode : "a",
		  maskedAccountNumber : "a",
		  bankAccountUniqueId: "a",
		  customResponse : "a",
		  checksum : "a",
		  sessionToken : "a"
		}
	});
	console.log(v);
	return v
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.