У реальному світі, як і в програмуванні, ніхто не застрахований від помилок. Клієнти можуть відправити неправильні дані, база даних може поводитися дивно, а розробник — ну, людина же, може забути щось важливе. Саме тому валідація і обробка помилок є критичними аспектами будь-якого API.
GraphQL спроєктований так, щоб мінімізувати кількість помилок завдяки суворій типізації і валідації схеми. Однак цього недостатньо, особливо при роботі з користувацькими запитами. Нам потрібні інструменти, щоб:
- Перевіряти вхідні дані на відповідність бізнес-логіці.
- Формувати інформативні повідомлення про помилки.
- Локалізувати помилки.
- Логувати проблеми для подальшого аналізу.
Давайте розбиратися, як це робиться в контексті GraphQL на Spring.
Вбудована валідація в GraphQL
GraphQL на найпростішому рівні використовує схему для автоматичної валідації запитів. Наприклад, якщо в схемі вказаний тип Int, GraphQL автоматично відхилить рядок, відправлену як це поле.
Приклад схеми з валідацією типів:
type Query {
getUserById(id: Int!): User
}
type User {
id: Int!
name: String!
email: String
}
У цьому прикладі поле id має бути цілим числом і воно обов'язкове для передачі. GraphQL сам перевіряє це до виконання запиту. Проте в більш складних сценаріях нам потрібно підключати додаткові інструменти.
Кастомна валідація вхідних даних
Іноді базової перевірки типу замало. Наприклад, хочеться переконатися, що ID додатній, ім'я не надто довге, а email введений у правильному форматі. Для таких випадків Spring дає багатий інструментарій валідації.
Використання анотації @Valid
Можемо використовувати javax.validation для перевірки вхідних даних. Розглянемо приклад:
1. Змінимо схему:
type Mutation {
createUser(input: CreateUserInput!): User
}
input CreateUserInput {
name: String!
email: String!
age: Int
}
type User {
id: Int!
name: String!
email: String
age: Int
}
2. Створимо DTO для обробки вхідних даних:
import jakarta.validation.constraints.*;
public class CreateUserInput {
@NotBlank(message = "Ім'я не може бути пустим")
@Size(max = 50, message = "Ім'я не може бути довшим за 50 символів")
private String name;
@NotBlank(message = "Email обов'язковий")
@Email(message = "Некоректний формат email")
private String email;
@Min(value = 18, message = "Вік має бути не менше 18")
@Max(value = 120, message = "Вік не може бути більше 120")
private Integer age;
// Геттери та сеттери
}
3. Оновимо резолвер для обробки мутації:
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Component
@Validated
public class UserMutationResolver {
public User createUser(@Valid CreateUserInput input) {
// Логіка створення користувача
User user = new User();
user.setId(1); // Мокаємо ID
user.setName(input.getName());
user.setEmail(input.getEmail());
user.setAge(input.getAge());
return user;
}
}
4. Обробляємо виключення: Якщо користувач відправить некоректні дані, Spring автоматично згенерує виключення MethodArgumentNotValidException. Нам потрібно його обробити й повернути зрозуміле повідомлення клієнту.
Обробка помилок:
import graphql.GraphQLError;
import graphql.kickstart.execution.error.GraphQLErrorHandler;
import graphql.language.SourceLocation;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class CustomGraphQLErrorHandler implements GraphQLErrorHandler {
@Override
public List<GraphQLError> processErrors(List<GraphQLError> errors) {
return errors.stream()
.map(error -> {
if (error.getMessage().contains("Validation failed")) {
return GraphQLError.newError()
.message("Валідація не пройдена: " + error.getMessage())
.location(new SourceLocation(error.getLocations().get(0).getLine(), error.getLocations().get(0).getColumn()))
.build();
}
return error;
}).toList();
}
}
Обробка помилок у GraphQL
У випадку будь-яких помилок GraphQL повертає структуру, яка містить як дані, так і масив помилок. Наприклад:
{
"data": null,
"errors": [
{
"message": "Валідація не пройдена: Ім'я не може бути пустим",
"locations": [
{
"line": 2,
"column": 3
}
],
"extensions": {
"code": "VALIDATION_ERROR"
}
}
]
}
Приклад створення кастомних помилок з GraphQLError
Можемо створювати власні помилки для точнішого контролю:
import graphql.GraphQLError;
import graphql.language.SourceLocation;
import java.util.List;
public class ValidationError implements GraphQLError {
private final String message;
public ValidationError(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
@Override
public List<SourceLocation> getLocations() {
return null; // Локацію можна лишити null, якщо вона не критична
}
@Override
public Map<String, Object> getExtensions() {
return Map.of("code", "VALIDATION_ERROR");
}
}
Щоб не ловити кожну помилку вручну, ми можемо визначити обробник типу @ControllerAdvice:
import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter;
import org.springframework.stereotype.Component;
@Component
public class GlobalExceptionHandler extends DataFetcherExceptionResolverAdapter {
@Override
protected GraphQLError resolveToSingleError(Throwable ex) {
if (ex instanceof IllegalArgumentException) {
return new ValidationError(ex.getMessage());
}
return null;
}
}
Локалізація повідомлень про помилки
Для міжнародних застосунків важливо надавати повідомлення про помилки мовою користувача. Spring надає вбудовану підтримку локалізації через файли messages.properties.
Налаштування локалізації:
1. Створіть property-файли:
messages.properties(за замовчуванням)messages_ru.properties(для російської мови)
2. Приклад вмісту файлу:
name.notBlank=Ім'я не може бути пустим
email.invalid=Некоректний формат email
3. Налаштуйте LocaleResolver:
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
@Configuration
public class LocaleConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("messages");
source.setDefaultEncoding("UTF-8");
return source;
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver resolver = new SessionLocaleResolver();
resolver.setDefaultLocale(Locale.ENGLISH);
return resolver;
}
}
Логування помилок
Важливо не лише обробляти помилки для клієнтів, а й логувати їх для подальшого аналізу. Використовуйте бібліотеки типу SLF4J і Logback:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class LoggingErrorHandler {
private static final Logger logger = LoggerFactory.getLogger(LoggingErrorHandler.class);
public void logError(Throwable ex) {
logger.error("Сталася помилка: {}", ex.getMessage(), ex);
}
}
Ці підходи забезпечать якісну валідацію даних і обробку помилок у вашому GraphQL API, роблячи його більш надійним і зручним для взаємодії з клієнтами та розробниками.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ