packages feed

amazonka-s3-2.0: src/Amazonka/S3/Internal.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

-- |
-- Module      : Amazonka.S3.Internal
-- Copyright   : (c) 2013-2023 Brendan Hay
-- License     : This Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka.com>
-- Stability   : experimental
-- Portability : non-portable (GHC extensions)
module Amazonka.S3.Internal
  ( -- * BucketName
    BucketName (..),
    _BucketName,

    -- * ETag
    ETag (..),
    _ETag,

    -- * Object Version ID
    ObjectVersionId (..),
    _ObjectVersionId,

    -- * Bucket Location
    LocationConstraint (..),
    _LocationConstraint,
    Region (..),

    -- * Object Key
    Delimiter,
    ObjectKey (..),
    _ObjectKey,
    objectKey_keyPrefix,
    objectKey_keyName,
    objectKey_keyComponents,

    -- * Website Endpoints
    getWebsiteEndpoint,
  )
where

import Amazonka.Core
import Amazonka.Core.Lens.Internal
  ( IndexedTraversal',
    coerced,
    prism,
    traversed,
    _1,
    _2,
  )
import Amazonka.Data
import Amazonka.Prelude
import qualified Data.Text as Text

newtype BucketName = BucketName {fromBucketName :: Text}
  deriving
    ( Eq,
      Ord,
      Read,
      Show,
      Generic,
      IsString,
      FromText,
      ToText,
      ToByteString,
      FromXML,
      ToXML,
      ToQuery,
      ToLog,
      FromJSON
    )

{-# INLINE _BucketName #-}
_BucketName :: Iso' BucketName Text
_BucketName = coerced

instance Hashable BucketName

instance NFData BucketName

-- FIXME: Add the difference between weak + strong ETags and their respective
-- equalities if necessary, see: https://github.com/brendanhay/amazonka/issues/76
newtype ETag = ETag {fromETag :: ByteString}
  deriving
    ( Eq,
      Ord,
      Read,
      Show,
      Generic,
      IsString,
      FromText,
      ToText,
      ToByteString,
      FromXML,
      ToXML,
      ToQuery,
      ToLog
    )

{-# INLINE _ETag #-}
_ETag :: Iso' ETag ByteString
_ETag = coerced

instance Hashable ETag

instance NFData ETag

newtype ObjectVersionId = ObjectVersionId {fromObjectVersionId :: Text}
  deriving
    ( Eq,
      Ord,
      Read,
      Show,
      Generic,
      IsString,
      FromText,
      ToText,
      ToByteString,
      FromXML,
      ToXML,
      ToQuery,
      ToLog
    )

_ObjectVersionId :: Iso' ObjectVersionId Text
_ObjectVersionId = coerced

instance Hashable ObjectVersionId

instance NFData ObjectVersionId

newtype LocationConstraint = LocationConstraint {constraintRegion :: Region}
  deriving
    ( Eq,
      Ord,
      Read,
      Show,
      Generic,
      ToText,
      ToByteString,
      ToLog
    )

{-# INLINE _LocationConstraint #-}
_LocationConstraint :: Iso' LocationConstraint Region
_LocationConstraint = coerced

instance Hashable LocationConstraint

instance NFData LocationConstraint

instance FromText LocationConstraint where
  fromText text =
    LocationConstraint
      <$> case Text.toLower text of
        "" -> pure NorthVirginia
        "eu" -> pure Ireland
        other -> pure $ Region' other

instance FromXML LocationConstraint where
  parseXML = \case
    [] -> pure (LocationConstraint NorthVirginia)
    ns -> parseXMLText "LocationConstraint" ns

instance ToXML LocationConstraint where
  toXML = \case
    LocationConstraint NorthVirginia -> XNull
    LocationConstraint r -> toXMLText r

newtype ObjectKey = ObjectKey Text
  deriving
    ( Eq,
      Ord,
      Read,
      Show,
      Generic,
      IsString,
      FromText,
      ToText,
      ToByteString,
      FromXML,
      ToXML,
      ToQuery,
      ToPath,
      ToLog
    )

instance Hashable ObjectKey

instance NFData ObjectKey

type Delimiter = Char

_ObjectKey :: Iso' ObjectKey Text
_ObjectKey = coerced
{-# INLINE _ObjectKey #-}

-- FIXME: Note about laws for combining objectKey_keyPrefix/objectKey_keyName.

-- | Traverse the prefix of an object key.
--
-- The prefix is classified as the entirety of the object key minus the name.
-- A leading prefix in the presence of a name, and no other delimiters is
-- interpreted as a blank prefix.
--
-- >>> "/home/jsmith/base.wiki" ^? objectKey_keyPrefix '/'
-- Just "/home/jsmith"
--
-- >>> "/home/jsmith/" ^? objectKey_keyPrefix '/'
-- Just "/home/jsmith"
--
-- >>> "/home" ^? objectKey_keyPrefix '/'
-- Nothing
--
-- >>> "/" ^? objectKey_keyPrefix '/'
-- Nothing
objectKey_keyPrefix :: Delimiter -> Traversal' ObjectKey Text
objectKey_keyPrefix c = _ObjectKeySnoc True c . _1
{-# INLINE objectKey_keyPrefix #-}

-- | Traverse the name of an object key.
--
-- The name is classified as last path component based on the given delimiter.
-- A trailing delimiter is interpreted as a blank name.
--
-- >>> "/home/jsmith/base.wiki" ^? objectKey_keyName '/'
-- Just "base.wiki"
--
-- >>> "/home/jsmith/" ^? objectKey_keyName '/'
-- Just ""
--
-- >>> "/home" ^? objectKey_keyName '/'
-- Just "home"
--
-- >>> "/" ^? objectKey_keyName '/'
-- Just ""
objectKey_keyName :: Delimiter -> Traversal' ObjectKey Text
objectKey_keyName c = _ObjectKeySnoc False c . _2
{-# INLINE objectKey_keyName #-}

-- | Traverse the path components of an object key using the specified delimiter.
objectKey_keyComponents :: Delimiter -> IndexedTraversal' Int ObjectKey Text
objectKey_keyComponents !c f (ObjectKey k) = cat <$> traversed f split
  where
    split = Text.split (== c) k
    cat = ObjectKey . Text.intercalate (Text.singleton c)
{-# INLINE objectKey_keyComponents #-}

-- | Modelled on the '_Snoc' type class from "Control.Lens.Cons".
_ObjectKeySnoc :: Bool -> Delimiter -> Prism' ObjectKey (Text, Text)
_ObjectKeySnoc dir !c = prism (ObjectKey . uncurry cat) split
  where
    split x@(ObjectKey k) =
      let (h, t) = Text.breakOnEnd suf k
       in if
              | Text.length h <= 1, dir -> Left x
              | otherwise -> Right (Text.dropEnd 1 h, t)

    cat h t
      | Text.null h = t
      | Text.null t = h
      | suf `Text.isSuffixOf` h = h <> t
      | suf `Text.isPrefixOf` t = h <> t
      | otherwise = h <> suf <> t

    suf = Text.singleton c

-- | Get the S3 website endpoint for a specific region.
--
-- When you configure your bucket as a website, the website is available using
-- this region-specific website endpoint.
--
-- /See:/ <https://docs.aws.amazon.com/general/latest/gr/s3.html#s3_website_region_endpoints Amazon Simple Storage Service Website Endpoints>.
getWebsiteEndpoint :: Region -> Maybe Text
getWebsiteEndpoint = \case
  Ohio -> Just "s3-website.us-east-2.amazonaws.com"
  NorthVirginia -> Just "s3-website-us-east-1.amazonaws.com"
  NorthCalifornia -> Just "s3-website-us-west-1.amazonaws.com"
  Oregon -> Just "s3-website-us-west-2.amazonaws.com"
  CapeTown -> Just "s3-website.af-south-1.amazonaws.com"
  HongKong -> Just "s3-website.ap-east-1.amazonaws.com"
  Hyderabad -> Just "s3-website.ap-south-2.amazonaws.com"
  Jakarta -> Just "s3-website.ap-southeast-3.amazonaws.com"
  Melbourne -> Just "s3-website.ap-southeast-4.amazonaws.com"
  Mumbai -> Just "s3-website.ap-south-1.amazonaws.com"
  Osaka -> Just "s3-website.ap-northeast-3.amazonaws.com"
  Seoul -> Just "s3-website.ap-northeast-2.amazonaws.com"
  Singapore -> Just "s3-website-ap-southeast-1.amazonaws.com"
  Sydney -> Just "s3-website-ap-southeast-2.amazonaws.com"
  Tokyo -> Just "s3-website-ap-northeast-1.amazonaws.com"
  Montreal -> Just "s3-website.ca-central-1.amazonaws.com"
  Ningxia -> Just "s3-website.cn-northwest-1.amazonaws.com.cn"
  Frankfurt -> Just "s3-website.eu-central-1.amazonaws.com"
  Ireland -> Just "s3-website-eu-west-1.amazonaws.com"
  London -> Just "s3-website.eu-west-2.amazonaws.com"
  Milan -> Just "s3-website.eu-south-1.amazonaws.com"
  Paris -> Just "s3-website.eu-west-3.amazonaws.com"
  Stockholm -> Just "s3-website.eu-north-1.amazonaws.com"
  Spain -> Just "s3-website.eu-south-2.amazonaws.com"
  Zurich -> Just "s3-website.eu-central-2.amazonaws.com"
  Bahrain -> Just "s3-website.me-south-1.amazonaws.com"
  UAE -> Just "s3-website.me-central-1.amazonaws.com"
  SaoPaulo -> Just "s3-website-sa-east-1.amazonaws.com"
  GovCloudEast -> Just "s3-website.us-gov-east-1.amazonaws.com"
  GovCloudWest -> Just "s3-website-us-gov-west-1.amazonaws.com"
  Region' _ -> Nothing