Skip to content

Commit cd7c6f1

Browse files
committed
Refactor Subject and related APIs to unify handling of paths, path variables, and query parameters, update tests, and improve API ergonomics.
1 parent be0db30 commit cd7c6f1

18 files changed

Lines changed: 205 additions & 72 deletions

File tree

buildSrc/src/main/kotlin/Constants.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ val containerEventRowEventUpsertClassName =
4747
val pathSupplierClassName = ClassName(apiPackageName, "PathSupplier")
4848
val requestClassName = ClassName(apiPackageName, "Request")
4949
val requestSupplierClassName = ClassName(apiPackageName, "RequestSupplier")
50+
val subjectClassName = ClassName("com.caplin.integration.datasourcex.util", "Subject")
5051
val channelRequestClassName = ClassName(apiPackageName, "ChannelRequest")
5152
val channelRequestSupplierClassName = ClassName(apiPackageName, "ChannelRequestSupplier")
5253
val channelSupplierClassName = ClassName(apiPackageName, "ChannelSupplier")

buildSrc/src/main/kotlin/GenerateApi.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import com.squareup.kotlinpoet.FileSpec
55
import com.squareup.kotlinpoet.FunSpec
66
import com.squareup.kotlinpoet.KModifier.INTERNAL
77
import com.squareup.kotlinpoet.KModifier.PRIVATE
8-
import com.squareup.kotlinpoet.LIST
98
import com.squareup.kotlinpoet.LambdaTypeName
109
import com.squareup.kotlinpoet.MAP
1110
import com.squareup.kotlinpoet.ParameterSpec
@@ -43,8 +42,8 @@ private val mappingMessageType =
4342
name = "Mapping",
4443
doc =
4544
"[mapping](https://www.caplin.com/developer/caplin-platform/platform-architecture/mapping) from one subject to another",
46-
sendClassName = LIST,
47-
sendTypeName = LIST.parameterizedBy(STRING),
45+
sendClassName = subjectClassName,
46+
sendTypeName = subjectClassName,
4847
)
4948

5049
private val jsonMessageType =

reactive/api/api/datasourcex-reactive-api.api

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,12 @@ public final class com/caplin/integration/datasourcex/reactive/api/ChannelConfig
103103
public final fun setRecordType (Lcom/caplin/integration/datasourcex/reactive/api/RecordType;)V
104104
}
105105

106-
public final class com/caplin/integration/datasourcex/reactive/api/ChannelRequest {
107-
public fun <init> (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/Object;)V
108-
public final fun getPath ()Ljava/lang/String;
109-
public final fun getPathVariables ()Ljava/util/Map;
110-
public final fun getQueryParameters ()Ljava/util/Map;
106+
public final class com/caplin/integration/datasourcex/reactive/api/ChannelRequest : com/caplin/integration/datasourcex/util/Subject {
107+
public fun <init> (Ljava/lang/String;Ljava/util/LinkedHashMap;Ljava/util/Map;Ljava/lang/Object;)V
108+
public fun getPath ()Ljava/lang/String;
109+
public fun getPathParameters ()Ljava/util/List;
110+
public final fun getPathVariables ()Ljava/util/LinkedHashMap;
111+
public fun getQueryParameters ()Ljava/util/Map;
111112
public final fun getReceive ()Ljava/lang/Object;
112113
}
113114

@@ -207,11 +208,12 @@ public final class com/caplin/integration/datasourcex/reactive/api/RecordType :
207208
public static fun values ()[Lcom/caplin/integration/datasourcex/reactive/api/RecordType;
208209
}
209210

210-
public final class com/caplin/integration/datasourcex/reactive/api/Request {
211-
public fun <init> (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;)V
212-
public final fun getPath ()Ljava/lang/String;
213-
public final fun getPathVariables ()Ljava/util/Map;
214-
public final fun getQueryParameters ()Ljava/util/Map;
211+
public final class com/caplin/integration/datasourcex/reactive/api/Request : com/caplin/integration/datasourcex/util/Subject {
212+
public fun <init> (Ljava/lang/String;Ljava/util/LinkedHashMap;Ljava/util/Map;)V
213+
public fun getPath ()Ljava/lang/String;
214+
public fun getPathParameters ()Ljava/util/List;
215+
public final fun getPathVariables ()Ljava/util/LinkedHashMap;
216+
public fun getQueryParameters ()Ljava/util/Map;
215217
}
216218

217219
public abstract interface class com/caplin/integration/datasourcex/reactive/api/RequestSupplier {
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
package com.caplin.integration.datasourcex.reactive.api
22

3+
import com.caplin.integration.datasourcex.util.Subject
4+
35
/**
46
* A decomposed subject request passed to a supplier for a channel bind.
57
*
68
* @property path The full requested subject.
7-
* @property pathVariables The path variables extracted from the subject, keyed by name.
9+
* @property pathVariables The path variables extracted from the subject, keyed by name, in pattern
10+
* order (which backs [pathParameters]).
811
* @property queryParameters The query parameters parsed from the subject's optional trailing
912
* `?a=b&c=d`, or empty when there is no query.
1013
* @property receive The flow of messages received from the client on this channel.
1114
*/
1215
class ChannelRequest<R>(
13-
val path: String,
14-
val pathVariables: Map<String, String>,
15-
val queryParameters: Map<String, String>,
16+
override val path: String,
17+
val pathVariables: LinkedHashMap<String, String>,
18+
override val queryParameters: Map<String, String>,
1619
val receive: R,
17-
)
20+
) : Subject {
21+
override val pathParameters: List<String>
22+
get() = pathVariables.values.toList()
23+
}
Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package com.caplin.integration.datasourcex.reactive.api
22

3+
import com.caplin.integration.datasourcex.util.Subject
4+
35
/**
46
* A decomposed subject request passed to a supplier for an active or container bind.
57
*
68
* @property path The full requested subject.
7-
* @property pathVariables The path variables extracted from the subject, keyed by name.
9+
* @property pathVariables The path variables extracted from the subject, keyed by name, in pattern
10+
* order (which backs [pathParameters]).
811
* @property queryParameters The query parameters parsed from the subject's optional trailing
912
* `?a=b&c=d`, or empty when there is no query.
1013
*/
1114
class Request(
12-
val path: String,
13-
val pathVariables: Map<String, String>,
14-
val queryParameters: Map<String, String>,
15-
)
15+
override val path: String,
16+
val pathVariables: LinkedHashMap<String, String>,
17+
override val queryParameters: Map<String, String>,
18+
) : Subject {
19+
override val pathParameters: List<String>
20+
get() = pathVariables.values.toList()
21+
}

reactive/core/src/main/kotlin/com/caplin/integration/datasourcex/reactive/core/Binder.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ import com.caplin.integration.datasourcex.reactive.api.Request
4040
import com.caplin.integration.datasourcex.reactive.api.ServiceConfig
4141
import com.caplin.integration.datasourcex.util.AntPatternNamespace
4242
import com.caplin.integration.datasourcex.util.AntPatternNamespace.Companion.addIncludeNamespace
43+
import com.caplin.integration.datasourcex.util.Subject
4344
import com.caplin.integration.datasourcex.util.getLogger
44-
import java.net.URLEncoder
4545
import java.util.concurrent.ConcurrentHashMap
4646
import kotlinx.coroutines.ExperimentalCoroutinesApi
4747
import kotlinx.coroutines.Job
@@ -102,22 +102,12 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
102102
private fun AntPatternNamespace.request(subject: String): Request =
103103
Request(subject, extractPathVariables(subject), extractQueryParameters(subject))
104104

105-
/**
106-
* Builds a mapping's target subject by URL-encoding each part and joining them under a leading
107-
* `/`, mirroring how [AntPatternNamespace.extractPathVariables] URL-decodes the parts of an
108-
* incoming subject.
109-
*/
110-
private fun List<String>.toMappingSubject(): String =
111-
joinToString(separator = "/", prefix = "/") {
112-
URLEncoder.encode(it, AntPatternNamespace.CHARSET).replace("+", "%20")
113-
}
114-
115105
private data class ServiceInfo(
116106
val service: Service,
117107
val serviceConfig: ServiceConfig,
118108
)
119109

120-
private data class MappingInfo(val state: StateFlow<List<String>?>, val job: Job)
110+
private data class MappingInfo(val state: StateFlow<Subject?>, val job: Job)
121111

122112
private data class ChannelInfo<R : Any>(val job: Job, val fromClientChannel: Channel<R>) {
123113
fun cancel() {
@@ -139,7 +129,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
139129
fun bindActiveMapping(
140130
configure: ConfigBlock<ActiveConfig.Mapping>,
141131
namespace: AntPatternNamespace,
142-
supplier: (Request) -> Flow<List<String>>,
132+
supplier: (Request) -> Flow<Subject>,
143133
) {
144134
val config = with(configure) { ActiveConfig.Mapping().apply { invoke() } }
145135
val namespace = namespace.withUsernameDecoding(config.objectMappings)
@@ -158,11 +148,11 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
158148
}
159149

160150
override fun onRequest(request: RequestEvent) {
161-
fun publishMapping(value: List<String>) {
151+
fun publishMapping(value: Subject) {
162152
publisher.publishMappingMessage(
163153
publisher.messageFactory.createMappingMessage(
164154
request.subject,
165-
value.toMappingSubject(),
155+
value.path,
166156
),
167157
)
168158
}
@@ -448,7 +438,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
448438
fun bindBroadcastMapping(
449439
configure: ConfigBlock<BroadcastConfig.Mapping>,
450440
namespace: AntPatternNamespace,
451-
flow: Flow<BroadcastEvent<List<String>>>,
441+
flow: Flow<BroadcastEvent<Subject>>,
452442
) {
453443
val config = with(configure) { BroadcastConfig.Mapping().apply { invoke() } }
454444
serviceInfo?.registerNamespace(namespace, emptyMap())
@@ -482,7 +472,7 @@ private constructor(val dataSource: ScopedDataSource, private val serviceInfo: S
482472
val message =
483473
publisher.messageFactory.createMappingMessage(
484474
it.subject,
485-
it.value.toMappingSubject(),
475+
it.value.path,
486476
)
487477
cachedEvents?.put(it.subject, message)
488478

reactive/java-flow/api/datasourcex-java-flow.api

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,11 @@ public final class com/caplin/integration/datasourcex/reactive/java/BindActiveMa
107107
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;)V
108108
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/java/BindActiveMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/PathVariablesSupplier;ILjava/lang/Object;)V
109109
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/java/BindActiveMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;ILjava/lang/Object;)V
110-
public final fun path (Ljava/lang/String;Ljava/util/List;)V
111-
public final fun path (Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
110+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;)V
111+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
112112
public final fun path (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;)V
113113
public final fun path (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
114-
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindActiveMapping;Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
114+
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindActiveMapping;Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
115115
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindActiveMapping;Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
116116
public final fun pattern (Ljava/lang/String;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/PathVariablesSupplier;)V
117117
public final fun pattern (Ljava/lang/String;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;)V
@@ -153,11 +153,11 @@ public final class com/caplin/integration/datasourcex/reactive/java/BindBroadcas
153153
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Ljava/util/concurrent/Flow$Publisher;)V
154154
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
155155
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/java/BindBroadcastMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
156-
public final fun path (Ljava/lang/String;Ljava/util/List;)V
157-
public final fun path (Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
156+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;)V
157+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
158158
public final fun path (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;)V
159159
public final fun path (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
160-
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindBroadcastMapping;Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
160+
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindBroadcastMapping;Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
161161
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/java/BindBroadcastMapping;Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
162162
public final fun pattern (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;)V
163163
public final fun pattern (Ljava/lang/String;Ljava/util/concurrent/Flow$Publisher;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V

reactive/kotlin/api/datasourcex-kotlin.api

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ public final class com/caplin/integration/datasourcex/reactive/kotlin/BindActive
9494
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;)V
9595
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindActiveMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/PathVariablesSupplier;ILjava/lang/Object;)V
9696
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindActiveMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;ILjava/lang/Object;)V
97-
public final fun path (Ljava/lang/String;Ljava/util/List;)V
98-
public final fun path (Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
97+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;)V
98+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
9999
public final fun path (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;)V
100100
public final fun path (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
101-
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindActiveMapping;Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
101+
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindActiveMapping;Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
102102
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindActiveMapping;Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
103103
public final fun pattern (Ljava/lang/String;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/PathVariablesSupplier;)V
104104
public final fun pattern (Ljava/lang/String;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;Lcom/caplin/integration/datasourcex/reactive/api/RequestSupplier;)V
@@ -140,11 +140,11 @@ public final class com/caplin/integration/datasourcex/reactive/kotlin/BindBroadc
140140
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lkotlinx/coroutines/flow/Flow;)V
141141
public final fun namespace (Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
142142
public static synthetic fun namespace$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindBroadcastMapping;Lcom/caplin/integration/datasourcex/util/AntPatternNamespace;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
143-
public final fun path (Ljava/lang/String;Ljava/util/List;)V
144-
public final fun path (Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
143+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;)V
144+
public final fun path (Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
145145
public final fun path (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;)V
146146
public final fun path (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V
147-
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindBroadcastMapping;Ljava/lang/String;Ljava/util/List;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
147+
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindBroadcastMapping;Ljava/lang/String;Lcom/caplin/integration/datasourcex/util/Subject;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
148148
public static synthetic fun path$default (Lcom/caplin/integration/datasourcex/reactive/kotlin/BindBroadcastMapping;Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;ILjava/lang/Object;)V
149149
public final fun pattern (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;)V
150150
public final fun pattern (Ljava/lang/String;Lkotlinx/coroutines/flow/Flow;Lcom/caplin/integration/datasourcex/reactive/api/ConfigBlock;)V

reactive/kotlin/src/test/kotlin/com/caplin/integration/datasourcex/reactive/kotlin/BindTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,7 @@ class BindTest :
684684
captured!!.also { request ->
685685
request.path shouldBeEqual "/QUERY/EUR%2FUSD?location=ONSHORE&tenor=SPOT"
686686
request.pathVariables shouldBeEqual mapOf("productPair" to "EUR/USD")
687+
request.pathParameters shouldBeEqual listOf("EUR/USD")
687688
request.queryParameters shouldBeEqual mapOf("location" to "ONSHORE", "tenor" to "SPOT")
688689
}
689690
}
@@ -728,6 +729,8 @@ class BindTest :
728729
// the ordinary path variable still is.
729730
captured!!.pathVariables shouldBeEqual
730731
mapOf("username" to "john%2Fdoe", "productPair" to "EUR/USD")
732+
// pathParameters mirrors pathVariables' values in pattern order.
733+
captured!!.pathParameters shouldBeEqual listOf("john%2Fdoe", "EUR/USD")
731734
}
732735
}
733736

0 commit comments

Comments
 (0)