1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.intentresolver.validation 17 18 import com.android.intentresolver.validation.Importance.CRITICAL 19 import com.android.intentresolver.validation.Importance.WARNING 20 21 /** 22 * Provides a mechanism for validating a result from a set of properties. 23 * 24 * The results of validation are provided as [findings]. 25 */ 26 interface Validation { 27 val findings: List<Finding> 28 29 /** 30 * Require a valid property. 31 * 32 * If [property] is not valid, this [Validation] will be immediately completed as [Invalid]. 33 * 34 * @param property the required property 35 * @return a valid **T** 36 */ 37 @Throws(InvalidResultError::class) fun <T> required(property: Validator<T>): T 38 39 /** 40 * Request an optional value for a property. 41 * 42 * If [property] is not valid, this [Validation] will be immediately completed as [Invalid]. 43 * 44 * @param property the required property 45 * @return a valid **T** 46 */ 47 fun <T> optional(property: Validator<T>): T? 48 49 /** 50 * Report a property as __ignored__. 51 * 52 * The presence of any value will report a warning citing [reason]. 53 */ 54 fun <T> ignored(property: Validator<T>, reason: String) 55 } 56 57 /** Performs validation for a specific key -> value pair. */ 58 interface Validator<T> { 59 val key: String 60 61 /** 62 * Performs validation on a specific value from [source]. 63 * 64 * @param source a source for reading the property value. Values are intentionally untyped 65 * (Any?) to avoid upstream code from making type assertions through type inference. Types are 66 * asserted later using a [Validator]. 67 * @param importance the importance of any findings 68 */ validatenull69 fun validate(source: (String) -> Any?, importance: Importance): ValidationResult<T> 70 } 71 72 internal class InvalidResultError internal constructor() : Error() 73 74 /** 75 * Perform a number of validations on the source, assembling and returning a Result. 76 * 77 * When an exception is thrown by [validate], it is caught here. In response, a failed 78 * [ValidationResult] is returned containing a [CRITICAL] [Finding] for the exception. 79 * 80 * @param validate perform validations and return a [ValidationResult] 81 */ 82 fun <T> validateFrom(source: (String) -> Any?, validate: Validation.() -> T): ValidationResult<T> { 83 val validation = ValidationImpl(source) 84 return runCatching { validate(validation) } 85 .fold( 86 onSuccess = { result -> Valid(result, validation.findings) }, 87 onFailure = { 88 when (it) { 89 // A validator has interrupted validation. Return the findings. 90 is InvalidResultError -> Invalid(validation.findings) 91 92 // Some other exception was thrown from [validate], 93 else -> Invalid(error = UncaughtException(it)) 94 } 95 } 96 ) 97 } 98 99 private class ValidationImpl(val source: (String) -> Any?) : Validation { 100 override val findings = mutableListOf<Finding>() 101 optionalnull102 override fun <T> optional(property: Validator<T>): T? = validate(property, WARNING) 103 104 override fun <T> required(property: Validator<T>): T { 105 return validate(property, CRITICAL) ?: throw InvalidResultError() 106 } 107 ignorednull108 override fun <T> ignored(property: Validator<T>, reason: String) { 109 val result = property.validate(source, WARNING) 110 if (result is Valid) { 111 // Note: Any warnings about the value itself (result.findings) are ignored. 112 findings += IgnoredValue(property.key, reason) 113 } 114 } 115 validatenull116 private fun <T> validate(property: Validator<T>, importance: Importance): T? { 117 return runCatching { property.validate(source, importance) } 118 .fold( 119 onSuccess = { result -> 120 return when (result) { 121 is Valid -> { 122 findings += result.warnings 123 result.value 124 } 125 is Invalid -> { 126 findings += result.errors 127 null 128 } 129 } 130 }, 131 onFailure = { 132 findings += UncaughtException(it, property.key) 133 null 134 } 135 ) 136 } 137 } 138