Skip to content

Commit 34f8106

Browse files
committed
fix(v3/api): Fix more undocumented API quirks
1 parent 7a5cc22 commit 34f8106

File tree

5 files changed

+78
-29
lines changed

5 files changed

+78
-29
lines changed

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Location.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ data class Location(
7777
Codec.STRING.fieldOf("continent_code").forGetter(Location::continentCode),
7878
Codec.STRING.fieldOf("country_name").forGetter(Location::countryName),
7979
Codec.STRING.fieldOf("country_code").forGetter(Location::countryCode),
80-
Codec.STRING.fieldOf("region_name").orNullIf("Unknown").forNullableGetter(Location::regionName),
81-
Codec.STRING.fieldOf("region_code").orNullIf("Unknown").forNullableGetter(Location::regionCode),
82-
Codec.STRING.fieldOf("city_name").orNullIf("Unknown").forNullableGetter(Location::cityName),
83-
Codec.STRING.optionalFieldOf("postal_code").orNullIf("Unknown").forNullableGetter(Location::postalCode),
80+
Codec.STRING.fieldOf("region_name").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::regionName),
81+
Codec.STRING.fieldOf("region_code").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::regionCode),
82+
Codec.STRING.fieldOf("city_name").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::cityName),
83+
Codec.STRING.optionalFieldOf("postal_code").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::postalCode),
8484
Codec.DOUBLE.fieldOf("latitude").forGetter(Location::latitude),
8585
Codec.DOUBLE.fieldOf("longitude").forGetter(Location::longitude),
86-
Codec.STRING.fieldOf("timezone").orNullIf("Unknown").forNullableGetter(Location::timeZone),
86+
Codec.STRING.fieldOf("timezone").orNullIf("unknown", ignoreCase = true).forNullableGetter(Location::timeZone),
8787
Currency.CODEC.fieldOf("currency").forGetter(Location::currency),
8888
).apply(instance, ::Location)
8989
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Network.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ data class Network(
5151
@ApiStatus.Internal
5252
internal val CODEC = buildMapCodec { instance ->
5353
instance.group(
54-
Codecs.ASN_STRING.fieldOf("asn").orNullIf("unknown").forNullableGetter(Network::asn),
55-
CidrIpRange.STRING_CODEC.fieldOf("range").orNullIf("unknown").forNullableGetter(Network::range),
56-
Codec.STRING.optionalFieldOf("hostname").orNullIf("Unknown").forNullableGetter(Network::hostName),
57-
Codec.STRING.fieldOf("provider").orNullIf("Unknown").forNullableGetter(Network::provider),
58-
Codec.STRING.fieldOf("organisation").orNullIf("Unknown").forNullableGetter(Network::organisation),
54+
Codecs.ASN_STRING.fieldOf("asn").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::asn),
55+
CidrIpRange.STRING_CODEC.fieldOf("range").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::range),
56+
Codec.STRING.optionalFieldOf("hostname").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::hostName),
57+
Codec.STRING.fieldOf("provider").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::provider),
58+
Codec.STRING.fieldOf("organisation").orNullIf("unknown", ignoreCase = true).forNullableGetter(Network::organisation),
5959
Codec.STRING.fieldOf("type").forGetter(Network::type),
6060
).apply(instance, ::Network)
6161
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Operator.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package ru.epserv.proxycheck.v3.api.model.response
33
import com.mojang.serialization.Codec
44
import org.jetbrains.annotations.ApiStatus
55
import ru.epserv.proxycheck.v3.api.util.buildMapCodec
6+
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.forNullableGetter
7+
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.orNullIf
68
import ru.epserv.proxycheck.v3.api.util.codec.Codecs.setOf
9+
import java.util.*
10+
import kotlin.jvm.optionals.getOrNull
711

812
/**
913
* VPN/proxy operator information.
@@ -26,14 +30,30 @@ data class Operator(
2630
val protocols: Set<String>,
2731
val policies: OperatorPolicies,
2832
) {
33+
constructor(
34+
name: String,
35+
url: String,
36+
anonymity: Optional<String>,
37+
popularity: Optional<String>,
38+
protocols: Set<String>,
39+
policies: OperatorPolicies,
40+
) : this(
41+
name = name,
42+
url = url,
43+
anonymity = anonymity.getOrNull(),
44+
popularity = popularity.getOrNull(),
45+
protocols = protocols,
46+
policies = policies,
47+
)
48+
2949
companion object {
3050
@ApiStatus.Internal
3151
internal val CODEC = buildMapCodec { instance ->
3252
instance.group(
3353
Codec.STRING.fieldOf("name").forGetter(Operator::name),
3454
Codec.STRING.fieldOf("url").forGetter(Operator::url),
35-
Codec.STRING.optionalFieldOf("anonymity", "unknown").forGetter(Operator::anonymity),
36-
Codec.STRING.optionalFieldOf("popularity", "unknown").forGetter(Operator::popularity),
55+
Codec.STRING.optionalFieldOf("anonymity").orNullIf("unknown", ignoreCase = true).forNullableGetter(Operator::anonymity),
56+
Codec.STRING.optionalFieldOf("popularity").orNullIf("unknown", ignoreCase = true).forNullableGetter(Operator::popularity),
3757
Codec.STRING.setOf().fieldOf("protocols").forGetter(Operator::protocols),
3858
OperatorPolicies.CODEC.optionalFieldOf("policies", OperatorPolicies.UNKNOWN).forGetter(Operator::policies),
3959
).apply(instance, ::Operator)

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/model/response/Response.kt

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ sealed interface Response {
3232
@get:ApiStatus.AvailableSince("1.0.0")
3333
val status: ResponseStatus
3434

35+
/**
36+
* Response message (if present).
37+
*
38+
* @since 1.0.0
39+
* @author metabrix
40+
*/
41+
@get:ApiStatus.AvailableSince("1.0.0")
42+
val message: String?
43+
44+
/**
45+
* API version used for this request (if present).
46+
*
47+
* @since 1.0.0
48+
* @author metabrix
49+
*/
50+
@get:ApiStatus.AvailableSince("1.0.0")
51+
val apiVersion: String?
52+
3553
/**
3654
* Whether the response is successful ([ResponseStatus.isSuccessful]).
3755
*
@@ -67,8 +85,9 @@ sealed interface Response {
6785
* Successful response.
6886
*
6987
* @property status response status
70-
* @property results map of IP addresses to their results
7188
* @property message optional message (present if [ResponseStatus.hasMessage] is `true`)
89+
* @property apiVersion API version used for this request (if present)
90+
* @property results map of IP addresses to their results
7291
* @property node optional node identifier (present if [RequestConfiguration.returnNode] was set to `true`)
7392
* @property queryTime time the server took to process the query in milliseconds, excluding network RTT
7493
* @since 1.0.0
@@ -77,8 +96,9 @@ sealed interface Response {
7796
@ApiStatus.AvailableSince("1.0.0")
7897
data class Success(
7998
override val status: ResponseStatus,
99+
override val message: String?,
100+
override val apiVersion: String?,
80101
val results: Map<InetAddress, AddressResult>,
81-
val message: String?,
82102
val node: String?,
83103
val queryTime: Long?,
84104
) : Response {
@@ -89,14 +109,16 @@ sealed interface Response {
89109

90110
private constructor(
91111
status: ResponseStatus,
92-
results: Map<InetAddress, AddressResult>,
93112
message: Optional<String>,
113+
apiVersion: Optional<String>,
114+
results: Map<InetAddress, AddressResult>,
94115
node: Optional<String>,
95116
queryTime: Optional<Long>,
96117
) : this(
97118
status = status,
98-
results = results,
99119
message = message.getOrNull(),
120+
apiVersion = apiVersion.getOrNull(),
121+
results = results,
100122
node = node.getOrNull(),
101123
queryTime = queryTime.getOrNull(),
102124
)
@@ -109,11 +131,13 @@ sealed interface Response {
109131
Codec.STRING.optionalFieldOf("message").forNullableGetter(Success::message),
110132
Codec.STRING.optionalFieldOf("node").forNullableGetter(Success::node),
111133
Codec.LONG.optionalFieldOf("query_time").forNullableGetter(Success::queryTime),
112-
).apply(instance) { status, message, node, queryTime ->
134+
Codec.STRING.optionalFieldOf("version").forNullableGetter(Success::apiVersion),
135+
).apply(instance) { status, message, node, queryTime, apiVersion ->
113136
Success(
114137
status = status,
115-
results = emptyMap(),
116138
message = message,
139+
apiVersion = apiVersion,
140+
results = emptyMap(),
117141
node = node,
118142
queryTime = queryTime,
119143
)
@@ -141,10 +165,10 @@ sealed interface Response {
141165
input: T,
142166
): DataResult<Pair<Success, T>> {
143167
val metadataResult = METADATA_CODEC.decode(ops, input)
144-
val nonMetadataKeys = METADATA_MAP_CODEC.keys(ops).toList()
168+
val metadataKeys = METADATA_MAP_CODEC.keys(ops).toList()
145169

146170
return ops.getMap(input)
147-
.map { mapLike -> ops.createMap(mapLike.entries().filter { (key, _) -> key !in nonMetadataKeys }) }
171+
.map { mapLike -> ops.createMap(mapLike.entries().filter { (key, _) -> key !in metadataKeys }) }
148172
.flatMap { remainingEntries -> AddressResult.IP_STRING_TO_RESULT_CODEC.decode(ops, remainingEntries) }
149173
.map { (results, _) -> results }
150174
.flatMap { results -> metadataResult.map { (metadata, _) -> metadata.copy(results = results) } }
@@ -159,13 +183,15 @@ sealed interface Response {
159183
*
160184
* @property status response status
161185
* @property message optional message (present if [ResponseStatus.hasMessage] is `true`)
186+
* @property apiVersion API version used for this request (if present)
162187
* @since 1.0.0
163188
* @author metabrix
164189
*/
165190
@ApiStatus.AvailableSince("1.0.0")
166191
data class Failure(
167192
override val status: ResponseStatus,
168-
val message: String? = null,
193+
override val message: String? = null,
194+
override val apiVersion: String? = null,
169195
) : Response {
170196
init {
171197
require(!this.status.isSuccessful) { "Cannot create an unsuccessful response with a successful status: ${this.status}" }
@@ -175,9 +201,11 @@ sealed interface Response {
175201
private constructor(
176202
status: ResponseStatus,
177203
message: Optional<String>,
204+
apiVersion: Optional<String>,
178205
) : this(
179206
status = status,
180207
message = message.getOrNull(),
208+
apiVersion = apiVersion.getOrNull(),
181209
)
182210

183211
companion object {
@@ -186,6 +214,7 @@ sealed interface Response {
186214
instance.group(
187215
ResponseStatus.NON_SUCCESSFUL_CODEC.fieldOf("status").forGetter(Failure::status),
188216
Codec.STRING.optionalFieldOf("message").forNullableGetter(Failure::message),
217+
Codec.STRING.optionalFieldOf("version").forNullableGetter(Failure::apiVersion),
189218
).apply(instance, ::Failure)
190219
}
191220
}

v3/api/src/main/kotlin/ru/epserv/proxycheck/v3/api/util/codec/Codecs.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,22 @@ internal object Codecs {
3838
return MapCodec.assumeMapUnsafe(Codec.unboundedMap(this, valueCodec))
3939
}
4040

41-
fun <A : Any> Codec<A>.constant(constantValue: A): Codec<A> {
42-
fun transform(providedValue: A): DataResult<A> {
43-
if (providedValue == constantValue) {
41+
fun Codec<String>.constant(constantValue: String, ignoreCase: Boolean = false): Codec<String> {
42+
fun transform(providedValue: String): DataResult<String> {
43+
if (providedValue.equals(constantValue, ignoreCase)) {
4444
return DataResult.success(providedValue)
4545
}
46-
return DataResult.error { "Expected constant value '$constantValue', but got '$providedValue'" }
46+
return DataResult.error { "Expected constant value '$constantValue' (ignoreCase = $ignoreCase), but got '$providedValue'" }
4747
}
4848

4949
return this.flatXmap(::transform, ::transform)
5050
}
5151

5252
@JvmName("orNullIfMapCodec")
53-
fun <A : Any> MapCodec<A>.orNullIf(nullValue: String): MapCodec<Optional<A>> {
53+
fun <A : Any> MapCodec<A>.orNullIf(nullValue: String, ignoreCase: Boolean = false): MapCodec<Optional<A>> {
5454
val fieldName = requireNotNull(this.name) { "MapCodec must have a name to use orNullIf" }
5555
return Codec.mapEither(
56-
Codec.STRING.constant(nullValue).optionalFieldOf(fieldName),
56+
Codec.STRING.constant(nullValue, ignoreCase).optionalFieldOf(fieldName),
5757
this,
5858
).xmap(
5959
{ either -> either.map({ Optional.empty() }, { value -> Optional.of(value) }) },
@@ -62,10 +62,10 @@ internal object Codecs {
6262
}
6363

6464
@JvmName("orNullIfMapCodecOptional")
65-
fun <A : Any> MapCodec<Optional<A>>.orNullIf(nullValue: String): MapCodec<Optional<A>> {
65+
fun <A : Any> MapCodec<Optional<A>>.orNullIf(nullValue: String, ignoreCase: Boolean = false): MapCodec<Optional<A>> {
6666
val fieldName = requireNotNull(this.name) { "MapCodec must have a name to use orNullIf" }
6767
return Codec.mapEither(
68-
Codec.STRING.constant(nullValue).optionalFieldOf(fieldName),
68+
Codec.STRING.constant(nullValue, ignoreCase).optionalFieldOf(fieldName),
6969
this,
7070
).xmap(
7171
{ either -> either.map({ Optional.empty() }, { value -> value }) },

0 commit comments

Comments
 (0)