Skip to content

Commit 034de29

Browse files
committed
Functora.Uri.FromQuery implemented using GHC.Generics
1 parent ec6c898 commit 034de29

File tree

8 files changed

+178
-8
lines changed

8 files changed

+178
-8
lines changed

ghcjs/delivery-calculator/src/App/Types.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ data St f = St
101101
stTheme :: Theme
102102
}
103103
deriving stock (Generic)
104+
deriving (ToQuery) via GenericType (St f)
104105

105106
deriving stock instance (Hkt f) => Eq (St f)
106107

ghcjs/miso-functora/src/Functora/Miso/Prelude.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Functora.Prelude as X hiding
1414
field,
1515
)
1616
import Functora.Rfc2397 as X
17+
import Functora.Uri as X
1718
import Miso as X hiding
1819
( Key,
1920
Text,

pub/functora/functora.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ common pkg-uri
163163
hs-source-dirs: src/uri
164164
build-depends:
165165
, base
166+
, containers
166167
, modern-uri
167168
, tomland
168169

@@ -181,6 +182,7 @@ common pkg-cfg
181182
, modern-uri
182183
, optparse-applicative
183184
, tomland
185+
, uri
184186

185187
common pkg-web
186188
import: pkg
@@ -316,6 +318,7 @@ library uri
316318
visibility: public
317319
exposed-modules:
318320
Functora.Uri
321+
Functora.Uri.FromQuery
319322
Functora.Uri.ToQuery
320323

321324
library cfg
@@ -465,6 +468,7 @@ test-suite functora-test
465468
Functora.TagsOrphan
466469
Functora.Unicode
467470
Functora.Uri
471+
Functora.Uri.FromQuery
468472
Functora.Uri.ToQuery
469473
Functora.Web
470474
Functora.WebOrphan

pub/functora/src/cfg/Functora/Cfg.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import qualified Data.ByteString.Lazy as BL
4545
import qualified Data.List.NonEmpty as NE
4646
import Functora.CfgOrphan as X ()
4747
import Functora.Prelude
48+
import Functora.Uri
4849
import qualified GHC.Generics as Generics
4950
import qualified Options.Applicative as Cli
5051
import Toml as X
@@ -216,6 +217,15 @@ newtype GenericType a = GenericType
216217
}
217218
deriving stock (Generic)
218219

220+
instance
221+
( Generic a,
222+
Typeable a,
223+
GToQuery (Rep a)
224+
) =>
225+
ToQuery (GenericType a)
226+
where
227+
toQuery = genericToQuery . unGenericType
228+
219229
instance
220230
( Generic a,
221231
Typeable a,

pub/functora/src/test/Functora/UriSpec.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ data Foo = Foo
1414

1515
instance ToQuery Foo
1616

17+
instance FromQuery Foo
18+
1719
mkSample :: (MonadThrow m) => m (Foo, [QueryParam])
1820
mkSample = do
1921
let bar = 123 :: Int
@@ -35,6 +37,7 @@ mkSample = do
3537

3638
spec :: Spec
3739
spec = do
38-
focus . it "ToQuery" $ do
40+
it "ToQuery/FromQuery" $ do
3941
sample <- mkSample
4042
toQuery (fst sample) `shouldBe` snd sample
43+
fromQuery (snd sample) `shouldBe` Just (fst sample)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module Functora.Uri (module X) where
22

3+
import Functora.Uri.FromQuery as X
34
import Functora.Uri.ToQuery as X
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{-# LANGUAGE CPP #-}
2+
3+
module Functora.Uri.FromQuery
4+
( FromQuery (..),
5+
GFromQuery (..),
6+
genericFromQuery,
7+
FromQueryField (..),
8+
textFromQueryField,
9+
readFromQueryField,
10+
)
11+
where
12+
13+
import qualified Data.Map as Map
14+
import Functora.Prelude
15+
import GHC.Generics hiding (from)
16+
import qualified GHC.Generics as G
17+
import Text.URI
18+
import qualified Toml
19+
#if defined(__GHCJS__) && defined(ghcjs_HOST_OS) && defined(wasi_HOST_OS)
20+
import Data.JSString (JSString)
21+
#endif
22+
23+
data FromQueryException
24+
= FromQueryMissingField (RText 'QueryKey)
25+
| FromQueryInvalidField (RText 'QueryKey) (RText 'QueryValue)
26+
deriving stock (Eq, Ord, Show, Data, Generic)
27+
28+
instance Exception FromQueryException
29+
30+
type QueryMap = Map (RText 'QueryKey) (RText 'QueryValue)
31+
32+
toQueryMap :: [QueryParam] -> QueryMap
33+
toQueryMap =
34+
foldl
35+
( \acc -> \case
36+
QueryFlag k ->
37+
Map.insert k (either impureThrow id $ mkQueryValue "True") acc
38+
QueryParam k v ->
39+
Map.insert k v acc
40+
)
41+
mempty
42+
43+
class FromQuery a where
44+
fromQuery ::
45+
(MonadThrow m) => [QueryParam] -> m a
46+
default fromQuery ::
47+
(Generic a, GFromQuery (Rep a), MonadThrow m) => [QueryParam] -> m a
48+
fromQuery =
49+
genericFromQuery
50+
51+
class GFromQuery f where
52+
gFromQuery :: (MonadThrow m) => QueryMap -> m (f p)
53+
54+
genericFromQuery ::
55+
forall a m.
56+
( Generic a,
57+
GFromQuery (Rep a),
58+
MonadThrow m
59+
) =>
60+
[QueryParam] ->
61+
m a
62+
genericFromQuery =
63+
fmap G.to . gFromQuery . toQueryMap
64+
65+
instance (GFromQuery a) => GFromQuery (M1 D c a) where
66+
gFromQuery params = M1 <$> gFromQuery params
67+
68+
instance (GFromQuery a) => GFromQuery (M1 C c a) where
69+
gFromQuery params = M1 <$> gFromQuery params
70+
71+
instance
72+
( Typeable a,
73+
Selector s,
74+
FromQueryField a
75+
) =>
76+
GFromQuery (M1 S s (K1 i a))
77+
where
78+
gFromQuery params = do
79+
k <-
80+
mkQueryKey
81+
. pack
82+
. Toml.stripTypeNamePrefix (Proxy @a)
83+
$ selName (error "selName" :: M1 S s (K1 i a) ())
84+
v <-
85+
maybe (throw $ FromQueryMissingField k) pure $
86+
Map.lookup k params
87+
fmap (M1 . K1) $
88+
fromQueryField k v
89+
90+
instance (GFromQuery a, GFromQuery b) => GFromQuery (a :*: b) where
91+
gFromQuery params = (:*:) <$> gFromQuery params <*> gFromQuery params
92+
93+
class FromQueryField a where
94+
fromQueryField ::
95+
( MonadThrow m
96+
) =>
97+
RText 'QueryKey ->
98+
RText 'QueryValue ->
99+
m a
100+
101+
textFromQueryField ::
102+
forall a m.
103+
( From Text a,
104+
Applicative m
105+
) =>
106+
RText 'QueryKey ->
107+
RText 'QueryValue ->
108+
m a
109+
textFromQueryField _ = pure . from @Text @a . unRText
110+
111+
readFromQueryField ::
112+
( Read a,
113+
MonadThrow m
114+
) =>
115+
RText 'QueryKey ->
116+
RText 'QueryValue ->
117+
m a
118+
readFromQueryField k v =
119+
maybe (throw $ FromQueryInvalidField k v) pure
120+
. readMaybe
121+
$ unRText v
122+
123+
instance FromQueryField Text where
124+
fromQueryField = textFromQueryField
125+
126+
instance FromQueryField String where
127+
fromQueryField = textFromQueryField
128+
129+
instance FromQueryField Int where
130+
fromQueryField = readFromQueryField
131+
132+
instance FromQueryField Integer where
133+
fromQueryField = readFromQueryField
134+
135+
#if defined(__GHCJS__) && defined(ghcjs_HOST_OS) && defined(wasi_HOST_OS)
136+
instance ToQueryField JSString where
137+
fromQueryField = textFromQueryField
138+
#endif

pub/functora/src/uri/Functora/Uri/ToQuery.hs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE UndecidableInstances #-}
32

4-
module Functora.Uri.ToQuery (ToQuery (..)) where
3+
module Functora.Uri.ToQuery
4+
( ToQuery (..),
5+
GToQuery (..),
6+
genericToQuery,
7+
)
8+
where
59

610
import Functora.Prelude
711
import GHC.Generics hiding (from)
@@ -15,30 +19,38 @@ import Data.JSString (JSString)
1519
class (Typeable a) => ToQuery a where
1620
toQuery :: a -> [QueryParam]
1721
default toQuery :: (Generic a, GToQuery (Rep a)) => a -> [QueryParam]
18-
toQuery = gToQuery (Toml.stripTypeNamePrefix $ Proxy @a) . G.from
22+
toQuery = genericToQuery
1923

2024
class GToQuery f where
2125
gToQuery :: (String -> String) -> f p -> [QueryParam]
2226

23-
-- Handle datatype metadata
27+
genericToQuery ::
28+
forall a.
29+
( Generic a,
30+
Typeable a,
31+
GToQuery (Rep a)
32+
) =>
33+
a ->
34+
[QueryParam]
35+
genericToQuery =
36+
gToQuery (Toml.stripTypeNamePrefix $ Proxy @a) . G.from
37+
2438
instance (GToQuery a) => GToQuery (M1 D c a) where
2539
gToQuery fmt (M1 x) = gToQuery fmt x
2640

27-
-- Handle constructor metadata
2841
instance (GToQuery a) => GToQuery (M1 C c a) where
2942
gToQuery fmt (M1 x) = gToQuery fmt x
3043

3144
instance (Selector s, ToQueryField a) => GToQuery (M1 S s (K1 i a)) where
3245
gToQuery fmt m1@(M1 (K1 a)) = do
3346
let name = selName m1
3447
if null name
35-
then mempty -- Skip if there is no field name (like unnamed tuples)
48+
then mempty
3649
else do
3750
k <- mkQueryKey . pack $ fmt name
3851
v <- mkQueryValue $ toQueryField a
3952
pure $ QueryParam k v
4053

41-
-- Handle product type
4254
instance (GToQuery a, GToQuery b) => GToQuery (a :*: b) where
4355
gToQuery fmt (a :*: b) = gToQuery fmt a ++ gToQuery fmt b
4456

0 commit comments

Comments
 (0)