airtable-api 0.1.0.4 → 0.2.0.0
raw patch · 3 files changed
+57/−36 lines, 3 filesdep +timePVP ok
version bump matches the API change (PVP)
Dependencies added: time
API changes (from Hackage documentation)
+ Airtable.Query: defaultAirtableOptions :: AirtableOptions
+ Airtable.Table: instance GHC.Base.Functor Airtable.Table.Table
+ Airtable.Table: parseFields :: (Value -> Parser a) -> Value -> Parser a
+ Airtable.Table: parseRecord :: (UTCTime -> RecordID -> Value -> Parser a) -> Value -> Parser a
Files
- airtable-api.cabal +3/−2
- src/Airtable/Query.hs +19/−18
- src/Airtable/Table.hs +35/−16
airtable-api.cabal view
@@ -1,6 +1,6 @@ name: airtable-api-version: 0.1.0.4-synopsis: Requesting and introspecting Tables within an Airtable project. +version: 0.2.0.0+synopsis: Requesting and introspecting Tables within an Airtable project. -- description: homepage: https://github.com/ooblahman/airtable-api license: BSD3@@ -25,6 +25,7 @@ , hashable , lens , unordered-containers+ , time default-language: Haskell2010 test-suite airtable-api-test
src/Airtable/Query.hs view
@@ -4,6 +4,7 @@ ( module Airtable.Table -- * Configuration for Airtable requests. , AirtableOptions(..)+ , defaultAirtableOptions -- * Main API , getTable ) where@@ -23,36 +24,36 @@ data AirtableOptions = AirtableOptions { -- | the API key for your project apiKey :: String- -- | the app ID for your project (http://api.airtable.com/v0/...)- , appId :: String - -- | api version (http://api.airtable.com/v../...)+ -- | the app ID for your project (http:\/\/api.airtable.com\/v0\/app...)+ , appId :: String+ -- | api version (http:\/\/api.airtable.com\/v..\/...) , apiVersion :: Int } --- | Airtable options defaulting to API version 0. Please change the +-- | Airtable options defaulting to API version 0. Please change the -- 'apiKey' and 'appId' fields.-defaultOptions :: AirtableOptions -defaultOptions = AirtableOptions {+defaultAirtableOptions :: AirtableOptions+defaultAirtableOptions = AirtableOptions { apiKey = "" , appId = "" , apiVersion = 0 } --- | Base airtable API string. +-- | Base airtable API string. base_url :: String base_url = "https://api.airtable.com/" -- | Retrieve a table from airtable.com given its name. Handles pagination correctly. getTable :: (FromJSON a) => AirtableOptions -> TableName -> IO (Table a)-getTable opts tname = getTableFromUrl net_otps url +getTable opts tname = getTableFromUrl net_otps url where- net_otps = defaults & header "Authorization" .~ ["Bearer " <> BC.pack (apiKey opts)] - url = base_url - <> "v" - <> show (apiVersion opts) - <> "/" - <> appId opts - <> "/" + net_otps = defaults & header "Authorization" .~ ["Bearer " <> BC.pack (apiKey opts)]+ url = base_url+ <> "v"+ <> show (apiVersion opts)+ <> "/"+ <> appId opts+ <> "/" <> tname getTableFromUrl :: (FromJSON a) => Options -> String -> IO (Table a)@@ -60,15 +61,15 @@ resp <- getWith opts url getMore (fromResp resp) where- getMore tbl = case tableOffset tbl of + getMore tbl = case tableOffset tbl of Just offset -> do resp <- getWith (opts & param "offset" .~ [offset]) url getMore $ fromResp resp <> tbl- Nothing -> + Nothing -> pure tbl fromResp r = decoder $ r ^. responseBody where- decoder b = case eitherDecode b of + decoder b = case eitherDecode b of Left e -> error $ e <> "\nSource string: " <> show b Right r -> r
src/Airtable/Table.hs view
@@ -3,17 +3,20 @@ {-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveFunctor #-} module Airtable.Table- ( - -- * RecordID + (+ -- * RecordID RecordID(..) , rec2str -- * IsRecord class , IsRecord(..)- -- * Table + -- * Table , Table(..) , TableName+ , parseRecord+ , parseFields -- * Table methods , toList , exists@@ -41,9 +44,10 @@ import Data.Monoid import Data.Hashable import Data.Foldable (foldlM)+import Data.Time (UTCTime) --- * RecordID +-- * RecordID -- | Airtable's record ID for use in indexing records newtype RecordID = RecordID Text deriving ( FromJSON@@ -54,7 +58,7 @@ , Ord ) -instance Hashable RecordID +instance Hashable RecordID rec2str :: RecordID -> String rec2str (RecordID rec) = T.unpack rec@@ -69,18 +73,19 @@ toRec = id instance IsRecord String where- toRec = RecordID . T.pack + toRec = RecordID . T.pack --- * Table +-- * Table -- | Airtable's table type data Table a = Table { tableRecords :: Map.HashMap RecordID a , tableOffset :: Maybe Text- } deriving - ( Show + } deriving+ ( Show , Read , Eq , Generic+ , Functor ) -- | Synonym used in querying tables from the API.@@ -93,12 +98,26 @@ offset <- v .:? "offset" return $ Table parsedRecs offset where- parseRec tbl (Object v) = + parseRec tbl json@(Object v) = do recId <- v .: "id"- obj <- v .: "fields" + obj <- parseJSON json return $ Map.insert recId obj tbl- <|> error ("could not decode: " <> show v)+ parseRec tbl invalid = typeMismatch "Table" invalid+ parseJSON invalid = typeMismatch "Table" invalid ++parseRecord :: (UTCTime -> RecordID -> Value -> Parser a) -> Value -> Parser a+parseRecord action (Object v) = do+ created <- v .: "createdTime"+ recordId <- v .: "id"+ fields <- v .: "fields"+ action created recordId fields+parseRecord _action invalid = typeMismatch "parseFields" invalid++parseFields :: (Value -> Parser a) -> Value -> Parser a+parseFields action (Object v) = v .: "fields" >>= action+parseFields _action invalid = typeMismatch "parseFields_" invalid+ instance Monoid (Table a) where mempty = Table mempty Nothing mappend (Table t1 o) (Table t2 _) = Table (mappend t1 t2) o@@ -118,13 +137,13 @@ select :: (HasCallStack, IsRecord r, Show a) => Table a -> r -> a select tbl rec = tableRecords tbl `lookup` toRec rec where- lookup mp k = case Map.lookup k mp of + lookup mp k = case Map.lookup k mp of Just v -> v Nothing -> error $ "lookup failed in map: " <> show k -- | Safely lookup a record using its RecordID. selectMaybe :: (IsRecord r) => Table a -> r -> Maybe a-selectMaybe tbl rec = toRec rec `Map.lookup` tableRecords tbl +selectMaybe tbl rec = toRec rec `Map.lookup` tableRecords tbl -- | Read all records. selectAll :: Table a -> [a]@@ -134,7 +153,7 @@ selectAllKeys :: Table a -> [RecordID] selectAllKeys = map fst . toList --- | Select all records satisfying a condition. +-- | Select all records satisfying a condition. selectWhere :: Table a -> (RecordID -> a -> Bool) -> [a] selectWhere tbl f = map snd $ filter (uncurry f) (toList tbl) @@ -142,6 +161,6 @@ selectKeyWhere :: Table a -> (RecordID -> a -> Bool) -> [RecordID] selectKeyWhere tbl f = map fst $ filter (uncurry f) (toList tbl) --- | Delete all Records satisfying a condition. +-- | 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