packages feed

aws-kinesis (empty) → 0.1

raw patch · 20 files changed

+3053/−0 lines, 20 filesdep +QuickCheckdep +aesondep +awssetup-changed

Dependencies added: QuickCheck, aeson, aws, aws-general, aws-kinesis, base, base64-bytestring, blaze-builder, bytestring, conduit, conduit-extra, errors, http-conduit, http-types, mtl, parsers, quickcheck-instances, resourcet, tagged, tasty, tasty-quickcheck, text, time, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,4 @@+0.1+===++First public version.
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2014 AlephCloud Systems, Inc.++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,68 @@+[![Build Status](https://travis-ci.org/alephcloud/hs-aws-general.svg?branch=master)](https://travis-ci.org/alephcloud/hs-aws-kinesis)+++Haskell Bindings for Amazon AWS Kinesis+=======================================++*API Version 2013-12-02*++[Amazon AWS Kinesis API Reference](http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference)++This package depends on the [aws-general](https://github.com/alephcloud/hs-aws-general) package+and the [aws](https://github.com/aristidb/aws) package. From the latter the it borrows the+machinery for [managing AWS credentials](https://hackage.haskell.org/package/aws-0.9/docs/Aws.html#g:17)+and [making requests](https://hackage.haskell.org/package/aws-0.9/docs/Aws.html). There+is also some documentation, including an usage example, in the+[README of the aws package](https://github.com/aristidb/aws/blob/master/README.org)+++Installation+============++Assuming that the Haskell compiler *GHC* and the Haskell build tool *cabal* is already+installed run the following command from the shell:++~~~{.sh}+cabal install --enable-tests+~~~++Running Tests+=============++There are few test cases included in the package. These tests require an AWS account+and AWS credentials stored in the file `~/.aws-keys` in the format described in the+[documentation of the aws package](https://hackage.haskell.org/package/aws-0.9/docs/Aws.html#g:17).++When running these tests some (low) costs may incur for usage of the AWS services.+Therefor the user must explicitly consent to the usage of the AWS credentials by+passing the commandline options `--run-with-aws-credentials` to the test application.++~~~{.sh}+cabal test --test-option=--run-with-aws-credentials+~~~++Example Usage+=============++Here is a very simple example for making a single request to AWS Kinesis. For more ellaborate+usage refer to the [documentation of the AWS package](https://hackage.haskell.org/package/aws).++~~~{.haskell}+import Aws+import Aws.Core+import Aws.General+import Aws.Kinesis+import Data.IORef++cfg <- Aws.baseConfiguration+creds <- Credentials "access-key-id" "secret-access-key" `fmap` newIORef []+let kinesisCfg = KinesisConfiguration UsWest2+simpleAws cfg kinesisCfg $ ListStreams Nothing Nothing+~~~++In order to run the example you must replace `"access-key-id"` and+`"secret-access-key"` with the respective values for your AWS account.++You may also take a look at the test examples in+[tests/Main.hs](https://github.com/alephcloud/hs-aws-kinesis/blob/master/tests/Main.hs).+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aws-kinesis.cabal view
@@ -0,0 +1,113 @@+-- ------------------------------------------------------ --+-- Copyright © 2014 AlephCloud Systems, Inc.+-- ------------------------------------------------------ --++Name: aws-kinesis+Version: 0.1+Synopsis: Bindings for AWS Kinesis Version 2013-12-02+description:+    Bindings for AWS Kinesis+    .+    /API Version: 2013-12-02/+    .+    <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference>++Homepage: https://github.com/alephcloud/hs-aws-kinesis+Bug-reports: https://github.com/alephcloud/hs-aws-kinesis/issues+License: MIT+License-file: LICENSE+Author: Lars Kuhtz <lars@alephcloud.com>+Maintainer: Lars Kuhtz <lars@alephcloud.com>+Copyright: Copyright (c) 2014 AlephCloud, Inc.+Category: Network, Web, AWS, Cloud, Distributed Computing+Build-type: Simple++cabal-version: >= 1.16++extra-doc-files:+    README.md,+    CHANGELOG.md++extra-source-files:+    constraints++source-repository head+    type: git+    location: https://github.com/alephcloud/hs-aws-kinesis.git++source-repository this+    type: git+    location: https://github.com/alephcloud/hs-aws-kinesis.git+    tag: 0.1++Library+    default-language: Haskell2010+    hs-source-dirs: src++    exposed-modules:+        Aws.Kinesis+        Aws.Kinesis.Core+        Aws.Kinesis.Types+        Aws.Kinesis.Commands.CreateStream+        Aws.Kinesis.Commands.DeleteStream+        Aws.Kinesis.Commands.PutRecord+        Aws.Kinesis.Commands.GetRecords+        Aws.Kinesis.Commands.GetShardIterator+        Aws.Kinesis.Commands.ListStreams+        Aws.Kinesis.Commands.DescribeStream+        Aws.Kinesis.Commands.SplitShard+        Aws.Kinesis.Commands.MergeShards++    build-depends:+        QuickCheck >= 2.7,+        aeson >= 0.7,+        aws >= 0.9,+        aws-general >= 0.1,+        base == 4.*,+        base64-bytestring >= 1.0,+        blaze-builder >= 0.3,+        bytestring >= 0.10,+        conduit >= 1.1,+        conduit-extra >= 1.1,+        http-conduit >= 2.1,+        http-types >= 0.8,+        parsers >= 0.11,+        quickcheck-instances >= 0.3,+        resourcet >= 1.1,+        text >= 1.1,+        time >= 1.4,+        transformers >= 0.3++    ghc-options: -Wall++test-suite kinesis-tests+    type: exitcode-stdio-1.0+    default-language: Haskell2010+    hs-source-dirs: tests+    main-is: Main.hs++    other-modules:+        Utils++    build-depends:+        QuickCheck >= 2.7,+        aeson >= 0.7,+        aws >= 0.9,+        aws-general >= 0.1,+        aws-kinesis,+        base == 4.*,+        bytestring >= 0.10,+        errors >= 1.4.7,+        mtl >= 2.0,+        tagged >= 0.7,+        tasty >= 0.8,+        tasty-quickcheck >= 0.8,+        text >= 1.1,+        transformers >= 0.3++    ghc-options: -Wall -threaded+++++
+ constraints view
@@ -0,0 +1,102 @@+constraints: QuickCheck ==2.7.5,+             aeson ==0.7.0.6,+             array ==0.5.0.0,+             asn1-encoding ==0.8.1.3,+             asn1-parse ==0.8.1,+             asn1-types ==0.2.3,+             attoparsec ==0.12.1.0,+             attoparsec-conduit ==1.1.0,+             aws ==0.9.2,+             aws-general ==0.1.1,+             aws-kinesis ==0.1,+             base ==4.7.0.1,+             base16-bytestring ==0.1.1.6,+             base64-bytestring ==1.0.0.1,+             blaze-builder ==0.3.3.2,+             blaze-builder-conduit ==1.1.0,+             blaze-html ==0.7.0.2,+             blaze-markup ==0.6.1.0,+             byteable ==0.1.1,+             bytestring ==0.10.4.0,+             case-insensitive ==1.2.0.0,+             cereal ==0.4.0.1,+             charset ==0.3.7,+             cipher-aes ==0.2.8,+             cipher-des ==0.0.6,+             cipher-rc4 ==0.1.4,+             conduit ==1.1.6,+             conduit-extra ==1.1.1,+             connection ==0.2.1,+             containers ==0.5.5.1,+             cookie ==0.4.1.1,+             cprng-aes ==0.5.2,+             crypto-cipher-types ==0.0.9,+             crypto-numbers ==0.2.3,+             crypto-pubkey ==0.2.4,+             crypto-pubkey-types ==0.4.2.2,+             crypto-random ==0.0.7,+             cryptohash ==0.11.6,+             data-default ==0.5.3,+             data-default-class ==0.0.1,+             data-default-instances-base ==0.0.1,+             data-default-instances-containers ==0.0.1,+             data-default-instances-dlist ==0.0.1,+             data-default-instances-old-locale ==0.0.1,+             deepseq ==1.3.0.2,+             directory ==1.2.1.0,+             dlist ==0.7.1,+             exceptions ==0.6.1,+             filepath ==1.3.0.2,+             ghc-prim ==0.3.1.0,+             hashable ==1.2.2.0,+             http-client ==0.3.4,+             http-client-tls ==0.2.1.2,+             http-conduit ==2.1.2.3,+             http-types ==0.8.5,+             integer-gmp ==0.5.1.0,+             lifted-base ==0.2.3.0,+             mime-types ==0.1.0.4,+             mmorph ==1.0.3,+             monad-control ==0.3.3.0,+             mtl ==2.1.3.1,+             nats ==0.2,+             network ==2.5.0.0,+             old-locale ==1.0.0.6,+             old-time ==1.1.0.2,+             parsec ==3.1.5,+             parsers ==0.12,+             pem ==0.2.2,+             pretty ==1.1.1.1,+             primitive ==0.5.3.0,+             process ==1.2.0.0,+             publicsuffixlist ==0.1,+             quickcheck-instances ==0.3.8,+             random ==1.0.1.1,+             resourcet ==1.1.2.2,+             rts ==1.0,+             scientific ==0.3.3.0,+             securemem ==0.1.3,+             semigroups ==0.15.1,+             socks ==0.5.4,+             streaming-commons ==0.1.3.1,+             syb ==0.4.2,+             system-filepath ==0.4.12,+             template-haskell ==2.9.0.0,+             text ==1.1.1.3,+             tf-random ==0.5,+             time ==1.4.2,+             tls ==1.2.8,+             transformers ==0.3.0.0,+             transformers-base ==0.4.2,+             unix ==2.7.0.1,+             unordered-containers ==0.2.5.0,+             utf8-string ==0.3.8,+             vector ==0.10.11.0,+             void ==0.6.1,+             x509 ==1.4.11,+             x509-store ==1.4.4,+             x509-system ==1.4.5,+             x509-validation ==1.5.0,+             xml-conduit ==1.2.0.3,+             xml-types ==0.3.4,+             zlib ==0.5.4.1
+ src/Aws/Kinesis.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module: Aws.Kinesis+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference>+--+-- The types and functions of this package are supposed to be+-- use with the machinery from the+-- <https://hackage.haskell.org/package/aws aws package>.+--+-- Here is a very simple example for making a single request to AWS Kinesis.+--+-- > import Aws+-- > import Aws.Core+-- > import Aws.General+-- > import Aws.Kinesis+-- > import Data.IORef+-- >+-- > cfg <- Aws.baseConfiguration+-- > creds <- Credentials "access-key-id" "secret-access-key" `fmap` newIORef []+-- > let kinesisCfg = KinesisConfiguration UsWest2+-- > simpleAws cfg kinesisCfg $ ListStreams Nothing Nothing+--+-- In order to run the example you must replace @"access-key-id"@ and+-- @"secret-access-key"@ with the respective values for your AWS account.+--+module Aws.Kinesis+( module Aws.Kinesis.Core+, module Aws.Kinesis.Types+, module Aws.Kinesis.Commands.CreateStream+, module Aws.Kinesis.Commands.DeleteStream+, module Aws.Kinesis.Commands.DescribeStream+, module Aws.Kinesis.Commands.GetRecords+, module Aws.Kinesis.Commands.GetShardIterator+, module Aws.Kinesis.Commands.ListStreams+, module Aws.Kinesis.Commands.MergeShards+, module Aws.Kinesis.Commands.PutRecord+, module Aws.Kinesis.Commands.SplitShard+) where++import Aws.Kinesis.Core+import Aws.Kinesis.Types+import Aws.Kinesis.Commands.CreateStream+import Aws.Kinesis.Commands.DeleteStream+import Aws.Kinesis.Commands.DescribeStream+import Aws.Kinesis.Commands.GetRecords+import Aws.Kinesis.Commands.GetShardIterator+import Aws.Kinesis.Commands.ListStreams+import Aws.Kinesis.Commands.MergeShards+import Aws.Kinesis.Commands.PutRecord+import Aws.Kinesis.Commands.SplitShard
+ src/Aws/Kinesis/Commands/CreateStream.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DeriveDataTypeable #-}++-- |+-- Module: Aws.Kinesis.Commands.CreateStream+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation adds a new Amazon Kinesis stream to your AWS account. A+-- stream captures and transports data records that are continuously emitted+-- from different data sources or producers. Scale-out within an Amazon Kinesis+-- stream is explicitly supported by means of shards, which are uniquely+-- identified groups of data records in an Amazon Kinesis stream.+--+-- You specify and control the number of shards that a stream is composed of.+-- Each open shard can support up to 5 read transactions per second, up to a+-- maximum total of 2 MB of data read per second. Each shard can support up to+-- 1000 write transactions per second, up to a maximum total of 1 MB data+-- written per second. You can add shards to a stream if the amount of data+-- input increases and you can remove shards if the amount of data input+-- decreases.+--+-- The stream name identifies the stream. The name is scoped to the AWS account+-- used by the application. It is also scoped by region. That is, two streams+-- in two different accounts can have the same name, and two streams in the+-- same account, but in two different regions, can have the same name.+--+-- CreateStream is an asynchronous operation. Upon receiving a CreateStream+-- request, Amazon Kinesis immediately returns and sets the stream status to+-- CREATING. After the stream is created, Amazon Kinesis sets the stream status+-- to ACTIVE. You should perform read and write operations only on an ACTIVE+-- stream.+--+-- You receive a LimitExceededException when making a CreateStream request if+-- you try to do one of the following:+--+-- Have more than five streams in the CREATING state at any point in time.+-- Create more shards than are authorized for your account. Note: The default+-- limit for an AWS account is 10 shards per stream. If you need to create a+-- stream with more than 10 shards, contact AWS Support to increase the limit+-- on your account.+--+-- You can use the DescribeStream operation to check the stream status, which+-- is returned in StreamStatus.+--+-- CreateStream has a limit of 5 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_CreateStream.html>+--+module Aws.Kinesis.Commands.CreateStream+( CreateStream(..)+, CreateStreamResponse(..)+, CreateStreamExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++createStreamAction :: KinesisAction+createStreamAction = KinesisCreateStream++data CreateStream = CreateStream+    { createStreamShardCound :: !Int+    -- ^ The number of shards that the stream will use. The throughput of the+    -- stream is a function of the number of shards; more shards are required+    -- for greater provisioned throughput.+    --+    -- Note: The default limit for an AWS account is 10 shards per stream. If+    -- you need to create a stream with more than 10 shards, contact AWS+    -- Support to increase the limit on your account.+    --+    -- Note that this limit is not checked by the code.++    , createStreamStreamName :: !StreamName+    -- ^ A name to identify the stream.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON CreateStream where+    toJSON CreateStream{..} = object+        [ "ShardCount" .= createStreamShardCound+        , "StreamName" .= createStreamStreamName+        ]++data CreateStreamResponse = CreateStreamResponse+    deriving (Show, Read, Eq, Ord, Typeable)++instance ResponseConsumer r CreateStreamResponse where+    type ResponseMetadata CreateStreamResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance FromJSON CreateStreamResponse where+    parseJSON _ = return CreateStreamResponse++instance SignQuery CreateStream where+    type ServiceConfiguration CreateStream = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = createStreamAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction CreateStream CreateStreamResponse++instance AsMemoryResponse CreateStreamResponse where+    type MemoryResponse CreateStreamResponse = CreateStreamResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data CreateStreamExceptions+    = CreateStreamInvalidArgumentException+    -- ^ /Code 400/++    | CreateStreamLimitExceededException+    -- ^ /Code 400/++    | CreateStreamResourceInUseException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Commands/DeleteStream.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Kinesis.Commands.DeleteStream+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation deletes a stream and all of its shards and data. You must+-- shut down any applications that are operating on the stream before you+-- delete the stream. If an application attempts to operate on a deleted+-- stream, it will receive the exception ResourceNotFoundException.+--+-- If the stream is in the ACTIVE state, you can delete it. After a+-- DeleteStream request, the specified stream is in the DELETING state until+-- Amazon Kinesis completes the deletion.+--+-- Note: Amazon Kinesis might continue to accept data read and write+-- operations, such as PutRecord and GetRecords, on a stream in the DELETING+-- state until the stream deletion is complete.+--+-- When you delete a stream, any shards in that stream are also deleted.+--+-- You can use the DescribeStream operation to check the state of the stream,+-- which is returned in StreamStatus.+--+-- DeleteStream has a limit of 5 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_DeleteStream.html>+--+module Aws.Kinesis.Commands.DeleteStream+( DeleteStream(..)+, DeleteStreamResponse(..)+, DeleteStreamExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++deleteStreamAction :: KinesisAction+deleteStreamAction = KinesisDeleteStream++data DeleteStream = DeleteStream+    { deleteStreamStreamName :: !StreamName+    -- ^ The name of the stream to delete.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON DeleteStream where+    toJSON DeleteStream{..} = object+        [ "StreamName" .= deleteStreamStreamName+        ]++data DeleteStreamResponse = DeleteStreamResponse+    deriving (Show, Read, Eq, Ord, Typeable)++instance FromJSON DeleteStreamResponse where+    parseJSON _ = return DeleteStreamResponse++instance ResponseConsumer r DeleteStreamResponse where+    type ResponseMetadata DeleteStreamResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance SignQuery DeleteStream where+    type ServiceConfiguration DeleteStream = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = deleteStreamAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction DeleteStream DeleteStreamResponse++instance AsMemoryResponse DeleteStreamResponse where+    type MemoryResponse DeleteStreamResponse = DeleteStreamResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data DeleteStreamExceptions+    = DeleteStreamLimitExceededException+    -- ^ /Code 400/++    | DeleteStreamResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Commands/DescribeStream.hs view
@@ -0,0 +1,124 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Sns.Commands.DescribeStream+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-03-31/+--+-- This operation returns the following information about the stream: the+-- current status of the stream, the stream Amazon Resource Name (ARN), and an+-- array of shard objects that comprise the stream. For each shard object there+-- is information about the hash key and sequence number ranges that the shard+-- spans, and the IDs of any earlier shards that played in a role in a+-- MergeShards or SplitShard operation that created the shard. A sequence+-- number is the identifier associated with every record ingested in the Amazon+-- Kinesis stream. The sequence number is assigned by the Amazon Kinesis+-- service when a record is put into the stream.+--+-- You can limit the number of returned shards using the Limit parameter. The+-- number of shards in a stream may be too large to return from a single call+-- to DescribeStream. You can detect this by using the HasMoreShards flag in+-- the returned output. HasMoreShards is set to true when there is more data+-- available.+--+-- If there are more shards available, you can request more shards by using the+-- shard ID of the last shard returned by the DescribeStream request, in the+-- ExclusiveStartShardId parameter in a subsequent request to DescribeStream.+-- DescribeStream is a paginated operation.+--+-- DescribeStream has a limit of 10 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_DescribeStream.html>+--+module Aws.Kinesis.Commands.DescribeStream+( DescribeStream(..)+, DescribeStreamResponse(..)+, DescribeStreamExceptions(..)+) where++import Control.Applicative++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++describeStreamAction :: KinesisAction+describeStreamAction = KinesisDescribeStream++data DescribeStream = DescribeStream+    { describeStreamExclusiveStartShardId:: !(Maybe ShardId)+    -- ^ The shard ID of the shard to start with for the stream description.++    , describeStreamLimit :: !(Maybe Int)+    -- ^ The maximum number of shards to return.++    , describeStreamStreamName :: !StreamName+    -- ^ The name of the stream to describe.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON DescribeStream where+    toJSON DescribeStream{..} = object+        [ "ExclusiveStartShardId" .= describeStreamExclusiveStartShardId+        , "Limit" .= describeStreamLimit+        , "StreamName" .= describeStreamStreamName+        ]++data DescribeStreamResponse = DescribeStreamResponse+    { describeStreamResStreamDescription :: !StreamDescription+    -- ^ Contains the current status of the stream, the stream ARN, an array of+    -- shard objects that comprise the stream, and states whether there are+    -- more shards available.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ResponseConsumer r DescribeStreamResponse where+    type ResponseMetadata DescribeStreamResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance FromJSON DescribeStreamResponse where+    parseJSON = withObject "DescribeStreamResponse" $ \o -> DescribeStreamResponse+        <$> o .: "StreamDescription"++instance SignQuery DescribeStream where+    type ServiceConfiguration DescribeStream = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = describeStreamAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction DescribeStream DescribeStreamResponse++instance AsMemoryResponse DescribeStreamResponse where+    type MemoryResponse DescribeStreamResponse = DescribeStreamResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data DescribeStreamExceptions+    = DescribeStreamLimitExceededException+    -- ^ /Code 400/++    | DescribeStreamResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++
+ src/Aws/Kinesis/Commands/GetRecords.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Kinesis.Commands.GetRecords+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation returns one or more data records from a shard. A GetRecords+-- operation request can retrieve up to 10 MB of data.+--+-- You specify a shard iterator for the shard that you want to read data from+-- in the ShardIterator parameter. The shard iterator specifies the position in+-- the shard from which you want to start reading data records sequentially. A+-- shard iterator specifies this position using the sequence number of a data+-- record in the shard. For more information about the shard iterator, see+-- GetShardIterator.+--+-- GetRecords may return a partial result if the response size limit is+-- exceeded. You will get an error, but not a partial result if the shard's+-- provisioned throughput is exceeded, the shard iterator has expired, or an+-- internal processing failure has occurred. Clients can request a smaller+-- amount of data by specifying a maximum number of returned records using the+-- Limit parameter. The Limit parameter can be set to an integer value of up to+-- 10,000. If you set the value to an integer greater than 10,000, you will+-- receive InvalidArgumentException.+--+-- A new shard iterator is returned by every GetRecords request in+-- NextShardIterator, which you use in the ShardIterator parameter of the next+-- GetRecords request. When you repeatedly read from an Amazon Kinesis stream+-- use a GetShardIterator request to get the first shard iterator to use in+-- your first GetRecords request and then use the shard iterator returned in+-- NextShardIterator for subsequent reads.+--+-- GetRecords can return null for the NextShardIterator to reflect that the+-- shard has been closed and that the requested shard iterator would never have+-- returned more data.+--+-- If no items can be processed because of insufficient provisioned throughput+-- on the shard involved in the request, GetRecords throws+-- ProvisionedThroughputExceededException.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_GetRecords.html>+--+module Aws.Kinesis.Commands.GetRecords+( GetRecords(..)+, GetRecordsResponse(..)+, GetRecordsExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Control.Applicative++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++getRecordsAction :: KinesisAction+getRecordsAction = KinesisGetRecords++data GetRecords = GetRecords+    { getRecordsLimit :: !(Maybe Int)+    -- ^ The maximum number of records to return, which can be set to a value+    -- of up to 10,000.++    , getRecordsShardIterator :: !ShardIterator+    -- ^ The position in the shard from which you want to start sequentially+    -- reading data records.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON GetRecords where+    toJSON GetRecords{..} = object+        [ "Limit" .= getRecordsLimit+        , "ShardIterator" .= getRecordsShardIterator+        ]++data GetRecordsResponse = GetRecordsResponse+    { getRecordsResNextShardIterator :: !(Maybe ShardIterator)+    -- ^ The next position in the shard from which to start sequentially+    -- reading data records. If set to null, the shard has been closed and the+    -- requested iterator will not return any more data.++    , getRecordsResRecords :: ![Record]+    -- ^ List of Records+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance FromJSON GetRecordsResponse where+    parseJSON = withObject "GetRecordsResponse" $ \o -> GetRecordsResponse+        <$> o .:? "NextShardIterator" .!= Nothing+        <*> o .: "Records"++instance ResponseConsumer r GetRecordsResponse where+    type ResponseMetadata GetRecordsResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance SignQuery GetRecords where+    type ServiceConfiguration GetRecords = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = getRecordsAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction GetRecords GetRecordsResponse++instance AsMemoryResponse GetRecordsResponse where+    type MemoryResponse GetRecordsResponse = GetRecordsResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data GetRecordsExceptions+    = GetRecordsExpiredIteratorException+    -- ^ /Code 400/++    | GetRecordsInvalidArgumentException+    -- ^ /Code 400/++    | GetRecordsProvisionedThroughputExceededException+    -- ^ /Code 400/++    | GetRecordsResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++
+ src/Aws/Kinesis/Commands/GetShardIterator.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module: Aws.Kinesis.Commands.GetShardIterator+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation returns a shard iterator in ShardIterator. The shard iterator+-- specifies the position in the shard from which you want to start reading+-- data records sequentially. A shard iterator specifies this position using+-- the sequence number of a data record in a shard. A sequence number is the+-- identifier associated with every record ingested in the Amazon Kinesis+-- stream. The sequence number is assigned by the Amazon Kinesis service when a+-- record is put into the stream.+--+-- You must specify the shard iterator type in the GetShardIterator request.+-- For example, you can set the ShardIteratorType parameter to read exactly+-- from the position denoted by a specific sequence number by using the+-- AT_SEQUENCE_NUMBER shard iterator type, or right after the sequence number+-- by using the AFTER_SEQUENCE_NUMBER shard iterator type, using sequence+-- numbers returned by earlier PutRecord, GetRecords or DescribeStream+-- requests. You can specify the shard iterator type TRIM_HORIZON in the+-- request to cause ShardIterator to point to the last untrimmed record in the+-- shard in the system, which is the oldest data record in the shard. Or you+-- can point to just after the most recent record in the shard, by using the+-- shard iterator type LATEST, so that you always read the most recent data in+-- the shard.+--+-- Note: Each shard iterator expires five minutes after it is returned to the+-- requester.+--+-- When you repeatedly read from an Amazon Kinesis stream use a+-- GetShardIterator request to get the first shard iterator to to use in your+-- first GetRecords request and then use the shard iterator returned by the+-- GetRecords request in NextShardIterator for subsequent reads. A new shard+-- iterator is returned by every GetRecords request in NextShardIterator, which+-- you use in the ShardIterator parameter of the next GetRecords request.+--+-- If a GetShardIterator request is made too often, you will receive a+-- ProvisionedThroughputExceededException. For more information about+-- throughput limits, see the Amazon Kinesis Developer Guide.+--+-- GetShardIterator can return null for its ShardIterator to indicate that the+-- shard has been closed and that the requested iterator will return no more+-- data. A shard can be closed by a SplitShard or MergeShards operation.+--+-- GetShardIterator has a limit of 5 transactions per second per account per+-- open shard.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_GetShardIterator.html>+--+module Aws.Kinesis.Commands.GetShardIterator+( GetShardIterator(..)+, GetShardIteratorResponse(..)+, GetShardIteratorExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Types+import Aws.Kinesis.Core++import Control.Applicative++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++getShardIteratorAction :: KinesisAction+getShardIteratorAction = KinesisGetShardIterator++data GetShardIterator = GetShardIterator+    { getShardIteratorShardId :: !ShardId+    -- ^ The shard ID of the shard to get the iterator for.++    , getShardIteratorShardIteratorType :: !ShardIteratorType+    -- ^ Determines how the shard iterator is used to start reading data+    -- records from the shard.++    , getShardIteratorStartingSequenceNumber :: !(Maybe SequenceNumber)+    -- ^ The sequence number of the data record in the shard from which to+    -- start reading from.++    , getShardIteratorStreamName :: !StreamName+    -- ^ The name of the stream.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON GetShardIterator where+    toJSON GetShardIterator{..} = object+        [ "ShardId" .= getShardIteratorShardId+        , "ShardIteratorType" .= getShardIteratorShardIteratorType+        , "SequenceNumber" .= getShardIteratorStartingSequenceNumber+        , "StreamName" .= getShardIteratorStreamName+        ]++data GetShardIteratorResponse = GetShardIteratorResponse+    { getShardIteratorResShardIterator :: !ShardIterator+    -- ^ The position in the shard from which to start reading data records+    -- sequentially. A shard iterator specifies this position using the+    -- sequence number of a data record in a shard.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance FromJSON GetShardIteratorResponse where+    parseJSON = withObject "GetShardIteratorResponse" $ \o -> GetShardIteratorResponse+        <$> o .: "ShardIterator"++instance ResponseConsumer r GetShardIteratorResponse where+    type ResponseMetadata GetShardIteratorResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance SignQuery GetShardIterator where+    type ServiceConfiguration GetShardIterator = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = getShardIteratorAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction GetShardIterator GetShardIteratorResponse++instance AsMemoryResponse GetShardIteratorResponse where+    type MemoryResponse GetShardIteratorResponse = GetShardIteratorResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data GetShardIteratorExceptions+    = GetShardIteratorInvalidArgumentException+    -- ^ /Code 400/++    | GetShardIteratorProvisionedThroughputExceededException+    -- ^ /Code 400/++    | GetShardIteratorResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Commands/ListStreams.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Kinesis.Commands.ListStreams+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation returns an array of the names of all the streams that are+-- associated with the AWS account making the ListStreams request. A given AWS+-- account can have many streams active at one time.+--+-- The number of streams may be too large to return from a single call to+-- ListStreams. You can limit the number of returned streams using the Limit+-- parameter. If you do not specify a value for the Limit parameter, Amazon+-- Kinesis uses the default limit, which is currently 10.+--+-- You can detect if there are more streams available to list by using the+-- HasMoreStreams flag from the returned output. If there are more streams+-- available, you can request more streams by using the name of the last stream+-- returned by the ListStreams request in the ExclusiveStartStreamName+-- parameter in a subsequent request to ListStreams. The group of stream names+-- returned by the subsequent request is then added to the list. You can+-- continue this process until all the stream names have been collected in the+-- list.+--+-- ListStreams has a limit of 5 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_ListStreams.html>+--+module Aws.Kinesis.Commands.ListStreams+( ListStreams(..)+, ListStreamsResponse(..)+, ListStreamsExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Types+import Aws.Kinesis.Core++import Control.Applicative++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++listStreamsAction :: KinesisAction+listStreamsAction = KinesisListStreams++data ListStreams = ListStreams+    { listStreamsExclusiveStartStreamName :: !(Maybe StreamName)+    -- ^ The name of the stream to start the list with.++    , listStreamsLimit :: !(Maybe Int)+    -- ^ The maximum number of streams to list.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON ListStreams where+    toJSON ListStreams{..} = object+        [ "ExclusiveStartStreamName" .= listStreamsExclusiveStartStreamName+        , "StreamName" .= listStreamsLimit+        ]++data ListStreamsResponse = ListStreamsResponse+    { listStreamsResHasMoreStreams :: !Bool+    -- ^ If set to true, there are more streams available to list.++    , listStreamsResStreamNames :: ![StreamName]+    -- ^ The names of the streams that are associated with the AWS account+    -- making the ListStreams request.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance FromJSON ListStreamsResponse where+    parseJSON = withObject "ListStreamsResponse" $ \o -> ListStreamsResponse+        <$> o .: "HasMoreStreams"+        <*> o .: "StreamNames"++instance ResponseConsumer r ListStreamsResponse where+    type ResponseMetadata ListStreamsResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance SignQuery ListStreams where+    type ServiceConfiguration ListStreams = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = listStreamsAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction ListStreams ListStreamsResponse++instance AsMemoryResponse ListStreamsResponse where+    type MemoryResponse ListStreamsResponse = ListStreamsResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data ListStreamsExceptions+    = ListStreamsLimitExceededException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Commands/MergeShards.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Kinesis.Commands.MergeShards+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation merges two adjacent shards in a stream and combines them into+-- a single shard to reduce the stream's capacity to ingest and transport data.+-- Two shards are considered adjacent if the union of the hash key ranges for+-- the two shards form a contiguous set with no gaps. For example, if you have+-- two shards, one with a hash key range of 276...381 and the other with a hash+-- key range of 382...454, then you could merge these two shards into a single+-- shard that would have a hash key range of 276...454. After the merge, the+-- single child shard receives data for all hash key values covered by the two+-- parent shards.+--+-- MergeShards is called when there is a need to reduce the overall capacity of+-- a stream because of excess capacity that is not being used. The operation+-- requires that you specify the shard to be merged and the adjacent shard for+-- a given stream. For more information about merging shards, see the Amazon+-- Kinesis Developer Guide.+--+-- If the stream is in the ACTIVE state, you can call MergeShards. If a stream+-- is in CREATING or UPDATING or DELETING states, then Amazon Kinesis returns a+-- ResourceInUseException. If the specified stream does not exist, Amazon+-- Kinesis returns a ResourceNotFoundException.+--+-- You can use the DescribeStream operation to check the state of the stream,+-- which is returned in StreamStatus.+--+-- MergeShards is an asynchronous operation. Upon receiving a MergeShards+-- request, Amazon Kinesis immediately returns a response and sets the+-- StreamStatus to UPDATING. After the operation is completed, Amazon Kinesis+-- sets the StreamStatus to ACTIVE. Read and write operations continue to work+-- while the stream is in the UPDATING state.+--+-- You use the DescribeStream operation to determine the shard IDs that are+-- specified in the MergeShards request.+--+-- If you try to operate on too many streams in parallel using CreateStream,+-- DeleteStream, MergeShards or SplitShard, you will receive a+-- LimitExceededException.+--+-- MergeShards has limit of 5 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_MergeShards.html>+--+module Aws.Kinesis.Commands.MergeShards+( MergeShards(..)+, MergeShardsResponse(..)+, MergeShardsExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++mergeShardsAction :: KinesisAction+mergeShardsAction = KinesisMergeShards++data MergeShards = MergeShards+    { mergeShardsAdjacentShardToMerge :: !ShardId+    -- ^ The shard ID of the adjacent shard for the merge.++    , mergeShardsShardToMerge :: !ShardId+    -- ^ The shard ID of the shard to combine with the adjacent shard for the+    -- merge.++    , mergeShardsStreamName :: !StreamName+    -- ^ The name of the stream for the merge.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON MergeShards where+    toJSON MergeShards{..} = object+        [ "AdjacentShardToMerge" .= mergeShardsAdjacentShardToMerge+        , "ShardToMerge" .= mergeShardsShardToMerge+        , "StreamName" .= mergeShardsStreamName+        ]++data MergeShardsResponse = MergeShardsResponse+    deriving (Show, Read, Eq, Ord, Typeable)++instance ResponseConsumer r MergeShardsResponse where+    type ResponseMetadata MergeShardsResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance FromJSON MergeShardsResponse where+    parseJSON _ = return MergeShardsResponse++instance SignQuery MergeShards where+    type ServiceConfiguration MergeShards = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = mergeShardsAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction MergeShards MergeShardsResponse++instance AsMemoryResponse MergeShardsResponse where+    type MemoryResponse MergeShardsResponse = MergeShardsResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data MergeShardsExceptions+    = MergeShardsInvalidArgumentException+    -- ^ /Code 400/++    | MergeShardsLimitExceededException+    -- ^ /Code 400/++    | MergeShardsResourceInUseException+    -- ^ /Code 400/++    | MergeShardsResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+++
+ src/Aws/Kinesis/Commands/PutRecord.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Kinesis.Commands.PutRecord+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- This operation puts a data record into an Amazon Kinesis stream from a+-- producer. This operation must be called to send data from the producer into+-- the Amazon Kinesis stream for real-time ingestion and subsequent processing.+-- The PutRecord operation requires the name of the stream that captures,+-- stores, and transports the data; a partition key; and the data blob itself.+-- The data blob could be a segment from a log file, geographic/location data,+-- website clickstream data, or any other data type.+--+-- The partition key is used to distribute data across shards. Amazon Kinesis+-- segregates the data records that belong to a data stream into multiple+-- shards, using the partition key associated with each data record to+-- determine which shard a given data record belongs to.+--+-- Partition keys are Unicode strings, with a maximum length limit of 256+-- bytes. An MD5 hash function is used to map partition keys to 128-bit integer+-- values and to map associated data records to shards using the hash key+-- ranges of the shards. You can override hashing the partition key to+-- determine the shard by explicitly specifying a hash value using the+-- ExplicitHashKey parameter. For more information, see the Amazon Kinesis+-- Developer Guide.+--+-- PutRecord returns the shard ID of where the data record was placed and the+-- sequence number that was assigned to the data record.+--+-- Sequence numbers generally increase over time. To guarantee strictly+-- increasing ordering, use the SequenceNumberForOrdering parameter. For more+-- information, see the Amazon Kinesis Developer Guide.+--+-- If a PutRecord request cannot be processed because of insufficient+-- provisioned throughput on the shard involved in the request, PutRecord+-- throws ProvisionedThroughputExceededException.+--+-- Data records are accessible for only 24 hours from the time that they are+-- added to an Amazon Kinesis stream.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_PutRecord.html>+--+module Aws.Kinesis.Commands.PutRecord+( PutRecord(..)+, PutRecordResponse(..)+, PutRecordExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Types+import Aws.Kinesis.Core++import Control.Applicative++import Data.Aeson+import Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Base64 as B64+import qualified Data.Text.Encoding as T+import Data.Typeable++putRecordAction :: KinesisAction+putRecordAction = KinesisPutRecord++data PutRecord = PutRecord+    { putRecordData :: !B.ByteString+    -- ^ The data blob to put into the record. The maximum size of the data+    -- blob is 50 kilobytes (KB)++    , putRecordExplicitHashKey :: !(Maybe PartitionHash)+    -- ^ The hash value used to explicitly determine the shard the data record+    -- is assigned to by overriding the partition key hash.+    --+    -- FIXME the specification is rather vague about the precise encoding+    -- of this value. The default is to compute it as an MD5 hash of+    -- the partition key. The API reference describes it as an Int128.+    -- However, it is not clear how the result of the hash function is+    -- encoded (big-endian or small endian, word size?) and how it is+    -- serialized to text, which is the type in the JSON serialization.++    , putRecordPartitionKey :: !PartitionKey+    -- ^ Determines which shard in the stream the data record is assigned to.++    , putRecordSequenceNumberForOrdering :: !(Maybe SequenceNumber)+    -- ^ Guarantees strictly increasing sequence numbers, for puts from the+    -- same client and to the same partition key. Usage: set the+    -- SequenceNumberForOrdering of record n to the sequence number of record+    -- n-1 (as returned in the PutRecordResult when putting record n-1). If+    -- this parameter is not set, records will be coarsely ordered based on+    -- arrival time.++    , putRecordStreamName :: !StreamName+    -- ^ The name of the stream to put the data record into.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON PutRecord where+    toJSON PutRecord{..} = object+        [ "Data" .= T.decodeUtf8 (B64.encode putRecordData)+        , "ExplicitHashKey" .= putRecordExplicitHashKey+        , "PartitionKey" .= putRecordPartitionKey+        , "SequenceNumberForOrdering" .= putRecordSequenceNumberForOrdering+        , "StreamName" .= putRecordStreamName+        ]++data PutRecordResponse = PutRecordResponse+    { putRecordResSequenceNumber :: !SequenceNumber+    -- ^ The sequence number identifier that was assigned to the put data+    -- record. The sequence number for the record is unique across all records+    -- in the stream. A sequence number is the identifier associated with every+    -- record put into the stream.++    , putRecordResShardId :: !ShardId+    -- ^ The shard ID of the shard where the data record was placed.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance FromJSON PutRecordResponse where+    parseJSON = withObject "PutRecordResponse" $ \o -> PutRecordResponse+        <$> o .: "SequenceNumber"+        <*> o .: "ShardId"++instance ResponseConsumer r PutRecordResponse where+    type ResponseMetadata PutRecordResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance SignQuery PutRecord where+    type ServiceConfiguration PutRecord = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = putRecordAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction PutRecord PutRecordResponse++instance AsMemoryResponse PutRecordResponse where+    type MemoryResponse PutRecordResponse = PutRecordResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data PutRecordExceptions+    = PutRecordInvalidArgumentException+    -- ^ /Code 400/++    | PutRecordProvisionedThroughputExceededException+    -- ^ /Code 400/++    | PutRecordResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Commands/SplitShard.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Module: Aws.Sns.Commands.SplitShard+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-03-31/+-- This operation splits a shard into two new shards in the stream, to increase+-- the stream's capacity to ingest and transport data. SplitShard is called+-- when there is a need to increase the overall capacity of stream because of+-- an expected increase in the volume of data records being ingested.+--+-- SplitShard can also be used when a given shard appears to be approaching its+-- maximum utilization, for example, when the set of producers sending data+-- into the specific shard are suddenly sending more than previously+-- anticipated. You can also call the SplitShard operation to increase stream+-- capacity, so that more Amazon Kinesis applications can simultaneously read+-- data from the stream for real-time processing.+--+-- The SplitShard operation requires that you specify the shard to be split and+-- the new hash key, which is the position in the shard where the shard gets+-- split in two. In many cases, the new hash key might simply be the average of+-- the beginning and ending hash key, but it can be any hash key value in the+-- range being mapped into the shard. For more information about splitting+-- shards, see the Amazon Kinesis Developer Guide.+--+-- You can use the DescribeStream operation to determine the shard ID and hash+-- key values for the ShardToSplit and NewStartingHashKey parameters that are+-- specified in the SplitShard request.+--+-- SplitShard is an asynchronous operation. Upon receiving a SplitShard+-- request, Amazon Kinesis immediately returns a response and sets the stream+-- status to UPDATING. After the operation is completed, Amazon Kinesis sets+-- the stream status to ACTIVE. Read and write operations continue to work+-- while the stream is in the UPDATING state.+--+-- You can use DescribeStream to check the status of the stream, which is+-- returned in StreamStatus. If the stream is in the ACTIVE state, you can call+-- SplitShard. If a stream is in CREATING or UPDATING or DELETING states, then+-- Amazon Kinesis returns a ResourceInUseException.+--+-- If the specified stream does not exist, Amazon Kinesis returns a+-- ResourceNotFoundException. If you try to create more shards than are+-- authorized for your account, you receive a LimitExceededException.+--+-- Note: The default limit for an AWS account is 10 shards per stream. If you+-- need to create a stream with more than 10 shards, contact AWS Support to+-- increase the limit on your account.+--+-- If you try to operate on too many streams in parallel using CreateStream,+-- DeleteStream, MergeShards or SplitShard, you will receive a+-- LimitExceededException.+--+-- SplitShard has limit of 5 transactions per second per account.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_SplitShard.html>+--+module Aws.Kinesis.Commands.SplitShard+( SplitShard(..)+, SplitShardResponse(..)+, SplitShardExceptions(..)+) where++import Aws.Core+import Aws.Kinesis.Core+import Aws.Kinesis.Types++import Data.Aeson+import qualified Data.ByteString.Lazy as LB+import Data.Typeable++splitShardAction :: KinesisAction+splitShardAction = KinesisSplitShard++data SplitShard = SplitShard+    { splitShardNewStartingHashKey :: !PartitionHash+    -- ^ A hash key value for the starting hash key of one of the child shards+    -- created by the split. The hash key range for a given shard constitutes a+    -- set of ordered contiguous positive integers. The value for+    -- NewStartingHashKey must be in the range of hash keys being mapped into+    -- the shard. The NewStartingHashKey hash key value and all higher hash key+    -- values in hash key range are distributed to one of the child shards. All+    -- the lower hash key values in the range are distributed to the other+    -- child shard.++    , splitShardShardToSplit :: !ShardId+    -- ^ The shard ID of the shard to split.++    , splitShardStreamName :: !StreamName+    -- ^ The name of the stream for the shard split.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON SplitShard where+    toJSON SplitShard{..} = object+        [ "NewStartingHashKey" .= splitShardNewStartingHashKey+        , "ShardToSplit" .= splitShardShardToSplit+        , "StreamName" .= splitShardStreamName+        ]++data SplitShardResponse = SplitShardResponse+    deriving (Show, Read, Eq, Ord, Typeable)++instance ResponseConsumer r SplitShardResponse where+    type ResponseMetadata SplitShardResponse = KinesisMetadata+    responseConsumer _ = kinesisResponseConsumer++instance FromJSON SplitShardResponse where+    parseJSON _ = return SplitShardResponse++instance SignQuery SplitShard where+    type ServiceConfiguration SplitShard = KinesisConfiguration+    signQuery cmd = kinesisSignQuery KinesisQuery+        { kinesisQueryAction = splitShardAction+        , kinesisQueryBody = Just $ LB.toStrict $ encode cmd+        }++instance Transaction SplitShard SplitShardResponse++instance AsMemoryResponse SplitShardResponse where+    type MemoryResponse SplitShardResponse = SplitShardResponse+    loadToMemory = return++-- -------------------------------------------------------------------------- --+-- Exceptions+--+-- Currently not used for requests. It's included for future usage+-- and as reference.++data SplitShardExceptions+    = SplitShardInvalidArgumentException+    -- ^ /Code 400/++    | SplitShardLimitExceededException+    -- ^ /Code 400/++    | SplitShardResourceInUseException+    -- ^ /Code 400/++    | SplitShardResourceNotFoundException+    -- ^ /Code 400/++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)+
+ src/Aws/Kinesis/Core.hs view
@@ -0,0 +1,510 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}++-- |+-- Module: Aws.Kinesis.Core+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference>+--+module Aws.Kinesis.Core+(+  KinesisVersion(..)++-- * Kinesis Client Configuration+, KinesisConfiguration(..)++-- * Kinesis Client Metadata+, KinesisMetadata(..)++-- * Kinesis Exceptions+, KinesisErrorResponse(..)++-- * Internal++-- ** Kinesis Actions+, KinesisAction(..)+, kinesisActionToText+, parseKinesisAction++-- ** Kinesis AWS Service Endpoints+, kinesisServiceEndpoint++-- ** Kinesis Queries+, KinesisQuery(..)+, kinesisSignQuery++-- ** Kinesis Responses+, kinesisResponseConsumer+, jsonResponseConsumer++-- ** Kinesis Errors and Common Parameters+, KinesisError(..)+, KinesisCommonParameters(..)+, KinesisCommonError(..)+) where++import Aws.Core+import Aws.General+import Aws.SignatureV4++import qualified Blaze.ByteString.Builder as BB++import Control.Applicative+import Control.Exception+import Control.Monad.IO.Class+import Control.Monad.Trans.Resource (throwM)++import Data.Aeson+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as LB+import qualified Data.ByteString.Char8 as B8+import Data.Conduit (($$+-))+import Data.Conduit.Binary (sinkLbs)+import Data.IORef+import Data.Maybe+import Data.Monoid+import Data.String+import Data.Time.Clock+import Data.Typeable+import qualified Data.Text as T+import qualified Data.Text.Encoding as T++import qualified Network.HTTP.Types as HTTP+import qualified Network.HTTP.Conduit as HTTP++import qualified Test.QuickCheck as Q++import qualified Text.Parser.Char as P+import Text.Parser.Combinators ((<?>))++data KinesisVersion+    = KinesisVersion_2013_12_02++kinesisTargetVersion :: IsString a => a+kinesisTargetVersion = "Kinesis_20131202"++-- -------------------------------------------------------------------------- --+-- Kinesis Actions++data KinesisAction+    = KinesisCreateStream+    | KinesisDeleteStream+    | KinesisDescribeStream+    | KinesisGetRecords+    | KinesisGetShardIterator+    | KinesisListStreams+    | KinesisMergeShards+    | KinesisPutRecord+    | KinesisSplitShard+    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++kinesisActionToText :: IsString a => KinesisAction -> a+kinesisActionToText KinesisCreateStream = "CreateStream"+kinesisActionToText KinesisDeleteStream = "DeleteStream"+kinesisActionToText KinesisDescribeStream = "DescribeStream"+kinesisActionToText KinesisGetRecords = "GetRecords"+kinesisActionToText KinesisGetShardIterator = "GetShardIterator"+kinesisActionToText KinesisListStreams = "ListStreams"+kinesisActionToText KinesisMergeShards = "MergeShards"+kinesisActionToText KinesisPutRecord = "PutRecord"+kinesisActionToText KinesisSplitShard = "SplitShard"++parseKinesisAction :: P.CharParsing m => m KinesisAction+parseKinesisAction =+        KinesisCreateStream <$ P.text "CreateStream"+    <|> KinesisDeleteStream <$ P.text "DeleteStream"+    <|> KinesisDescribeStream <$ P.text "DescribeStream"+    <|> KinesisGetRecords <$ P.text "GetRecords"+    <|> KinesisGetShardIterator <$ P.text "GetShardIterator"+    <|> KinesisListStreams <$ P.text "ListStreams"+    <|> KinesisMergeShards <$ P.text "MergeShards"+    <|> KinesisPutRecord <$ P.text "PutRecord"+    <|> KinesisSplitShard <$ P.text "SplitShard"+    <?> "KinesisAction"++instance AwsType KinesisAction where+    toText = kinesisActionToText+    parse = parseKinesisAction++instance Q.Arbitrary KinesisAction where+    arbitrary = Q.elements [minBound..maxBound]++kinesisTargetHeader :: KinesisAction -> HTTP.Header+kinesisTargetHeader a = ("X-Amz-Target", kinesisTargetVersion <> "." <> toText a)++-- -------------------------------------------------------------------------- --+-- Kinesis AWS Service Endpoints++-- | Kinesis Endpoints as specified in AWS General API version 0.1+--+-- <http://docs.aws.amazon.com/general/1.0/gr/rande.html#ak_region>+--+kinesisServiceEndpoint :: Region -> B8.ByteString+kinesisServiceEndpoint ApNortheast1 = "kinesis.ap-northeast-1.amazonaws.com"+kinesisServiceEndpoint ApSoutheast1 = "kinesis.ap-southeast-1.amazonaws.com"+kinesisServiceEndpoint ApSoutheast2 = "kinesis.ap-southeast-2.amazonaws.com"+kinesisServiceEndpoint EuWest1 = "kinesis.eu-west-1.amazonaws.com"+kinesisServiceEndpoint UsEast1 = "kinesis.us-east-1.amazonaws.com"+kinesisServiceEndpoint UsWest2 = "kinesis.us-west-2.amazonaws.com"+kinesisServiceEndpoint r = error $ "Aws.Kinesis.Core.kinesisServiceEndpoint: unsupported region " <> show r -- FIXME++-- -------------------------------------------------------------------------- --+-- Kinesis Metadata++data KinesisMetadata = KinesisMetadata+    { kinesisMAmzId2 :: Maybe T.Text+    , kinesisMRequestId :: Maybe T.Text+    }+    deriving (Show)++instance Loggable KinesisMetadata where+    toLogText (KinesisMetadata rid id2) =+        "Kinesis: request ID=" <> fromMaybe "<none>" rid+        <> ", x-amz-id-2=" <> fromMaybe "<none>" id2++instance Monoid KinesisMetadata where+    mempty = KinesisMetadata Nothing Nothing+    KinesisMetadata id1 r1 `mappend` KinesisMetadata id2 r2 = KinesisMetadata (id1 <|> id2) (r1 <|> r2)++-- -------------------------------------------------------------------------- --+-- Kinesis Configuration++data KinesisConfiguration qt = KinesisConfiguration+    { kinesisConfRegion :: Region+    }+    deriving (Show)++-- -------------------------------------------------------------------------- --+-- Kinesis Query++data KinesisQuery = KinesisQuery+    { kinesisQueryAction :: !KinesisAction+    , kinesisQueryBody :: !(Maybe B.ByteString)+    }+    deriving (Show, Eq)++-- | Creates a signed query.+--+-- Uses AWS Signature V4. All requests are POST requests+-- with the signature placed in an HTTP header+--+kinesisSignQuery :: KinesisQuery -> KinesisConfiguration qt -> SignatureData -> SignedQuery+kinesisSignQuery query conf sigData = SignedQuery+    { sqMethod = Post+    , sqProtocol = HTTPS+    , sqHost = host+    , sqPort = port+    , sqPath = BB.toByteString $ HTTP.encodePathSegments path+    , sqQuery = reqQuery+    , sqDate = Nothing+    , sqAuthorization = authorization+    , sqContentType = contentType+    , sqContentMd5 = Nothing+    , sqAmzHeaders = amzHeaders+    , sqOtherHeaders = [] -- we put everything into amzHeaders+    , sqBody = HTTP.RequestBodyBS <$> body+    , sqStringToSign = mempty -- Let me know if you really need this...+    }+  where+    path = []+    reqQuery = []+    host = kinesisServiceEndpoint $ kinesisConfRegion conf+    headers = [("host", host), kinesisTargetHeader (kinesisQueryAction query)]+    port = 443+    contentType = Just "application/x-amz-json-1.1"+    body = kinesisQueryBody query++    amzHeaders = filter ((/= "Authorization") . fst) sig+    authorization = return <$> lookup "authorization" sig+    sig = either error id $ signPostRequest+            (cred2cred $ signatureCredentials sigData)+            (kinesisConfRegion conf)+            ServiceNamespaceKinesis+            (signatureTime sigData)+            "POST"+            path+            reqQuery+            headers+            (fromMaybe "" body)++#if MIN_VERSION_aws(0,9,2)+    cred2cred (Credentials a b c _) = SignatureV4Credentials a b c+#else+    cred2cred (Credentials a b c) = SignatureV4Credentials a b c+#endif++-- -------------------------------------------------------------------------- --+-- Kinesis Response Consumer++-- | Create a complete 'HTTPResponseConsumer' for response types with an FromJSON instance+--+jsonResponseConsumer+    :: FromJSON a+    => HTTPResponseConsumer a+jsonResponseConsumer res = do+    doc <- HTTP.responseBody res $$+- sinkLbs+    case eitherDecode (if doc == mempty then "{}" else doc) of+        Left err -> throwM . KinesisResponseJsonError $ T.pack err+        Right v -> return v++kinesisResponseConsumer+    :: FromJSON a+    => IORef KinesisMetadata+    -> HTTPResponseConsumer a+kinesisResponseConsumer metadata resp = do++    let headerString = fmap T.decodeUtf8 . flip lookup (HTTP.responseHeaders resp)+        amzId2 = headerString "x-amz-id-2"+        requestId = headerString "x-amz-request-id"+        m = KinesisMetadata+            { kinesisMAmzId2 = amzId2+            , kinesisMRequestId = requestId+            }++    liftIO $ tellMetadataRef metadata m++    if HTTP.responseStatus resp >= HTTP.status400+        then errorResponseConsumer resp+        else jsonResponseConsumer resp++-- | Parse Error Responses+--+errorResponseConsumer :: HTTPResponseConsumer a+errorResponseConsumer resp = do+    doc <- HTTP.responseBody resp $$+- sinkLbs+    if HTTP.responseStatus resp == HTTP.status400+        then kinesisError doc+        else throwM KinesisOtherError+            { kinesisOtherErrorStatus = HTTP.responseStatus resp+            , kinesisOtherErrorMessage = T.decodeUtf8 $ LB.toStrict doc+            }+  where+    kinesisError doc = case eitherDecode doc of+        Left e -> throwM . KinesisResponseJsonError $ T.pack e+        Right a -> do+            liftIO $ print doc+            throwM (a :: KinesisErrorResponse)++-- -------------------------------------------------------------------------- --+-- Kinesis Errors++-- | TODO integrate typed errors+--+data KinesisError a+    = KinesisErrorCommon KinesisCommonError+    | KinesisErrorCommand a+    deriving (Show, Read, Eq, Ord, Typeable)++-- | All Kinesis exceptions have HTTP status code 400 and include+-- a JSON body with an exception type property and a short message.+--+data KinesisErrorResponse+    = KinesisErrorResponse+        { kinesisErrorCode :: !T.Text+        , kinesisErrorMessage :: !T.Text+        }+    | KinesisResponseJsonError T.Text+    | KinesisOtherError+        { kinesisOtherErrorStatus :: !HTTP.Status+        , kinesisOtherErrorMessage :: !T.Text+        }+    deriving (Show, Eq, Ord, Typeable)++instance Exception KinesisErrorResponse++-- | This instance captures only the 'KinesisErrorResponse' constructor+--+instance FromJSON KinesisErrorResponse where+    parseJSON = withObject "KinesisErrorResponse" $ \o -> KinesisErrorResponse+        <$> o .: "__type"+        <*> o .: "message"++-- | Common Kinesis Errors+--+-- <http://docs.aws.amazon.com/sns/2010-03-31/APIReference/CommonErrors.html>+--+-- TODO add function to provide info about the error (content of haddock comments)+--+data KinesisCommonError++    -- | The request signature does not conform to AWS standards.+    --+    -- /Code 400/+    --+    = ErrorIncompleteSignature++    -- | The request processing has failed because of an unknown error,+    -- exception or failure.+    --+    -- /Code 500/+    --+    | ErrorInternalFailure++    -- | The action or operation requested is invalid. Verify that the action+    -- is typed correctly.+    --+    -- /Code 400/+    --+    | ErrorInvalidAction++    -- | The X.509 certificate or AWS access key ID provided does not exist in+    -- our records.+    --+    -- /Code 403/+    --+    | ErrorInvalidClientTokenId++    -- | Parameters that must not be used together were used together.+    --+    -- /Code 400/+    --+    | ErrorInvalidParameterCombination++    -- | An invalid or out-of-range value was supplied for the input parameter.+    --+    -- /Code 400/+    --+    | ErrorInvalidParameterValue++    -- | The AWS query string is malformed or does not adhere to AWS standards.+    --+    -- /Code 400/+    --+    | ErrorInvalidQueryParamter++    -- | The query string contains a syntax error.+    --+    -- /Code 404/+    --+    | ErrorMalformedQueryString++    -- | The request is missing an action or a required parameter.+    --+    -- /Code 400/+    --+    | ErrorMissingAction++    -- | The request must contain either a valid (registered) AWS access key ID+    -- or X.509 certificate.+    --+    -- /Code 403/+    --+    | ErrorMissingAuthenticationToken++    -- | A required parameter for the specified action is not supplied.+    --+    -- /Code 400/+    --+    | ErrorMissingParameter++    -- | The AWS access key ID needs a subscription for the service.+    --+    -- /Code 403/+    --+    | ErrorOptInRequired++    -- | The request reached the service more than 15 minutes after the date+    -- stamp on the request or more than 15 minutes after the request+    -- expiration date (such as for pre-signed URLs), or the date stamp on the+    -- request is more than 15 minutes in the future.+    --+    -- /Code 400/+    --+    | ErrorRequestExpired++    -- | The request has failed due to a temporary failure of the server.+    --+    -- /Code 503/+    --+    | ErrorServiceUnavailable++    -- | The request was denied due to request throttling.+    --+    -- /Code 400/+    --+    | ErrorThrottling++    -- | The input fails to satisfy the constraints specified by an AWS+    -- service.+    --+    -- /Code 400/+    --+    | ErrorValidationError+    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++-- -------------------------------------------------------------------------- --+-- Common Parameters++-- | Common Kinesis Parameters+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/CommonParameters.html>+--+-- The user of this API hardy needs to deal with the data type directly.+--+-- This API supports only signature version 4 with signature method @AWS4-HMAC-SHA256@.+--+-- /This is not currently used for computing the requests, but serves as+-- documentation and reference for the implementation of yet missing features./+--+data KinesisCommonParameters = KinesisCommonParameters+    { kinesisAction :: !KinesisAction+    -- ^ The action to be performed.++    , kinesisAuthParams :: !(Maybe ()) --+    -- ^ The parameters that are required to authenticate a Conditional request. Contains:+    --+    -- * AWSAccessKeyID+    --+    -- * SignatureVersion+    --+    -- * Timestamp+    --+    -- * Signature++    , kinesisAWSAccessKeyId :: !B8.ByteString+    -- ^ The access key ID that corresponds to the secret access key that you used to sign the request.++    , kinesisExpires :: !UTCTime+    -- ^ The date and time when the request signature expires.+    -- Precisely one of snsExpires or snsTimestamp must be present.+    --+    -- format: @YYYY-MM-DDThh:mm:ssZ@ (ISO 8601)++    , kinesisTimestamp :: !UTCTime+    -- ^ The date and time of the request.+    -- Precisely one of snsExpires or snsTimestamp must be present.+    --+    -- format: @YYYY-MM-DDThh:mm:ssZ@ (ISO 8601)++    , kinesisSecurityToken :: () -- !(Maybe SecurityToken)+    -- ^ TODO+    --+    -- The temporary security token that was obtained through a call to AWS+    -- Security Token Service. For a list of services that support AWS Security+    -- Token Service, go to Using Temporary Security Credentials to Access AWS+    -- in Using Temporary Security Credentials.++    , kinesisSignature :: !Signature+    -- ^ The digital signature that you created for the request. For+    -- information about generating a signature, go to the service's developer+    -- documentation.++    , kinesisSignatureMethod :: !SignatureMethod+    -- ^ The hash algorithm that you used to create the request signature.+    --+    -- Valid Values: @HmacSHA256@ | @HmacSHA1@++    , kinesisSignatureVersion :: !SignatureVersion+    -- ^ The signature version you use to sign the request. Set this to the value that is recommended for your service.++    , kinesisVersion :: KinesisVersion+    -- ^ The API version that the request is written for.+    }+
+ src/Aws/Kinesis/Types.hs view
@@ -0,0 +1,500 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}++-- |+-- Module: Aws.Kinesis.Types+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- /API Version: 2013-12-02/+--+-- The Amazon Kinesis Service API Reference API contains several data types+-- that various actions use.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_Types.html>+--+module Aws.Kinesis.Types+( StreamName+, streamName+, streamNameText++, ShardId+, SequenceNumber++, PartitionHash+, partitionHash+, partitionHashInteger++, PartitionKey+, partitionKey+, partitionKeyText++, ShardIterator+, ShardIteratorType(..)+, Record(..)+, StreamDescription(..)+, StreamStatus(..)+, Shard(..)+) where++import Aws.General++import Control.Applicative++import Data.Aeson+import qualified Data.ByteString as B+import qualified Data.ByteString.Base64 as B64+import Data.Monoid+import Data.String+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.Text.Read as T+import Data.Typeable++import Test.QuickCheck+import Test.QuickCheck.Instances ()++-- -------------------------------------------------------------------------- --+-- Internal Utils++sshow :: (Show a, IsString b) => a -> b+sshow = fromString . show++tryM :: Monad m => Either T.Text a -> m a+tryM = either (fail . T.unpack) return++-- -------------------------------------------------------------------------- --+-- | Stream Name+--+-- The stream name is scoped to the AWS account used by the application that+-- creates the stream. It is also scoped by region. That is, two streams in two+-- different AWS accounts can have the same name, and two streams in the same+-- AWS account, but in two different regions, can have the same name.+--+-- Length constraints: Minimum length of 1. Maximum length of 128.+--+-- Error responses from AWS Kinesis indicate that stream names must+-- match the following regular expressions:+--+-- > [a-zA-Z0-9_.-]++--+newtype StreamName = StreamName { streamNameText :: T.Text }+    deriving (Show, Read, Eq, Ord, Typeable)++-- | Smart Constructor for 'StreamName' that enforces size constraints.+--+-- For static construction you may also use the 'IsString' instance+-- that throws an 'error' on invalid stream names.+--+streamName :: T.Text -> Either T.Text StreamName+streamName t+    | T.length t < 1 = Left $ "Illegal StreamName " <> sshow t <> "; StreamName must be of length at least 1"+    | T.length t > 128 = Left $ "Illegal StreamName " <> sshow t <> "; StreamName must be of length at most 128"+    | otherwise = Right $ StreamName t++-- | The 'fromString' function of this instance raises and asynchronous 'error'+-- if the argument 'String' is not a legal 'StreamName'.+--+instance IsString StreamName where+    fromString = either (error . T.unpack) id . streamName . T.pack++instance ToJSON StreamName where+    toJSON = toJSON . streamNameText++instance FromJSON StreamName where+    parseJSON = withText "StreamName" $ tryM . streamName++instance Arbitrary StreamName where+    arbitrary = StreamName . T.pack <$> (resize 128 . listOf1 $ elements chars)+      where+        chars = ['a' .. 'z'] <> ['A' .. 'Z'] <> ['0' .. '9'] <> "_.-"++-- -------------------------------------------------------------------------- --+-- | Identifier for a shard as returned by PutRecord.+--+-- Length constraints: Minimum length of 1. Maximum length of 128.+--+newtype ShardId = ShardId { shardIdText :: T.Text }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON ShardId where+    toJSON = toJSON . shardIdText++instance FromJSON ShardId where+    parseJSON = withText "ShardId" $ return . ShardId++instance Arbitrary ShardId where+    arbitrary = ShardId <$> (resize 128 arbitrary `suchThat` (not . T.null))++-- -------------------------------------------------------------------------- --+-- | Opaque sequence number as returned by PutRecord.+--+newtype SequenceNumber = SequenceNumber { sequenceNumberText :: T.Text }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON SequenceNumber where+    toJSON = toJSON . sequenceNumberText++instance FromJSON SequenceNumber where+    parseJSON = withText "SequenceNumber" $ return . SequenceNumber++instance Arbitrary SequenceNumber where+    arbitrary = SequenceNumber . T.pack . show . getPositive+        <$> (arbitrary :: Gen (Positive Integer))++-- -------------------------------------------------------------------------- --+-- | An 128 bit interger that identifies the partition. The default+-- is the MD5 hash of the partition key.+--+-- FIXME the specification is rather vague about the precise encoding+-- of this value. The default is to compute it as an MD5 hash of+-- the partition key. The API reference describes it as an Int128.+-- However, it is not clear how the result of the hash function is+-- encoded (big-endian or small endian, word size?) and how it is+-- serialized to text, which is the type in the JSON serialization.+--+-- <http://javadox.com/com.amazonaws/aws-java-sdk/1.7.1/com/amazonaws/services/kinesis/model/PutRecordRequest.html>+-- seems to indicate that the value is actually unsigned.+--+newtype PartitionHash = PartitionHash { partitionHashInteger :: Integer {- FIXME -} }+    deriving (Show, Read, Eq, Ord, Typeable)++-- | Smart Constructor for 'PartitionHash' that enforces size constraints.+--+partitionHash :: Integer -> Either T.Text PartitionHash+partitionHash i+    | i >= 2^(128 :: Int) = Left $ "partition hash " <> sshow i <> "is out of range; a partition hash must be a 128 bit unsigned integer"+    | i < 0 = Left $ "partition hash " <> sshow i <> "is out of range; a partition hash must be a 128 bit unsigned integer"+    | otherwise = Right $ PartitionHash i++instance ToJSON PartitionHash where+    toJSON = toJSON . show . partitionHashInteger++instance FromJSON PartitionHash where+    parseJSON = withText "PartitionHash" $ \t -> case T.decimal t of+        Right (a, "") -> tryM $ partitionHash a+        Right (_, x) -> fail $ "trailing text: " <> T.unpack x+        Left e -> fail e++instance Arbitrary PartitionHash where+    arbitrary = PartitionHash . getPositive <$> resize (2^(128 :: Int) - 1) arbitrary++-- -------------------------------------------------------------------------- --+-- | PartitionKey+--+-- Identifies which shard in the stream the data record is assigned to.+--+-- Partition keys are Unicode strings with a maximum length limit of 256+-- bytes. Amazon Kinesis uses the partition key as input to a hash function+-- that maps the partition key and associated data to a specific shard.+-- Specifically, an MD5 hash function is used to map partition keys to+-- 128-bit integer values and to map associated data records to shards. As+-- a result of this hashing mechanism, all data records with the same+-- partition key will map to the same shard within the stream.+--+-- Length constraints: Minimum length of 1. Maximum length of 256.+--+newtype PartitionKey = PartitionKey { partitionKeyText :: T.Text }+    deriving (Show, Read, Eq, Ord, IsString, Typeable)++partitionKey :: T.Text -> Either T.Text PartitionKey+partitionKey t+    | T.length t < 1 = Left $ "Illegal PartitionKey " <> sshow t <> "; PartitionKey must be of length at least 1"+    | T.length t > 256 = Left $ "Illegal PartitionKey " <> sshow t <> "; PartitionKey must be of length at most 256"+    | otherwise = Right $ PartitionKey t++instance ToJSON PartitionKey where+    toJSON = toJSON . partitionKeyText++instance FromJSON PartitionKey where+    parseJSON = withText "PartitionKey" $ tryM . partitionKey++instance Arbitrary PartitionKey where+    arbitrary = PartitionKey+        <$> (resize 256 arbitrary `suchThat` (not . T.null))++-- -------------------------------------------------------------------------- --+-- | Iterator for records within a shard.+--+-- Length constraints: Minimum length of 1. Maximum length of 512.+--+newtype ShardIterator = ShardIterator { shardIteratorText :: T.Text }+    deriving (Show, Read, Eq, Ord, Typeable)++shardIterator :: T.Text -> Either T.Text ShardIterator+shardIterator t+    | T.length t < 1 = Left $ "Illegal ShardIterator " <> sshow t <> "; ShardIterator must be of length at least 1"+    | T.length t > 512 = Left $ "Illegal ShardIterator " <> sshow t <> "; ShardIterator must be of length at most 512"+    | otherwise = Right $ ShardIterator t++instance ToJSON ShardIterator where+    toJSON = toJSON . shardIteratorText++instance FromJSON ShardIterator where+    parseJSON = withText "ShardIterator" $ tryM . shardIterator++instance Arbitrary ShardIterator where+    arbitrary = ShardIterator+        <$> (resize 512 arbitrary `suchThat` (not . T.null))++-- -------------------------------------------------------------------------- --+-- | ShardIteratorType+--+-- Determines how the shard iterator is used to start reading data records from+-- the shard.+--+data ShardIteratorType+    = AtSequenceNumber+    -- ^ Start reading exactly from the position denoted by a specific sequence+    -- number.++    | AfterSequenceNumber+    -- ^ Start reading right after the position denoted by a specific sequence+    -- number.++    | TrimHorizon+    -- ^ Start reading at the last untrimmed record in the shard in the system,+    -- which is the oldest data record in the shard.++    | Latest+    -- ^ Start reading just after the most recent record in the shard, so that+    -- you always read the most recent data in the shard.++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++instance ToJSON ShardIteratorType where+    toJSON AtSequenceNumber = "AT_SEQUENCE_NUMBER"+    toJSON AfterSequenceNumber = "AFTER_SEQUENCE_NUMBER"+    toJSON TrimHorizon = "TRIM_HORIZON"+    toJSON Latest = "LATEST"++instance FromJSON ShardIteratorType where+    parseJSON = withText "SharedIteratorType" $ \t -> case t of+        "AT_SEQUENCE_NUMBER" -> return AtSequenceNumber+        "AFTER_SEQUENCE_NUMBER" -> return AfterSequenceNumber+        "TRIM_HORIZON" -> return TrimHorizon+        "LATEST" -> return Latest+        e -> fail $ "unexpected value for SharedIteratorType: " <> T.unpack e++instance Arbitrary ShardIteratorType where+    arbitrary = elements [minBound .. maxBound]++-- -------------------------------------------------------------------------- --+-- | Record+--+-- The unit of data of the Amazon Kinesis stream, which is composed of a+-- sequence number, a partition key, and a data blob.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_Record.html>+--+data Record = Record+    { recordData :: !B.ByteString+    -- ^ The data blob. The data in the blob is both opaque and immutable to+    -- the Amazon Kinesis service, which does not inspect, interpret, or change+    -- the data in the blob in any way. The maximum size of the data blob (the+    -- payload after Base64-decoding) is 50 kilobytes (KB)+    --+    -- Length constraints: Minimum length of 0. Maximum length of 51200.+    --+    -- Note that the size constraint is not currently enforced in the code.++    , recordPartitionKey :: !PartitionKey+    -- ^ Identifies which shard in the stream the data record is assigned to.++    , recordSequenceNumber :: !SequenceNumber+    -- ^ The unique identifier for the record in the Amazon Kinesis stream.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON Record where+    toJSON Record{..} = object+        [ "Data" .= T.decodeUtf8 (B64.encode recordData)+        , "PartitionKey" .= recordPartitionKey+        , "SequenceNumber" .= recordSequenceNumber+        ]++instance FromJSON Record where+    parseJSON = withObject "Record" $ \o -> Record+        <$> (from64 =<< o .: "Data")+        <*> o .: "PartitionKey"+        <*> o .: "SequenceNumber"+      where+        from64 x = case B64.decode (T.encodeUtf8 x) of+            Left e -> fail $ "failed to decode base64 encoded data: " <> e+            Right a -> return a++instance Arbitrary Record where+    arbitrary = Record+        <$> (arbitrary `suchThat` ((<= 51200) . B.length))+        <*> arbitrary+        <*> arbitrary++-- -------------------------------------------------------------------------- --+-- Stream Description+--+-- Represents the output of a DescribeStream operation.+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_StreamDescription.html>+--+data StreamDescription = StreamDescription+    { streamDescriptionHasMoreShards :: !Bool+    -- ^ If set to true there are more shards in the stream available to+    -- describe.++    , streamDescriptionShards :: ![Shard]+    -- ^ The shards that comprise the stream.++    , streamDescriptionStreamARN :: !Arn+    -- ^The Amazon Resource Name (ARN) for the stream being described.++    , streamDescriptionStreamName :: !StreamName+    -- ^ The name of the stream being described.++    , streamDescriptionStreamStatus :: !StreamStatus+    -- ^ The current status of the stream being described.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON StreamDescription where+    toJSON StreamDescription{..} = object+        [ "HasMoreShards" .= streamDescriptionHasMoreShards+        , "Shards" .= streamDescriptionShards+        , "StreamARN" .= streamDescriptionStreamARN+        , "StreamName" .= streamDescriptionStreamName+        , "StreamStatus" .= streamDescriptionStreamStatus+        ]++instance FromJSON StreamDescription where+    parseJSON = withObject "StreamDescription" $ \o -> StreamDescription+        <$> o .: "HasMoreShards"+        <*> o .: "Shards"+        <*> o .: "StreamARN"+        <*> o .: "StreamName"+        <*> o .: "StreamStatus"++instance Arbitrary StreamDescription where+    arbitrary = StreamDescription+        <$> arbitrary+        <*> arbitrary+        <*> arbitrary+        <*> arbitrary+        <*> arbitrary++-- | Stream Status+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_StreamDescription.html>+--+data StreamStatus+    = StreamStatusCreating+    -- ^ The stream is being created. Upon receiving a CreateStream request,+    -- Amazon Kinesis immediately returns and sets StreamStatus to CREATING.++    | StreamStatusDeleting+    -- ^ The stream is being deleted. After a DeleteStream request, the+    -- specified stream is in the DELETING state until Amazon Kinesis completes+    -- the deletion.++    | StreamStatusActive+    -- ^ The stream exists and is ready for read and write operations or+    -- deletion. You should perform read and write operations only on an ACTIVE+    -- stream.++    | StreamStatusUpdating+    -- ^ Shards in the stream are being merged or split. Read and write+    -- operations continue to work while the stream is in the UPDATING state.++    deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)++instance ToJSON StreamStatus where+    toJSON StreamStatusCreating = "CREATING"+    toJSON StreamStatusDeleting = "DELETING"+    toJSON StreamStatusActive = "ACTIVE"+    toJSON StreamStatusUpdating = "UPDATING"++instance FromJSON StreamStatus where+    parseJSON = withText "StreamStatus" $ \t -> case t of+        "CREATING" -> return StreamStatusCreating+        "DELETING" -> return StreamStatusDeleting+        "ACTIVE" -> return StreamStatusActive+        "UPDATING" -> return StreamStatusUpdating+        e -> fail $ "unexpected value for StreamStatus: " <> T.unpack e++instance Arbitrary StreamStatus where+    arbitrary = elements [minBound .. maxBound]++-- | Shard+--+-- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_Shard.html>+--+data Shard = Shard+    { shardAdjacentParentShardId :: !(Maybe ShardId)+    -- ^ The shard Id of the shard adjacent to the shard's parent.++    , shardHashKeyRange :: !(PartitionHash, PartitionHash)+    -- ^ The (inclusive) range of possible hash key values for the shard, which is a set of+    -- ordered contiguous positive integers.+    --+    -- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_HashKeyRange.html>++    , shardParentShardId :: !(Maybe ShardId)+    -- ^ The shard Id of the shard's parent.++    , shardSequenceNumberRange :: !(SequenceNumber, Maybe SequenceNumber)+    -- ^ The (inclusive) range of possible sequence numbers for the shard.+    --+    -- Shards that are in the OPEN state have an ending sequence number of 'Nothing'.+    --+    -- <http://docs.aws.amazon.com/kinesis/2013-12-02/APIReference/API_SequenceNumberRange.html>++    , shardShardId :: ShardId+    -- ^ The unique identifier of the shard within the Amazon Kinesis stream.+    }+    deriving (Show, Read, Eq, Ord, Typeable)++instance ToJSON Shard where+    toJSON Shard{..} = object+        [ "AdjacentParentShardId" .= shardAdjacentParentShardId+        , "HashKeyRange" .= object+            [ "StartingHashKey" .= fst shardHashKeyRange+            , "EndingHashKey" .= snd shardHashKeyRange+            ]+        , "ParentShardId" .= shardParentShardId+        , "SequenceNumberRange" .= object+            [ "StartingSequenceNumber" .= fst shardSequenceNumberRange+            , "EndingSequenceNumber" .= snd shardSequenceNumberRange+            ]+        , "ShardId" .= shardShardId+        ]++instance FromJSON Shard where+    parseJSON = withObject "Shard" $ \o -> Shard+        <$> o .:? "AdjacentParentShardId" .!= Nothing+        <*> (hashKeyRange =<< o .: "HashKeyRange")+        <*> o .:? "ParentShardId" .!= Nothing+        <*> (sequenceNumberRange =<< o .: "SequenceNumberRange")+        <*> o .: "ShardId"+      where+        hashKeyRange = withObject "HashKeyRange" $ \o -> (,)+            <$> o .: "StartingHashKey"+            <*> o .: "EndingHashKey"+        sequenceNumberRange = withObject "SequenceNumberRange" $ \o -> (,)+            <$> o .: "StartingSequenceNumber"+            <*> o .:? "EndingSequenceNumber" .!= Nothing++instance Arbitrary Shard where+    arbitrary = Shard+        <$> arbitrary+        <*> do+            u <- arbitrary+            l <- resize (fromIntegral $ partitionHashInteger u) arbitrary+            return (l,u)+        <*> arbitrary+        <*> do+            u <- arbitrary+            l <- resize (maybe maxBound (read . T.unpack . sequenceNumberText) u) arbitrary+            return (l,u)+        <*> arbitrary
+ tests/Main.hs view
@@ -0,0 +1,286 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GADTs #-}++-- |+-- Module: Main+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- Tests for Haskell Kinesis bindings+--+module Main+( main+) where++import Aws+import Aws.Kinesis++import Control.Error+import Control.Exception+import Control.Monad+import Control.Monad.IO.Class++import qualified Data.ByteString as B+import qualified Data.List as L+import Data.Monoid+import Data.Proxy+import qualified Data.Text as T++import Test.Tasty++import System.Exit+import System.Environment++import Utils++defaultStreamName :: StreamName+defaultStreamName = "test-stream"++-- -------------------------------------------------------------------------- --+-- Main++-- | Since these tests generate costs there should be a warning and+-- we also should require an explicit command line argument that expresses+-- the concent of the user.+--+main :: IO ()+main = getArgs >>= runMain+  where+    runMain args+        | "--help" `elem` args || "-h" `elem` args = defaultMain tests+        | "--run-with-aws-credentials" `elem` args =+            withArgs (filter (/= "--run-with-aws-credentials") args) $ defaultMain tests+        | otherwise = putStrLn help >> exitFailure++help :: String+help = L.intercalate "\n"+    [ ""+    , "NOTE"+    , ""+    , "This test suite accesses the AWS account that is associated with"+    , "the default credentials from the credential file ~/.aws-keys."+    , ""+    , "By running the tests in this test-suite costs for usage of AWS"+    , "services may incur."+    , ""+    , "In order to actually excute the tests in this test-suite you must"+    , "provide the command line option:"+    , ""+    , "    --run-with-aws-credentials"+    , ""+    , "When running this test-suite through cabal you may use the following"+    , "command:"+    , ""+    , "    cabal test kinesis-tests --test-option=--run-with-aws-credentials"+    , ""+    ]++tests :: TestTree+tests = testGroup "Kinesis Tests"+    [ test_jsonRoundtrips+    , test_createStream+    , test_stream1+    ]++-- -------------------------------------------------------------------------- --+-- Kinesis Utils++kinesisConfiguration :: KinesisConfiguration qt+kinesisConfiguration = KinesisConfiguration testRegion++simpleKinesis+    :: (AsMemoryResponse a, Transaction r a, ServiceConfiguration r ~ KinesisConfiguration, MonadIO m)+    => r+    -> m (MemoryResponse a)+simpleKinesis command = do+    c <- baseConfiguration+    simpleAws c kinesisConfiguration command++simpleKinesisT+    :: (AsMemoryResponse a, Transaction r a, ServiceConfiguration r ~ KinesisConfiguration, MonadIO m)+    => r+    -> EitherT T.Text m (MemoryResponse a)+simpleKinesisT = tryT . simpleKinesis++testStreamName :: StreamName -> StreamName+testStreamName = either (error . T.unpack) id+        . streamName . T.take 128 . testData . streamNameText++-- |+--+withStream+    :: StreamName -- ^ Stream Name+    -> Int -- ^ Shard count+    -> IO a+    -> IO a+withStream stream shardCount = bracket_ createStream deleteStream+  where+    createStream = simpleKinesis $ CreateStream shardCount stream+    deleteStream = void $ simpleKinesis (DeleteStream stream)++-- | The function 'withResource' from "Tasty" synchronizes the aquired+-- resource through a 'TVar'. We don't need that for a stream. So instead+-- of passing the 'IO StreamName' from 'withResource' we directly pass+-- 'StreamName' to the inner function.+--+withStreamTest+    :: StreamName -- ^ stream name suffix+    -> Int -- ^ shard count+    -> (StreamName -> TestTree)+    -> TestTree+withStreamTest stream shardCount f = withResource createStream deleteStream+    $ const (f tstream)+  where+    createStream = do+        void . simpleKinesis $ CreateStream shardCount tstream+        return tstream+    deleteStream = const . void . simpleKinesis $ DeleteStream tstream+    tstream = testStreamName stream+++-- | Wait for a stream to become active+--+waitActiveT+    :: Int+    -- ^ upper bound on the number of seconds to wait.+    -- The actual maximal number of seconds is closest smaller+    -- power of two.+    -> StreamName+    -> EitherT T.Text IO StreamDescription+waitActiveT sec stream = retryT maxRetry $ do+    DescribeStreamResponse d <- simpleKinesisT+        $ DescribeStream Nothing Nothing stream+    unless (streamDescriptionStreamStatus d == StreamStatusActive)+        $ left "Stream is not active"+    return d+  where+    maxRetry = floor $ logBase 2 (fromIntegral sec :: Double)++-- -------------------------------------------------------------------------- --+-- Types++test_jsonRoundtrips :: TestTree+test_jsonRoundtrips = testGroup "JSON encoding roundtrips"+    [ test_jsonRoundtrip (Proxy :: Proxy StreamName)+    , test_jsonRoundtrip (Proxy :: Proxy ShardId)+    , test_jsonRoundtrip (Proxy :: Proxy SequenceNumber)+    , test_jsonRoundtrip (Proxy :: Proxy PartitionHash)+    , test_jsonRoundtrip (Proxy :: Proxy PartitionKey)+    , test_jsonRoundtrip (Proxy :: Proxy ShardIterator)+    , test_jsonRoundtrip (Proxy :: Proxy ShardIteratorType)+    , test_jsonRoundtrip (Proxy :: Proxy Record)+    , test_jsonRoundtrip (Proxy :: Proxy StreamDescription)+    , test_jsonRoundtrip (Proxy :: Proxy StreamStatus)+    , test_jsonRoundtrip (Proxy :: Proxy Shard)+    ]++-- -------------------------------------------------------------------------- --+-- Stream Tests++test_stream1 :: TestTree+test_stream1 = withStreamTest defaultStreamName 1 $ \stream ->+    testGroup "Perform a series of tests on a single stream"+        [ eitherTOnceTest0 "list streams" (prop_streamList stream)+        , eitherTOnceTest0 "describe stream" (prop_streamDescribe 1 stream)+        , eitherTOnceTest2 "put and get stream" (prop_streamPutGet stream)+        ]++prop_streamList :: StreamName -> EitherT T.Text IO ()+prop_streamList stream = do+    ListStreamsResponse _ streams <- simpleKinesisT $ ListStreams Nothing Nothing+    unless (stream `elem` streams) $+        left $ "stream " <> streamNameText stream <> " is not listed"++prop_streamDescribe+    :: Int -- ^  expected number of shards+    -> StreamName+    -> EitherT T.Text IO ()+prop_streamDescribe shardNum stream = do+    desc <- waitActiveT 64 stream++    unless (streamDescriptionStreamName desc == stream)+        . left $ "unexpected stream name in description: "+        <> streamNameText (streamDescriptionStreamName desc)++    let l = length $ streamDescriptionShards desc+    unless (l == shardNum)+        . left $ "unexpected number of shards in stream description: " <> sshow l++prop_streamPutGet+    :: StreamName+    -> B.ByteString -- ^ Message data+    -> PartitionKey+    -> EitherT T.Text IO ()+prop_streamPutGet stream dat key = do+    desc <- waitActiveT 64 stream++    let shards = streamDescriptionShards desc++    PutRecordResponse putSeqNr putShard <- simpleKinesisT PutRecord+        { putRecordData = dat+        , putRecordExplicitHashKey = Nothing+        , putRecordPartitionKey = key+        , putRecordSequenceNumberForOrdering = Nothing+        , putRecordStreamName = stream+        }++    let shardIds = map shardShardId shards+    unless (putShard `elem` shardIds) . left+        $ "unexpected shard id: expected on of " <> sshow shardIds <> "; got " <> sshow putShard++    record <- retryT 5 $ do+        GetShardIteratorResponse it <- simpleKinesisT GetShardIterator+            { getShardIteratorShardId = putShard+            , getShardIteratorShardIteratorType = TrimHorizon+            , getShardIteratorStartingSequenceNumber = Nothing+            , getShardIteratorStreamName = stream+            }+        GetRecordsResponse _nextIt records <- simpleKinesisT GetRecords+            { getRecordsLimit = Nothing+            , getRecordsShardIterator = it+            }+        case records of+            [] -> left "no record found in stream"+            [r] -> return r+            t -> left $ "unexpected records found in stream: " <> sshow t++    let getData = recordData record+    unless (getData == dat) . left+        $ "data does not match: expected " <> sshow dat <> "; got " <> sshow getData++    let getSeqNr = recordSequenceNumber record+    unless (getSeqNr == putSeqNr) . left+        $ "sequence numbers don't match: expected " <> sshow putSeqNr+        <> "; got " <> sshow getSeqNr++    let getPartKey = recordPartitionKey record+    unless (getPartKey == key) . left+        $ "partition keys don't match: expected " <> sshow key+        <> "; got " <> sshow getPartKey++-- -------------------------------------------------------------------------- --+-- Stream Creation Tests++test_createStream :: TestTree+test_createStream = testGroup "Stream creation"+    [ eitherTOnceTest1 "create list delete" prop_createListDelete+    ]++prop_createListDelete+    :: StreamName -- ^ stream name+    -> EitherT T.Text IO ()+prop_createListDelete stream = do+    CreateStreamResponse <- simpleKinesisT $ CreateStream 1 tstream+    handleT (\e -> deleteStream >> left e) $ do+        ListStreamsResponse _ allStreams <- simpleKinesisT+            $ ListStreams Nothing Nothing+        unless (tstream `elem` allStreams)+            . left $ "stream " <> streamNameText tstream <> " not listed"+        deleteStream+  where+    deleteStream = void $ simpleKinesisT (DeleteStream tstream)+    tstream = testStreamName stream+
+ tests/Utils.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE CPP #-}++-- |+-- Module: Utils+-- Copyright: Copyright © 2014 AlephCloud Systems, Inc.+-- License: MIT+-- Maintainer: Lars Kuhtz <lars@alephcloud.com>+-- Stability: experimental+--+-- Utils for Tests for Haskell AWS bindints+--+module Utils+(+-- * Parameters+  testRegion+, testDataPrefix++-- * General Utils+, sshow+, tryT+, retryT+, testData++, evalTestT+, evalTestTM+, eitherTOnceTest0+, eitherTOnceTest1+, eitherTOnceTest2++-- * Generic Tests+, test_jsonRoundtrip+, prop_jsonRoundtrip+) where++import Aws.General++import Control.Concurrent (threadDelay)+import Control.Error+import Control.Monad+import Control.Monad.Identity+import Control.Monad.IO.Class++import Data.Aeson (FromJSON, ToJSON, encode, eitherDecode)+import Data.Monoid+import Data.Proxy+import Data.String+import qualified Data.Text as T+import Data.Typeable++import Test.QuickCheck.Property+import Test.QuickCheck.Monadic+import Test.Tasty+import Test.Tasty.QuickCheck++import System.IO++-- -------------------------------------------------------------------------- --+-- Static Test parameters+--+-- TODO make these configurable++testRegion :: Region+testRegion = UsWest2++-- | This prefix is used for the IDs and names of all entities that are+-- created in the AWS account.+--+testDataPrefix :: IsString a => a+testDataPrefix = "__TEST_AWSHASKELLBINDINGS__"++-- -------------------------------------------------------------------------- --+-- General Utils++tryT :: MonadIO m => IO a -> EitherT T.Text m a+tryT = fmapLT (T.pack . show) . syncIO++testData :: (IsString a, Monoid a) => a -> a+testData a = testDataPrefix <> a++retryT :: MonadIO m => Int -> EitherT T.Text m a -> EitherT T.Text m a+retryT i f = go 1+  where+    go x+        | x >= i = fmapLT (\e -> "error after " <> sshow x <> " retries: " <> e) f+        | otherwise = f `catchT` \_ -> do+            liftIO $ threadDelay (1000000 * min 60 (2^(x-1)))+            go (succ x)++sshow :: (Show a, IsString b) => a -> b+sshow = fromString . show++evalTestTM+    :: Functor f+    => String -- ^ test name+    -> f (EitherT T.Text IO a) -- ^ test+    -> f (IO Bool)+evalTestTM name = fmap $+    runEitherT >=> \r -> case r of+        Left e -> do+            hPutStrLn stderr $ "failed to run stream test \"" <> name <> "\": " <> show e+            return False+        Right _ -> return True++evalTestT+    :: String -- ^ test name+    -> EitherT T.Text IO a -- ^ test+    -> IO Bool+evalTestT name = runIdentity . evalTestTM name . Identity++eitherTOnceTest0+    :: String -- ^ test name+    -> EitherT T.Text IO a -- ^ test+    -> TestTree+eitherTOnceTest0 name = testProperty name . once . ioProperty . evalTestT name++eitherTOnceTest1+    :: (Arbitrary a, Show a)+    => String -- ^ test name+    -> (a -> EitherT T.Text IO b)+    -> TestTree+eitherTOnceTest1 name test = testProperty name . once $ monadicIO . liftIO+    . evalTestTM name test++eitherTOnceTest2+    :: (Arbitrary a, Show a, Arbitrary b, Show b)+    => String -- ^ test name+    -> (a -> b -> EitherT T.Text IO c)+    -> TestTree+eitherTOnceTest2 name test = testProperty name . once $ \a b -> monadicIO . liftIO+    $ (evalTestTM name $ uncurry test) (a, b)++-- -------------------------------------------------------------------------- --+-- Generic Tests++test_jsonRoundtrip+    :: forall a . (Eq a, Show a, FromJSON a, ToJSON a, Typeable a, Arbitrary a)+    => Proxy a+    -> TestTree+test_jsonRoundtrip proxy = testProperty msg (prop_jsonRoundtrip :: a -> Property)+  where+    msg = "JSON roundtrip for " <> show typ+#if MIN_VERSION_base(4,7,0)+    typ = typeRep proxy+#else+    typ = typeOf (undefined :: a)+#endif++prop_jsonRoundtrip :: forall a . (Eq a, Show a, FromJSON a, ToJSON a) => a -> Property+prop_jsonRoundtrip a = either (const $ property False) (\(b :: [a]) -> [a] === b) $+    eitherDecode $ encode [a]+