Exibindo erros de validação no servidor - Asp Core Web API + Axios + React Hook Form
Tags: aspnetcore, dotnet, axios, react
Published: 2020-08-25 02:42:55

## Cenário
A partir do ASP.NET CORE 2.1, erros de validação no servidor, são enviados para o _client_ no formato [Validation Problem Details](https://docs.microsoft.com/pt-br/dotnet/api/microsoft.aspnetcore.mvc.validationproblemdetails?view=aspnetcore-3.1), que segue uma estrutura semelhante a seguir:

```json
{
   "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title":"One or more validation errors occurred.",
   "status":400,
   "traceId":"|52643794-491d9e1d05c828e6.",
   "errors":{
      "Cnpj":[
         "'Cnpj' must not be empty."
      ],
      "CompanyPublicName":[
         "'Company Public Name' must not be empty."
      ],
      "CompanyInternalName":[
         "'Company Internal Name' must not be empty."
      ]
   }
}
```
Perceba que na response, há a propriedade *errors*, contendo as propriedades com falhas de validação.

Estou utilizando [Axios](https://github.com/axios/axios) para requisições HTTP, e para checar, se o retorno da requisição é um feedback de validação, eu checo se há a presença do *response header*:
```
Content-Type: application/problem+json; charset=utf-8
```

Sendo assim, vejamos meu bloco try...catch responsável por invocar o *Axios*:

```javascript
try {
    let response = await AxiosInstance.post(
        '/general/companies', {}, {}
    )
} catch (e) {

    const serverSideErrors = e.response.data.errors;

    // Faz iteração nas propriedades do objeto Errors, e usa o método 
    // *SetError* do ReactHookForm, para informar um erro.
    Object.entries(serverSideErrors).map(([key, value]) =>
        setError(key, {
            message: value,
            type: 'serverSide'
        })
    )

}
```

## Axios interceptor:

Agora, antes de tratar o retorno da API, é preciso converter os nomes das propriedades para *lowerCamelCase*.
Eu faço isso usando um interceptor, ao criar uma instância do Axios.

```javascript
// Add a response interceptor
instance.interceptors.response.use(function(response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
}, function(error) {
    let newError = Object.assign({}, error);
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    if (newError.response.status === 400) {
        if (newError.response.headers['content-type'].includes('application/problem+json')) {
            /*
            Rename Problem Json response field names, to lower camel case
             */
            let serverData = newError.response.data;

            let errorsLowerCamelCase = Object.fromEntries(
                Object.entries(serverData.errors).map(([key, value]) =>
                    // Modify key here
                    [`${stringUtils.toLowerCamelCase(key)}`, value]
                )
            )

            newError.response.data.hasValidationErrors = true;
            newError.response.data.errors = errorsLowerCamelCase;
        }
    }
    return Promise.reject(newError);
});
```

## Conclusão

Para renderizar os erros inline, estou usando bootstrap form-group, e o component [ErrorMessage](https://github.com/react-hook-form/error-message) do próprio react-hook-form.

```javascript
<ErrorMessage name="companyInternalName"
              errors={errors}
              render={({message}) =>
                <div className={'field-error text-danger'}>{message}</div>
              }
```

Configuro esse componente pra cada campo do formulário, e ao fazer submit do formulário, uma requisição HTTP é feita, o retorno é tratado, e o resultado é exibido abaixo:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1vtc2ayle4tzd95ld7wb.png)

# Bônus: como exibir mensagens de validação globais? (que não estão associadas a nenhum campo específico)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4f1pcrfrzzmw8x5h37xs.png)

A mensagem de resposta do servidor, para uma mensagem de validação global, deve ser conforme a seguir:

```json
{
   "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title":"One or more validation errors occurred.",
   "status":400,
   "traceId":"|526437a6-491d9e1d05c828e6.",
   "errors":{
      "$":[
         "essa mensagem de validação aqui, não está associada a nenhum campo específico. 📍"
      ]
   }
}
```

Perceba que o identificador do campo, no objeto errors, é um símbolo <code>  $  </code>.

Sendo assim, coloquei no topo da tela, um component <code> < ErrorMessage /> </code>, onde o atributo <code> name="$" </code>.
Isso me permite exibir no topo do formulário, erros globais de validação.

```javascript
<ErrorMessage name="$"
errors={errors}
render={({message}) =>
  <>
    <FormGroup row>
      <Col md="4">
        <span>⚠️ Atenção:</span>
</Col>
<Col md="8">
  <div className={'field-error text-danger'}>{message}</div>
</Col>
</FormGroup>
</>
}
/>
```

---

Me avisa nos comentários se precisar de alguma ajuda.