-- |
-- 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 :: ByteString -> ByteString
toPath = ByteString -> ByteString
forall a. a -> a
id

instance ToPath Text where
  toPath :: Text -> ByteString
toPath = Text -> ByteString
forall a. ToByteString a => a -> ByteString
toBS

rawPath :: ToPath a => a -> Path 'NoEncoding
rawPath :: forall a. ToPath a => a -> Path 'NoEncoding
rawPath = [ByteString] -> Path 'NoEncoding
Raw ([ByteString] -> Path 'NoEncoding)
-> (a -> [ByteString]) -> a -> Path 'NoEncoding
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> [ByteString]
strip ([ByteString] -> [ByteString])
-> (a -> [ByteString]) -> a -> [ByteString]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> ByteString -> [ByteString]
BS8.split Char
sep (ByteString -> [ByteString])
-> (a -> ByteString) -> a -> [ByteString]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> ByteString
forall a. ToPath a => a -> ByteString
toPath
  where
    strip :: [ByteString] -> [ByteString]
strip (ByteString
x : [ByteString]
xs)
      | ByteString -> Bool
BS.null ByteString
x = [ByteString]
xs
    strip [ByteString]
xs = [ByteString]
xs

data Encoding = NoEncoding | Percent
  deriving stock (Encoding -> Encoding -> Bool
(Encoding -> Encoding -> Bool)
-> (Encoding -> Encoding -> Bool) -> Eq Encoding
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Encoding -> Encoding -> Bool
== :: Encoding -> Encoding -> Bool
$c/= :: Encoding -> Encoding -> Bool
/= :: Encoding -> Encoding -> Bool
Eq, Int -> Encoding -> ShowS
[Encoding] -> ShowS
Encoding -> String
(Int -> Encoding -> ShowS)
-> (Encoding -> String) -> ([Encoding] -> ShowS) -> Show Encoding
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Encoding -> ShowS
showsPrec :: Int -> Encoding -> ShowS
$cshow :: Encoding -> String
show :: Encoding -> String
$cshowList :: [Encoding] -> ShowS
showList :: [Encoding] -> ShowS
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 (TwiceEscapedPath -> TwiceEscapedPath -> Bool
(TwiceEscapedPath -> TwiceEscapedPath -> Bool)
-> (TwiceEscapedPath -> TwiceEscapedPath -> Bool)
-> Eq TwiceEscapedPath
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: TwiceEscapedPath -> TwiceEscapedPath -> Bool
== :: TwiceEscapedPath -> TwiceEscapedPath -> Bool
$c/= :: TwiceEscapedPath -> TwiceEscapedPath -> Bool
/= :: TwiceEscapedPath -> TwiceEscapedPath -> Bool
Eq, Int -> TwiceEscapedPath -> ShowS
[TwiceEscapedPath] -> ShowS
TwiceEscapedPath -> String
(Int -> TwiceEscapedPath -> ShowS)
-> (TwiceEscapedPath -> String)
-> ([TwiceEscapedPath] -> ShowS)
-> Show TwiceEscapedPath
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> TwiceEscapedPath -> ShowS
showsPrec :: Int -> TwiceEscapedPath -> ShowS
$cshow :: TwiceEscapedPath -> String
show :: TwiceEscapedPath -> String
$cshowList :: [TwiceEscapedPath] -> ShowS
showList :: [TwiceEscapedPath] -> ShowS
Show, TwiceEscapedPath -> ByteString
(TwiceEscapedPath -> ByteString) -> ToByteString TwiceEscapedPath
forall a. (a -> ByteString) -> ToByteString a
$ctoBS :: TwiceEscapedPath -> ByteString
toBS :: TwiceEscapedPath -> ByteString
ToByteString)

instance Semigroup RawPath where
  Raw [ByteString]
xs <> :: Path 'NoEncoding -> Path 'NoEncoding -> Path 'NoEncoding
<> Raw [ByteString]
ys = [ByteString] -> Path 'NoEncoding
Raw ([ByteString]
xs [ByteString] -> [ByteString] -> [ByteString]
forall a. [a] -> [a] -> [a]
++ [ByteString]
ys)

instance Monoid RawPath where
  mempty :: Path 'NoEncoding
mempty = [ByteString] -> Path 'NoEncoding
Raw []
  mappend :: Path 'NoEncoding -> Path 'NoEncoding -> Path 'NoEncoding
mappend = Path 'NoEncoding -> Path 'NoEncoding -> Path 'NoEncoding
forall a. Semigroup a => a -> a -> a
(<>)

instance ToByteString EscapedPath where
  toBS :: Path 'Percent -> ByteString
toBS (Encoded []) = ByteString
slash
  toBS (Encoded [ByteString]
xs) = ByteString
slash ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString -> [ByteString] -> ByteString
BS8.intercalate ByteString
slash [ByteString]
xs

escapePath :: Path a -> EscapedPath
escapePath :: forall (a :: Encoding). Path a -> Path 'Percent
escapePath (Raw [ByteString]
xs) = [ByteString] -> Path 'Percent
Encoded ((ByteString -> ByteString) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> ByteString -> ByteString
URI.urlEncode Bool
True) [ByteString]
xs)
escapePath (Encoded [ByteString]
xs) = [ByteString] -> Path 'Percent
Encoded [ByteString]
xs

-- | Escape a path twice. Used when computing the SigV4 canonical path.
escapePathTwice :: Path a -> TwiceEscapedPath
escapePathTwice :: forall (a :: Encoding). Path a -> TwiceEscapedPath
escapePathTwice Path a
p = Path 'Percent -> TwiceEscapedPath
TwiceEscapedPath (Path 'Percent -> TwiceEscapedPath)
-> Path 'Percent -> TwiceEscapedPath
forall a b. (a -> b) -> a -> b
$
  [ByteString] -> Path 'Percent
Encoded ([ByteString] -> Path 'Percent) -> [ByteString] -> Path 'Percent
forall a b. (a -> b) -> a -> b
$ case Path a
p of
    Raw [ByteString]
xs -> (ByteString -> ByteString) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> ByteString -> ByteString
URI.urlEncode Bool
True (ByteString -> ByteString)
-> (ByteString -> ByteString) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> ByteString -> ByteString
URI.urlEncode Bool
True) [ByteString]
xs
    Encoded [ByteString]
xs -> (ByteString -> ByteString) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (Bool -> ByteString -> ByteString
URI.urlEncode Bool
True) [ByteString]
xs

collapsePath :: Path a -> Path a
collapsePath :: forall (a :: Encoding). Path a -> Path a
collapsePath = \case
  Raw [ByteString]
xs -> [ByteString] -> Path 'NoEncoding
Raw ([ByteString] -> [ByteString]
go [ByteString]
xs)
  Encoded [ByteString]
xs -> [ByteString] -> Path 'Percent
Encoded ([ByteString] -> [ByteString]
go [ByteString]
xs)
  where
    go :: [ByteString] -> [ByteString]
go = [ByteString] -> [ByteString]
forall a. [a] -> [a]
reverse ([ByteString] -> [ByteString])
-> ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> [ByteString]
f ([ByteString] -> [ByteString])
-> ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [ByteString] -> [ByteString]
forall a. [a] -> [a]
reverse

    f :: [ByteString] -> [ByteString]
    f :: [ByteString] -> [ByteString]
f [] = []
    f (ByteString
x : [ByteString]
xs)
      | ByteString
x ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
dot = [ByteString] -> [ByteString]
f [ByteString]
xs
      | ByteString
x ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
dots = Int -> [ByteString] -> [ByteString]
forall a. Int -> [a] -> [a]
drop Int
1 ([ByteString] -> [ByteString]
f [ByteString]
xs)
      | Bool
otherwise = ByteString
x ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
: [ByteString] -> [ByteString]
f [ByteString]
xs

    dot :: ByteString
dot = ByteString
"."
    dots :: ByteString
dots = ByteString
".."

slash :: ByteString
slash :: ByteString
slash = Char -> ByteString
BS8.singleton Char
sep

sep :: Char
sep :: Char
sep = Char
'/'