From e592bd3c306d4525e467e7879bde0d4dceb20806 Mon Sep 17 00:00:00 2001 From: metabrixkt Date: Fri, 20 Feb 2026 00:24:45 +0500 Subject: [PATCH] chore(v3/api): Replace arbitrary string types for `operator.anonymity`, `operator.popularity` and `operator.protocols` with proper DTOs --- .../v3/api/model/response/Operator.kt | 18 +++--- .../v3/api/model/response/OperatorProtocol.kt | 60 +++++++++++++++++++ .../v3/api/model/response/OperatorRating.kt | 35 +++++++++++ 3 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorProtocol.kt create mode 100644 v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorRating.kt diff --git a/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt index 8ed6f88..7da0fdc 100644 --- a/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt +++ b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt @@ -31,20 +31,20 @@ import kotlin.jvm.optionals.getOrNull data class Operator( val name: String, val url: String, - val anonymity: String?, - val popularity: String?, + val anonymity: OperatorRating?, + val popularity: OperatorRating?, val services: Set?, - val protocols: Set, + val protocols: Set, val policies: OperatorPolicies, val additionalOperators: Set?, ) { constructor( name: String, url: String, - anonymity: Optional, - popularity: Optional, + anonymity: Optional, + popularity: Optional, services: Optional>, - protocols: Set, + protocols: Set, policies: OperatorPolicies, additionalOperators: Optional>, ) : this( @@ -64,10 +64,10 @@ data class Operator( instance.group( Codec.STRING.fieldOf("name").forGetter(Operator::name), Codec.STRING.fieldOf("url").forGetter(Operator::url), - Codec.STRING.optionalFieldOf("anonymity").forNullableGetter(Operator::anonymity), - Codec.STRING.optionalFieldOf("popularity").forNullableGetter(Operator::popularity), + OperatorRating.CODEC.optionalFieldOf("anonymity").forNullableGetter(Operator::anonymity), + OperatorRating.CODEC.optionalFieldOf("popularity").forNullableGetter(Operator::popularity), OperatorService.CODEC.setOf().optionalFieldOf("services").forNullableGetter(Operator::services), - Codec.STRING.setOf().fieldOf("protocols").forGetter(Operator::protocols), + OperatorProtocol.CODEC.setOf().fieldOf("protocols").forGetter(Operator::protocols), OperatorPolicies.CODEC.optionalFieldOf("policies", OperatorPolicies.UNKNOWN).forGetter(Operator::policies), Codec.STRING.setOf().optionalFieldOf("additional_operators").forNullableGetter(Operator::additionalOperators), ).apply(instance, ::Operator) diff --git a/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorProtocol.kt b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorProtocol.kt new file mode 100644 index 0000000..d7b58a7 --- /dev/null +++ b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorProtocol.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026 ElectroPlay + * SPDX-License-Identifier: MPL-2.0 + */ + +package ru.epserv.proxycheck.v3.api.model.response + +import com.mojang.serialization.Codec +import org.jetbrains.annotations.ApiStatus + +/** + * A protocol an operator offers their services through. + * + * Operator protocols are not standardized. New ones may be introduced (and existing ones may be + * removed) within the same API version, which is the reason why [OperatorProtocol] is not an enum + * class. + * + * @property id operator protocol identifier + * @since 1.0.0 + * @author metabrix + * @see [OperatorProtocol.fromId] + */ +@ConsistentCopyVisibility +@ApiStatus.AvailableSince("1.0.0") +data class OperatorProtocol private constructor(val id: String) { + companion object { + private val cache = mutableMapOf() + + fun fromId(id: String): OperatorProtocol { + return synchronized(cache) { cache.getOrPut(id) { OperatorProtocol(id) } } + } + + @ApiStatus.Internal + val CODEC: Codec = Codec.STRING.xmap(::fromId, OperatorProtocol::id) + + val OPENVPN = fromId("OpenVPN") + + val WIREGUARD = fromId("Wireguard") + + val ONION_ROUTING = fromId("Onion Routing") + + val IPSEC = fromId("IPSec") + + val SSH2 = fromId("SSH2") + + val PPTP = fromId("PPTP") + + val L2TP = fromId("L2TP") + + val SOCKS5 = fromId("SOCKS5") + + val IKEV2 = fromId("IKEv2") + + val HTTP = fromId("HTTP") + + val HTTPS = fromId("HTTPS") + + val SOCKS4 = fromId("SOCKS4") + } +} diff --git a/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorRating.kt b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorRating.kt new file mode 100644 index 0000000..0877175 --- /dev/null +++ b/v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/OperatorRating.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 ElectroPlay + * SPDX-License-Identifier: MPL-2.0 + */ + +package ru.epserv.proxycheck.v3.api.model.response + +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import org.jetbrains.annotations.ApiStatus + +/** + * Value for [Operator.anonymity] and [Operator.popularity] fields. + * + * @property id string representation of the rating + * @since 1.0.0 + * @author metabrix + */ +@ApiStatus.AvailableSince("1.0.0") +enum class OperatorRating(val id: String) { + LOW("low"), + MEDIUM("medium"), + HIGH("high"), + ; + + companion object { + private val values = entries.associateBy { it.id } + + @ApiStatus.Internal + val CODEC: Codec = Codec.STRING.comapFlatMap( + { value -> values[value]?.let { DataResult.success(it) } ?: DataResult.error { "Unknown operator rating: $value" } }, + { it.id }, + ) + } +}