git.iko.soy/typeable/pagerduty
on develop help sign in
Adding generics-sop to serialise query strings
commitd784bca110a6b22bb8a85d22e08437c0b1ef3c13 authorBrendan Hay <brendan.g.hay@gmail.com> dateWed 1 Oct 2014, 17:24 (12 years ago) parent084a256
8 files changed+358−201
pagerduty.cabal+20−19 src/Generics/SOP/Query.hs → src/Network/PagerDuty/Generics.hs+35−16 src/Network/PagerDuty/Options.hs+56 src/Network/PagerDuty/REST/LogEntries.hs+73−4 src/Network/PagerDuty/REST/Reports.hs+49−47 src/Network/PagerDuty/REST/Services.hs+1−1 src/Network/PagerDuty/TH.hs+60−44 src/Network/PagerDuty/Types.hs+64−70
pagerduty.cabal+20−19
@@ -38,7 +38,7 @@ library
3838 -- , Network.PagerDuty.REST.Incidents
3939 -- , Network.PagerDuty.REST.LogEntries
4040 -- , Network.PagerDuty.REST.MaintenanceWindows
41− -- , Network.PagerDuty.REST.Reports
41+ Network.PagerDuty.REST.Reports
4242 -- , Network.PagerDuty.REST.Schedules
4343 -- , Network.PagerDuty.REST.Services
4444 -- , Network.PagerDuty.REST.Users
@@ -46,33 +46,34 @@ library
4646
4747
4848-- other-modules:
49− Generics.SOP.Query
49+ Network.PagerDuty.Generics
5050 , Network.PagerDuty.IO
51+ , Network.PagerDuty.Options
5152 , Network.PagerDuty.TH
5253 , Network.PagerDuty.Types
5354
5455 build-depends:
55− aeson >= 0.8 && < 1.0
56− , base >= 4.5 && < 5.0
57−-- , base64-bytestring >= 1.0 && < 1.1
58− , bytestring >= 0.9 && < 1.0
56+ aeson > 0.7 && <= 8.0
57+ , base >= 4.5 && < 5.0
58+-- , base64-bytestring >= 1.0 && < 1.1
59+ , bytestring >= 0.9 && < 1.0
5960 , bytestring-conversion >= 0.2.1
6061 , conduit >= 0.5.5
61− , mmorph >= 1.0
62−
63− , exceptions >= 0.6
6462 , data-default >= 0.5
65− , http-client >= 0.3.6 && < 0.5
66− , http-types >= 0.8 && < 1.0
67− , lens >= 4.0 && < 5.0
63+ , exceptions >= 0.6
64+ , http-client >= 0.3.6 && < 0.5
65+ , http-types >= 0.8 && < 1.0
66+ , lens >= 4.0 && < 5.0
6867 , lens-aeson >= 1.0
69− , mtl >= 2.2.1 && < 3.0
70− , text >= 1.1 && < 2.0
71− , time >= 1.4 && < 2.0
72− , transformers >= 0.3 && < 0.5
68+ , mmorph >= 1.0
69+ , mtl >= 2.2.1 && < 3.0
70+ , old-locale
71+ , text >= 1.1 && < 2.0
72+ , time == 1.4.*
73+ , transformers >= 0.4.1 && < 0.5
7374 , unordered-containers >= 0.2.3
7475
75− , transformers-base
76− , monad-control
77−
7876 , generics-sop
77+ , monad-control
78+ , template-haskell
79+ , transformers-base
src/Generics/SOP/Query.hs → src/Network/PagerDuty/Generics.hsrenamed+35−16
@@ -3,9 +3,8 @@
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE ScopedTypeVariables #-}
55{-# LANGUAGE TupleSections #-}
6−{-# LANGUAGE ViewPatterns #-}
76
8−-- Module : Generics.SOP.Query
7+-- Module : Network.PagerDuty.Generics
98-- Copyright : (c) 2013-2014 Brendan Hay <brendan.g.hay@gmail.com>
109-- License : This Source Code Form is subject to the terms of
1110-- the Mozilla Public License, v. 2.0.
@@ -15,20 +14,29 @@
1514-- Stability : experimental
1615-- Portability : non-portable (GHC extensions)
1716
18−module Generics.SOP.Query
17+module Network.PagerDuty.Generics
1918 ( gquery
19+ , gqueryWith
2020 ) where
2121
22+import Data.Aeson.Types
2223import Data.ByteString (ByteString)
2324import qualified Data.ByteString.Char8 as BS
2425import Generics.SOP
2526import Network.HTTP.Types
2627import Network.HTTP.Types.QueryLike
28+import Network.PagerDuty.Options
2729
2830gquery :: forall a. (Generic a, HasDatatypeInfo a, All2 QueryValueLike (Code a))
2931 => a
3032 -> Query
31−gquery a = case datatypeInfo (Proxy :: Proxy a) of
33+gquery = gqueryWith underscored
34+
35+gqueryWith :: forall a. (Generic a, HasDatatypeInfo a, All2 QueryValueLike (Code a))
36+ => Options
37+ -> a
38+ -> Query
39+gqueryWith o a = case datatypeInfo (Proxy :: Proxy a) of
3240 ADT _ _ cs -> go cs (from a)
3341 Newtype _ _ c -> go (c :* Nil) (from a)
3442 where
@@ -36,27 +44,38 @@ gquery a = case datatypeInfo (Proxy :: Proxy a) of
3644 => NP ConstructorInfo xss
3745 -> SOP I xss
3846 -> Query
39− go cs (SOP sop) = unI . hcollapse $ hcliftA2' p gctor cs sop
47+ go cs (SOP sop) = unI . hcollapse $ hcliftA2' p (gctor o) cs sop
4048
41−gctor :: All QueryValueLike xs => ConstructorInfo xs -> NP I xs -> K Query xs
42−gctor (Constructor (BS.pack -> n)) args =
43− K . hcollapse $ hcliftA p (K . (n,) . toQueryValue . unI) args
49+gctor :: All QueryValueLike xs
50+ => Options
51+ -> ConstructorInfo xs
52+ -> NP I xs
53+ -> K Query xs
54+gctor o (Constructor n) args =
55+ K . hcollapse $ hcliftA p (K . (k,) . toQueryValue . unI) args
56+ where
57+ k = BS.pack $ (constructorTagModifier o) n
4458
45−gctor (Record _ ns) args =
46− K . hcollapse $ hcliftA2 p gfield ns args
59+gctor o (Record _ ns) args =
60+ K . hcollapse $ hcliftA2 p (gfield o) ns args
4761
48−gctor (Infix (BS.pack -> n) _ _) (x :* y :* Nil) =
49− K [ (n, toQueryValue (unI x))
50− , (n, toQueryValue (unI y))
62+gctor o (Infix n _ _) (x :* y :* Nil) =
63+ K [ (k, toQueryValue (unI x))
64+ , (k, toQueryValue (unI y))
5165 ]
66+ where
67+ k = BS.pack $ (constructorTagModifier o) n
5268
53−gctor (Infix _ _ _) _ = error "inaccessible"
69+gctor _ (Infix _ _ _) _ =
70+ error "Network.PagerDuty.Generics.inaccessible"
5471
5572gfield :: QueryValueLike a
56− => FieldInfo a
73+ => Options
74+ -> FieldInfo a
5775 -> I a
5876 -> K (ByteString, Maybe ByteString) a
59−gfield (FieldInfo f) (I a) = K (BS.pack f, toQueryValue a)
77+gfield o (FieldInfo f) (I a) =
78+ K (BS.pack $ (fieldLabelModifier o) f, toQueryValue a)
6079
6180p :: Proxy QueryValueLike
6281p = Proxy
src/Network/PagerDuty/Options.hsnew file+56
@@ -0,0 +1,56 @@
1+-- Module : Network.PagerDuty.Options
2+-- Copyright : (c) 2013-2014 Brendan Hay <brendan.g.hay@gmail.com>
3+-- License : This Source Code Form is subject to the terms of
4+-- the Mozilla Public License, v. 2.0.
5+-- A copy of the MPL can be found in the LICENSE file or
6+-- you can obtain it at http://mozilla.org/MPL/2.0/.
7+-- Maintainer : Brendan Hay <brendan.g.hay@gmail.com>
8+-- Stability : experimental
9+-- Portability : non-portable (GHC extensions)
10+
11+module Network.PagerDuty.Options
12+ ( dropped
13+ , hyphenated
14+ , underscored
15+ ) where
16+
17+import Data.Aeson.Types
18+import Data.Char
19+import Data.List
20+
21+dropped :: Int -> Options -> Options
22+dropped n o = o
23+ { constructorTagModifier = constructorTagModifier o . drop n
24+ }
25+
26+hyphenated :: Options
27+hyphenated = underscored
28+ { fieldLabelModifier = unsuffix . hyphenate . unprefix
29+ , constructorTagModifier = hyphenate
30+ }
31+
32+underscored :: Options
33+underscored = defaultOptions
34+ { fieldLabelModifier = unsuffix . underscore . unprefix
35+ , constructorTagModifier = underscore
36+ , omitNothingFields = True
37+ , allNullaryToStringTag = True
38+ }
39+
40+hyphenate :: String -> String
41+hyphenate = intercalate "-" . map lower . splitBy isUpper
42+
43+underscore :: String -> String
44+underscore = intercalate "_" . map lower . splitBy isUpper
45+
46+lower :: String -> String
47+lower = map toLower
48+
49+unsuffix :: String -> String
50+unsuffix = takeWhile (/= '\'')
51+
52+unprefix :: String -> String
53+unprefix = dropWhile (not . isUpper)
54+
55+splitBy :: (a -> Bool) -> [a] -> [[a]]
56+splitBy p = groupBy (const (not . p))
src/Network/PagerDuty/REST/LogEntries.hs+73−4
@@ -25,26 +25,90 @@ module Network.PagerDuty.REST.LogEntries
2525logs :: Path
2626logs = "log_entries"
2727
28+includes :: Query
29+includes =
30+ [ ("include[]", Just "channel")
31+ , ("include[]", Just "incident")
32+ , ("include[]", Just "service")
33+ ]
34+
35+data LogEntry = LogEntry
36+ deriving (Eq, Show)
37+
38+data GetLogs
39+ { _leTimeZone' :: Maybe TimeZone
40+ , _leSince' :: Maybe Date
41+ , _leUntil' :: Maybe Date
42+ , _leIsOverview' :: Bool
43+ } deriving (Eq, Show)
44+
45+instance Paginated GetLogs
46+
47+deriveJSON ''LogEntry
48+makeLenses ''LogEntry
49+
50+-- | Time zone in which dates in the result will be rendered.
51+--
52+-- Defaults to UTC.
53+leTimeZone :: Lens' (Request LogEntry s b) TimeZone
54+leTimeZone = upd.leTimeZone'
55+
56+-- | The start of the date range over which you want to search.
57+leSince :: Lens' (Request LogEntry s b) (Maybe Date)
58+leSince = upd.leSince'
59+
60+-- | The end of the date range over which you want to search.
61+leUntil :: Lens' (Request LogEntry s b) (Maybe Date)
62+leUntil = upd.leUntil'
63+
64+-- | If true, will only return log entries of type trigger, acknowledge, or resolve.
65+--
66+-- Defaults to false.
67+leIsOverview :: Lens' (Request LogEntry s b) Bool
68+leIsOverview = upd.leIsOverview'
69+
2870-- | List all incident log entries across the entire account.
2971--
3072-- @GET \/log_entries@
3173--
3274-- See: <http://developer.pagerduty.com/documentation/rest/log_entries/list>
33−listLogs = undefined
75+listLogs :: Request GetLogs s [LogEntry]
76+listLogs =
77+ mk GetLogs
78+ { _leTimeZone' = def
79+ , _leSince' = Nothing
80+ , _leUntil' = Nothing
81+ , _leIsOverview' = False
82+ } & path .~ logs
83+ & query .~ includes
84+ & unwrap .~ key "log_entries"
3485
3586-- | List all incident log entries that describe interactions with a specific user.
3687--
3788-- @GET \/users\/\:user_id\/log_entries@
3889--
3990-- See: <http://developer.pagerduty.com/documentation/rest/log_entries/user_log_entries>
40−listUserLogs = undefined
91+listUserLogs :: UserId -> Request GetLogs s [LogEntry]
92+listUserLogs u = listLogs & path .~ "users" % u % logs
4193
4294-- | List all incident log entries for a specific incident.
4395--
4496-- @GET \/incidents\/\:incident_id\/log_entries@
4597--
4698-- See: <http://developer.pagerduty.com/documentation/rest/log_entries/incident_log_entries>
47−listIncidentLogs = undefined
99+listIncidentLogs :: Request GetLogs s [LogEntry]
100+listIncidentLogs i = listLogs & path .~ "incidents" % u % logs
101+
102+newtype GetLogEntry = GetLogEntry
103+ { _gleTimeZone' :: TimeZone
104+ } deriving (Eq, Show)
105+
106+deriveJSON ''GetLogEntry
107+makeLenses ''GetLogEntry
108+
109+-- | Time zone in which dates in the result will be rendered.
110+--
111+-- Defaults to UTC.
48112
49113-- | Get details for a specific incident log entry. This method provides additional
50114-- information you can use to get at raw event data.
@@ -52,4 +116,9 @@ listIncidentLogs = undefined
52116-- @GET \/log_entries\/\:id@
53117--
54118-- See: <http://developer.pagerduty.com/documentation/rest/log_entries/show>
55−getLog = undefined
119+getLog :: LogEntryId -> Request GetLogEntry s LogEntry
120+getLog l =
121+ mk GetLogEntry
122+ { _gleTimeZone' = def
123+ } & path .~ logs % l
124+ & query .~ includes
src/Network/PagerDuty/REST/Reports.hs+49−47
@@ -1,6 +1,8 @@
1+{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE ExtendedDefaultRules #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE TemplateHaskell #-}
5+{-# LANGUAGE TypeFamilies #-}
46
57{-# OPTIONS_GHC -fno-warn-type-defaults #-}
68
@@ -24,6 +26,11 @@ module Network.PagerDuty.REST.Reports
2426 -- * Incidents Per Time
2527 , incidentsPerTime
2628
29+ -- * Lenses
30+ , rSince
31+ , rUntil
32+ , rRollup
33+
2734 -- * Types
2835 , Rollup (..)
2936
@@ -52,8 +59,6 @@ module Network.PagerDuty.REST.Reports
5259import Control.Lens
5360import Data.Aeson.Lens
5461import Data.ByteString.Builder (Builder)
55−import Data.Text (Text)
56−import Network.HTTP.Types
5762import Network.PagerDuty.TH
5863import Network.PagerDuty.Types
5964
@@ -66,9 +71,9 @@ data Rollup
6671 = Daily
6772 | Weekly
6873 | Monthly
69− deriving (Eq Show)
74+ deriving (Eq, Show)
7075
71−deriveJSON ''Rollup
76+deriveNullary ''Rollup
7277
7378data Report = Report
7479 { _rSince' :: Date
@@ -76,10 +81,29 @@ data Report = Report
7681 , _rRollup' :: !Rollup
7782 } deriving (Eq, Show)
7883
79−deriveJSON ''Report
80−makeLenses ''Report
84+deriveQuery ''Report
85+
86+-- | Start of the date range over which you want to search. The time element
87+-- is optional.
88+rSince :: Lens' (Request Report s b) Date
89+rSince = upd.rSince'
90+
91+-- | The end of the date range over which you want to search. This should be
92+-- in the same format as since.
93+rUntil :: Lens' (Request Report s b) Date
94+rUntil = upd.rUntil'
95+
96+-- | Specifies the bucket duration for each summation.
97+--
98+-- Defaults to monthly.
99+--
100+-- Example: A time window of two years (based on since and until) with a
101+-- rollup of monthly will result in 24 sets of data points being returned
102+-- (one for each month in the span).
103+rRollup :: Lens' (Request Report s b) Rollup
104+rRollup = upd.rRollup'
81105
82−data AlertReport
106+data AlertReport = AlertReport
83107 { _arStart :: Date
84108 , _arEnd :: Date
85109 , _arNumberOfAlerts :: !Int
@@ -88,8 +112,7 @@ data AlertReport
88112 , _arNumberOfEmailAlerts :: !Int
89113 } deriving (Eq, Show)
90114
91−deriveJSON ''AlertReport
92−makeLenses ''AlertReport
115+deriveRecord ''AlertReport
93116
94117data AlertTotals = AlertTotals
95118 { _atAlerts :: [AlertReport]
@@ -100,37 +123,7 @@ data AlertTotals = AlertTotals
100123 , _atTotalNumberOfBillableAlerts :: !Int
101124 } deriving (Eq, Show)
102125
103−deriveJSON ''AlertTotals
104−makeLenses ''AlertTotals
105−
106−data IncidentReport = IncidentReport
107− { _irStart :: Date
108− , _irEnd :: Date
109− , _irNumberOfIncidents :: !Int
110− } deriving (Eq, Show)
111−
112−deriveJSON ''IncidentReport
113−makeLenses ''IncidentReport
114−
115−-- | Start of the date range over which you want to search. The time element
116−-- is optional.
117−rSince :: Lens' (Request Report s r) Date
118−rSince = upd.rSince'
119−
120−-- | The end of the date range over which you want to search. This should be
121−-- in the same format as since.
122−rUntil :: Lens' (Request Report s r) Date
123−rUntil = upd.rUntil'
124−
125−-- | Specifies the bucket duration for each summation.
126−--
127−-- Defaults to monthly.
128−--
129−-- Example: A time window of two years (based on since and until) with a
130−-- rollup of monthly will result in 24 sets of data points being returned
131−-- (one for each month in the span).
132−rSince :: Lens' (Request Report s r) Rollup
133−rSince = upd.rSince'
126+deriveRecord ''AlertTotals
134127
135128-- | Get high level statistics about the number of alerts (SMSes, phone calls and
136129-- emails) sent for the desired time period, summed daily, weekly or monthly.
@@ -143,11 +136,19 @@ alertsPerTime :: Date -- ^ 'rSince'
143136 -> Request Report s AlertTotals
144137alertsPerTime s u =
145138 mk Report
146− { _rSince = s
147− , _rUntil = u
148− , _rRollup = Monthly
139+ { _rSince' = s
140+ , _rUntil' = u
141+ , _rRollup' = Monthly
149142 } & path .~ reports % "alerts_per_time"
150143
144+data IncidentReport = IncidentReport
145+ { _irStart :: Date
146+ , _irEnd :: Date
147+ , _irNumberOfIncidents :: !Int
148+ } deriving (Eq, Show)
149+
150+deriveRecord ''IncidentReport
151+
151152-- | Get high level statistics about the number of incidents created for the
152153-- desired time period, summed daily, weekly or monthly.
153154--
@@ -159,7 +160,8 @@ incidentsPerTime :: Date -- ^ 'rSince'
159160 -> Request Report s [IncidentReport]
160161incidentsPerTime s u =
161162 mk Report
162− { _rSince = s
163− , _rUntil = u
164− , _rRollup = Monthly
165− } & path .~ reports % "incidents_per_time"
163+ { _rSince' = s
164+ , _rUntil' = u
165+ , _rRollup' = Monthly
166+ } & path .~ reports % "incidents_per_time"
167+ & unwrap .~ key "incidents"
src/Network/PagerDuty/REST/Services.hs+1−1
@@ -372,7 +372,7 @@ listServices =
372372 mk ListServices
373373 { _lsTimeZone' = Nothing
374374 } & path .~ services
375− & query <>~ includes
375+ & query .~ includes
376376 & unwrap .~ key "escalation_policies"
377377
378378-- | Time zone in which dates in the result will be rendered.
src/Network/PagerDuty/TH.hs+60−44
@@ -1,4 +1,4 @@
1−{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
1+{-# LANGUAGE TemplateHaskell #-}
22
33-- Module : Network.PagerDuty.TH
44-- Copyright : (c) 2013-2014 Brendan Hay <brendan.g.hay@gmail.com>
@@ -12,64 +12,80 @@
1212
1313module Network.PagerDuty.TH
1414 (
15− -- * Lenses
16− makeLens
15+ -- * Compound expressions
16+ deriveNullary
17+ , deriveRecord
18+ , deriveQuery
19+ , deriveBody
20+
1721 -- * JSON
1822 , deriveJSON
1923 , deriveJSONWith
20− -- ** Options
24+
25+ -- * Lenses
26+ , makeLens
27+
28+ -- * Re-exported generics
29+ , deriveGeneric
30+ , gquery
31+ , gqueryWith
32+
33+ -- * Re-exported options
2134 , dropped
2235 , hyphenated
2336 , underscored
2437 ) where
2538
39+import Control.Applicative
2640import Control.Lens
27−import qualified Data.Aeson.TH as Aeson
41+import qualified Data.Aeson.TH as Aeson
2842import Data.Aeson.Types
29−import Data.Char
30−import Data.List
31−
32−makeLens k = makeLensesWith
33− $ lensRulesFor [(k, drop 1 k)]
34− & simpleLenses .~ True
43+import Data.ByteString (ByteString)
44+import qualified Data.Text.Encoding as Text
45+import Generics.SOP.TH
46+import Language.Haskell.TH
47+import Network.HTTP.Types.QueryLike
48+import Network.PagerDuty.Generics
49+import Network.PagerDuty.Options
3550
36−deriveJSON = deriveJSONWith underscored
37−
38−deriveJSONWith = Aeson.deriveJSON
39−
40−dropped :: Int -> Options -> Options
41−dropped n o = o
42− { constructorTagModifier = constructorTagModifier o . drop n
43− }
44−
45−hyphenated :: Options
46−hyphenated = underscored
47− { fieldLabelModifier = unsuffix . hyphenate . unprefix
48− , constructorTagModifier = hyphenate
49− }
51+deriveNullary :: Name -> Q [Dec]
52+deriveNullary n = concat <$> sequence
53+ [ deriveJSON n
54+ , [d|instance QueryValueLike $(conT n) where toQueryValue = value . toJSON|]
55+ ]
5056
51−underscored :: Options
52−underscored = defaultOptions
53− { fieldLabelModifier = unsuffix . underscore . unprefix
54− , constructorTagModifier = underscore
55− , omitNothingFields = True
56− , allNullaryToStringTag = True
57− }
57+deriveRecord :: Name -> Q [Dec]
58+deriveRecord n = concat <$> sequence
59+ [ deriveJSON n
60+ , makeLenses n
61+ ]
5862
59−hyphenate :: String -> String
60−hyphenate = intercalate "-" . map lower . splitBy isUpper
63+deriveBody :: Name -> Q [Dec]
64+deriveBody n = concat <$> sequence
65+ [ makeLenses n
66+ , deriveJSON n
67+ , [d|instance QueryLike $(conT n) where toQuery = const []|]
68+ ]
6169
62−underscore :: String -> String
63−underscore = intercalate "_" . map lower . splitBy isUpper
70+deriveQuery :: Name -> Q [Dec]
71+deriveQuery n = concat <$> sequence
72+ [ deriveGeneric n
73+ , makeLenses n
74+ , [d|instance ToJSON $(conT n) where toJSON = const (toJSON (object []))|]
75+ , [d|instance QueryLike $(conT n) where toQuery = gquery|]
76+ ]
6477
65−lower :: String -> String
66−lower = map toLower
78+deriveJSON :: Name -> Q [Dec]
79+deriveJSON = deriveJSONWith underscored
6780
68−unsuffix :: String -> String
69−unsuffix = takeWhile (/= '\'')
81+deriveJSONWith :: Options -> Name -> Q [Dec]
82+deriveJSONWith = Aeson.deriveJSON
7083
71−unprefix :: String -> String
72−unprefix = dropWhile (not . isUpper)
84+makeLens :: String -> Name -> Q [Dec]
85+makeLens k = makeLensesWith
86+ $ lensRulesFor [(k, drop 1 k)]
87+ & simpleLenses .~ True
7388
74−splitBy :: (a -> Bool) -> [a] -> [[a]]
75−splitBy p = groupBy (const (not . p))
89+value :: Value -> Maybe ByteString
90+value (String t) = Just (Text.encodeUtf8 t)
91+value _ = Nothing
src/Network/PagerDuty/Types.hs+64−70
@@ -57,28 +57,21 @@ import Data.Aeson.Types (Parser)
5757import Data.ByteString (ByteString)
5858import qualified Data.ByteString.Char8 as BS
5959import Data.ByteString.Conversion
60−import Data.Function
60+import Data.Default
6161import qualified Data.HashMap.Strict as Map
6262import Data.Monoid
6363import Data.String
6464import Data.Text (Text)
65−import qualified Data.Time as Time
66−import Data.Time hiding (TimeZone)
65+import qualified Data.Text.Encoding as Text
66+import Data.Time
6767import GHC.TypeLits
6868import Network.HTTP.Types
6969import Network.HTTP.Types.QueryLike
7070import Network.PagerDuty.TH
71+import System.Locale
7172
72−-- FIXME: Query String parameters vs JSON bodies for GET
73−
74−-- FIXME: UTCTime: http://developer.pagerduty.com/documentation/rest/types#datetime
75−newtype Date = Date { unDate :: ZonedTime }
76− deriving (Show)
77−
78−instance Eq Date where
79− (Date a) == (Date b) =
80− on (==) zonedTimeToLocalTime a b
81− || on (==) zonedTimeZone a b
73+newtype Date = Date { unDate :: UTCTime }
74+ deriving (Eq, Ord, Show)
8275
8376instance FromJSON Date where
8477 parseJSON = fmap Date . parseJSON
@@ -86,22 +79,38 @@ instance FromJSON Date where
8679instance ToJSON Date where
8780 toJSON = toJSON . unDate
8881
89−newtype TimeZone = TimeZone { unTZ :: Time.TimeZone }
82+instance ToByteString Date where
83+ builder = builder
84+ . formatTime defaultTimeLocale (iso8601DateFormat $ Just "%XZ")
85+ . unDate
86+
87+instance QueryValueLike Date where
88+ toQueryValue = Just . toByteString'
89+
90+newtype TZ = TZ { unTZ :: TimeZone }
9091 deriving (Eq, Ord, Show)
9192
92−instance FromJSON TimeZone where
93+instance FromJSON TZ where
9394 parseJSON = undefined
9495
95−instance ToJSON TimeZone where
96− toJSON = undefined
96+instance ToJSON TZ where
97+ toJSON = toJSON . Text.decodeUtf8 . toByteString'
98+
99+instance ToByteString TZ where
100+ builder = builder . timeZoneName . unTZ
97101
98−data Security = Basic | Token -- None
102+instance QueryValueLike TZ where
103+ toQueryValue = Just . toByteString'
104+
105+instance Default TZ where
106+ def = TZ utc
107+
108+data Security = Basic | Token
99109 deriving (Eq, Show)
100110
101111data Auth (a :: Security) where
102112 AuthBasic :: ByteString -> ByteString -> Auth Basic
103113 AuthToken :: ByteString -> Auth Token
104−-- AuthNone :: Auth None
105114
106115deriving instance Eq (Auth a)
107116deriving instance Show (Auth a)
@@ -145,8 +154,7 @@ data IntegrationError = IntegrationError
145154 , _ieErrors :: [Text]
146155 } deriving (Eq, Show)
147156
148−deriveJSON ''IntegrationError
149−makeLenses ''IntegrationError
157+deriveRecord ''IntegrationError
150158
151159data RestError = RestError
152160 { _reCode :: Code
@@ -154,8 +162,7 @@ data RestError = RestError
154162 , _reErrors :: [Text]
155163 } deriving (Eq, Show)
156164
157−deriveJSON ''RestError
158−makeLenses ''RestError
165+deriveRecord ''RestError
159166
160167data Error
161168 = Internal String
@@ -228,43 +235,43 @@ renderPath = toByteString' . mappend v1
228235 v1 = "/api/v1"
229236
230237data Request a (s :: Security) b where
231− Request :: ToJSON a
232− => { _rqPayload :: a
233− , _rqMethod :: !StdMethod
234− , _rqPath :: Path
235− , _rqQuery :: Query
236− , _rqPager :: Maybe Pager
237− , _rqUnwrap :: Value -> Parser Value
238+ Request :: (QueryLike a, ToJSON a)
239+ => { _rqMeth :: !StdMethod
240+ , _rqPath :: Path
241+ , _rqQuery :: Query
242+ , _rqBody :: a
243+ , _rqPager :: Maybe Pager
244+ , _rqUnwrap :: Value -> Parser Value
238245 }
239246 -> Request a s b
240247
241248instance ToJSON (Request a s b) where
242249 -- Manually unwrapped to ensure GADT constraint holds.
243− toJSON (Request p _ _ _ q _) = Object $
244− let Object x = toJSON p
245− in case toJSON q of
250+ toJSON (Request _ _ _ b p _) = Object $
251+ let Object x = toJSON b
252+ in case toJSON p of
246253 (Object y) -> x <> y
247254 _ -> x
248255
249256type Unwrap = Getting (First Value) Value Value
250257
251258-- | Create a defaulted request from the payload type.
252−mk :: ToJSON a => a -> Request a s b
253−mk x = Request x GET mempty mempty Nothing pure
259+mk :: (QueryLike a, ToJSON a) => a -> Request a s b
260+mk x = Request GET mempty mempty x Nothing pure
254261
255262empty :: Request Empty s r
256263empty = mk Empty
257264
258−-- | Modify the request state.
259−upd :: ToJSON a => Lens' (Request a s b) a
260−upd = lens _rqPayload (\(Request _ m p q g u) x -> Request x m p q g u)
265+-- | Lens into the body of a request.
266+upd :: (QueryLike a, ToJSON a) => Lens' (Request a s b) a
267+upd = lens _rqBody (\(Request m p q _ g u) x -> Request m p q x g u)
261268
262269-- | Drop the security constraint.
263270auth :: Request a s b -> Request a t b
264271auth (Request x m p q g u) = Request x m p q g u
265272
266273meth :: Lens' (Request a s b) StdMethod
267−meth = lens _rqMethod (\r x -> r { _rqMethod = x })
274+meth = lens _rqMeth (\r x -> r { _rqMeth = x })
268275
269276path :: Lens' (Request a s b) Path
270277path = lens _rqPath (\r x -> r { _rqPath = x })
@@ -293,15 +300,15 @@ class Paginate a where
293300newtype Key (a :: Symbol) = Key Text
294301 deriving (Eq, Show, IsString)
295302
296−instance ToByteString (Key a) where
297− builder (Key k) = builder k
298−
299303instance FromJSON (Key a) where
300304 parseJSON = withText "key" (return . Key)
301305
302306instance ToJSON (Key a) where
303307 toJSON (Key k) = toJSON k
304308
309+instance ToByteString (Key a) where
310+ builder (Key k) = builder k
311+
305312instance QueryValueLike (Key a) where
306313 toQueryValue = Just . toByteString'
307314
@@ -311,15 +318,15 @@ type IncidentKey = Key "incident"
311318newtype Id (a :: Symbol) = Id Text
312319 deriving (Eq, Show, IsString)
313320
314−instance ToByteString (Id a) where
315− builder (Id i) = builder i
316−
317321instance FromJSON (Id a) where
318322 parseJSON = withText "id" (return . Id)
319323
320324instance ToJSON (Id a) where
321325 toJSON (Id i) = toJSON i
322326
327+instance ToByteString (Id a) where
328+ builder (Id i) = builder i
329+
323330instance QueryValueLike (Id a) where
324331 toQueryValue = Just . toByteString'
325332
@@ -345,37 +352,25 @@ instance FromJSON Empty where
345352 f !o | Map.null o = pure Empty
346353 | otherwise = fail "Unexpected non-empty JSON object."
347354
348−newtype Email = Email Text
349− deriving (Eq, Show, IsString)
350−
351−deriveJSON ''Email
352−
353−newtype Phone = Phone Text
354− deriving (Eq, Show, IsString)
355−
356−deriveJSON ''Phone
355+instance QueryLike Empty where
356+ toQuery = const []
357357
358−data Address
359− = AddrEmail Email
360− | AddrPhone Phone
361− deriving (Eq, Show)
358+newtype Address = Address Text
359+ deriving (Eq, Show)
362360
363−instance FromJSON Address where
364− parseJSON o =
365− AddrEmail <$> parseJSON o
366− <|> AddrPhone <$> parseJSON o
361+deriveJSON ''Address
367362
368−instance ToJSON Address where
369− toJSON (AddrEmail e) = toJSON e
370− toJSON (AddrPhone p) = toJSON p
363+instance ToByteString Address where
364+ builder (Address a) = builder a
371365
372−makePrisms ''Address
366+instance QueryValueLike Address where
367+ toQueryValue = Just . toByteString'
373368
374369data User = User
375370 { _userId :: UserId
376371 , _userName :: Text
377− , _userEmail :: Email
378− , _userTimeZone :: TimeZone
372+ , _userEmail :: Address
373+ , _userTimeZone :: TZ
379374 , _userColor :: Text
380375 , _userRole :: Text
381376 , _userAvatarUrl :: Text
@@ -383,5 +378,4 @@ data User = User
383378 , _userInvitationSent :: Bool
384379 } deriving (Eq, Show)
385380
386−deriveJSON ''User
387−makeLenses ''User
381+deriveRecord ''User