Skip to content

AnnotatedCodec #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-22.04

env:
STYLISH_HASKELL_VERSION: "0.14.4.0"
STYLISH_HASKELL_VERSION: "0.14.6.0"

steps:
- name: Set cache version
Expand All @@ -116,8 +116,8 @@ jobs:
uses: haskell-actions/setup@v2
id: setup-haskell
with:
ghc-version: 9.2.5
cabal-version: 3.8.1.0
ghc-version: 9.8
cabal-version: 3.12.1.0

- name: "Setup cabal bin path"
run: |
Expand Down
6 changes: 5 additions & 1 deletion cabal.project
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
index-state: 2025-05-21T15:48:46Z
index-state: 2025-07-08T15:23:02Z

packages: ./typed-protocols
./typed-protocols-doc
Expand All @@ -10,6 +10,10 @@ if impl(ghc >= 9.12)
, serdoc-core:template-haskell
, serdoc-core:th-abstraction

allow-newer:
, serdoc-core:QuickCheck
, serdoc-core:tasty-quickcheck

if os(windows)
package text
flags: -simdutf
4 changes: 2 additions & 2 deletions typed-protocols-doc/typed-protocols-doc.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ library
, th-abstraction >=0.6.0.0 && <0.8
, time >=1.12 && <1.14
, serdoc-core
, typed-protocols ^>= 1.0
, typed-protocols ^>= 1.0 || ^>= 1.1
hs-source-dirs: src
default-language: GHC2021
default-extensions: DataKinds
Expand Down Expand Up @@ -89,7 +89,7 @@ test-suite typed-protocols-doc-test
build-depends: base >=4.14.0.0 && <5
, blaze-html >=0.9.1.2 && <0.10
, tasty >=1.5 && <1.6
, tasty-quickcheck >=0.10.3 && <0.11
, tasty-quickcheck >=0.10.3 && <0.12
, typed-protocols
, typed-protocols-doc
, serdoc-core
Expand Down
17 changes: 17 additions & 0 deletions typed-protocols/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Revision history for typed-protocols

## 1.1.0.0 -- 05.08.2025

### Breaking changes

* Annotated codecs which allow to retain original bytes received from the network.
The `Codec` type evolved into a new `CodecF` data type, and two type aliases
`AnnotatedCodec`, `Codec`.
* `prop_codec` properties moved to `typed-protocols:codec-properties` library
(`Network.TypedProtocol.Codec.Properties` module). They now return the
`QuickCheck`'s `Property` rather than a `Bool`.

### Non-breaking changes

## 1.0.0.0

* Hackage release.

## 0.3.0.0

* `AnyMessageWithAgency` pattern synonym is exported as a constructor of `AnyMessage`.
Expand Down
30 changes: 16 additions & 14 deletions typed-protocols/cborg/Network/TypedProtocol/Codec/CBOR.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import Network.TypedProtocol.Codec
import Network.TypedProtocol.Core


-- | Construct a 'Codec' for a CBOR based serialisation format, using strict
-- | Construct a 'CodecF' for a CBOR based serialisation format, using strict
-- 'BS.ByteString's.
--
-- This is an adaptor between the @cborg@ library and the 'Codec' abstraction.
-- This is an adaptor between the @cborg@ library and the 'CodecF' abstraction.
--
-- It takes encode and decode functions for the protocol messages that use the
-- CBOR library encoder and decoder.
Expand All @@ -38,7 +38,7 @@ import Network.TypedProtocol.Core
-- natively produces chunks).
--
mkCodecCborStrictBS
:: forall ps m. MonadST m
:: forall ps m f. MonadST m

=> (forall (st :: ps) (st' :: ps).
StateTokenI st
Expand All @@ -49,10 +49,10 @@ mkCodecCborStrictBS
-> (forall (st :: ps) s.
ActiveState st
=> StateToken st
-> CBOR.Decoder s (SomeMessage st))
-> CBOR.Decoder s (f st))
-- ^ cbor decoder

-> Codec ps CBOR.DeserialiseFailure m BS.ByteString
-> CodecF ps CBOR.DeserialiseFailure m f BS.ByteString
mkCodecCborStrictBS cborMsgEncode cborMsgDecode =
Codec {
encode = \msg -> convertCborEncoder cborMsgEncode msg,
Expand All @@ -65,11 +65,12 @@ mkCodecCborStrictBS cborMsgEncode cborMsgDecode =
. cborEncode

convertCborDecoder
:: (forall s. CBOR.Decoder s a)
-> m (DecodeStep BS.ByteString CBOR.DeserialiseFailure m a)
:: (forall s. CBOR.Decoder s (f a))
-> m (DecodeStep BS.ByteString CBOR.DeserialiseFailure m (f a))
convertCborDecoder cborDecode =
convertCborDecoderBS cborDecode stToIO


convertCborDecoderBS
:: forall s m a. Functor m
=> CBOR.Decoder s a
Expand All @@ -89,16 +90,16 @@ convertCborDecoderBS cborDecode liftST =
go (CBOR.Partial k) = DecodePartial (fmap go . liftST . k)


-- | Construct a 'Codec' for a CBOR based serialisation format, using lazy
-- | Construct a 'CodecF' for a CBOR based serialisation format, using lazy
-- 'BS.ByteString's.
--
-- This is an adaptor between the @cborg@ library and the 'Codec' abstraction.
-- This is an adaptor between the @cborg@ library and the 'CodecF' abstraction.
--
-- It takes encode and decode functions for the protocol messages that use the
-- CBOR library encoder and decoder.
--
mkCodecCborLazyBS
:: forall ps m. MonadST m
:: forall ps m f. MonadST m

=> (forall (st :: ps) (st' :: ps).
StateTokenI st
Expand All @@ -109,10 +110,10 @@ mkCodecCborLazyBS
-> (forall (st :: ps) s.
ActiveState st
=> StateToken st
-> CBOR.Decoder s (SomeMessage st))
-> CBOR.Decoder s (f st))
-- ^ cbor decoder

-> Codec ps CBOR.DeserialiseFailure m LBS.ByteString
-> CodecF ps CBOR.DeserialiseFailure m f LBS.ByteString
mkCodecCborLazyBS cborMsgEncode cborMsgDecode =
Codec {
encode = \msg -> convertCborEncoder cborMsgEncode msg,
Expand All @@ -126,11 +127,12 @@ mkCodecCborLazyBS cborMsgEncode cborMsgDecode =
. cborEncode

convertCborDecoder
:: (forall s. CBOR.Decoder s a)
-> m (DecodeStep LBS.ByteString CBOR.DeserialiseFailure m a)
:: (forall s. CBOR.Decoder s (f a))
-> m (DecodeStep LBS.ByteString CBOR.DeserialiseFailure m (f a))
convertCborDecoder cborDecode =
convertCborDecoderLBS cborDecode stToIO


convertCborDecoderLBS
:: forall s m a. Monad m
=> CBOR.Decoder s a
Expand Down
64 changes: 64 additions & 0 deletions typed-protocols/examples/Network/TypedProtocol/ReqResp/Codec.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# LANGUAGE BlockArguments #-}

module Network.TypedProtocol.ReqResp.Codec where

import Network.TypedProtocol.Codec
Expand Down Expand Up @@ -43,6 +45,68 @@ codecReqResp =
where failure = CodecFailure ("unexpected server message: " ++ str)


data WithBytes a = WithBytes {
bytes :: String,
message :: a
}
deriving (Show, Eq)

mkWithBytes :: Show a => a -> WithBytes a
mkWithBytes message = WithBytes { bytes = show message, message }


anncodecReqResp ::
forall req resp m
. (Monad m, Show req, Show resp, Read req, Read resp)
=> AnnotatedCodec (ReqResp (WithBytes req) (WithBytes resp)) CodecFailure m String
anncodecReqResp =
Codec{encode, decode}
where
encode :: forall req' resp'
(st :: ReqResp (WithBytes req') (WithBytes resp'))
(st' :: ReqResp (WithBytes req') (WithBytes resp'))
. ( Show req'
, Show resp'
)
=> Message (ReqResp (WithBytes req') (WithBytes resp')) st st'
-> String
-- NOTE: we're not using 'Show (Message ...)' instance. If `req ~ Int`,
-- then negative numbers will be surrounded with braces (e.g. @"(-1)"@) and
-- the `Read` type class doesn't have a way to see that brackets were consumed
-- from the input string.
encode (MsgReq WithBytes { message })
= "MsgReq " ++ show message ++ "\n"
encode (MsgResp WithBytes { message })
= "MsgResp " ++ show message ++ "\n"
encode MsgDone
= "MsgDone" ++ "\n"

decode :: forall req' resp' m'
(st :: ReqResp (WithBytes req') (WithBytes resp'))
. (Monad m', Read req', Read resp', ActiveState st)
=> StateToken st
-> m' (DecodeStep String CodecFailure m' (Annotator String st))
decode stok =
decodeTerminatedFrame '\n' $ \str trailing ->
case (stok, break (==' ') str) of
(SingIdle, ("MsgReq", str'))
| Just req <- readMaybe @req' str'
-> DecodeDone (Annotator \str'' ->
let used = init $ drop 7 str'' in
SomeMessage (MsgReq (WithBytes used req))) trailing
(SingIdle, ("MsgDone", ""))
-> DecodeDone (Annotator \_str'' -> SomeMessage MsgDone) trailing
(SingBusy, ("MsgResp", str'))
| Just resp <- readMaybe @resp' str'
-> DecodeDone (Annotator \str'' ->
let used = init $ drop 8 str'' in
SomeMessage (MsgResp (WithBytes used resp))) trailing

(_ , _ ) -> DecodeFail failure
where failure = CodecFailure ("unexpected server message: " ++ str)



codecReqRespId ::
forall req resp m
. (Monad m, Show req, Show resp)
Expand Down
Loading
Loading