packages feed

amazonka-core-2.0: src/Amazonka/Data/Path.hs

-- |
-- Module      : Amazonka.Data.Path
-- Copyright   : (c) 2013-2023 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
module Amazonka.Data.Path
  ( -- * Path Types
    Path (..),
    RawPath,
    EscapedPath,
    TwiceEscapedPath,

    -- * Constructing Paths
    ToPath (..),
    rawPath,

    -- * Manipulating Paths
    escapePath,
    escapePathTwice,
    collapsePath,
  )
where

import Amazonka.Data.ByteString
import Amazonka.Data.Text
import Amazonka.Prelude
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Network.HTTP.Types.URI as URI

class ToPath a where
  toPath :: a -> ByteString

instance ToPath ByteString where
  toPath = id

instance ToPath Text where
  toPath = toBS

rawPath :: ToPath a => a -> Path 'NoEncoding
rawPath = Raw . strip . BS8.split sep . toPath
  where
    strip (x : xs)
      | BS.null x = xs
    strip xs = xs

data Encoding = NoEncoding | Percent
  deriving stock (Eq, Show)

data Path :: Encoding -> Type where
  Raw :: [ByteString] -> Path 'NoEncoding
  Encoded :: [ByteString] -> Path 'Percent

deriving stock instance Show (Path a)

deriving stock instance Eq (Path a)

type RawPath = Path 'NoEncoding

type EscapedPath = Path 'Percent

-- | Used in SigV4
newtype TwiceEscapedPath = TwiceEscapedPath (Path 'Percent)
  deriving newtype (Eq, Show, ToByteString)

instance Semigroup RawPath where
  Raw xs <> Raw ys = Raw (xs ++ ys)

instance Monoid RawPath where
  mempty = Raw []
  mappend = (<>)

instance ToByteString EscapedPath where
  toBS (Encoded []) = slash
  toBS (Encoded xs) = slash <> BS8.intercalate slash xs

escapePath :: Path a -> EscapedPath
escapePath (Raw xs) = Encoded (map (URI.urlEncode True) xs)
escapePath (Encoded xs) = Encoded xs

-- | Escape a path twice. Used when computing the SigV4 canonical path.
escapePathTwice :: Path a -> TwiceEscapedPath
escapePathTwice p = TwiceEscapedPath $
  Encoded $ case p of
    Raw xs -> map (URI.urlEncode True . URI.urlEncode True) xs
    Encoded xs -> map (URI.urlEncode True) xs

collapsePath :: Path a -> Path a
collapsePath = \case
  Raw xs -> Raw (go xs)
  Encoded xs -> Encoded (go xs)
  where
    go = reverse . f . reverse

    f :: [ByteString] -> [ByteString]
    f [] = []
    f (x : xs)
      | x == dot = f xs
      | x == dots = drop 1 (f xs)
      | otherwise = x : f xs

    dot = "."
    dots = ".."

slash :: ByteString
slash = BS8.singleton sep

sep :: Char
sep = '/'