on master help sign in
26 files changed+210−133
since splitsnapshots
unifiedsplit
.github/workflows/cabal.yml+64 .github/workflows/ci.yml+2−2 README.md+2−1 package.yaml+1−1 roboservant.cabal+6−4 src/Roboservant/Client.hs+3−2 src/Roboservant/Direct.hs+11−12 src/Roboservant/Server.hs+2−2 src/Roboservant/Types.hs+1 src/Roboservant/Types/Breakdown.hs+6−1 src/Roboservant/Types/BuildFrom.hs+5−1 src/Roboservant/Types/Internal.hs+2−2 src/Roboservant/Types/Orphans.hs+7 src/Roboservant/Types/ReifiedApi.hs+7−1 src/Roboservant/Types/ReifiedApi/Server.hs+1−1 stack.yaml+5−10 stack.yaml.lock+9−58 test/Breakdown.hs+1−1 test/Nested.hs+2−2 test/Post.hs+2−2 test/Product.hs+1−1 test/Put.hs+33 test/QueryParams.hs+3−3 test/Spec.hs+30−21 test/UnsafeIO.hs+3−3 test/Valid.hs+1−2
.github/workflows/cabal.ymlnew file+64
@@ -0,0 +1,64 @@
1+# modified from https://github.com/jgm/pandoc/blob/master/.github/workflows/ci.yml
2+name: Cabal CI
3+
4+on:
5+ push:
6+ branches:
7+ - '**'
8+ paths-ignore: []
9+ pull_request:
10+ paths-ignore: []
11+
12+jobs:
13+ linux:
14+
15+ runs-on: ubuntu-20.04
16+ strategy:
17+ fail-fast: false
18+ matrix:
19+ versions:
20+ - ghc: '8.8.4'
21+ cabal: '3.4'
22+ - ghc: '8.10.4'
23+ cabal: '3.4'
24+ steps:
25+ - uses: actions/checkout@v2
26+
27+ # need to install older cabal/ghc versions from ppa repository
28+
29+ - name: Install recent cabal/ghc
30+ uses: actions/setup-haskell@v1.1.3
31+ with:
32+ ghc-version: ${{ matrix.versions.ghc }}
33+ cabal-version: ${{ matrix.versions.cabal }}
34+
35+ # declare/restore cached things
36+ # caching doesn't work for scheduled runs yet
37+ # https://github.com/actions/cache/issues/63
38+
39+ - name: Cache cabal global package db
40+ id: cabal-global
41+ uses: actions/cache@v2
42+ with:
43+ path: |
44+ ~/.cabal
45+ key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-global-${{ hashFiles('cabal.project') }}
46+
47+ - name: Cache cabal work
48+ id: cabal-local
49+ uses: actions/cache@v2
50+ with:
51+ path: |
52+ dist-newstyle
53+ key: ${{ runner.os }}-${{ matrix.versions.ghc }}-${{ matrix.versions.cabal }}-cabal-local
54+
55+ - name: Install dependencies
56+ run: |
57+ cabal update
58+ cabal build all --dependencies-only --enable-tests --disable-optimization
59+ - name: Build
60+ run: |
61+ cabal build all --enable-tests --disable-optimization 2>&1 | tee build.log
62+ - name: Test
63+ run: |
64+ cabal test all --disable-optimization
.github/workflows/ci.yml+2−2
@@ -1,4 +1,4 @@
1−name: CI
1+name: Stack CI
22
33# Trigger the workflow on push or pull request, but only for the master branch
44on:
@@ -12,7 +12,7 @@ jobs:
1212 strategy:
1313 matrix:
1414 stack: ["2.3.3"]
15− ghc: ["8.8.3"]
15+ ghc: ["8.10.4"]
1616 os: [ubuntu-latest]
1717 steps:
1818 - uses: actions/checkout@v2
README.md+2−1
@@ -2,7 +2,8 @@
22
33Automatically fuzz your servant apis in a contextually-aware way.
44
5−![CI](https://github.com/mwotton/roboservant/workflows/CI/badge.svg)
5+[![Stack CI](https://github.com/mwotton/roboservant/actions/workflows/ci.yml/badge.svg)](https://github.com/mwotton/roboservant/actions/workflows/ci.yml)
6+[![Cabal CI](https://github.com/mwotton/roboservant/actions/workflows/cabal.yml/badge.svg)](https://github.com/mwotton/roboservant/actions/workflows/cabal.yml)
67
78## example
89
package.yaml+1−1
@@ -1,6 +1,6 @@
11
22name: roboservant
3−version: 0.1.0.2
3+version: 0.1.0.3
44github: "mwotton/roboservant"
55license: BSD3
66copyright: "2020 Mark Wotton, Samuel Schlesinger"
roboservant.cabal+6−4
@@ -1,13 +1,13 @@
11cabal-version: 1.12
22
3−-- This file has been generated from package.yaml by hpack version 0.33.0.
3+-- This file has been generated from package.yaml by hpack version 0.35.0.
44--
55-- see: https://github.com/sol/hpack
66--
7−-- hash: 596d33d4f2e29f15b48525514def52b101c9e89b6193275bd5c0a4c253de8d5c
7+-- hash: 0c0c35e1dca1d6c52e5bc61591cbd8c0da9d811b83341bbc676c5f8b93b0b20d
88
99name: roboservant
10−version: 0.1.0.2
10+version: 0.1.0.3
1111synopsis: Automatic session-aware servant testing
1212description: Please see the README on GitHub at <https://github.com/mwotton/roboservant#readme>
1313category: Web
@@ -36,6 +36,7 @@ library
3636 Roboservant.Types.BuildFrom
3737 Roboservant.Types.Config
3838 Roboservant.Types.Internal
39+ Roboservant.Types.Orphans
3940 Roboservant.Types.ReifiedApi
4041 Roboservant.Types.ReifiedApi.Server
4142 other-modules:
@@ -72,7 +73,7 @@ test-suite example
7273 other-modules:
7374 Paths_roboservant
7475 hs-source-dirs:
75− ./.
76+ ./
7677 ghc-options: -Wall -fwrite-ide-info -hiedir=.hie -pgmL markdown-unlit
7778 build-tool-depends:
7879 markdown-unlit:markdown-unlit
@@ -115,6 +116,7 @@ test-suite roboservant-test
115116 Nested
116117 Post
117118 Product
119+ Put
118120 QueryParams
119121 Seeded
120122 UnsafeIO
src/Roboservant/Client.hs+3−2
@@ -1,5 +1,6 @@
11{-# LANGUAGE RankNTypes #-}
22{-# LANGUAGE TypeOperators #-}
3+{-# LANGUAGE NamedFieldPuns #-}
34{-# LANGUAGE DataKinds #-}
45{-# LANGUAGE AllowAmbiguousTypes #-}
56{-# LANGUAGE DataKinds #-}
@@ -7,7 +8,7 @@
78{-# LANGUAGE PolyKinds #-}
89{-# LANGUAGE ScopedTypeVariables #-}
910{-# LANGUAGE TypeApplications #-}
10−{-# LANGUAGE TypeOperators #-}
11+
1112{-# LANGUAGE UndecidableInstances #-}
1213{-# LANGUAGE TypeFamilies #-}
1314{-# LANGUAGE FlexibleContexts #-}
@@ -96,7 +97,7 @@ instance (Typeable x, Hashable x, Breakdown x) => NormalizeFunction (ClientM x)
9697 where
9798 renderClientError :: ClientError -> InteractionError
9899 renderClientError err = case err of
99− FailureResponse _ (Response{responseStatusCode}) -> InteractionError textual (responseStatusCode == status500)
100+ FailureResponse _ Response{responseStatusCode} -> InteractionError textual (responseStatusCode == status500)
100101 _ -> InteractionError textual True
101102
102103 where textual = T.pack $ show err
src/Roboservant/Direct.hs+11−12
@@ -7,7 +7,7 @@
77{-# LANGUAGE RecordWildCards #-}
88{-# LANGUAGE ScopedTypeVariables #-}
99{-# LANGUAGE TupleSections #-}
10−{-# LANGUAGE TypeApplications #-}
10+
1111{-# LANGUAGE TypeFamilies #-}
1212{-# LANGUAGE UndecidableInstances #-}
1313
@@ -150,12 +150,12 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
150150 Report
151151 (unlines [show failureType, show exception])
152152 r
153− displayDiagnostics FuzzState {..} = liftIO $ do
153+ displayDiagnostics FuzzState {..} = liftIO $
154154 logInfo $ unlines $
155− ["api endpoints covered"]
156− <> (map show . Set.toList . Set.fromList $ map apiOffset path)
157− <> ["", "types in stash"]
158− <> DM.foldrWithKey (\_ v r -> (show . NEL.length . getStashValue $ v) : r) [] (getStash stash)
155+ ["api endpoints covered"]
156+ <> (map show . Set.toList . Set.fromList $ map apiOffset path)
157+ <> ["", "types in stash"]
158+ <> DM.foldrWithKey (\_ v r -> (show . NEL.length . getStashValue $ v) : r) [] (getStash stash)
159159 -- <> (map (show . NEL.length . getStashValue ) $ DM.assocs (getStash stash))
160160 -- $ \_k v ->
161161 -- (show . NEL.length $ getStashValue v))
@@ -246,11 +246,11 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
246246 if fatalError e
247247 then throw e
248248 else pure ()
249− Right (dyn :: NEL.NonEmpty (Dynamic, Int)) -> do
249+ Right (dyn :: NEL.NonEmpty (Dynamic, Int)) ->
250250 modify'
251− ( \fs@FuzzState {..} ->
252− fs {stash = addToStash (NEL.toList dyn) stash}
253− )
251+ ( \fs@FuzzState {..} ->
252+ fs {stash = addToStash (NEL.toList dyn) stash}
253+ )
254254 where
255255 argVals = V.rmap (\(_ :*: V.Identity x) -> V.Identity x) args
256256 -- argTypes = recordToList' (\(tr :*: _) -> R.SomeTypeRep tr) args
@@ -262,8 +262,7 @@ fuzz' reifiedApi Config {..} = handle (pure . Just . formatException) $ do
262262 (execute op func args)
263263 [ Handler (\(e :: SomeAsyncException) -> throw e),
264264 Handler
265− ( \(e :: SomeException) -> do
266− -- displayDiagnostics =<< get
265+ ( \(e :: SomeException) ->
267266 throw . RoboservantException ServerCrashed (Just e) =<< get
268267 )
269268 ]
src/Roboservant/Server.hs+2−2
@@ -5,7 +5,7 @@
55{-# LANGUAGE FlexibleContexts #-}
66{-# LANGUAGE GADTs #-}
77{-# LANGUAGE PolyKinds #-}
8−{-# LANGUAGE RankNTypes #-}
8+
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE TypeApplications #-}
1111{-# LANGUAGE TypeFamilies #-}
@@ -27,7 +27,7 @@ fuzz :: forall api.
2727 Server api ->
2828 Config ->
2929 IO (Maybe Report)
30−fuzz s = fuzz' (reifyServer s)
30+fuzz s = fuzz' (reifyServer s)
3131 -- todo: how do we pull reifyServer out?
3232 where reifyServer :: (FlattenServer api, ToReifiedApi (Endpoints api))
3333 => Server api -> ReifiedApi
src/Roboservant/Types.hs+1
@@ -23,3 +23,4 @@ import Roboservant.Types.Config
2323import Roboservant.Types.Internal
2424import Roboservant.Types.ReifiedApi
2525import Roboservant.Types.ReifiedApi.Server
26+import Roboservant.Types.Orphans()
src/Roboservant/Types/Breakdown.hs+6−1
@@ -1,5 +1,6 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
34{-# LANGUAGE DerivingVia #-}
45{-# LANGUAGE FlexibleInstances #-}
56{-# LANGUAGE GADTs #-}
@@ -22,6 +23,8 @@ import qualified Data.List.NonEmpty as NEL
2223import Data.Typeable (Typeable)
2324import GHC.Generics
2425import Roboservant.Types.Internal
26+import Servant
27+import Roboservant.Types.Orphans()
2528
2629breakdown ::
2730 (Hashable x, Typeable x, Breakdown x) =>
@@ -46,7 +49,7 @@ instance (Hashable x, Typeable x, Breakdown x) => Breakdown [x] where
4649 breakdownExtras stash = concatMap (NEL.toList . breakdown) stash
4750
4851
49−class GBreakdown (f :: k -> *) where
52+class GBreakdown (f :: k -> Type) where
5053 gBreakdownExtras :: f a -> [(Dynamic, Int)]
5154
5255instance (Hashable x, Typeable x, Generic x, GBreakdown (Rep x)) => Breakdown (Compound (x :: Type)) where
@@ -74,3 +77,5 @@ instance (Hashable a, Typeable a, Breakdown a) => GBreakdown (K1 R a) where
7477
7578instance GBreakdown U1 where
7679 gBreakdownExtras U1 = []
80+
81+deriving via (Atom NoContent) instance Breakdown NoContent
src/Roboservant/Types/BuildFrom.hs+5−1
@@ -23,6 +23,8 @@ import Data.Typeable (Typeable)
2323import GHC.Generics
2424import Roboservant.Types.Internal
2525import qualified Type.Reflection as R
26+import Servant(NoContent)
27+import Roboservant.Types.Orphans()
2628
2729buildFrom :: forall x. (Hashable x, BuildFrom x, Typeable x) => Stash -> Maybe (StashValue x)
2830buildFrom = buildStash . buildFrom'
@@ -75,7 +77,7 @@ deriving via (Atom Int) instance BuildFrom Int
7577
7678deriving via (Atom Char) instance BuildFrom Char
7779
78−class GBuildFrom (f :: k -> *) where
80+class GBuildFrom (f :: k -> Type) where
7981 gExtras :: Stash -> [([Provenance], f a)]
8082
8183instance GBuildFrom b => GBuildFrom (M1 D a b) where
@@ -101,3 +103,5 @@ instance BuildFrom a => GBuildFrom (K1 i a) where
101103
102104instance GBuildFrom U1 where
103105 gExtras _ = [([], U1)]
106+
107+deriving via (Atom NoContent) instance BuildFrom NoContent
src/Roboservant/Types/Internal.hs+2−2
@@ -43,11 +43,11 @@ instance Show Stash where
4343
4444-- | Can't be built up from parts, can't be broken down further.
4545newtype Atom x = Atom {unAtom :: x}
46− deriving newtype (Hashable, Typeable)
46+ deriving newtype (Hashable, Typeable,Eq)
4747
4848-- | can be broken down and built up from generic pieces
4949newtype Compound x = Compound {unCompound :: x}
50− deriving newtype (Hashable, Typeable)
50+ deriving newtype (Hashable, Typeable, Eq)
5151
5252hashedDyn :: (Hashable a, Typeable a) => a -> (Dynamic, Int)
5353hashedDyn a = (toDyn a, hash a)
src/Roboservant/Types/Orphans.hsnew file+7
@@ -0,0 +1,7 @@
1+{-# OPTIONS_GHC -fno-warn-orphans #-}
2+module Roboservant.Types.Orphans where
3+
4+import Servant
5+import Data.Hashable
6+
7+instance Hashable NoContent
src/Roboservant/Types/ReifiedApi.hs+7−1
@@ -57,7 +57,7 @@ instance Show ReifiedEndpoint where
5757
5858class ( V.RecordToList (EndpointArgs endpoint)
5959 , V.RMap (EndpointArgs endpoint)
60− ) => ToReifiedEndpoint (endpoint :: *) where
60+ ) => ToReifiedEndpoint (endpoint :: Type) where
6161 type EndpointArgs endpoint :: [Type]
6262 type EndpointRes endpoint :: Type
6363
@@ -84,6 +84,12 @@ instance
8484 type EndpointRes (Verb method statusCode contentTypes responseType) = responseType
8585 reifiedEndpointArguments = V.RNil
8686
87+instance ToReifiedEndpoint (NoContentVerb method)
88+ where
89+ type EndpointArgs (NoContentVerb method) = '[]
90+ type EndpointRes (NoContentVerb method) = NoContent
91+ reifiedEndpointArguments = V.RNil
92+
8793instance
8894 (ToReifiedEndpoint endpoint) =>
8995 ToReifiedEndpoint ((x :: Symbol) :> endpoint)
src/Roboservant/Types/ReifiedApi/Server.hs+1−1
@@ -6,7 +6,7 @@
66{-# LANGUAGE GADTs #-}
77{-# LANGUAGE LambdaCase #-}
88{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE RankNTypes #-}
9+
1010{-# LANGUAGE ScopedTypeVariables #-}
1111{-# LANGUAGE TypeApplications #-}
1212{-# LANGUAGE TypeFamilies #-}
stack.yaml+5−10
@@ -1,15 +1,10 @@
1−resolver: lts-16.27
1+resolver: lts-20.4
22packages:
33- .
44
55extra-deps:
6+# - dependent-map-0.4.0.0@sha256:ca2b131046f4340a1c35d138c5a003fe4a5be96b14efc26291ed35fd08c62221,1657
7+# - dependent-sum-0.7.1.0@sha256:5599aa89637db434431b1dd3fa7c34bc3d565ee44f0519bfbc877be1927c2531,2068
68- servant-flatten-0.2@sha256:276896f7c5cdec5b8f8493f6205fded0cc602d050b58fdb09a6d7c85c3bb0837,1234
7−- vinyl-0.13.0
8−- dependent-sum-0.7.1.0
9−- constraints-extras-0.3.0.2
10−- dependent-map-0.4.0.0
11−
12−- servant-0.18.2
13−- servant-client-0.18.2
14−- servant-client-core-0.18.2
15−- servant-server-0.18.2
9+# - constraints-extras-0.3.0.2@sha256:013b8d0392582c6ca068e226718a4fe8be8e22321cc0634f6115505bf377ad26,1853
10+- some-1.0.3
stack.yaml.lock+9−58
@@ -7,69 +7,20 @@ packages:
77- completed:
88 hackage: servant-flatten-0.2@sha256:276896f7c5cdec5b8f8493f6205fded0cc602d050b58fdb09a6d7c85c3bb0837,1234
99 pantry-tree:
10− size: 325
1110 sha256: 04f12c7bef2c3f9a25d94eb9489752ed498db8e243069fe95838dbb51df1dcb3
11+ size: 325
1212 original:
1313 hackage: servant-flatten-0.2@sha256:276896f7c5cdec5b8f8493f6205fded0cc602d050b58fdb09a6d7c85c3bb0837,1234
1414- completed:
15− hackage: vinyl-0.13.0@sha256:0f247cd3f8682b30881a07de18e6fec52d540646fbcb328420049cc8d63cd407,3724
16− pantry-tree:
17− size: 1857
18− sha256: 860fb95820b595161cdbdec5f376100ebae2d14e5ef0dbe311546202f7525d01
19− original:
20− hackage: vinyl-0.13.0
21−- completed:
22− hackage: dependent-sum-0.7.1.0@sha256:5599aa89637db434431b1dd3fa7c34bc3d565ee44f0519bfbc877be1927c2531,2068
23− pantry-tree:
24− size: 290
25− sha256: 9cbfb32b5a8a782b7a1c941803fd517633cb699159b851c1d82267a9e9391b50
26− original:
27− hackage: dependent-sum-0.7.1.0
28−- completed:
29− hackage: constraints-extras-0.3.0.2@sha256:013b8d0392582c6ca068e226718a4fe8be8e22321cc0634f6115505bf377ad26,1853
30− pantry-tree:
31− size: 594
32− sha256: 3ce1012bfb02e4d7def9df19ce80b8cd2b472c691b25b181d9960638673fecd1
33− original:
34− hackage: constraints-extras-0.3.0.2
35−- completed:
36− hackage: dependent-map-0.4.0.0@sha256:ca2b131046f4340a1c35d138c5a003fe4a5be96b14efc26291ed35fd08c62221,1657
37− pantry-tree:
38− size: 551
39− sha256: 5defa30010904d2ad05a036f3eaf83793506717c93cbeb599f40db1a3632cfc5
40− original:
41− hackage: dependent-map-0.4.0.0
42−- completed:
43− hackage: servant-0.18.2@sha256:f8c9f0e9891a3ada1337a3c0b369333a3b5a2d0909dd3cd09d79bc26adeaca44,5298
44− pantry-tree:
45− size: 2662
46− sha256: e930e814de1aa4d24274bdf18341a50b7ed38604ae4734f730e09238ac5bf7e2
47− original:
48− hackage: servant-0.18.2
49−- completed:
50− hackage: servant-client-0.18.2@sha256:82578ade7468873259bb2fdc9d62290a0f998550900683e1410a237ed4b05410,4591
51− pantry-tree:
52− size: 1300
53− sha256: 6324892c77bedbce32f0d6f1612fc2cb0d82c163d3be39efb951d3cc3792ce4a
54− original:
55− hackage: servant-client-0.18.2
56−- completed:
57− hackage: servant-client-core-0.18.2@sha256:ad63ae0f227373fea7e547d4c2a7b0b69e112ff409a83cbadffc9f6ee049926f,3763
58− pantry-tree:
59− size: 1444
60− sha256: 9e37bc5f8cbb70cf1accb20bd6f83fca4c2ca42472cd0fc4b22183c2c57cbe3b
61− original:
62− hackage: servant-client-core-0.18.2
63−- completed:
64− hackage: servant-server-0.18.2@sha256:56679af62ab8820a2108da6153d9ae9dde37199e62172365bdaea1458c3f7c2d,5482
15+ hackage: some-1.0.3@sha256:938cec5fc0d3a08dde34cfc0acd5a0aa2e9fd489c9dbb64417f16588dfb47af0,2399
6516 pantry-tree:
66− size: 2614
67− sha256: 3ac7430134439e4b67f0f5333f63b89d0cb7de5e2e07f0af7801c8e223942b9c
17+ sha256: b88dddc6b991cd570c6750f8d309a9feb76938b29a690abdabc4ababb8c7b2f8
18+ size: 709
6819 original:
69− hackage: servant-server-0.18.2
20+ hackage: some-1.0.3
7021snapshots:
7122- completed:
72− size: 533252
73− url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/16/27.yaml
74− sha256: c2aaae52beeacf6a5727c1010f50e89d03869abfab6d2c2658ade9da8ed50c73
75− original: lts-16.27
23+ sha256: 3770dfd79f5aed67acdcc65c4e7730adddffe6dba79ea723cfb0918356fc0f94
24+ size: 648660
25+ url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/4.yaml
26+ original: lts-20.4
test/Breakdown.hs+1−1
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE TypeOperators #-}
77
test/Nested.hs+2−2
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE TypeOperators #-}
77
test/Post.hs+2−2
@@ -1,8 +1,8 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE OverloadedStrings #-}
4+
5+
66{-# LANGUAGE TypeOperators #-}
77
88module Post where
test/Product.hs+1−1
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE TypeOperators #-}
77
test/Put.hsnew file+33
@@ -0,0 +1,33 @@
1+{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE DerivingStrategies #-}
4+
5+
6+{-# LANGUAGE TypeOperators #-}
7+
8+module Put where
9+
10+import Data.Aeson
11+import Data.Hashable
12+import GHC.Generics (Generic)
13+import Servant
14+
15+type Api =
16+ Get '[JSON] Foo
17+ :<|> ReqBody '[JSON] Foo :> Put '[JSON] ()
18+ :<|> ReqBody '[JSON] Foo :> Put '[JSON] NoContent
19+
20+data Foo = Foo
21+ deriving (Eq, Show, Generic)
22+
23+instance Hashable Foo
24+
25+instance ToJSON Foo
26+
27+instance FromJSON Foo
28+
29+server :: Server Api
30+server =
31+ pure Foo
32+ :<|> const (pure ())
33+ :<|> const (pure NoContent)
test/QueryParams.hs+3−3
@@ -1,8 +1,8 @@
11{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE OverloadedStrings #-}
4+
5+
66{-# LANGUAGE TypeOperators #-}
77
88module QueryParams where
test/Spec.hs+30−21
@@ -21,6 +21,7 @@ import qualified Foo
2121import qualified Headers
2222import qualified Nested
2323import qualified Post
24+import qualified Put
2425import qualified Product
2526import qualified QueryParams
2627import qualified Roboservant as R
@@ -36,7 +37,7 @@ import Servant.Client(ClientEnv, mkClientEnv, baseUrlPort, parseBaseUrl,HasClien
3637import Network.Wai(Application)
3738import qualified Network.Wai.Handler.Warp as Warp
3839import Network.HTTP.Client (newManager, defaultManagerSettings)
39−import Servant(Endpoints,HasServer)
40+import Control.Monad((>=>))
4041
4142main :: IO ()
4243main = hspec spec
@@ -47,15 +48,15 @@ fuzzBoth
4748 HasClient ClientM a)
4849 => String -> Server a -> R.Config -> (Maybe R.Report -> IO ()) -> Spec
4950fuzzBoth name server config condition = do
50− it (name <> " via server") $ do
51+ it (name <> " via server") $
5152 RS.fuzz @a server config >>= condition
5253
53− around (withServer (serve (Proxy :: Proxy a) server)) $ do
54+ around (withServer (serve (Proxy :: Proxy a) server)) $
5455 it (name <> " via client") $ \(clientEnv::ClientEnv) -> do
55− RC.fuzz @a clientEnv config >>= condition
56+ RC.fuzz @a clientEnv config >>= condition
5657
5758withServer :: Application -> ActionWith ClientEnv -> IO ()
58−withServer app action = Warp.testWithApplication (pure app) (\p -> genClientEnv p >>= action)
59+withServer app action = Warp.testWithApplication (pure app) (genClientEnv >=> action)
5960 where genClientEnv port = do
6061 baseUrl <- parseBaseUrl "http://localhost"
6162 manager <- newManager defaultManagerSettings
@@ -74,9 +75,15 @@ spec = do
7475 Just (R.InsufficientCoverage _) -> True
7576 _ -> False
7677 ))
77− describe "posted body" $ do
78+ describe "posted body" $
7879 fuzzBoth @Post.Api "passes a coverage check using a posted body" Post.server R.defaultConfig {R.coverageThreshold = 0.99}
79− (`shouldSatisfy` isNothing)
80+ (`shouldSatisfy` isNothing)
81+
82+
83+ describe "PUTted body" $
84+ fuzzBoth @Put.Api "passes a coverage check using a posted body" Put.server R.defaultConfig {R.coverageThreshold = 0.99}
85+ (`shouldSatisfy` isNothing)
86+
8087
8188 describe "seeded" $ do
8289 let res = Seeded.Seed 1
@@ -84,31 +91,29 @@ spec = do
8491 (R.defaultConfig {R.seed = [(toDyn res, hash res)]})
8592 (`shouldSatisfy` isNothing)
8693
87− describe "Foo" $ do
94+ describe "Foo" $
8895 fuzzBoth @Foo.Api "finds an error in a basic app" Foo.server R.defaultConfig (`shouldSatisfy` serverFailure)
8996
90− describe "QueryParams" $ do
97+ describe "QueryParams" $
9198 fuzzBoth @QueryParams.Api "can handle query params" QueryParams.server R.defaultConfig { R.seed = [R.hashedDyn (12::Int)] }
92− (`shouldSatisfy` isNothing)
99+ (`shouldSatisfy` isNothing)
93100
94101 describe "BuildFrom" $ do
95− describe "headers (and sum types)" $ do
102+ describe "headers (and sum types)" $
96103 fuzzBoth @Headers.Api "should find a failure that's dependent on using header info" Headers.server R.defaultConfig
97− (`shouldSatisfy` serverFailure)
98− describe "product types" $ do
104+ (`shouldSatisfy` serverFailure)
105+ describe "product types" $
99106 fuzzBoth @Product.Api "should find a failure that's dependent on creating a product" Product.server
100− R.defaultConfig {R.seed = [R.hashedDyn 'a', R.hashedDyn (1 :: Int)]}
101− (`shouldSatisfy` serverFailure)
107+ R.defaultConfig {R.seed = [R.hashedDyn 'a', R.hashedDyn (1 :: Int)]}
108+ (`shouldSatisfy` serverFailure)
102109 describe "Breakdown" $ do
103110 fuzzBoth @Breakdown.ProductApi "handles products" Breakdown.productServer R.defaultConfig
104111 (`shouldSatisfy` serverFailure)
105112 fuzzBoth @Breakdown.SumApi "handles sums" Breakdown.sumServer R.defaultConfig
106113 (`shouldSatisfy` serverFailure)
107− describe "flattening" $ do
108− -- we don't actually do much here, this is just here to document the appropriate response
109− -- if you get a type error with a nested api.
114+ describe "flattening" $
110115 fuzzBoth @Nested.FlatApi "can handle nested apis" Nested.server R.defaultConfig {R.coverageThreshold = 0.99}
111− (`shouldSatisfy` isNothing)
116+ (`shouldSatisfy` isNothing)
112117
113118serverFailure :: Maybe R.Report -> Bool
114119serverFailure = \case
@@ -132,9 +137,13 @@ deriving via (R.Atom Seeded.Seed) instance R.BuildFrom Seeded.Seed
132137deriving via (R.Atom Void) instance R.BuildFrom Void
133138
134139deriving via (R.Atom Post.FooPost) instance R.Breakdown Post.FooPost
135−
136140deriving via (R.Atom Post.FooPost) instance R.BuildFrom Post.FooPost
137141
142+deriving via (R.Atom Put.Foo) instance R.Breakdown Put.Foo
143+deriving via (R.Atom Put.Foo) instance R.BuildFrom Put.Foo
144+
145+
146+
138147deriving via (R.Compound Breakdown.Foo) instance R.Breakdown Breakdown.Foo
139148
140149deriving via (R.Compound Product.Foo) instance R.BuildFrom Product.Foo
@@ -149,7 +158,7 @@ shouldFail =
149158 ( \i ->
150159 i
151160 { itemExample = \p a cb -> do
152− r <- (itemExample i) p a cb
161+ r <- itemExample i p a cb
153162 pure
154163 r
155164 { resultStatus = case resultStatus r of
test/UnsafeIO.hs+3−3
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE TypeOperators #-}
77
@@ -22,7 +22,7 @@ healthcheck ref = do
2222 t <- liftIO $ readIORef ref
2323 case t of
2424 0 -> pure ()
25− n -> throwError $ err500 {errBody = "observed inconsistency: " <> (BL8.pack $ show n)}
25+ n -> throwError $ err500 {errBody = "observed inconsistency: " <> BL8.pack (show n)}
2626
2727makeServer :: IO (Server UnsafeApi)
2828makeServer = do
test/Valid.hs+1−2
@@ -1,7 +1,7 @@
11{-# LANGUAGE DataKinds #-}
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DerivingStrategies #-}
4−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE TypeOperators #-}
77
@@ -10,7 +10,6 @@ module Valid where
1010import Data.Void
1111import GHC.Generics
1212import Servant
13−import Servant.API.Generic
1413import Servant.Server.Generic
1514
1615type Api =