packages feed

tablestorage 0.1.0.2 → 0.1.0.3

raw patch · 11 files changed

+393/−234 lines, 11 filesdep +mtlPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: mtl

API changes (from Hackage documentation)

- Network.TableStorage.Types: type AccountKey = String
- Network.TableStorage.Types: type AuthHeader = String
- Network.TableStorage.Types: type Signature = String
+ Network.TableStorage.Types: AccountKey :: String -> AccountKey
+ Network.TableStorage.Types: AuthHeader :: String -> AuthHeader
+ Network.TableStorage.Types: Signature :: String -> Signature
+ Network.TableStorage.Types: instance Eq Account
+ Network.TableStorage.Types: instance Eq AccountKey
+ Network.TableStorage.Types: instance Eq AuthHeader
+ Network.TableStorage.Types: instance Eq ComparisonType
+ Network.TableStorage.Types: instance Eq EntityColumn
+ Network.TableStorage.Types: instance Eq EntityFilter
+ Network.TableStorage.Types: instance Eq EntityKey
+ Network.TableStorage.Types: instance Eq EntityQuery
+ Network.TableStorage.Types: instance Eq SharedKeyAuth
+ Network.TableStorage.Types: instance Eq Signature
+ Network.TableStorage.Types: instance Show AccountKey
+ Network.TableStorage.Types: instance Show AuthHeader
+ Network.TableStorage.Types: instance Show Signature
+ Network.TableStorage.Types: newtype AccountKey
+ Network.TableStorage.Types: newtype AuthHeader
+ Network.TableStorage.Types: newtype Signature
+ Network.TableStorage.Types: unAccountKey :: AccountKey -> String
+ Network.TableStorage.Types: unAuthHeader :: AuthHeader -> String
+ Network.TableStorage.Types: unSignature :: Signature -> String

Files

src/Network/TableStorage/API.hs view
@@ -9,15 +9,41 @@ ) where  import Network.HTTP+    ( HeaderName(HdrCustom),+      Header(Header),+      RequestMethod(Custom, DELETE, GET, POST, PUT),+      Response_String ) import Text.XML.Light-import Text.Printf+    ( Element(elName),+      QName(qName, qURI),+      showTopElement,+      filterChildren,+      findAttr,+      findChild,+      findChildren,+      strContent ) import Network.TableStorage.Types-import Network.TableStorage.Auth+    ( EntityQuery(..),+      Entity(..),+      EntityColumn(EdmDateTime, EdmString),+      EntityKey(..),+      Account(..),+      AccountKey )+import Network.TableStorage.Auth ( authenticatedRequest ) import Network.TableStorage.Request+    ( propertyList, entityKeyResource, buildQueryString ) import Network.TableStorage.Response+    ( parseEmptyResponse, parseXmlResponseOrError, parseEntityColumn ) import Network.TableStorage.Atom-import Data.Time.Clock (getCurrentTime)-import Data.Maybe (fromMaybe)+    ( dataServicesNamespace,+      qualifyAtom,+      qualifyDataServices,+      qualifyMetadata,+      wrapContent )+import Control.Monad ( (>=>), unless )+import Control.Monad.Error ( ErrorT(..) )+import Data.Time.Clock ( getCurrentTime )+import Data.Maybe ( fromMaybe )  -- | -- Parse the response body of the Query Tables web method@@ -28,11 +54,11 @@   readTables feed = sequence $ do     entry <- findChildren (qualifyAtom "entry") feed     return $ readTableName entry-  readTableName entry = do-    content <- findChild (qualifyAtom "content") entry-    properties <- findChild (qualifyMetadata "properties") content-    tableName <- findChild (qualifyDataServices "TableName") properties-    return $ strContent tableName+  readTableName = +    findChild (qualifyAtom "content") +    >=> findChild (qualifyMetadata "properties") +    >=> findChild (qualifyDataServices "TableName") +    >=> return . strContent  -- | -- List the names of tables for an account or returns an error message@@ -63,23 +89,16 @@ -- Creates a new table with the specified name if it does not already exist, or returns an erro message --  createTableIfNecessary :: Account -> String -> IO (Either String ())-createTableIfNecessary acc tableName = do -  tables <- queryTables acc-  case tables of-    Left err -> return $ Left err-    Right tables' -> -      if any (== tableName) tables'-      then-        return $ Right ()-      else -        createTable acc tableName+createTableIfNecessary acc tableName = runErrorT $ do +  tables <- ErrorT $ queryTables acc+  unless (tableName `elem` tables) $ ErrorT $ createTable acc tableName  -- | -- Deletes the table with the specified name or returns an error message -- deleteTable :: Account -> String -> IO (Either String ()) deleteTable acc tableName = do -  let resource = printf "/Tables('%s')" tableName+  let resource = "/Tables('" ++ tableName ++ "')"    response <- authenticatedRequest acc DELETE [] resource resource ""   return $ response >>= parseEmptyResponse (2, 0, 4) @@ -100,7 +119,7 @@ -- insertEntity :: Account -> String -> Entity -> IO (Either String ()) insertEntity acc tableName entity = do -  let resource = printf "/%s" tableName+  let resource = '/' : tableName   requestXml <- createInsertEntityXml entity   response <- authenticatedRequest acc POST [] resource resource $ showTopElement requestXml   return $ response >>= parseEmptyResponse (2, 0, 1)  @@ -144,8 +163,10 @@ -- readEntity :: Element -> Maybe Entity readEntity entry = do-    content <- findChild (qualifyAtom "content") entry-    properties <- findChild (qualifyMetadata "properties") content+    properties <- +      findChild (qualifyAtom "content")+      >=> findChild (qualifyMetadata "properties")+      $ entry     partitionKey <- findChild (qualifyDataServices "PartitionKey") properties     rowKey <- findChild (qualifyDataServices "RowKey") properties     let columnData = filterChildren filterProperties properties@@ -192,9 +213,9 @@ -- queryEntities :: Account -> String -> EntityQuery -> IO (Either String [Entity]) queryEntities acc tableName query = do -  let canonicalizedResource = printf "/%s()" tableName+  let canonicalizedResource = '/' : tableName ++ "()"   let queryString = buildQueryString query-  let resource = printf "%s?%s" canonicalizedResource queryString+  let resource = canonicalizedResource ++ '?' : queryString   response <- authenticatedRequest acc GET [] resource canonicalizedResource ""   return $ response >>= parseQueryEntitiesResponse   
src/Network/TableStorage/Atom.hs view
@@ -3,11 +3,22 @@ -- request and response bodies of the various web methods. -- -module Network.TableStorage.Atom where+module Network.TableStorage.Atom (+  atomNamespace, dataServicesNamespace, metadataNamespace,+  qualifyAtom, qualifyDataServices, qualifyMetadata,+  atomElement, atomAttr, wrapContent+) where  import Network.TableStorage.XML-import Network.TableStorage.Format+    ( qualify, cDataText, namespaceAttr )+import Network.TableStorage.Format ( atomDate ) import Text.XML.Light+    ( Element(elAttribs, elContent, elName),+      Content(Elem),+      QName,+      Attr(..),+      blank_element,+      unqual )  atomNamespace :: String atomNamespace = "http://www.w3.org/2005/Atom"
src/Network/TableStorage/Auth.hs view
@@ -10,22 +10,38 @@   authenticatedRequest ) where -import qualified Data.ByteString.Base64 as Base64C ( encode, decode )+import qualified Data.ByteString.Base64 as Base64C+    ( encode, decode ) import qualified Codec.Binary.UTF8.String as UTF8C ( encodeString )-import qualified Data.ByteString as B (ByteString, concat)-import qualified Data.ByteString.UTF8 as UTF8 (toString, fromString)-import qualified Data.ByteString.Lazy.UTF8 as UTF8L (fromString) -import qualified Data.ByteString.Lazy.Char8 as Char8L (toChunks)-import qualified Data.ByteString.Lazy as L (fromChunks)-import qualified Data.Digest.Pure.SHA as SHA ( bytestringDigest, hmacSha256 )-import Network.TCP+import qualified Data.ByteString as B ( ByteString, concat )+import qualified Data.ByteString.UTF8 as UTF8+    ( toString, fromString )+import qualified Data.ByteString.Lazy.UTF8 as UTF8L ( fromString )+import qualified Data.ByteString.Lazy.Char8 as Char8L ( toChunks )+import qualified Data.ByteString.Lazy as L ( fromChunks )+import qualified Data.Digest.Pure.SHA as SHA+    ( bytestringDigest, hmacSha256 )+import Network.TCP ( HStream(close, openStream) ) import Network.URI+    ( URIAuth(URIAuth, uriPort, uriRegName, uriUserInfo), URI(..) ) import Network.HTTP-import Network.HTTP.Base ( )+    ( HeaderName(HdrAccept, HdrAuthorization, HdrContentLength,+                 HdrContentType, HdrDate),+      Header(..),+      Request(Request, rqBody, rqHeaders, rqMethod, rqURI),+      RequestMethod,+      Response_String,+      sendHTTP )+import Network.HTTP.Base () import Network.Stream ( Result ) import Network.TableStorage.Types-import Network.TableStorage.Format-import Text.Printf ( printf )+    ( SharedKeyAuth(..),+      Account(accountHost, accountKey, accountName, accountPort,+              accountResourcePrefix, accountScheme),+      AuthHeader(..),+      Signature(..),+      AccountKey(unAccountKey) )+import Network.TableStorage.Format ( rfc1123Date )  authenticationType :: String authenticationType = "SharedKey"@@ -34,46 +50,53 @@ -- Constructs the unencrypted content of the Shared Key authentication token  -- printSharedKeyAuth :: SharedKeyAuth -> String-printSharedKeyAuth auth = printf "%s\n%s\n%s\n%s\n%s"-  (show (sharedKeyAuthVerb auth))-  (sharedKeyAuthContentMD5 auth)-  (sharedKeyAuthContentType auth)-  (sharedKeyAuthDate auth)-  (sharedKeyAuthCanonicalizedResource auth)-+printSharedKeyAuth auth = +  show (sharedKeyAuthVerb auth)+  ++ "\n" +  ++ sharedKeyAuthContentMD5 auth+  ++ "\n" +  ++ sharedKeyAuthContentType auth+  ++ "\n" +  ++ sharedKeyAuthDate auth +  ++ "\n" +  ++ sharedKeyAuthCanonicalizedResource auth+   hmacSha256' :: AccountKey -> String -> B.ByteString hmacSha256' base64Key = -  let (Right key) = Base64C.decode $ UTF8.fromString base64Key in+  let (Right key) = Base64C.decode . UTF8.fromString . unAccountKey $ base64Key in   B.concat . Char8L.toChunks . SHA.bytestringDigest . SHA.hmacSha256 (L.fromChunks $ return key) . UTF8L.fromString    -- | -- Constructs the authorization signature -- signature :: AccountKey -> SharedKeyAuth -> Signature-signature key = UTF8.toString . Base64C.encode . hmacSha256' key . UTF8C.encodeString . printSharedKeyAuth+signature key = Signature . UTF8.toString . Base64C.encode . hmacSha256' key . UTF8C.encodeString . printSharedKeyAuth  -- | -- Constructs the authorization header including account name and signature -- authHeader :: Account -> SharedKeyAuth -> AuthHeader-authHeader acc auth = printf "%s %s:%s"+authHeader acc auth = AuthHeader $   authenticationType -  (accountName acc) -  (signature (accountKey acc) auth)+  ++ " " +  ++ accountName acc +  ++ ":"+  ++ unSignature (signature (accountKey acc) auth)  -- | -- Constructs an absolute URI from an Account and relative URI  -- qualifyResource :: String -> Account -> URI qualifyResource res acc =-  URI { uriScheme = accountScheme acc,-        uriAuthority =  -          Just URIAuth { uriRegName = accountHost acc, -                         uriPort = ':' : show (accountPort acc),-                         uriUserInfo = "" },-        uriQuery = "",-        uriFragment = "",-        uriPath = (accountResourcePrefix acc) ++ res }+  URI { uriScheme = accountScheme acc+      , uriAuthority =  +          Just URIAuth +          { uriRegName = accountHost acc+          , uriPort = ':' : show (accountPort acc)+          , uriUserInfo = "" }+      , uriQuery = ""+      , uriFragment = ""+      , uriPath = accountResourcePrefix acc ++ res }  -- | -- Creates and executes an authenticated request including the Authorization header.@@ -86,20 +109,23 @@ authenticatedRequest acc method hdrs resource canonicalizedResource body = do   time <- rfc1123Date    connection <- openStream (accountHost acc) (accountPort acc) -  let auth = SharedKeyAuth { sharedKeyAuthVerb = method,-                             sharedKeyAuthContentMD5 = "",-                             sharedKeyAuthContentType = "application/atom+xml",-                             sharedKeyAuthDate = time,-                             sharedKeyAuthCanonicalizedResource = printf "/%s%s%s" (accountName acc) (accountResourcePrefix acc) canonicalizedResource }-  let basicHeaders = [ Header HdrAuthorization (authHeader acc auth),-                       Header HdrContentType "application/atom+xml",-                       Header HdrContentLength (show $ length body),-                       Header HdrAccept "application/atom+xml,application/xml",-                       Header HdrDate time ]-  let request = Request { rqURI = qualifyResource resource acc, -                          rqMethod = method,  -                          rqHeaders = basicHeaders ++ hdrs,-                          rqBody = body }+  let { auth = SharedKeyAuth +    { sharedKeyAuthVerb = method+    , sharedKeyAuthContentMD5 = ""+    , sharedKeyAuthContentType = "application/atom+xml"+    , sharedKeyAuthDate = time+    , sharedKeyAuthCanonicalizedResource = "/" ++ accountName acc ++ accountResourcePrefix acc ++ canonicalizedResource } }+  let { basicHeaders =+    [ Header HdrAuthorization $ unAuthHeader $ authHeader acc auth+    , Header HdrContentType "application/atom+xml"+    , Header HdrContentLength $ show $ length body+    , Header HdrAccept "application/atom+xml,application/xml"+    , Header HdrDate time ] }+  let { request = Request +    { rqURI = qualifyResource resource acc+    , rqMethod = method+    , rqHeaders = basicHeaders ++ hdrs+    , rqBody = body } }   result <- sendHTTP connection request :: IO (Result Response_String)   _ <- close connection   return $ either (Left . show) Right result
src/Network/TableStorage/Development.hs view
@@ -2,9 +2,12 @@ -- This module contains constants for working with the storage emulator.   -- -module Network.TableStorage.Development where+module Network.TableStorage.Development (+  developmentAccount+) where  import Network.TableStorage.Types+    ( Account(..), AccountKey(AccountKey) )  -- | -- An account for the storage emulator@@ -15,4 +18,4 @@                                accountName              = "devstoreaccount1",                                accountPort              = 10002,                                accountResourcePrefix    = "/devstoreaccount1",-                               accountKey               = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="}+                               accountKey               = AccountKey "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="}
src/Network/TableStorage/Format.hs view
@@ -2,10 +2,13 @@ -- Helper methods for working with formatted dates -- -module Network.TableStorage.Format where+module Network.TableStorage.Format (+  getFormattedTime, rfc1123Date, atomDate,  +  rfc1123Format, atomDateFormat+) where -import Data.Time-import System.Locale+import Data.Time ( getCurrentTime, formatTime )+import System.Locale ( defaultTimeLocale )  getFormattedTime :: String -> IO String getFormattedTime formatString = fmap (formatTime defaultTimeLocale formatString) getCurrentTime
src/Network/TableStorage/Query.hs view
@@ -1,79 +1,85 @@--- |
--- This module contains functions which help when unmarshalling query responses
-
-module Network.TableStorage.Query where
-
-import Data.Time
-import Network.TableStorage.Types
-
--- |
--- Find the value in a binary-valued column or return Nothing if no such column exists
-edmBinary :: String -> Entity -> Maybe String
-edmBinary key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmBinary s -> s
-    _ -> Nothing
-    
--- |
--- Find the value in a string-valued column or return Nothing if no such column exists
-edmString :: String -> Entity -> Maybe String
-edmString key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmString s -> s
-    _ -> Nothing
-
--- |
--- Find the value in a boolean-valued column or return Nothing if no such column exists
-edmBoolean :: String -> Entity -> Maybe Bool
-edmBoolean key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmBoolean s -> s
-    _ -> Nothing
-
--- |
--- Find the value in a date-valued column or return Nothing if no such column exists
-edmDateTime :: String -> Entity -> Maybe UTCTime
-edmDateTime key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmDateTime s -> s
-    _ -> Nothing
-
--- |
--- Find the value in a double-valued column or return Nothing if no such column exists
-edmDouble :: String -> Entity -> Maybe Double
-edmDouble key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmDouble s -> s
-    _ -> Nothing
-
--- |
--- Find the value in a Guid-valued column or return Nothing if no such column exists
-edmGuid :: String -> Entity -> Maybe String
-edmGuid key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmGuid s -> s
-    _ -> Nothing
-
--- |
--- Find the value in an integer-valued column or return Nothing if no such column exists
-edmInt32 :: String -> Entity -> Maybe Int
-edmInt32 key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmInt32 s -> s
-    _ -> Nothing
-
--- |
--- Find the value in an integer-valued column or return Nothing if no such column exists
-edmInt64 :: String -> Entity -> Maybe Int
-edmInt64 key en = do
-  col <- lookup key $ entityColumns en
-  case col of
-    EdmInt64 s -> s
+-- |+-- This module contains functions which help when unmarshalling query responses++module Network.TableStorage.Query (+  edmBinary, edmBoolean, edmDateTime, edmDouble,+  edmGuid, edmInt32, edmInt64, edmString+) where++import Data.Time ( UTCTime )+import Network.TableStorage.Types+    ( Entity(entityColumns),+      EntityColumn(EdmBinary, EdmBoolean, EdmDateTime, EdmDouble,+                   EdmGuid, EdmInt32, EdmInt64, EdmString) )++-- |+-- Find the value in a binary-valued column or return Nothing if no such column exists+edmBinary :: String -> Entity -> Maybe String+edmBinary key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmBinary s -> s+    _ -> Nothing+    +-- |+-- Find the value in a string-valued column or return Nothing if no such column exists+edmString :: String -> Entity -> Maybe String+edmString key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmString s -> s+    _ -> Nothing++-- |+-- Find the value in a boolean-valued column or return Nothing if no such column exists+edmBoolean :: String -> Entity -> Maybe Bool+edmBoolean key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmBoolean s -> s+    _ -> Nothing++-- |+-- Find the value in a date-valued column or return Nothing if no such column exists+edmDateTime :: String -> Entity -> Maybe UTCTime+edmDateTime key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmDateTime s -> s+    _ -> Nothing++-- |+-- Find the value in a double-valued column or return Nothing if no such column exists+edmDouble :: String -> Entity -> Maybe Double+edmDouble key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmDouble s -> s+    _ -> Nothing++-- |+-- Find the value in a Guid-valued column or return Nothing if no such column exists+edmGuid :: String -> Entity -> Maybe String+edmGuid key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmGuid s -> s+    _ -> Nothing++-- |+-- Find the value in an integer-valued column or return Nothing if no such column exists+edmInt32 :: String -> Entity -> Maybe Int+edmInt32 key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmInt32 s -> s+    _ -> Nothing++-- |+-- Find the value in an integer-valued column or return Nothing if no such column exists+edmInt64 :: String -> Entity -> Maybe Int+edmInt64 key en = do+  col <- lookup key $ entityColumns en+  case col of+    EdmInt64 s -> s     _ -> Nothing
src/Network/TableStorage/Request.hs view
@@ -2,20 +2,37 @@ -- Helper methods used to construct requests. -- -module Network.TableStorage.Request where+module Network.TableStorage.Request (+  propertyList,+  entityKeyResource,+  columnToTypeString,+  printEntityColumn,+  printComparisonType,+  buildFilterString,+  buildQueryString+) where -import Data.Time-import System.Locale-import Data.Maybe (fromMaybe)-import Data.List (intercalate)-import Text.XML.Light.Types (elAttribs)+import Data.Time ( formatTime )+import System.Locale ( defaultTimeLocale )+import Data.Maybe ( fromMaybe )+import Data.List ( intercalate )+import Text.XML.Light.Types ( elAttribs ) import Text.XML.Light+    ( Element(elAttribs, elContent, elName),+      Content(Elem),+      Attr(Attr),+      blank_element ) import Network.TableStorage.Types-import Network.TableStorage.XML+    ( EntityFilter(..),+      ComparisonType(..),+      EntityQuery(eqFilter, eqPageSize),+      EntityColumn(..),+      EntityKey(ekPartitionKey, ekRowKey) )+import Network.TableStorage.XML ( cDataText ) import Network.TableStorage.Atom-import Network.TableStorage.Format-import Network.HTTP.Base-import Text.Printf (printf)+    ( qualifyDataServices, qualifyMetadata )+import Network.TableStorage.Format ( atomDateFormat )+import Network.HTTP.Base ( urlEncode )  -- | -- Formats a list of entity properties for inclusion in an Atom entry. @@ -36,10 +53,7 @@ -- and entity key.  -- entityKeyResource :: String -> EntityKey -> String-entityKeyResource tableName key = printf "%s(PartitionKey='%s',RowKey='%s')" -  tableName -  (ekPartitionKey key) -  (ekRowKey key)+entityKeyResource tableName key = tableName ++ "(PartitionKey='" ++ ekPartitionKey key ++ "',RowKey='" ++ ekRowKey key ++ "')"  -- | -- Converts an entity column into its type name@@ -62,13 +76,16 @@ printEntityColumn (EdmBoolean (Just True))     = Just "true" printEntityColumn (EdmBoolean (Just False))    = Just "false" printEntityColumn (EdmDateTime (Just val))     = Just $ formatTime defaultTimeLocale atomDateFormat val-printEntityColumn (EdmDouble (Just val))       = Just $ printf "%f" val+printEntityColumn (EdmDouble (Just val))       = Just $ show val printEntityColumn (EdmGuid (Just val))         = Just val-printEntityColumn (EdmInt32 (Just val))        = Just $ printf "%d" val-printEntityColumn (EdmInt64 (Just val))        = Just $ printf "%d" val+printEntityColumn (EdmInt32 (Just val))        = Just $ show val+printEntityColumn (EdmInt64 (Just val))        = Just $ show val printEntityColumn (EdmString (Just val))       = Just val printEntityColumn _                            = Nothing +-- |+-- Formats a comparison type to appear in the query string+-- printComparisonType :: ComparisonType -> String printComparisonType Equal               = "eq" printComparisonType GreaterThan         = "gt"@@ -82,21 +99,60 @@ -- portion of the Query Entities URI.  -- buildFilterString :: EntityFilter -> String-buildFilterString (And fs) = printf "(%s)" $ intercalate "%%20and%%20" $ map buildFilterString fs-buildFilterString (Or fs) = printf "(%s)" $ intercalate "%%20or%%20" $ map buildFilterString fs-buildFilterString (Not f) = "(not%%20" ++ buildFilterString f ++ ")"-buildFilterString (CompareBoolean prop val) = printf "%s%%20eq%%20%s" (urlEncode prop) (if val then "true" else "false")-buildFilterString (CompareDateTime prop cmp val) = printf "%s%%20%s%%20datetime'%s'" (urlEncode prop) (printComparisonType cmp) (formatTime defaultTimeLocale atomDateFormat val)-buildFilterString (CompareDouble prop cmp val) = printf "%s%%20%s%%20%f" (urlEncode prop) (printComparisonType cmp) val-buildFilterString (CompareGuid prop val) = printf "%s%%20eq%%20guid'%s'" (urlEncode prop) val-buildFilterString (CompareInt32 prop cmp val) = printf "%s%%20%s%%20%d" (urlEncode prop) (printComparisonType cmp) val-buildFilterString (CompareInt64 prop cmp val) = printf "%s%%20%s%%20%d" (urlEncode prop) (printComparisonType cmp) val-buildFilterString (CompareString prop cmp val) = printf "%s%%20%s%%20'%s'" (urlEncode prop) (printComparisonType cmp) (urlEncode val)+buildFilterString (And fs) = '(' : intercalate "%20and%20" (map buildFilterString fs) ++ ")"+buildFilterString (Or fs) = '(' : intercalate "%20or%20" (map buildFilterString fs) ++ ")"+buildFilterString (Not f) = +  "(not%20" +  ++ buildFilterString f +  ++ ")"+buildFilterString (CompareBoolean prop val) = +  urlEncode prop +  ++ "%20eq%20" +  ++ if val then "true" else "false"+buildFilterString (CompareDateTime prop cmp val) = +  urlEncode prop +  ++ "%20"+  ++ printComparisonType cmp +  ++ "%20datetime'" +  ++ formatTime defaultTimeLocale atomDateFormat val +  ++ "'"+buildFilterString (CompareDouble prop cmp val) = +  urlEncode prop+  ++ "%20" +  ++ printComparisonType cmp +  ++ "%20" +  ++ show val+buildFilterString (CompareGuid prop val) = +  urlEncode prop +  ++ "%20eq%20guid'" +  ++ val+  ++ "'"+buildFilterString (CompareInt32 prop cmp val) = +  urlEncode prop +  ++ "%20" +  ++ printComparisonType cmp +  ++ "%20" +  ++ show val+buildFilterString (CompareInt64 prop cmp val) = +  urlEncode prop +  ++ "%20" +  ++ printComparisonType cmp +  ++ "%20" +  ++ show val+buildFilterString (CompareString prop cmp val) = +  urlEncode prop +  ++ "%20" +  ++ printComparisonType cmp+  ++ "%20'" +  ++ urlEncode val +  ++ "'"  -- | -- Constructs the full query string for the Query Entities web method.  -- buildQueryString :: EntityQuery -> String-buildQueryString query = printf "$filter=%s&$top=%s"-  (maybe "" buildFilterString $ eqFilter query)-  (maybe "" show $ eqPageSize query)+buildQueryString query = +  "$filter="+  ++ maybe "" buildFilterString (eqFilter query) +  ++ "&$top=" +  ++ maybe "" show (eqPageSize query)  
src/Network/TableStorage/Response.hs view
@@ -2,17 +2,23 @@ -- Helper methods for parsing web method response bodies.  -- -module Network.TableStorage.Response where+module Network.TableStorage.Response (+  parseError, errorToString,+  parseEmptyResponse, parseXmlResponseOrError,+  parseEntityColumn+) where -import Data.Time-import System.Locale+import Data.Time ( readTime )+import System.Locale ( defaultTimeLocale ) import Text.XML.Light-import Control.Monad (guard)-import Data.Maybe (fromMaybe)-import Network.TableStorage.Atom-import Network.TableStorage.Types-import Network.TableStorage.Format+    ( Element(elName), parseXMLDoc, findChild, strContent )+import Control.Monad ( guard )+import Data.Maybe ( fromMaybe )+import Network.TableStorage.Atom ( qualifyMetadata )+import Network.TableStorage.Types ( EntityColumn(..) )+import Network.TableStorage.Format ( atomDateFormat ) import Network.HTTP.Base+    ( ResponseCode, Response(rspBody, rspCode), Response_String )  -- | -- Extracts the error message from an error response 
src/Network/TableStorage/Types.hs view
@@ -1,51 +1,68 @@ -- | -- Data types used to construct the various web method requests.  ----module Network.TableStorage.Types where+module Network.TableStorage.Types (+  AccountKey(..),+  Signature(..),+  AuthHeader(..),+  Account(..),+  SharedKeyAuth(..),+  EntityKey(..),+  EntityColumn(..),+  Entity(..),+  EntityQuery(..),+  ComparisonType(..),+  EntityFilter(..)+) where -import Data.Time-import Network.HTTP.Base+import Data.Time ( UTCTime )+import Network.HTTP.Base ( RequestMethod )  -- | -- The Base-64 encoded account secret key ---type AccountKey = String+newtype AccountKey = AccountKey { unAccountKey :: String } deriving (Show, Eq)  -- | -- The type of authorization header signatures  ---type Signature = String+newtype Signature = Signature { unSignature :: String } deriving (Show, Eq)  -- | -- The type of authorization headers  ---type AuthHeader = String+newtype AuthHeader = AuthHeader { unAuthHeader :: String } deriving (Show, Eq)  -- | -- Account information: host, port, secret key and account name  ---data Account = Account { accountScheme         :: String,-                         accountHost           :: String,-                         accountPort           :: Int,-                         accountKey            :: AccountKey,-                         accountName           :: String,-                         accountResourcePrefix :: String } deriving Show               +data Account = Account +  { accountScheme         :: String+  , accountHost           :: String+  , accountPort           :: Int+  , accountKey            :: AccountKey+  , accountName           :: String+  , accountResourcePrefix :: String +  } deriving (Show, Eq)  -- | -- The unencrypted content of the Shared Key authorization header  ---data SharedKeyAuth = SharedKeyAuth { sharedKeyAuthVerb                  :: RequestMethod,-                                     sharedKeyAuthContentMD5            :: String,-                                     sharedKeyAuthContentType           :: String,-                                     sharedKeyAuthDate                  :: String,-                                     sharedKeyAuthCanonicalizedResource :: String } deriving Show+data SharedKeyAuth = SharedKeyAuth +  { sharedKeyAuthVerb                  :: RequestMethod+  , sharedKeyAuthContentMD5            :: String+  , sharedKeyAuthContentType           :: String+  , sharedKeyAuthDate                  :: String+  , sharedKeyAuthCanonicalizedResource :: String+  } deriving (Show, Eq)  -- | -- Uniquely identifies an entity in a table : a partition key and row key pair.  ---data EntityKey = EntityKey { ekPartitionKey :: String,-                             ekRowKey       :: String } deriving Show+data EntityKey = EntityKey +  { ekPartitionKey :: String+  , ekRowKey       :: String +  } deriving (Show, Eq)  -- | -- Represents a column in an entity.@@ -63,7 +80,7 @@   EdmInt32 (Maybe Int) |   EdmInt64 (Maybe Int) |   EdmString (Maybe String)-  deriving Show+  deriving (Show, Eq)  -- | -- An entity consists of a key and zero or more additional columns.@@ -76,8 +93,10 @@ -- -- Projections are not currently supported.  ---data EntityQuery = EntityQuery { eqPageSize :: Maybe Int,-                                 eqFilter   :: Maybe EntityFilter } deriving Show+data EntityQuery = EntityQuery +  { eqPageSize :: Maybe Int+  , eqFilter   :: Maybe EntityFilter +  } deriving (Show, Eq)                                   -- | -- The various comparisons supported in entity queries. @@ -89,7 +108,7 @@   LessThan |   LessThanOrEqual |    NotEqual -  deriving Show+  deriving (Show, Eq)                                   -- | -- The data type of entity filters @@ -105,4 +124,4 @@   CompareInt32 String ComparisonType Integer |   CompareInt64 String ComparisonType Integer |   CompareString String ComparisonType String-  deriving Show+  deriving (Show, Eq)
src/Network/TableStorage/XML.hs view
@@ -2,9 +2,16 @@ -- Helper methods for working with XML  -- -module Network.TableStorage.XML where+module Network.TableStorage.XML (+  qualify, cDataText, namespaceAttr+) where  import Text.XML.Light.Types+    ( Content(Text),+      CDataKind(CDataText),+      CData(CData, cdData, cdLine, cdVerbatim),+      QName(..),+      Attr(..) )  -- | -- Qualify a name for a specific namespace and/or prefix 
tablestorage.cabal view
@@ -1,5 +1,5 @@ name:           tablestorage-version:        0.1.0.2+version:        0.1.0.3 cabal-version:  >= 1.2 build-type:     Simple author:         Phil Freeman <paf31-at-cantab.net>@@ -32,19 +32,20 @@                    network,
                    time,
                    xml,
-                   old-locale
+                   old-locale,
+                   mtl
   ghc-options:     -Wall-  exposed-modules: 
-                   Network.TableStorage,
-                   Network.TableStorage.API,
-                   Network.TableStorage.Auth,
-                   Network.TableStorage.Development,
-                   Network.TableStorage.Query,
-                   Network.TableStorage.Types
-  other-modules:   
-                   Network.TableStorage.Atom,
-                   Network.TableStorage.Format,
-                   Network.TableStorage.Request,
-                   Network.TableStorage.Response,
-                   Network.TableStorage.XML
+  exposed-modules: +                   Network.TableStorage,+                   Network.TableStorage.API,+                   Network.TableStorage.Auth,+                   Network.TableStorage.Development,+                   Network.TableStorage.Query,+                   Network.TableStorage.Types+  other-modules:   +                   Network.TableStorage.Atom,+                   Network.TableStorage.Format,+                   Network.TableStorage.Request,+                   Network.TableStorage.Response,+                   Network.TableStorage.XML