module PostgREST.RangeQuery (
rangeParse
, rangeRequested
, rangeLimit
, rangeOffset
, restrictRange
, rangeGeq
, allRange
, NonnegRange
, rangeStatusHeader
, contentRangeH
) where
import qualified Data.ByteString.Char8 as BS
import Data.List (lookup)
import Text.Regex.TDFA ((=~))
import Control.Applicative
import Data.Ranged.Boundaries
import Data.Ranged.Ranges
import Network.HTTP.Types.Header
import Network.HTTP.Types.Status
import Protolude hiding (toS)
import Protolude.Conv (toS)
type NonnegRange = Range Integer
rangeParse :: BS.ByteString -> NonnegRange
rangeParse :: ByteString -> NonnegRange
rangeParse ByteString
range = do
let rangeRegex :: ByteString
rangeRegex = ByteString
"^([0-9]+)-([0-9]*)$" :: BS.ByteString
case [[ByteString]] -> Maybe [ByteString]
forall a. [a] -> Maybe a
listToMaybe (ByteString
range ByteString -> ByteString -> [[ByteString]]
forall source source1 target.
(RegexMaker Regex CompOption ExecOption source,
RegexContext Regex source1 target) =>
source1 -> source -> target
=~ ByteString
rangeRegex :: [[BS.ByteString]]) of
Just [ByteString]
parsedRange ->
let [Maybe Integer
_, Maybe Integer
mLower, Maybe Integer
mUpper] = String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe (String -> Maybe Integer)
-> (ByteString -> String) -> ByteString -> Maybe Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> String
forall a b. StringConv a b => a -> b
toS (ByteString -> Maybe Integer) -> [ByteString] -> [Maybe Integer]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [ByteString]
parsedRange
lower :: NonnegRange
lower = NonnegRange
-> (Integer -> NonnegRange) -> Maybe Integer -> NonnegRange
forall b a. b -> (a -> b) -> Maybe a -> b
maybe NonnegRange
forall v. DiscreteOrdered v => Range v
emptyRange Integer -> NonnegRange
rangeGeq Maybe Integer
mLower
upper :: NonnegRange
upper = NonnegRange
-> (Integer -> NonnegRange) -> Maybe Integer -> NonnegRange
forall b a. b -> (a -> b) -> Maybe a -> b
maybe NonnegRange
allRange Integer -> NonnegRange
rangeLeq Maybe Integer
mUpper in
NonnegRange -> NonnegRange -> NonnegRange
forall v. DiscreteOrdered v => Range v -> Range v -> Range v
rangeIntersection NonnegRange
lower NonnegRange
upper
Maybe [ByteString]
Nothing -> NonnegRange
allRange
rangeRequested :: RequestHeaders -> NonnegRange
rangeRequested :: RequestHeaders -> NonnegRange
rangeRequested RequestHeaders
headers = NonnegRange
-> (ByteString -> NonnegRange) -> Maybe ByteString -> NonnegRange
forall b a. b -> (a -> b) -> Maybe a -> b
maybe NonnegRange
allRange ByteString -> NonnegRange
rangeParse (Maybe ByteString -> NonnegRange)
-> Maybe ByteString -> NonnegRange
forall a b. (a -> b) -> a -> b
$ HeaderName -> RequestHeaders -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
hRange RequestHeaders
headers
restrictRange :: Maybe Integer -> NonnegRange -> NonnegRange
restrictRange :: Maybe Integer -> NonnegRange -> NonnegRange
restrictRange Maybe Integer
Nothing NonnegRange
r = NonnegRange
r
restrictRange (Just Integer
limit) NonnegRange
r =
NonnegRange -> NonnegRange -> NonnegRange
forall v. DiscreteOrdered v => Range v -> Range v -> Range v
rangeIntersection NonnegRange
r (NonnegRange -> NonnegRange) -> NonnegRange -> NonnegRange
forall a b. (a -> b) -> a -> b
$
Boundary Integer -> Boundary Integer -> NonnegRange
forall v. Boundary v -> Boundary v -> Range v
Range Boundary Integer
forall a. Boundary a
BoundaryBelowAll (Integer -> Boundary Integer
forall a. a -> Boundary a
BoundaryAbove (Integer -> Boundary Integer) -> Integer -> Boundary Integer
forall a b. (a -> b) -> a -> b
$ NonnegRange -> Integer
rangeOffset NonnegRange
r Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
limit Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1)
rangeLimit :: NonnegRange -> Maybe Integer
rangeLimit :: NonnegRange -> Maybe Integer
rangeLimit NonnegRange
range =
case [NonnegRange -> Boundary Integer
forall v. Ord v => Range v -> Boundary v
rangeLower NonnegRange
range, NonnegRange -> Boundary Integer
forall v. Ord v => Range v -> Boundary v
rangeUpper NonnegRange
range] of
[BoundaryBelow Integer
lower, BoundaryAbove Integer
upper] -> Integer -> Maybe Integer
forall a. a -> Maybe a
Just (Integer
1 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
upper Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
lower)
[Boundary Integer]
_ -> Maybe Integer
forall a. Maybe a
Nothing
rangeOffset :: NonnegRange -> Integer
rangeOffset :: NonnegRange -> Integer
rangeOffset NonnegRange
range =
case NonnegRange -> Boundary Integer
forall v. Ord v => Range v -> Boundary v
rangeLower NonnegRange
range of
BoundaryBelow Integer
lower -> Integer
lower
Boundary Integer
_ -> Text -> Integer
forall a. HasCallStack => Text -> a
panic Text
"range without lower bound"
rangeGeq :: Integer -> NonnegRange
rangeGeq :: Integer -> NonnegRange
rangeGeq Integer
n =
Boundary Integer -> Boundary Integer -> NonnegRange
forall v. Boundary v -> Boundary v -> Range v
Range (Integer -> Boundary Integer
forall a. a -> Boundary a
BoundaryBelow Integer
n) Boundary Integer
forall a. Boundary a
BoundaryAboveAll
allRange :: NonnegRange
allRange :: NonnegRange
allRange = Integer -> NonnegRange
rangeGeq Integer
0
rangeLeq :: Integer -> NonnegRange
rangeLeq :: Integer -> NonnegRange
rangeLeq Integer
n =
Boundary Integer -> Boundary Integer -> NonnegRange
forall v. Boundary v -> Boundary v -> Range v
Range Boundary Integer
forall a. Boundary a
BoundaryBelowAll (Integer -> Boundary Integer
forall a. a -> Boundary a
BoundaryAbove Integer
n)
rangeStatusHeader :: NonnegRange -> Int64 -> Maybe Int64 -> (Status, Header)
NonnegRange
topLevelRange Int64
queryTotal Maybe Int64
tableTotal =
let lower :: Integer
lower = NonnegRange -> Integer
rangeOffset NonnegRange
topLevelRange
upper :: Integer
upper = Integer
lower Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Int64 -> Integer
forall a. Integral a => a -> Integer
toInteger Int64
queryTotal Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1
contentRange :: Header
contentRange = Integer -> Integer -> Maybe Integer -> Header
forall a. (Integral a, Show a) => a -> a -> Maybe a -> Header
contentRangeH Integer
lower Integer
upper (Int64 -> Integer
forall a. Integral a => a -> Integer
toInteger (Int64 -> Integer) -> Maybe Int64 -> Maybe Integer
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Int64
tableTotal)
status :: Status
status = Integer -> Integer -> Maybe Integer -> Status
rangeStatus Integer
lower Integer
upper (Int64 -> Integer
forall a. Integral a => a -> Integer
toInteger (Int64 -> Integer) -> Maybe Int64 -> Maybe Integer
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe Int64
tableTotal)
in (Status
status, Header
contentRange)
where
rangeStatus :: Integer -> Integer -> Maybe Integer -> Status
rangeStatus :: Integer -> Integer -> Maybe Integer -> Status
rangeStatus Integer
_ Integer
_ Maybe Integer
Nothing = Status
status200
rangeStatus Integer
lower Integer
upper (Just Integer
total)
| Integer
lower Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
total = Status
status416
| (Integer
1 Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
upper Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
lower) Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
total = Status
status206
| Bool
otherwise = Status
status200
contentRangeH :: (Integral a, Show a) => a -> a -> Maybe a -> Header
contentRangeH :: a -> a -> Maybe a -> Header
contentRangeH a
lower a
upper Maybe a
total =
(HeaderName
"Content-Range", Text -> ByteString
forall a. ConvertText a Text => a -> ByteString
toUtf8 Text
headerValue)
where
headerValue :: Text
headerValue = Text
rangeString Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
totalString :: Text
rangeString :: Text
rangeString
| Bool
totalNotZero Bool -> Bool -> Bool
&& Bool
fromInRange = a -> Text
forall a b. (Show a, ConvertText String b) => a -> b
show a
lower Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> a -> Text
forall a b. (Show a, ConvertText String b) => a -> b
show a
upper
| Bool
otherwise = Text
"*"
totalString :: Text
totalString = Text -> (a -> Text) -> Maybe a -> Text
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Text
"*" a -> Text
forall a b. (Show a, ConvertText String b) => a -> b
show Maybe a
total
totalNotZero :: Bool
totalNotZero = a -> Maybe a
forall a. a -> Maybe a
Just a
0 Maybe a -> Maybe a -> Bool
forall a. Eq a => a -> a -> Bool
/= Maybe a
total
fromInRange :: Bool
fromInRange = a
lower a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= a
upper