mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
201 lines
7.3 KiB
Markdown
201 lines
7.3 KiB
Markdown
|
# Warning in `replace_metadata` API
|
||
|
|
||
|
Warnings 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).
|
||
|
|
||
|
## Proposed API output
|
||
|
|
||
|
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):
|
||
|
```json
|
||
|
{
|
||
|
"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.
|
||
|
```json
|
||
|
{
|
||
|
"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.
|
||
|
|
||
|
## Strict-mode
|
||
|
|
||
|
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:
|
||
|
```json
|
||
|
{
|
||
|
"fail_on_warning": ...,
|
||
|
"allow_inconsistent_metadata": ...,
|
||
|
"metadata": {
|
||
|
...
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Possible use cases
|
||
|
|
||
|
1. If a source is inconsistent and it is removed via `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.
|
||
|
2. Adding an inherited role with overlapping permissions successfully creates the inherited role but marks it inconsistent. The response of `replace_metadata` is success but there should be a warning because the inherited role is created but inconsistent.
|
||
|
3. (Cloud only). Adding a time limit > 60 will go through but it doesn't take affect because there is an infra limit of 60. The response of `replace_metadata` is success but should have been a warning.
|
||
|
4. Applying a metadata from a pro hasura having some pro-only content (like event trigger auto cleanup) to an OSS hasura goes through, but the features doesn't work. The response of `replace_metadata` is success but should have been a warning.
|
||
|
5. Possibly report the new non-fatal inconsistencies created [inconsistencies related to inherited permissions where the metadata API doesn't fail] as warnings.
|
||
|
|
||
|
## Open questions
|
||
|
|
||
|
- What can be the fields of warning?
|
||
|
- For unsuccessful API calls, should we show warnings and error separately?
|
||
|
Or should we include warnings as error?
|
||
|
|
||
|
## Possible implemetation using `MonadWriter`
|
||
|
|
||
|
Writer monad allows us to accumulate some logs (or warnings in our case) while
|
||
|
performing actions applicatively.
|
||
|
|
||
|
### Writer monad with Except monad
|
||
|
|
||
|
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`).
|
||
|
|
||
|
<details>
|
||
|
<summary>example use of Writer monad along with Except monad</summary>
|
||
|
|
||
|
```haskell
|
||
|
{-# 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
|
||
|
-- -----------------------------
|
||
|
```
|
||
|
</details>
|
||
|
|
||
|
|
||
|
Writer monad behaves as expected in the example above. However we need to be
|
||
|
careful because they are known for memory leaks.
|
||
|
|
||
|
## Final implementation
|
||
|
There are a few changes in the implementation of warnings from this RFC:
|
||
|
- We are using `MonadWarnings` (based on `StateT`) for collecting warnings.
|
||
|
- `MonadWarnings` provides a `warn` method to add a warning.
|
||
|
- We have omitted the `path` field from the warning object. This can be added later if needed.
|
||
|
- We have included `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:
|
||
|
```haskell
|
||
|
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 ()
|
||
|
```
|
||
|
## Future enhancements
|
||
|
- Extend `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.
|