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