hq-json-0.1.1: src/HQ/Query.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE NoImplicitPrelude #-}
module HQ.Query where
import HQ.Optic
import HQ.Optic.AST (inferFocusType)
import HQ.Transformation (Transformation)
import HQ.Transformation.AST
import HQ.Transformation.TransformationType (ValueType (..))
import Relude hiding (Compose, many, not, or, some, subtract)
data Query
= Fold Optic
| Preview Optic
| -- | Apply a transformation to each of the focused values.
Over Optic Transformation
| Delete Optic
deriving stock (Show, Eq)
getOptic :: Query -> Optic
getOptic (Fold optic) = optic
getOptic (Preview optic) = optic
getOptic (Over optic _) = optic
getOptic (Delete optic) = optic
data TypeError
= InvalidTransformationType TransformationTypeError
| -- | The focused values certainly reject the transformation: the
-- optic focuses one type, the transformation needs another, and
-- neither side is 'ValueAny'. Offenders kept for reporting.
InvalidFocusType Optic Transformation ValueType ValueType
deriving stock (Eq, Show)
renderTypeError :: TypeError -> Text
renderTypeError (InvalidTransformationType (InvalidDivideByZero t)) =
"division by zero in " <> show t
renderTypeError (InvalidTransformationType (InvalidCompose t out inn)) =
"transformation mismatch in "
<> show t
<> ": produces "
<> valueTypeName out
<> " but the next step expects "
<> valueTypeName inn
renderTypeError (InvalidTransformationType (InvalidOr t out)) =
renderBoolOp "or" t out
renderTypeError (InvalidTransformationType (InvalidAnd t out)) =
renderBoolOp "and" t out
renderTypeError (InvalidTransformationType (InvalidXor t out)) =
renderBoolOp "xor" t out
renderTypeError (InvalidFocusType optic t focus expected) =
"type mismatch: transformation "
<> show t
<> " expects "
<> valueTypeName expected
<> " but the optic "
<> show optic
<> " focuses "
<> valueTypeName focus
renderTypeError (InvalidTransformationType (InvalidFilter t out)) =
"filter transformation "
<> show t
<> " must produce a boolean, but produces "
<> valueTypeName out
-- | Render a boolean-connective mismatch: @or@/@and@/@xor@ share
-- everything but the connective's name.
renderBoolOp :: Text -> Transformation -> ValueType -> Text
renderBoolOp op t out =
"transformation mismatch in "
<> show t
<> ": both sides of "
<> op
<> " must produce booleans, but a branch produces "
<> valueTypeName out
valueTypeName :: ValueType -> Text
valueTypeName ValueObject = "object"
valueTypeName ValueArray = "array"
valueTypeName ValueString = "string"
valueTypeName ValueNumber = "number"
valueTypeName ValueBool = "boolean"
valueTypeName ValueNull = "null"
valueTypeName ValueAny = "any value"
-- | Check a query before running it: @filter@ booleans and 'Over'
-- composition types. Every query accepts any optic.
typecheckQuery :: Query -> Either TypeError Query
typecheckQuery q = checkOptic q >>= checkTransformation
checkOptic :: Query -> Either TypeError Query
checkOptic q = go (getOptic q) >> pure q
where
go :: Optic -> Either TypeError ()
go (Filter o t) = checkPredicate o t >> go o
go (Compose l r) = go l >> go r
go _ = pure ()
checkPredicate :: Optic -> Transformation -> Either TypeError ()
checkPredicate o t = case inferTransformationType t of
Left e -> Left (InvalidTransformationType e)
Right tt
| transformationOutput tt /= ValueBool ->
Left
$ InvalidTransformationType
$ InvalidFilter t (transformationOutput tt)
| otherwise -> checkFocus o t tt
-- | A transformation accepts focused values of unknown shape
-- ('ValueAny' either side); anything else must match exactly.
checkFocus ::
Optic -> Transformation -> TransformationType -> Either TypeError ()
checkFocus optic t tt
| focus == ValueAny || expected == ValueAny || focus == expected = pure ()
| otherwise = Left (InvalidFocusType optic t focus expected)
where
focus = inferFocusType optic
expected = transformationInput tt
checkTransformation :: Query -> Either TypeError Query
checkTransformation q@(Over optic transformation) =
case inferTransformationType transformation of
Left transformationTypeError ->
Left $ InvalidTransformationType transformationTypeError
Right tt -> checkFocus optic transformation tt >> pure q
checkTransformation q = pure q