Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
This module provides Haskell support for decoding and encoding the Candid data format. See https://github.com/dfinity/candid/blob/master/spec/Candid.md for the official Candid specification.
Synopsis
- encode :: CandidArg a => a -> ByteString
- encodeBuilder :: forall a. CandidArg a => a -> Builder
- decode :: forall a. CandidArg a => ByteString -> Either String a
- class (Typeable a, CandidVal (AsCandid a)) => Candid a where
- type AsCandid a
- toCandid :: a -> AsCandid a
- fromCandid :: AsCandid a -> a
- type CandidRow r = (Typeable r, AllUniqueLabels r, AllUniqueLabels (Map (Either String) r), Forall r Candid, Forall r Unconstrained1)
- type CandidArg a = (CandidSeq (AsTuple a), Tuplable a, Typeable a)
- class Typeable a => CandidVal a
- seqDesc :: forall a. CandidArg a => SeqDesc
- data SeqDesc
- tieKnot :: SeqDesc -> [Type Void]
- typeDesc :: forall a. Candid a => Type Void
- newtype Unary a = Unary {
- unUnary :: a
- newtype Principal = Principal {}
- prettyPrincipal :: Principal -> Text
- parsePrincipal :: Text -> Either String Principal
- data Reserved = Reserved
- data FuncRef r = FuncRef {}
- data AnnTrue
- data AnnFalse
- newtype ServiceRef (r :: Row Type) = ServiceRef {}
- newtype AsRecord a = AsRecord {
- unAsRecord :: a
- newtype AsVariant a = AsVariant {
- unAsVariant :: a
- type CandidService m r = (Forall r (CandidMethod m), AllUniqueLabels r)
- type RawService m = Text -> ByteString -> m ByteString
- toCandidService :: forall m r. CandidService m r => (forall a. String -> m a) -> RawService m -> Rec r
- fromCandidService :: forall m r. CandidService m r => (forall a. Text -> m a) -> (forall a. String -> m a) -> Rec r -> RawService m
- candid :: QuasiQuoter
- candidFile :: QuasiQuoter
- candidType :: QuasiQuoter
- candidDefs :: QuasiQuoter
- candidDefsFile :: QuasiQuoter
- data Type a
- data MethodType a = MethodType {
- methParams :: [Type a]
- methResults :: [Type a]
- methQuery :: Bool
- methOneway :: Bool
- type Fields a = [(FieldName, Type a)]
- data FieldName
- labledField :: Text -> FieldName
- hashedField :: Word32 -> FieldName
- fieldHash :: FieldName -> Word32
- escapeFieldName :: FieldName -> Text
- unescapeFieldName :: Text -> FieldName
- candidHash :: Text -> Word32
- data Value
- = NumV Scientific
- | NatV Natural
- | Nat8V Word8
- | Nat16V Word16
- | Nat32V Word32
- | Nat64V Word64
- | IntV Integer
- | Int8V Int8
- | Int16V Int16
- | Int32V Int32
- | Int64V Int64
- | Float32V Float
- | Float64V Double
- | BoolV Bool
- | TextV Text
- | NullV
- | ReservedV
- | OptV (Maybe Value)
- | VecV (Vector Value)
- | RecV [(FieldName, Value)]
- | TupV [Value]
- | VariantV FieldName Value
- | FuncV Principal Text
- | ServiceV Principal
- | PrincipalV Principal
- | BlobV ByteString
- | AnnV Value (Type Void)
- | FutureV
- isSubtypeOf :: (Pretty k1, Pretty k2, Ord k1, Ord k2) => Type (Ref k1 Type) -> Type (Ref k2 Type) -> Either String ()
- decodeVals :: ByteString -> Either String (SeqDesc, [Value])
- fromCandidVals :: CandidArg a => [Value] -> Either String a
- toCandidVals :: CandidArg a => a -> [Value]
- encodeDynValues :: [Value] -> Either String Builder
- encodeTextual :: String -> Either String ByteString
- data DidFile
- parseDid :: String -> Either String DidFile
- parseValue :: String -> Either String Value
- parseValues :: String -> Either String [Value]
Tutorial
Candid is inherently typed, so before encoding or decoding, you have to indicate the types to use. In most cases, you can use Haskell types for that:
Haskell types
The easiest way is to use this library is to use the canonical Haskell types. Any type that is an instance of Candid
can be used:
>>>
encode ([True, False], Just 100)
"DIDL\STXm~n|\STX\NUL\SOH\STX\SOH\NUL\SOH\228\NUL">>>
decode (encode ([True, False], Just 100)) == Right ([True, False], Just 100)
True
Here, no type annotations are needed, the library can infer them from the types of the Haskell values. You can see the Candid types used using seqDesc
(with tieKnot
) for an argument sequence, or typeDesc
for a single type:
>>>
:type +d ([True, False], Just 100)
([True, False], Just 100) :: ([Bool], Maybe Integer)>>>
:set -XTypeApplications
>>>
pretty (tieKnot (seqDesc @([Bool], Maybe Integer)))
(vec bool, opt int)
This library is integrated with the row-types
library, so you can use their
records directly:
>>>
:set -XOverloadedLabels
>>>
import Data.Row
>>>
encode (#foo .== [True, False] .+ #bar .== Just 100)
"DIDL\ETXl\STX\211\227\170\STX\SOH\134\142\183\STX\STXn|m~\SOH\NUL\SOH\228\NUL\STX\SOH\NUL">>>
:set -XDataKinds -XTypeOperators
>>>
pretty (typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== [Bool])))
record {bar : opt int; foo : vec bool}
NB: typeDesc
cannot work with recursive types, but see seqDesc
together with tieKnot
.
Custom types
If you want to use your own types directly, you have to declare an instance of the Candid
type class. In this instance, you indicate a canonical Haskell type to describe how your type should serialize, and provide conversion functions to the corresponding AsCandid
.
>>>
:set -XTypeFamilies
>>>
newtype Age = Age Integer
>>>
:{
instance Candid Age where type AsCandid Age = Integer toCandid (Age i) = i fromCandid = Age :}
>>>
encode (Age 42)
"DIDL\NUL\SOH|*"
This is more or less the only way to introduce recursive types:
>>>
data Peano = N | S Peano deriving (Show, Eq)
>>>
:{
instance Candid Peano where type AsCandid Peano = Maybe Peano toCandid N = Nothing toCandid (S p) = Just p fromCandid Nothing = N fromCandid (Just p) = S p :}
>>>
peano = S (S (S N))
>>>
encode peano
"DIDL\SOHn\NUL\SOH\NUL\SOH\SOH\SOH\NUL"
Generic types
Especially for Haskell record types, you can use magic involving generic types to create the Candid
instance automatically. The best way is using the DerivingVia
langauge extension, using the AsRecord
newtype to indicate that this strategy should be used:
>>>
:set -XDerivingVia -XDeriveGeneric -XUndecidableInstances
>>>
import GHC.Generics (Generic)
>>>
:{
data SimpleRecord = SimpleRecord { foo :: [Bool], bar :: Maybe Integer } deriving Generic deriving Candid via (AsRecord SimpleRecord) :}
>>>
pretty (typeDesc @SimpleRecord)
record {bar : opt int; foo : vec bool}>>>
encode (SimpleRecord { foo = [True, False], bar = Just 100 })
"DIDL\ETXl\STX\211\227\170\STX\SOH\134\142\183\STX\STXn|m~\SOH\NUL\SOH\228\NUL\STX\SOH\NUL"
Unfortunately, this feature requires UndecidableInstances
.
This works for variants too:
>>>
:{
data Shape = Point () | Sphere Double | Rectangle (Double, Double) deriving Generic deriving Candid via (AsVariant Shape) :}
>>>
pretty (typeDesc @Shape)
variant {Point; Rectangle : record {0 : float64; 1 : float64}; Sphere : float64}>>>
encode (Rectangle (100,100))
"DIDL\STXk\ETX\176\200\244\205\ENQ\DEL\143\232\190\218\v\SOH\173\198\172\140\SIrl\STX\NULr\SOHr\SOH\NUL\SOH\NUL\NUL\NUL\NUL\NUL\NULY@\NUL\NUL\NUL\NUL\NUL\NULY@"
Because data constructors are capitalized in Haskell, you cannot derive enums or variants with lower-case names. Also, nullary data constructors are not supported by row-types
, and thus here, even though they would nicely map onto variants with arguments of type 'null
.
Candid services
Very likely you want to either implement or use whole Candid interfaces. In order to apply the encoding/decoding in one go, you can use fromCandidService
and toCandidService
. These convert between a raw service (RawService
, takes a method name and bytes, and return bytes), and a typed CandidService
(expressed as an Rec
record).
Let us create a simple service:
>>>
:set -XOverloadedLabels
>>>
import Data.Row
>>>
import Data.Row.Internal
>>>
import Data.IORef
>>>
c <- newIORef 0
>>>
let service = #get .== (\() -> readIORef c) .+ #inc .== (\d -> modifyIORef c (d +))
>>>
service .! #get $ ()
0>>>
service .! #inc $ 5
>>>
service .! #get $ ()
5
For convenience, we name its type
>>>
:t service
service :: Rec ('R '[ "get" ':-> (() -> IO Integer), "inc" ':-> (Integer -> IO ())])>>>
:set -XTypeOperators -XDataKinds -XFlexibleContexts
>>>
type Interface = 'R '[ "get" ':-> (() -> IO Integer), "inc" ':-> (Integer -> IO ())]
Now we can turn this into a raw service operating on bytes:
>>>
let raw = fromCandidService (error . show) error service
>>>
raw (T.pack "get") (BS.pack "DUDE")
*** Exception: Failed reading: Expected magic bytes "DIDL", got "DUDE" ...>>>
raw (T.pack "get") (BS.pack "DIDL\NUL\NUL")
"DIDL\NUL\SOH|\ENQ">>>
raw (T.pack "inc") (BS.pack "DIDL\NUL\SOH|\ENQ")
"DIDL\NUL\NUL">>>
service .! #get $ ()
10
And finally, we can turn this raw function back into a typed interface:
>>>
let service' :: Rec Interface = toCandidService error raw
>>>
service .! #get $ ()
10>>>
service .! #inc $ 5
>>>
service .! #get $ ()
15
In a real application you would more likely pass some networking code to toCandidService
.
Importing Candid
In the example above, we wrote the type of the service in Haskell. But very
likely you want to talk to a service whose is given to you in the form of a
.did
files, like
service : { get : () -> (int); inc : (int) -> (); }
You can parse such a description:
>>>
either error pretty $ parseDid "service : { get : () -> (int); inc : (int) -> (); }"
service : {get : () -> (int); inc : (int) -> ();}
And you can even, using Template Haskell, turn this into a proper Haskell type. The candid
antiquotation produces a type, and expects a free type variable m
for the monad you want to use.
>>>
:set -XQuasiQuotes
>>>
import Data.Row.Internal
>>>
type Counter m = [candid| service : { get : () -> (int); inc : (int) -> (); } |]
>>>
:info Counter
type Counter :: (* -> *) -> Row (*) type Counter m = ("get" .== (() -> m Integer)) .+ ("inc" .== (Integer -> m ())) :: Row (*) ...
You can then use this with toCandidService
to talk to a service.
If you want to read the description from a .did
file, you can use candidFile
.
If this encounters a Candid type definition, it will just inline them. This means that cyclic type definitions are not supported.
Dynamic use
Sometimes one needs to interact with Candid in a dynamic way, without static type information.
This library allows the parsing and pretty-printing of candid values:
>>>
import Data.Row
>>>
:set -XDataKinds -XTypeOperators
>>>
let bytes = encode (#bar .== Just 100 .+ #foo .== [True,False])
>>>
let Right (_typs, vs) = decodeVals bytes
>>>
pretty vs
(record {bar = opt +100; foo = vec {true; false}})
If you know Candid well you might be surprised to see the fieldnames here, because the Candid binary format does actually transmit the field name, but only a hash. This library tries to invert this hash, trying to find the shortest field name consisting of lower case letters and underscores that is equivalent to it. It does not work always:
>>>
let Right (_typs, vs) = decodeVals $ encode (#stopped .== True .+ #canister_id .== Principal (BS.pack []))
>>>
pretty vs
(record {stopped = true; hymijyo = principal "aaaaa-aa"})
Future versions of this library will allow you to specify the (dynamic) Type
at which you want to decode these values, in which case the field name would be taken from there.
Conversely, you can encode from the textual representation:
>>>
let Right bytes = encodeTextual "record { foo = vec { true; false }; bar = opt 100 }"
>>>
bytes
"DIDL\ETXl\STX\211\227\170\STX\STX\134\142\183\STX\SOHm~n}\SOH\NUL\SOHd\STX\SOH\NUL">>>
decode @(Rec ("bar" .== Maybe Integer .+ "foo" .== [Bool])) bytes
Right (#bar .== Just 100 .+ #foo .== [True,False])
This function does not support the full textual format yet; in particular type annotations can only be used around number literals.
Related to dynamic use is the ability to perform a subtype check, using isSubtypeOf
(but you have to set up the arguments correctly first):
>>>
isSubtypeOf (vacuous $ typeDesc @Natural) (vacuous $ typeDesc @Integer)
Right ()>>>
isSubtypeOf (vacuous $ typeDesc @Integer) (vacuous $ typeDesc @Natural)
Left "Type int is not a subtype of nat">>>
isSubtypeOf (vacuous $ typeDesc @(Rec ("foo" .== [Bool]))) (vacuous $ typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== Maybe [Bool])))
Right ()>>>
isSubtypeOf (vacuous $ typeDesc @(Rec ("bar" .== Maybe Integer .+ "foo" .== Maybe [Bool]))) (vacuous $ typeDesc @(Rec ("foo" .== [Bool])))
Left "Type opt vec bool is not a subtype of vec bool">>>
isSubtypeOf (vacuous $ typeDesc @(Rec ("bar" .== Integer))) (vacuous $ typeDesc @(Rec ("foo" .== Integer)))
Left "Missing record field foo of type int"
Missing features
- Generating interface descriptions (.did files) from Haskell functions
- Parsing the textual representation dynamically against an expected type
Reference
Encoding and decoding
encode :: CandidArg a => a -> ByteString Source #
Encode based on Haskell type
encodeBuilder :: forall a. CandidArg a => a -> Builder Source #
Encode to a Builder
based on Haskell type
Type classes
class (Typeable a, CandidVal (AsCandid a)) => Candid a where Source #
The class of Haskell types that can be converted to Candid.
You can create intances of this class for your own types, see the tutorial above for examples. The default instance is mostly for internal use.
Nothing
toCandid :: a -> AsCandid a Source #
fromCandid :: AsCandid a -> a Source #
default fromCandid :: a ~ AsCandid a => AsCandid a -> a Source #
Instances
type CandidRow r = (Typeable r, AllUniqueLabels r, AllUniqueLabels (Map (Either String) r), Forall r Candid, Forall r Unconstrained1) Source #
type CandidArg a = (CandidSeq (AsTuple a), Tuplable a, Typeable a) Source #
The class of types that can be used as Candid argument sequences.
Essentially all types that are in Candid
, but tuples need to be treated specially.
class Typeable a => CandidVal a Source #
The internal class of Haskell types that canonically map to Candid.
You would add instances to the Candid
type class.
asType, toCandidVal', fromCandidVal'
Instances
seqDesc :: forall a. CandidArg a => SeqDesc Source #
Calculate a Candid type description from a Haskell type. The SeqDesc
type is roughly [Type]
, with extra bookkeeping for recursive types
tieKnot :: SeqDesc -> [Type Void] Source #
This takes a type description and replaces all named types with their definition.
This can produce an infinite type! Only use this in sufficiently lazy contexts, or when the type is known to be not recursive.
Special types
A newtype to stand in for the unary tuple
Instances
Instances
Show Principal Source # | |
Candid Principal Source # | |
CandidVal Principal Source # | |
Defined in Codec.Candid.Class | |
Eq Principal Source # | |
Ord Principal Source # | |
Defined in Codec.Candid.Data | |
type AsCandid Principal Source # | |
Defined in Codec.Candid.Class |
prettyPrincipal :: Principal -> Text Source #
Instances
Show Reserved Source # | |
Candid Reserved Source # | |
CandidVal Reserved Source # | |
Defined in Codec.Candid.Class | |
Eq Reserved Source # | |
Ord Reserved Source # | |
Defined in Codec.Candid.Data | |
type AsCandid Reserved Source # | |
Defined in Codec.Candid.Class |
Instances
Show (FuncRef r) Source # | |
CandidMethodType mt => Candid (FuncRef mt) Source # | |
CandidMethodType mt => CandidVal (FuncRef mt) Source # | |
Defined in Codec.Candid.Class asType :: Type (Ref TypeRep Type) toCandidVal' :: FuncRef mt -> Value fromCandidVal' :: Value -> Either DeserializeError (FuncRef mt) fromMissingField :: Maybe (FuncRef mt) | |
Eq (FuncRef r) Source # | |
Ord (FuncRef r) Source # | |
Defined in Codec.Candid.Data | |
type AsCandid (FuncRef mt) Source # | |
Defined in Codec.Candid.Class |
newtype ServiceRef (r :: Row Type) Source #
Instances
Generics
This newtype encodes a Haskell record type using generic programming. Best used with DerivingVia
, as shown in the tutorial.
AsRecord | |
|
This newtype encodes a Haskell data type as a variant using generic programming. Best used with DerivingVia
, as shown in the tutorial.
AsVariant | |
|
Candid services
type CandidService m r = (Forall r (CandidMethod m), AllUniqueLabels r) Source #
A Candid service. The r
describes the type of a Rec
.
type RawService m = Text -> ByteString -> m ByteString Source #
A raw service, operating on bytes
:: forall m r. CandidService m r | |
=> (forall a. String -> m a) | What to do if the raw service returns unparsable data |
-> RawService m | |
-> Rec r |
Turns a raw service (function operating on bytes) into a typed Candid service (a record of typed methods). The raw service is typically code that talks over the network.
:: forall m r. CandidService m r | |
=> (forall a. Text -> m a) | What to do if the method name does not exist |
-> (forall a. String -> m a) | What to do when the caller provides unparsable data |
-> Rec r | |
-> RawService m |
Turns a typed candid service into a raw service. Typically used in a framework warpping Candid services.
Meta-programming
candid :: QuasiQuoter Source #
This quasi-quoter turns a Candid service description into a Haskell type. It assumes a type variable m
to be in scope, and uses that as the monad for the service's methods.
Recursive types are not supported.
candidFile :: QuasiQuoter Source #
As candid
, but takes a filename
candidType :: QuasiQuoter Source #
This quasi-quoter turns works on individual candid types, e.g.
type InstallMode = [candidType| variant {install : null; reinstall : null; upgrade : null}; |]
candidDefs :: QuasiQuoter Source #
This quasi-quoter turns all type definitions of a Canddi file into Haskell types, as one Row
. The service
of the candid file is ignored.
Recursive types are not supported.
This quasi-quoter works differently depending on context:
As a _type_, it expands to a row-types record with one entry per type defined in the Candid file:
type MyDefs = [candidDefs|type t = text; ... |] foo :: MyDefs .! "t"
As a _declaration_ (i.e. the module top level), it generates one type
synonym (type Foo = ...
) per definition. This only works if the candid
type name is a valid Haskell type name (in particular, upper case). This may
improve in the future.
[candidDefs|type Foo = text; ... |] foo :: Foo
You can use `-ddump-splices` to see the generated code.
candidDefsFile :: QuasiQuoter Source #
As candid
, but takes a filename
Types and values
NatT | |
Nat8T | |
Nat16T | |
Nat32T | |
Nat64T | |
IntT | |
Int8T | |
Int16T | |
Int32T | |
Int64T | |
Float32T | |
Float64T | |
BoolT | |
TextT | |
NullT | |
ReservedT | |
EmptyT | |
OptT (Type a) | |
VecT (Type a) | |
RecT (Fields a) | |
VariantT (Fields a) | |
FuncT (MethodType a) | |
ServiceT [(Text, MethodType a)] | |
PrincipalT | |
BlobT | |
FutureT | |
RefT a | A reference to a named type |
Instances
Foldable Type Source # | |
Defined in Codec.Candid.Types fold :: Monoid m => Type m -> m # foldMap :: Monoid m => (a -> m) -> Type a -> m # foldMap' :: Monoid m => (a -> m) -> Type a -> m # foldr :: (a -> b -> b) -> b -> Type a -> b # foldr' :: (a -> b -> b) -> b -> Type a -> b # foldl :: (b -> a -> b) -> b -> Type a -> b # foldl' :: (b -> a -> b) -> b -> Type a -> b # foldr1 :: (a -> a -> a) -> Type a -> a # foldl1 :: (a -> a -> a) -> Type a -> a # elem :: Eq a => a -> Type a -> Bool # maximum :: Ord a => Type a -> a # | |
Traversable Type Source # | |
Applicative Type Source # | |
Functor Type Source # | |
Monad Type Source # | |
Show a => Show (Type a) Source # | |
Eq a => Eq (Type a) Source # | |
Ord a => Ord (Type a) Source # | |
Pretty a => Pretty (Type a) Source # | |
Defined in Codec.Candid.Types |
data MethodType a Source #
The type of a candid method
MethodType | |
|
Instances
A type for a Candid field name. Essentially a Word32
with maybe a textual label attached
escapeFieldName :: FieldName -> Text Source #
unescapeFieldName :: Text -> FieldName Source #
The inverse of escapeFieldName
candidHash :: Text -> Word32 Source #
The Candid field label hashing algorithm
NumV Scientific | |
NatV Natural | |
Nat8V Word8 | |
Nat16V Word16 | |
Nat32V Word32 | |
Nat64V Word64 | |
IntV Integer | |
Int8V Int8 | |
Int16V Int16 | |
Int32V Int32 | |
Int64V Int64 | |
Float32V Float | |
Float64V Double | |
BoolV Bool | |
TextV Text | |
NullV | |
ReservedV | |
OptV (Maybe Value) | |
VecV (Vector Value) | |
RecV [(FieldName, Value)] | |
TupV [Value] | |
VariantV FieldName Value | |
FuncV Principal Text | |
ServiceV Principal | |
PrincipalV Principal | |
BlobV ByteString | |
AnnV Value (Type Void) | |
FutureV | An opaque value of a future type |
isSubtypeOf :: (Pretty k1, Pretty k2, Ord k1, Ord k2) => Type (Ref k1 Type) -> Type (Ref k2 Type) -> Either String () Source #
Dynamic use
decodeVals :: ByteString -> Either String (SeqDesc, [Value]) Source #
Decode binay value into the type description and the untyped value representation.
fromCandidVals :: CandidArg a => [Value] -> Either String a Source #
Decode (dynamic) values to Haskell type
This applies some best-effort subtyping/coercion, suitable for liberal parsing of the textual representation, but not the coercion algorithm as specified in the specification, which requires a provided type.
toCandidVals :: CandidArg a => a -> [Value] Source #
Turn haskell types into a dynamic Candid value. This may lose type information.
encodeDynValues :: [Value] -> Either String Builder Source #
Encodes a Candid value given in the dynamic Value
form, at inferred type.
This may fail if the values have inconsistent types. It does not use the
reserved
supertype (unless explicitly told to).
Not all possible values are encodable this way. For example, all function
references will be encoded at type () - ()
.
encodeTextual :: String -> Either String ByteString Source #
Encodes a Candid value given in textual form.
This may fail if the textual form cannot be parsed or has inconsistent
types. It does not use the reserved
supertype (unless explicitly told to).