Элегантная проверка нескольких свойств на ненулевые значения и отчет об ошибках в Kotlin

Вопрос или проблема

Учитывая, что у меня есть API модель, где все поля могут быть пустыми, и соответствующая доменная модель, где все поля обязательны:

data class MyApiModel(
    val property1: Int?,
    val property2: String?,
    val property3: Boolean?,
)

data class MyDomainModel(
    val property1: Int,
    val property2: String,
    val property3: Boolean,
)

Я хочу написать функцию сопоставления, которая будет конвертировать экземпляр API модели в доменную модель, проверяя, что все поля не нулевые. Я хочу, чтобы функция возвращала этот ModelMapperResult, содержащий список ошибок валидации:

data class ModelMapperResult<T>(
    val value: T,
    val validationErrors: List<String>,
)

Я пытаюсь найти шаблон кода для этого, который удовлетворяет двум целям:

  1. Он должен быть достаточно эргономичным; код должен быть относительно простым для чтения и написания.
  2. Он должен быть безопасным, например, предпочитать умные приведения типов и конструкции, такие как let, двойному восклицательному знаку (!!).

На данный момент я не нашел хорошего способа сделать это, поэтому я использую код сопоставления, который возвращает только первую найденную ошибку валидации:

fun MyApiModel.toDomainModel(): ModelMapperResult<MyDomainModel?> {
    val property1 = property1 ?: return ModelMapperResult(null, listOf("Отсутствует property1"))
    val property2 = property2 ?: return ModelMapperResult(null, listOf("Отсутствует property2"))
    val property3 = property3 ?: return ModelMapperResult(null, listOf("Отсутствует property3"))

    return ModelMapperResult(
        value = MyDomainModel(
            property1 = property1,
            property2 = property2,
            property3 = property3,
        ),
        validationErrors = emptyList(),
    )
}

Есть какие-нибудь идеи?

Ответ или решение

Для того чтобы эффективно валидировать несколько свойств модели API и собирать ошибки в Kotlin, можно воспользоваться функциональными возможностями языка и избегать использования конструкции с двойным восклицательным знаком (!!). Ниже представлен пример, как можно реализовать функцию маппинга с учетом ваших требований:

data class MyApiModel(
    val property1: Int?,
    val property2: String?,
    val property3: Boolean?,
)

data class MyDomainModel(
    val property1: Int,
    val property2: String,
    val property3: Boolean,
)

data class ModelMapperResult<T>(
    val value: T?,
    val validationErrors: List<String>,
)

fun MyApiModel.toDomainModel(): ModelMapperResult<MyDomainModel> {
    val validationErrors = mutableListOf<String>()

    // Проверка свойства property1
    val safeProperty1 = property1 ?: run {
        validationErrors.add("Отсутствует property1")
        null
    }

    // Проверка свойства property2
    val safeProperty2 = property2 ?: run {
        validationErrors.add("Отсутствует property2")
        null
    }

    // Проверка свойства property3
    val safeProperty3 = property3 ?: run {
        validationErrors.add("Отсутствует property3")
        null
    }

    return if (validationErrors.isEmpty()) {
        ModelMapperResult(
            value = MyDomainModel(
                property1 = safeProperty1!!, // Здесь !! безопасно, так как мы проверили на null выше
                property2 = safeProperty2!!,
                property3 = safeProperty3!!
            ),
            validationErrors = emptyList()
        )
    } else {
        ModelMapperResult(value = null, validationErrors = validationErrors)
    }
}

Пояснение:

  1. Сбор ошибок в список: Вместо немедленного возврата при нахождении первого пустого значения, мы собираем все ошибки в список validationErrors.

  2. Безопасность и читаемость: Используя конструкцию run, мы можем добавлять ошибки в список в случае нахождения значения null, что делает код более читаемым и безопасным.

  3. Проверка на наличие ошибок: В конце функции мы проверяем, пуст ли список ошибок. Если он пуст, то создаем экземпляр MyDomainModel с использованием безопасных свойств.

  4. Возврат результата: Если ошибки были собраны, мы возвращаем ModelMapperResult с null в качестве значения, иначе возвращаем успешно созданный объект доменной модели.

Таким образом, вы получите удобную и безопасную функцию для преобразования моделей с полной проверкой на наличие значений и аккуратным сбором ошибок.

Оцените материал
Добавить комментарий

Капча загружается...