rfcs/warning-in-replace-metadata-API.md
replace_metadata APIWarnings are an important part of REST APIs as they allow the API to communicate additional information to the client that may be relevant to the request being made. Warnings can also help the API consumer to identify and fix issues with the API. For example, if a client receives a warning saying "the SQL triggers cannot be cleaned up while removing the sources", the developer can investigate the issue and delete the SQL triggers manually.
Currently, the metadata APIs don't throw warnings (except for the
inconsistencies when allow_inconsistent_metadata is set to True in the
replace_metadata API).
Add a root level field called warnings (a list of Objects). This field will
only exist if there are warnings, i.e. if there are no warnings, this field
would not exist in the response.
The output of a replace_metadata API call will look something like the
following for successful calls (with warnings):
{
"message": "success",
"warnings": [
{
"path": "$.args.metadata.sources[0]",
"message": "the SQL triggers cannot be cleaned up while removing the source \"default\""
},
{
"path": "$.args.metadata.sources[0].table[1].event_triggers[0]",
"message": "the event trigger with name \"system.send_email\" may not work as expected, hasura suggests to use only alphanumeric, underscore and hyphens in an event trigger name"
}
]
}
In case of successfull API call (without warning), the API response remains unchanged. i.e.
{
"message": "success"
}
| :memo: NOTE |
|---|
We do not have a situation where a replace_metadata metadata API call could fail and introduce a side-effect that we cannot rollback. Therefore, implementing a warning for such a scenario is not necessary at this time. However, if this situation were to arise in the future, a warning may be useful. We will revisit the possibility of adding this enhancement at a later date. |
Note:
In case of unsuccessful API calls, the API response will not contain any warnings.
Throwing warning should never short-circuit the workflow.
We should also include a strict-mode (fail_on_warning: true). In strict-mode, the warnings in replace_metadata API call will be fatal and the will result in error. The metadata changes will not be applied in such cases. The arguments of the replace_metadata will look something like the following:
{
"fail_on_warning": ...,
"allow_inconsistent_metadata": ...,
"metadata": {
...
}
}
replace_metadata, then Postgres triggers (because of Event Triggers) are not cleaned. The response is success but ideally there should be a warning to clean the triggers manually.replace_metadata is success but there should be a warning because the inherited role is created but inconsistent.replace_metadata is success but should have been a warning.replace_metadata is success but should have been a warning.MonadWriterWriter monad allows us to accumulate some logs (or warnings in our case) while performing actions applicatively.
We use Except monad for handling errors in API handlers. Before adding the
MonadWriter in our monad stack, we should consider it's implications and
behaviour with other monads in our monad stack (specially MonadError or
ExceptT).
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
module Main where
import Control.Monad
import Control.Monad.Except
import Control.Monad.IO.Class (liftIO, MonadIO)
import Prelude
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Control.Monad.Writer.Class
type Warnings = [String]
example ::
(
MonadIO m,
MonadError String m,
MonadWriter Warnings m
) =>
Int -> m Int
example x = do
tell ["warning: starting the operation"]
when (x < 0) $ throwError "error: negative input"
when (x < 10) $ tell ["warning: input is less than 10"]
when (x > 20) $ tell ["warning: input is more than 20"]
liftIO $ putStrLn "hello" -- performs some action for non error case
return x
handleInput :: Int -> IO ()
handleInput input = do
putStrLn $ "--------- input: "<> show input <>" ---------"
(res, warnings) <- CPS.runWriterT $ runExceptT (example input)
print warnings
case res of
Left err -> print err
Right result -> print result
putStrLn "-----------------------------"
main :: IO ()
main = do
handleInput (-1)
handleInput 5
handleInput 15
handleInput 25
-- >>> main
-- --------- input: -1 ---------
-- ["warning: starting the operation"]
-- "error: negative input"
-- -----------------------------
--
-- --------- input: 5 ---------
-- hello
-- ["warning: starting the operation","warning: input is less than 10"]
-- 5
-- -----------------------------
--
-- --------- input: 15 ---------
-- hello
-- ["warning: starting the operation"]
-- 15
-- -----------------------------
--
-- --------- input: 25 ---------
-- hello
-- ["warning: starting the operation","warning: input is more than 20"]
-- 25
-- -----------------------------
Writer monad behaves as expected in the example above. However we need to be careful because they are known for memory leaks.
There are a few changes in the implementation of warnings from this RFC:
MonadWarnings (based on StateT) for collecting warnings.MonadWarnings provides a warn method to add a warning.path field from the warning object. This can be added later if needed.type and name fields in the warning object. This is to make it easier to identify the object
which caused the warning.An example use of MonadWarnings is shown below:
import Hasura.RQL.DDL.Warnings
someMetadataAPIHandler :: args -> m EncJSON
someMetadataAPIHandler args = successMsgWithWarnings $ do
-- do some stuff
let warning = MetadataWarning (MOSource defaultSource) "some warning message"
-- we use the `warn` method to add a warning
warn $ warning
-- do some more stuff
pure ()
warnings to other metadata APIs. Please note that we may end up in
a situation where an API call fails and it made some change which was not
rolled back and thus we would like to throw that as a warning or error.