packages feed

erpnext-api-client-0.3.0.0: src/ERPNext/Client/Filter.hs

{-# LANGUAGE OverloadedStrings #-}

module ERPNext.Client.Filter
  ( Filter (..)
  , Fieldname
  , FilterValue (..)
  , renderFilters
  ) where

import Data.Text (Text, intercalate, pack) -- pack is used in doctests!
import Data.Time.Calendar (Day)
import Data.String
import ERPNext.Client.Helper

-- | Filter that can be used as arguments for
-- 'ERPNext.Client.QueryStringParam.AndFilter' or
-- 'ERPNext.Client.QueryStringParam.OrFilter'.
--
-- The filters are case-insensitive on API server side!
-- I verified by testing this with 'Eq', 'Like', and 'In'.
data Filter
  = Eq Fieldname FilterValue
  | NotEq Fieldname FilterValue
  | Greater Fieldname FilterValue
  | GreaterOrEq Fieldname FilterValue
  | Less Fieldname FilterValue
  | LessOrEq Fieldname FilterValue
  | Like Fieldname FilterValue
  | NotLike Fieldname FilterValue
  | In Fieldname [FilterValue]
  | NotIn Fieldname [FilterValue]
  | Between Fieldname FilterValue FilterValue
  | IsNull Fieldname
  | IsNotNull Fieldname
  deriving (Show, Eq)

data FilterValue
  = FilterText Text
  | FilterNumber Double
  | FilterBool Bool
  | FilterDay Day
  deriving (Show, Eq)

instance IsString FilterValue where
  fromString = FilterText . pack

renderFilter :: Filter -> Text
renderFilter f =
  case f of
    Eq field val         -> renderWithValue field "=" val
    NotEq field val      -> renderWithValue field "!=" val
    Greater field val    -> renderWithValue field ">" val
    GreaterOrEq field val-> renderWithValue field ">=" val
    Less field val       -> renderWithValue field "<" val
    LessOrEq field val   -> renderWithValue field "<=" val
    Like field val       -> renderWithValue field "like" val
    NotLike field val    -> renderWithValue field "not like" val
    In field vals        -> renderWithArray field "in" vals
    NotIn field vals     -> renderWithArray field "not in" vals
    Between field val1 val2    -> renderWithArray field "between" [val1, val2]
    IsNull field         -> renderWithText field "is" "not set"
    IsNotNull field      -> renderWithText field "is" "set"

renderWithValue :: Fieldname -> Text -> FilterValue -> Text
renderWithValue field op val =
  renderTextArray [quote field, quote op, renderFilterValue val]

renderWithText :: Fieldname -> Text -> Text -> Text
renderWithText field op txt =
  renderTextArray [quote field, quote op, quote txt]

renderWithArray :: Fieldname -> Text -> [FilterValue] -> Text
renderWithArray field op vals =
  renderTextArray [quote field, quote op, renderTextArray (map renderFilterValue vals)]

renderTextArray :: [Text] -> Text
renderTextArray xs =
  "[" <> intercalate "," xs <> "]"

renderFilterValue :: FilterValue -> Text
renderFilterValue fv =
  case fv of
    FilterText t -> quote t
    FilterNumber n -> tshow n
    FilterBool b -> if b then "1" else "0"
    FilterDay d -> quote (tshow d)

-- | Render the filter terms for the URL query string.
--
-- >>> urlDecode $ renderFilters [Eq (pack "idx") (FilterNumber 99)]
-- "[[\"idx\",\"=\",99.0]]"
--
-- >>> urlDecode $ renderFilters [IsNull (pack "status"), In (pack "name") [FilterText (pack "a"), FilterText (pack "b")]]
-- "[[\"status\",\"is\",\"not set\"],[\"name\",\"in\",[\"a\",\"b\"]]]"
--
renderFilters :: [Filter] -> Text
renderFilters filters =
  let
    encoded = map renderFilter filters
    str = "[" <> intercalate "," encoded <> "]"
  in
    urlEncode str