packages feed

airtable-api 0.2.1.1 → 0.3.2.2

raw patch · 4 files changed

+74/−67 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Airtable.Query: airtableOptionsToWreqOptions :: AirtableOptions -> Options
- Airtable.Table: class IsRecord a
- Airtable.Table: instance Airtable.Table.IsRecord (Airtable.Table.Record a)
- Airtable.Table: instance Airtable.Table.IsRecord Airtable.Table.RecordID
- Airtable.Table: instance Airtable.Table.IsRecord GHC.Base.String
- Airtable.Table: parseFields :: (Value -> Parser a) -> Value -> Parser a
- Airtable.Table: parseRecord :: (UTCTime -> RecordID -> Value -> Parser a) -> Value -> Parser a
+ Airtable.Query: getRecordsFromUrl :: (HasCallStack, FromJSON a) => Options -> String -> IO (Table a)
+ Airtable.Query: mkWreqOptions :: AirtableOptions -> Options
+ Airtable.Query: respJsonBody :: (HasCallStack, FromJSON a) => Response ByteString -> a
+ Airtable.Table: class HasRecordId a
+ Airtable.Table: instance Airtable.Table.HasRecordId (Airtable.Table.Record a)
+ Airtable.Table: instance Airtable.Table.HasRecordId Airtable.Table.RecordID
+ Airtable.Table: instance Airtable.Table.HasRecordId GHC.Base.String
+ Airtable.Table: instance Data.Aeson.Types.ToJSON.ToJSON Airtable.Table.RecordID
+ Airtable.Table: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Airtable.Table.Record a)
+ Airtable.Table: instance GHC.Base.Functor Airtable.Table.Record
+ Airtable.Table: instance GHC.Classes.Eq a => GHC.Classes.Eq (Airtable.Table.Record a)
+ Airtable.Table: instance GHC.Classes.Ord a => GHC.Classes.Ord (Airtable.Table.Record a)
+ Airtable.Table: instance GHC.Generics.Generic (Airtable.Table.Record a)
+ Airtable.Table: instance GHC.Read.Read a => GHC.Read.Read (Airtable.Table.Record a)
+ Airtable.Table: instance GHC.Show.Show a => GHC.Show.Show (Airtable.Table.Record a)
- Airtable.Table: Table :: HashMap RecordID a -> Maybe Text -> Table a
+ Airtable.Table: Table :: HashMap RecordID (Record a) -> Maybe Text -> Table a
- Airtable.Table: [tableRecords] :: Table a -> HashMap RecordID a
+ Airtable.Table: [tableRecords] :: Table a -> HashMap RecordID (Record a)
- Airtable.Table: deleteWhere :: Table a -> (RecordID -> a -> Bool) -> Table a
+ Airtable.Table: deleteWhere :: Table a -> (Record a -> Bool) -> Table a
- Airtable.Table: exists :: (IsRecord r) => Table a -> r -> Bool
+ Airtable.Table: exists :: (HasRecordId r) => Table a -> r -> Bool
- Airtable.Table: select :: (HasCallStack, IsRecord r, Show a) => Table a -> r -> a
+ Airtable.Table: select :: (HasCallStack, HasRecordId r, Show a) => Table a -> r -> Record a
- Airtable.Table: selectAll :: Table a -> [a]
+ Airtable.Table: selectAll :: Table a -> [Record a]
- Airtable.Table: selectKeyWhere :: Table a -> (RecordID -> a -> Bool) -> [RecordID]
+ Airtable.Table: selectKeyWhere :: Table a -> (Record a -> Bool) -> [RecordID]
- Airtable.Table: selectMaybe :: (IsRecord r) => Table a -> r -> Maybe a
+ Airtable.Table: selectMaybe :: (HasRecordId r) => Table a -> r -> Maybe (Record a)
- Airtable.Table: selectWhere :: Table a -> (RecordID -> a -> Bool) -> [a]
+ Airtable.Table: selectWhere :: Table a -> (Record a -> Bool) -> [Record a]
- Airtable.Table: toList :: Table a -> [(RecordID, a)]
+ Airtable.Table: toList :: Table a -> [(RecordID, Record a)]
- Airtable.Table: toRecId :: IsRecord a => a -> RecordID
+ Airtable.Table: toRecId :: HasRecordId a => a -> RecordID

Files

README.md view
@@ -8,6 +8,6 @@ ... - location:     git: https://github.com/ooblahman/airtable-api.git-    commit: 6895cbbc4f83069a8455fcc00f6e313d867ca296+    commit: HEAD   extra-dep: true ```
airtable-api.cabal view
@@ -1,5 +1,5 @@ name:                airtable-api-version:             0.2.1.1+version:             0.3.2.2 synopsis:            Requesting and introspecting Tables within an Airtable project. -- description: homepage:            https://github.com/ooblahman/airtable-api
src/Airtable/Query.hs view
@@ -5,13 +5,16 @@       -- * Configuration for Airtable requests.     , AirtableOptions(..)     , defaultAirtableOptions-    , airtableOptionsToWreqOptions+    , mkWreqOptions     , tableNameToUrl       -- * API     , getRecords+    , getRecordsFromUrl     , createRecord     , updateRecord     , deleteRecord+    -- * Low-level helpers+    , respJsonBody     ) where  @@ -54,8 +57,8 @@ base_url = "https://api.airtable.com/"  -- | Produce Wreq options from 'AirtableOptions'-airtableOptionsToWreqOptions :: AirtableOptions -> Options-airtableOptionsToWreqOptions opts = +mkWreqOptions :: AirtableOptions -> Options+mkWreqOptions opts =    defaults & header "Authorization" .~ ["Bearer " <> BC.pack (apiKey opts)]  -- | Produce a request URL to a table.@@ -71,28 +74,21 @@  -- * API -fromResp :: (HasCallStack, FromJSON a) => Response ByteString -> a-fromResp r = decoder $ r ^. responseBody-  where-    decoder b = case eitherDecode b of-      Left e  -> error e-      Right r -> r- -- | Retrieve the records for a table from airtable.com given its name. Handles pagination correctly. getRecords :: (HasCallStack, FromJSON a) => AirtableOptions -> TableName -> IO (Table a) getRecords opts tname = -  getRecordsFromUrl (airtableOptionsToWreqOptions opts) (tableNameToUrl opts tname)+  getRecordsFromUrl (mkWreqOptions opts) (tableNameToUrl opts tname) --- | Retrieve the records for a table given a URL and network options.+-- | Retrieve the records for a table given a URL and network (Wreq) options. getRecordsFromUrl :: (HasCallStack, FromJSON a) => Options -> String -> IO (Table a) getRecordsFromUrl opts url = do   resp <- getWith opts url-  getMore (fromResp resp)+  getMore (respJsonBody resp)   where     getMore tbl = case tableOffset tbl of       Just offset -> do         resp <- getWith (opts & param "offset" .~ [offset]) url-        getMore $ fromResp resp <> tbl+        getMore $ respJsonBody resp <> tbl       Nothing ->         pure tbl @@ -100,10 +96,10 @@ createRecord :: (HasCallStack, ToJSON a, FromJSON a) => AirtableOptions -> TableName -> a -> IO (Record a) createRecord opts tname a = do   resp <- postWith netOpts url payload-  return $ fromResp resp+  return $ respJsonBody resp   where     url = tableNameToUrl opts tname-    netOpts = airtableOptionsToWreqOptions opts +    netOpts = mkWreqOptions opts      payload = toJSON a  -- | Update a record on a given table, using the supplied fields ('a').@@ -113,7 +109,7 @@     customPayloadMethodWith "PATCH" netOpts url payload   where     url = tableNameToUrl opts tname <> "/" <> rec2str recId-    netOpts = airtableOptionsToWreqOptions opts+    netOpts = mkWreqOptions opts     payload = toJSON a  -- | Delete a record on a table. @@ -121,6 +117,14 @@ deleteRecord opts tname recId =    void $ deleteWith netOpts url    where-    netOpts = airtableOptionsToWreqOptions opts+    netOpts = mkWreqOptions opts     url = tableNameToUrl opts tname <> "/" <> rec2str recId +-- * Low-level helpers++respJsonBody :: (HasCallStack, FromJSON a) => Response ByteString -> a+respJsonBody r = decoder $ r ^. responseBody+  where+    decoder b = case eitherDecode b of+      Left e  -> error e+      Right r -> r
src/Airtable/Table.hs view
@@ -12,13 +12,11 @@     -- * RecordID     , RecordID(..)     , rec2str-    -- * IsRecord class-    , IsRecord(..)+    -- * HasRecordId class+    , HasRecordId(..)     -- * Table     , Table(..)     , TableName-    , parseRecord-    , parseFields     -- * Table methods     , toList     , exists@@ -53,6 +51,7 @@  -- | Airtable's record ID for use in indexing records newtype RecordID = RecordID Text deriving ( FromJSON+                                          , ToJSON                                           , Show                                           , Read                                           , Eq@@ -65,22 +64,32 @@ rec2str :: RecordID -> String rec2str (RecordID rec) = T.unpack rec --- * IsRecord class+-- * HasRecordId class  -- | A convenience typeclass for selecting records using RecordID-like keys.-class IsRecord a where+class HasRecordId a where   toRecId :: a -> RecordID -instance IsRecord RecordID where+instance HasRecordId RecordID where   toRecId = id -instance IsRecord String where+instance HasRecordId String where   toRecId = RecordID . T.pack  -- * Table  -- | An airtable record.-data Record a = Record { recordId :: RecordID, recordObj :: a, createdTime :: UTCTime }+data Record a = Record +  { recordId :: RecordID+  , recordObj :: a+  , createdTime :: UTCTime +  } deriving ( Show+             , Read+             , Generic+             , Eq+             , Ord+             , Functor+             )  instance (FromJSON a) => FromJSON (Record a) where   parseJSON = withObject "record object" $ \v -> do@@ -88,19 +97,26 @@            <*> v .: "fields"            <*> v .: "createdTime" -instance IsRecord (Record a) where+instance (ToJSON a) => ToJSON (Record a) where+  toJSON rec = object [ "id" .= recordId rec+                      , "fields" .= recordObj rec+                      , "createdTime" .= createdTime rec+                      ]++instance HasRecordId (Record a) where   toRecId = recordId --- | Airtable's table type-data Table a = Table { tableRecords :: Map.HashMap RecordID a-                     , tableOffset :: Maybe Text-                     } deriving-                     ( Show-                     , Read-                     , Eq-                     , Generic-                     , Functor-                     )+-- | An airtable table. +data Table a = Table +  { tableRecords :: Map.HashMap RecordID (Record a) -- ^ A mapping from RecordID to Record+  , tableOffset :: Maybe Text -- ^ The "offset" parameter for the table. Is used to deal with airtable's pagination.+  } deriving+  ( Show+  , Read+  , Eq+  , Generic+  , Functor+  )  -- | Synonym used in querying tables from the API. type TableName = String@@ -112,22 +128,9 @@     offset <- v .:? "offset"     return $ Table parsedRecs offset     where-      parseRec tbl = withObject "record object" $ \v -> -            do  recId <- v .: "id"-                fields <- v .: "fields"-                obj <- parseJSON fields-                return $ Map.insert recId obj tbl--parseRecord :: (UTCTime -> RecordID -> Value -> Parser a) -> Value -> Parser a-parseRecord action = withObject "record object" $ \v -> do-    created  <- v .: "createdTime"-    recordId <- v .: "id"-    fields   <- v .: "fields"-    action created recordId fields--parseFields :: (Value -> Parser a) -> Value -> Parser a-parseFields action (Object v) = v .: "fields" >>= action-parseFields _action invalid = typeMismatch "parseFields_" invalid+      parseRec tbl v = do  +        rec <- parseJSON v+        return $ Map.insert (recordId rec) rec tbl  instance Monoid (Table a) where   mempty = Table mempty Nothing@@ -136,16 +139,16 @@ -- * Table methods  -- | Convert a 'Table' to a list of key-record pairs.-toList :: Table a -> [(RecordID, a)]+toList :: Table a -> [(RecordID, Record a)] toList = Map.toList . tableRecords  -- | Check if a record exists at the given key in a table.-exists :: (IsRecord r) => Table a -> r -> Bool+exists :: (HasRecordId r) => Table a -> r -> Bool exists tbl rec = Map.member (toRecId rec) (tableRecords tbl)  -- | Unsafely lookup a record using its RecordID. Will throw a pretty-printed error --   if record does not exist.-select :: (HasCallStack, IsRecord r, Show a) => Table a -> r -> a+select :: (HasCallStack, HasRecordId r, Show a) => Table a -> r -> Record a select tbl rec = tableRecords tbl `lookup` toRecId rec   where     lookup mp k = case Map.lookup k mp of@@ -153,11 +156,11 @@       Nothing -> error $ "lookup failed in map: " <> show k  -- | Safely lookup a record using its RecordID.-selectMaybe :: (IsRecord r) => Table a -> r -> Maybe a+selectMaybe :: (HasRecordId r) => Table a -> r -> Maybe (Record a) selectMaybe tbl rec = toRecId rec `Map.lookup` tableRecords tbl  -- | Read all records.-selectAll :: Table a -> [a]+selectAll :: Table a -> [Record a] selectAll = map snd . toList  -- | Read all RecordID's.@@ -165,13 +168,13 @@ selectAllKeys = map fst . toList  -- | Select all records satisfying a condition.-selectWhere :: Table a -> (RecordID -> a -> Bool) -> [a]-selectWhere tbl f = map snd $ filter (uncurry f) (toList tbl)+selectWhere :: Table a -> (Record a -> Bool) -> [Record a]+selectWhere tbl f = filter f (selectAll tbl)  -- | Select all RecordID's satisfying a condition.-selectKeyWhere :: Table a -> (RecordID -> a -> Bool) -> [RecordID]-selectKeyWhere tbl f = map fst $ filter (uncurry f) (toList tbl)+selectKeyWhere :: Table a -> (Record a -> Bool) -> [RecordID]+selectKeyWhere tbl f = map recordId $ filter f (selectAll tbl)  -- | Delete all Records satisfying a condition.-deleteWhere :: Table a -> (RecordID -> a -> Bool) -> Table a-deleteWhere (Table recs off) f = Table (Map.filterWithKey (\k v -> not $ f k v) recs) off+deleteWhere :: Table a -> (Record a -> Bool) -> Table a+deleteWhere (Table recs off) f = Table (Map.filter (\v -> not $ f v) recs) off