Coverage Summary for Class: SdkValidation (cloud.mindbox.mobile_sdk)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| SdkValidation |
75%
(6/8)
|
100%
(10/10)
|
88.9%
(16/18)
|
88%
(110/125)
|
| SdkValidation$Error |
0%
(0/3)
|
|
0%
(0/9)
|
0%
(0/83)
|
| SdkValidation$validateConfiguration$1 |
0%
(0/1)
|
0%
(0/24)
|
0%
(0/16)
|
0%
(0/106)
|
| Total |
50%
(6/12)
|
29.4%
(10/34)
|
37.2%
(16/43)
|
35%
(110/314)
|
package cloud.mindbox.mobile_sdk
import androidx.core.util.PatternsCompat
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
internal object SdkValidation {
enum class Error(val critical: Boolean, val message: String) {
EMPTY_DOMAIN(true, "Domain must not be empty"),
INVALID_FORMAT_DOMAIN(true, "The domain format is not valid"),
INVALID_DOMAIN(true, "The domain is not valid"),
EMPTY_ENDPOINT(true, "Endpoint must not be empty"),
INVALID_DEVICE_ID(false, "Invalid previous device UUID format"),
INVALID_INSTALLATION_ID(false, "Invalid UUID format of previous installationId"),
INVALID_OPERATIONS_DOMAIN(true, "The operationsDomain is not valid");
override fun toString() = "$name(critical=$critical, message=$message)"
}
/**
* Strips http:// or https:// scheme and trailing slashes from [input].
* "https://api.mindbox.ru/" → "api.mindbox.ru"
* "api.mindbox.ru/" → "api.mindbox.ru"
*/
fun extractHost(input: String): String =
input.trim()
.removePrefix("https://")
.removePrefix("http://")
.trimEnd('/')
/**
* Returns a full base URL. If [hostOrUrl] already contains a scheme (http:// or https://),
* it is preserved. Otherwise https:// is prepended.
* "api.mindbox.ru" → "https://api.mindbox.ru"
* "http://proxy.example.com" → "http://proxy.example.com"
*/
fun toBaseUrl(hostOrUrl: String): String {
val trimmed = hostOrUrl.trim()
return if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
trimmed.trimEnd('/')
} else {
"https://${trimmed.trimEnd('/')}"
}
}
/**
* Returns true if [domain] is a valid domain host, accepting optional http:// or https:// prefix
* and optional trailing slash.
*/
fun isValidDomain(domain: String): Boolean {
val host = extractHost(domain)
return host.isNotBlank() && isDomainValid(host)
}
/**
* Path-prefix rule for [isValidOperationsDomain]: zero or more non-empty segments made
* of unreserved URL characters or complete `%XX` percent-encoded octets. Query, fragment,
* empty segments, and a bare/incomplete `%` (not followed by two hex digits) do not
* match — an incomplete escape passes this far but corrupts (or fails) URL building at
* request time, so it must be rejected at validation, not discovered later at runtime.
*/
private val PATH_PREFIX_REGEX = Regex("^(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*$")
/**
* Returns true if [value] is a valid operations domain: a host optionally followed by
* a path prefix (e.g. "domain.com/api/v2"). Accepts optional http:// or https:// prefix
* and optional trailing slash. Query, fragment and empty path segments are rejected.
*/
fun isValidOperationsDomain(value: String): Boolean {
val hostAndPath = extractHost(value)
val host = hostAndPath.substringBefore('/')
if (host.isBlank() || !isDomainValid(host)) return false
return PATH_PREFIX_REGEX.matches(hostAndPath.removePrefix(host))
}
fun validateConfiguration(
domain: String,
endpointId: String,
previousDeviceUUID: String,
previousInstallationId: String,
operationsDomain: String? = null,
) = LoggingExceptionHandler.runCatching(defaultValue = listOf()) {
mutableListOf<Error>().apply {
when {
domain.isBlank() -> add(Error.EMPTY_DOMAIN)
else -> {
val host = extractHost(domain)
when {
host.isBlank() -> add(Error.INVALID_FORMAT_DOMAIN)
!isDomainValid(host) -> add(Error.INVALID_DOMAIN)
}
}
}
if (endpointId.isBlank()) {
add(Error.EMPTY_ENDPOINT)
}
if (previousDeviceUUID.isNotEmpty() && !previousDeviceUUID.isUuid()) {
add(Error.INVALID_DEVICE_ID)
}
if (previousInstallationId.isNotEmpty() && !previousInstallationId.isUuid()) {
add(Error.INVALID_INSTALLATION_ID)
}
if (operationsDomain != null && !isValidOperationsDomain(operationsDomain)) {
add(Error.INVALID_OPERATIONS_DOMAIN)
}
}
}
private fun isDomainValid(domain: String) = PatternsCompat.DOMAIN_NAME.matcher(domain).matches()
}