module Aws.General
( AwsType(..)
, GeneralVersion(..)
, generalVersionToText
, parseGeneralVersion
, SignatureVersion(..)
, signatureVersionToText
, parseSignatureVersion
, SignatureMethod(..)
, signatureMethodToText
, parseSignatureMethod
, Region(..)
, regionToText
, parseRegion
, AccountId(..)
, accountIdToText
, parseAccountId
, CanonicalUserId(..)
, canonicalUserIdToText
, parseCanonicalUserId
, ServiceNamespace(..)
, serviceNamespaceToText
, parseServiceNamespace
, Arn(..)
, arnToText
, parseArn
) where
import Control.Applicative
import Control.Monad
import Data.Aeson (ToJSON(..), FromJSON(..), withText)
import qualified Data.Attoparsec.Text as AP
import Data.Hashable (Hashable, hashWithSalt, hashUsing)
import qualified Data.List as L
import Data.Monoid
import Data.String
import qualified Data.Text as T
import Data.Typeable
import qualified Test.QuickCheck as Q
import Test.QuickCheck.Instances ()
import qualified Text.Parser.Char as P
import qualified Text.Parser.Combinators as P
import Text.Parser.Combinators ((<?>))
import Text.Printf
class AwsType a where
toText :: (IsString b, Monoid b) => a -> b
parse :: (Monad m, P.CharParsing m) => m a
fromText :: T.Text -> Either String a
fromText = AP.parseOnly $ parse <* P.eof
data GeneralVersion
= GeneralVersion_1_0
deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)
generalVersionToText :: (IsString a) => GeneralVersion -> a
generalVersionToText GeneralVersion_1_0 = "1.0"
parseGeneralVersion :: P.CharParsing m => m GeneralVersion
parseGeneralVersion = GeneralVersion_1_0 <$ P.text "1.0"
<?> "General Version"
instance AwsType GeneralVersion where
toText = generalVersionToText
parse = parseGeneralVersion
instance Q.Arbitrary GeneralVersion where
arbitrary = Q.elements [minBound..maxBound]
data SignatureVersion
= SignatureVersion2
| SignatureVersion4
deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)
signatureVersionToText :: IsString a => SignatureVersion -> a
signatureVersionToText SignatureVersion2 = "2"
signatureVersionToText SignatureVersion4 = "4"
parseSignatureVersion :: P.CharParsing m => m SignatureVersion
parseSignatureVersion =
SignatureVersion2 <$ P.text "2"
<|> SignatureVersion4 <$ P.text "4"
<?> "SignatureVersion"
instance AwsType SignatureVersion where
toText = signatureVersionToText
parse = parseSignatureVersion
instance Q.Arbitrary SignatureVersion where
arbitrary = Q.elements [minBound..maxBound]
data SignatureMethod
= SignatureMethodSha1
| SignatureMethodSha256
deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)
signatureMethodToText :: IsString a => SignatureMethod -> a
signatureMethodToText SignatureMethodSha1 = "HmacSHA1"
signatureMethodToText SignatureMethodSha256 = "HmacSHA256"
parseSignatureMethod :: P.CharParsing m => m SignatureMethod
parseSignatureMethod =
SignatureMethodSha1 <$ P.text "HmacSHA1"
<|> SignatureMethodSha256 <$ P.text "HmacSHA256"
<?> "SignatureMethod"
instance AwsType SignatureMethod where
toText = signatureMethodToText
parse = parseSignatureMethod
instance Q.Arbitrary SignatureMethod where
arbitrary = Q.elements [minBound..maxBound]
data Region
= ApNortheast1
| ApSoutheast1
| ApSoutheast2
| EuWest1
| SaEast1
| UsEast1
| UsWest1
| UsWest2
| CustomEndpoint !T.Text !Int
deriving (Show, Read, Eq, Ord, Typeable)
regionToText :: (Monoid a, IsString a) => Region -> a
regionToText ApNortheast1 = "ap-northeast-1"
regionToText ApSoutheast1 = "ap-southeast-1"
regionToText ApSoutheast2 = "ap-southeast-2"
regionToText EuWest1 = "eu-west-1"
regionToText SaEast1 = "sa-east-1"
regionToText UsEast1 = "us-east-1"
regionToText UsWest1 = "us-west-1"
regionToText UsWest2 = "us-west-2"
regionToText (CustomEndpoint e p) = "custom:" <> fromString (T.unpack e) <> ":" <> fromString (show p)
parseRegion :: P.CharParsing m => m Region
parseRegion =
ApNortheast1 <$ P.text "ap-northeast-1"
<|> ApSoutheast1 <$ P.text "ap-southeast-1"
<|> ApSoutheast2 <$ P.text "ap-southeast-2"
<|> EuWest1 <$ P.text "eu-west-1"
<|> SaEast1 <$ P.text "sa-east-1"
<|> UsEast1 <$ P.text "us-east-1"
<|> UsWest1 <$ P.text "us-west-1"
<|> UsWest2 <$ P.text "us-west-2"
<|> parseCustomEndpoint
<?> "Region"
where
parseCustomEndpoint = CustomEndpoint
<$> (fmap T.pack $ P.text "custom:" *> many (P.notChar ':'))
<*> (fmap read $ P.text ":" *> some P.digit)
instance AwsType Region where
toText = regionToText
parse = parseRegion
standardRegions :: [Region]
standardRegions =
[ ApNortheast1
, ApSoutheast1
, ApSoutheast2
, EuWest1
, SaEast1
, UsEast1
, UsWest1
, UsWest2
]
instance Hashable Region where
hashWithSalt s (CustomEndpoint e p) = s `hashWithSalt` (0 :: Int) `hashWithSalt` (e, p)
hashWithSalt s r =
case L.elemIndex r standardRegions of
Just i -> hashWithSalt s (succ i)
Nothing -> hashWithSalt s (length standardRegions + 1)
instance Q.Arbitrary Region where
arbitrary = Q.oneof
[ Q.elements standardRegions
, CustomEndpoint <$> arbitraryEndpoint <*> arbitraryPort
]
where
arbitraryEndpoint = fmap T.pack . Q.listOf . Q.elements $ '.' : ['a'..'z']
arbitraryPort = Q.choose (0, 10000)
newtype AccountId = AccountId T.Text
deriving (Show, Read, Eq, Ord, IsString, Typeable)
accountIdToText :: (IsString a) => AccountId -> a
accountIdToText (AccountId t) = fromString $ T.unpack t
parseAccountId :: P.CharParsing m => m AccountId
parseAccountId = AccountId . T.pack
<$> P.count 12 P.digit
<?> "Account ID"
instance AwsType AccountId where
toText = accountIdToText
parse = parseAccountId
instance Q.Arbitrary AccountId where
arbitrary = AccountId . T.pack . printf "%012d" <$> Q.choose (0::Integer, 999999999999)
newtype CanonicalUserId = CanonicalUserId T.Text
deriving (Show, Read, Eq, Ord, IsString, Typeable)
canonicalUserIdToText :: (IsString a) => CanonicalUserId -> a
canonicalUserIdToText (CanonicalUserId t) = fromString $ T.unpack t
parseCanonicalUserId :: P.CharParsing m => m CanonicalUserId
parseCanonicalUserId = CanonicalUserId . T.pack
<$> some P.hexDigit
<?> "Canonical User ID"
instance AwsType CanonicalUserId where
toText = canonicalUserIdToText
parse = parseCanonicalUserId
instance Q.Arbitrary CanonicalUserId where
arbitrary = CanonicalUserId . T.pack <$> do
i <- Q.choose (32,128)
replicateM i (Q.elements $ ['0'..'9'] <> ['a'..'f'])
data ServiceNamespace
= ServiceNamespaceAwsPortal
| ServiceNamespaceAutoscaling
| ServiceNamespaceCloudformation
| ServiceNamespaceCloudfront
| ServiceNamespaceCloudwatch
| ServiceNamespaceDynamodb
| ServiceNamespaceEc2
| ServiceNamespaceElasticbeanstalk
| ServiceNamespaceElasticloadbalancing
| ServiceNamespaceElasticmapreduce
| ServiceNamespaceElasticache
| ServiceNamespaceGlacier
| ServiceNamespaceIam
| ServiceNamespaceKinesis
| ServiceNamespaceAwsMarketplaceManagement
| ServiceNamespaceOpsworks
| ServiceNamespaceRds
| ServiceNamespaceRedshift
| ServiceNamespaceRoute53
| ServiceNamespaceS3
| ServiceNamespaceSes
| ServiceNamespaceSdb
| ServiceNamespaceSqs
| ServiceNamespaceSns
| ServiceNamespaceStoragegateway
| ServiceNamespaceSts
| ServiceNamespaceSupport
| ServiceNamespaceSwf
| ServiceNamespaceHost
deriving (Show, Read, Eq, Ord, Enum, Bounded, Typeable)
serviceNamespaceToText :: IsString a => ServiceNamespace -> a
serviceNamespaceToText ServiceNamespaceAwsPortal = "aws-portal"
serviceNamespaceToText ServiceNamespaceAutoscaling = "autoscaling"
serviceNamespaceToText ServiceNamespaceCloudformation = "cloudformation"
serviceNamespaceToText ServiceNamespaceCloudfront = "cloudfront"
serviceNamespaceToText ServiceNamespaceCloudwatch = "cloudwatch"
serviceNamespaceToText ServiceNamespaceDynamodb = "dynamodb"
serviceNamespaceToText ServiceNamespaceEc2 = "ec2"
serviceNamespaceToText ServiceNamespaceElasticbeanstalk = "elasticbeanstalk"
serviceNamespaceToText ServiceNamespaceElasticloadbalancing = "elasticloadbalancing"
serviceNamespaceToText ServiceNamespaceElasticmapreduce = "elasticmapreduce"
serviceNamespaceToText ServiceNamespaceElasticache = "elasticache"
serviceNamespaceToText ServiceNamespaceGlacier = "glacier"
serviceNamespaceToText ServiceNamespaceIam = "iam"
serviceNamespaceToText ServiceNamespaceKinesis = "kinesis"
serviceNamespaceToText ServiceNamespaceAwsMarketplaceManagement = "aws-marketplace-management"
serviceNamespaceToText ServiceNamespaceOpsworks = "opsworks"
serviceNamespaceToText ServiceNamespaceRds = "rds"
serviceNamespaceToText ServiceNamespaceRedshift = "redshift"
serviceNamespaceToText ServiceNamespaceRoute53 = "route53"
serviceNamespaceToText ServiceNamespaceS3 = "s3"
serviceNamespaceToText ServiceNamespaceSes = "ses"
serviceNamespaceToText ServiceNamespaceSdb = "sdb"
serviceNamespaceToText ServiceNamespaceSqs = "sqs"
serviceNamespaceToText ServiceNamespaceSns = "sns"
serviceNamespaceToText ServiceNamespaceStoragegateway = "storagegateway"
serviceNamespaceToText ServiceNamespaceSts = "sts"
serviceNamespaceToText ServiceNamespaceSupport = "support"
serviceNamespaceToText ServiceNamespaceSwf = "swf"
serviceNamespaceToText ServiceNamespaceHost = "host"
parseServiceNamespace :: P.CharParsing m => m ServiceNamespace
parseServiceNamespace =
ServiceNamespaceAwsPortal <$ P.text "aws-portal"
<|> ServiceNamespaceAutoscaling <$ P.text "autoscaling"
<|> ServiceNamespaceCloudformation <$ P.text "cloudformation"
<|> ServiceNamespaceCloudfront <$ P.text "cloudfront"
<|> ServiceNamespaceCloudwatch <$ P.text "cloudwatch"
<|> ServiceNamespaceDynamodb <$ P.text "dynamodb"
<|> ServiceNamespaceEc2 <$ P.text "ec2"
<|> ServiceNamespaceElasticbeanstalk <$ P.text "elasticbeanstalk"
<|> ServiceNamespaceElasticloadbalancing <$ P.text "elasticloadbalancing"
<|> ServiceNamespaceElasticmapreduce <$ P.text "elasticmapreduce"
<|> ServiceNamespaceElasticache <$ P.text "elasticache"
<|> ServiceNamespaceGlacier <$ P.text "glacier"
<|> ServiceNamespaceIam <$ P.text "iam"
<|> ServiceNamespaceKinesis <$ P.text "kinesis"
<|> ServiceNamespaceAwsMarketplaceManagement <$ P.text "aws-marketplace-management"
<|> ServiceNamespaceOpsworks <$ P.text "opsworks"
<|> ServiceNamespaceRds <$ P.text "rds"
<|> ServiceNamespaceRedshift <$ P.text "redshift"
<|> ServiceNamespaceRoute53 <$ P.text "route53"
<|> ServiceNamespaceS3 <$ P.text "s3"
<|> ServiceNamespaceSes <$ P.text "ses"
<|> ServiceNamespaceSdb <$ P.text "sdb"
<|> ServiceNamespaceSqs <$ P.text "sqs"
<|> ServiceNamespaceSns <$ P.text "sns"
<|> ServiceNamespaceStoragegateway <$ P.text "storagegateway"
<|> ServiceNamespaceSts <$ P.text "sts"
<|> ServiceNamespaceSupport <$ P.text "support"
<|> ServiceNamespaceSwf <$ P.text "swf"
<|> ServiceNamespaceHost <$ P.text "host"
<?> "Service Namespace"
instance AwsType ServiceNamespace where
toText = serviceNamespaceToText
parse = parseServiceNamespace
instance Hashable ServiceNamespace where
hashWithSalt = hashUsing fromEnum
instance Q.Arbitrary ServiceNamespace where
arbitrary = Q.elements [minBound..maxBound]
data Arn = Arn
{ arnService :: ServiceNamespace
, arnRegion :: Maybe Region
, arnAccount :: Maybe AccountId
, arnResource :: [T.Text]
}
deriving (Show, Read, Eq, Ord, Typeable)
arnToText :: (IsString a, Monoid a) => Arn -> a
arnToText arn = "arn:aws"
<> ":" <> serviceNamespaceToText (arnService arn)
<> ":" <> maybe "" regionToText (arnRegion arn)
<> ":" <> maybe "" accountIdToText (arnAccount arn)
<> ":" <> (fromString . T.unpack) (T.intercalate ":" (arnResource arn))
parseArn :: P.CharParsing m => m Arn
parseArn = P.text "arn:aws" *> p <?> "ARN"
where
p = Arn
<$> (P.char ':' *> parseServiceNamespace)
<*> (P.char ':' *> P.optional parseRegion)
<*> (P.char ':' *> P.optional parseAccountId)
<*> (P.char ':' *> P.sepBy1 (T.pack <$> many (P.notChar ':')) (P.char ':'))
instance AwsType Arn where
toText = arnToText
parse = parseArn
instance ToJSON Arn where
toJSON = toJSON . (arnToText :: Arn -> T.Text)
instance FromJSON Arn where
parseJSON = withText "Arn" $ either fail return . fromText
instance Q.Arbitrary Arn where
arbitrary = Arn
<$> Q.arbitrary
<*> Q.arbitrary
<*> Q.arbitrary
<*> (map (T.filter (/= ':')) . Q.getNonEmpty <$> Q.arbitrary)