Skip to content

Commit 698944b

Browse files
committed
feat: Add a URL template utility.
1 parent f9d69e3 commit 698944b

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright @ 2025 - present 8x8, Inc.
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 org.jitsi.utils
17+
18+
import java.net.URI
19+
20+
class TemplatedUrl(
21+
private val template: String,
22+
private val map: MutableMap<String, String> = mutableMapOf(),
23+
private val requiredKeys: Set<String> = emptySet(),
24+
) {
25+
26+
/** Saves the given key:value pair in the map. */
27+
fun set(key: String, value: String) {
28+
map[key] = value
29+
}
30+
31+
/** Resolve the template with the current map. */
32+
fun resolve() = doResolve(map)
33+
34+
/** Resolve the template with the current map and a new key:value pair (does not save the new pair). */
35+
fun resolve(key: String, value: String) = resolve(mapOf(key to value))
36+
37+
/** Resolve the template with the current map and a new map pair (does not save the new map). */
38+
fun resolve(newMap: Map<String, String>) = doResolve(map + newMap)
39+
40+
private fun doResolve(map: Map<String, String>): URI {
41+
if (!requiredKeys.all { it in map }) {
42+
throw IllegalArgumentException("Missing required keys: ${requiredKeys - map.keys}")
43+
}
44+
var resolvedUrl = template
45+
for ((key, value) in map) {
46+
resolvedUrl = resolvedUrl.replace("{{$key}}", value)
47+
}
48+
return URI(resolvedUrl)
49+
}
50+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright @ 2025 - present 8x8, Inc.
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 org.jitsi.utils
17+
18+
import io.kotest.assertions.throwables.shouldThrow
19+
import io.kotest.core.spec.style.ShouldSpec
20+
import io.kotest.matchers.shouldBe
21+
import java.net.URI
22+
import java.net.URISyntaxException
23+
24+
class TemplatedUrlTest : ShouldSpec({
25+
context("TemplatedUrl") {
26+
should("resolve a simple template") {
27+
val template = "https://example.com/{{path}}"
28+
val templatedUrl = TemplatedUrl(template)
29+
templatedUrl.set("path", "test")
30+
31+
templatedUrl.resolve() shouldBe URI("https://example.com/test")
32+
}
33+
34+
should("resolve a template with multiple keys") {
35+
val template = "wss://{{host}}/{{path}}?param={{param}}"
36+
val templatedUrl = TemplatedUrl(template)
37+
templatedUrl.set("host", "example.com")
38+
templatedUrl.set("path", "api/resource")
39+
templatedUrl.set("param", "value")
40+
41+
templatedUrl.resolve() shouldBe URI("wss://example.com/api/resource?param=value")
42+
}
43+
44+
should("resolve a template with multiple keys when they are not set()") {
45+
val template = "wss://{{host}}/{{path}}?param={{param}}"
46+
val templatedUrl = TemplatedUrl(template)
47+
templatedUrl.set("host", "example.com")
48+
templatedUrl.set("path", "api/resource")
49+
templatedUrl.set("param", "value")
50+
51+
templatedUrl.resolve(
52+
mapOf("host" to "example.com", "path" to "api/resource", "param" to "value")
53+
) shouldBe URI("wss://example.com/api/resource?param=value")
54+
}
55+
56+
should("resolve with a new key-value pair without saving it") {
57+
val template = "https://example.com/{{path}}?param={{param}}"
58+
val templatedUrl = TemplatedUrl(template)
59+
templatedUrl.set("path", "api")
60+
61+
// Resolve with temporary param value
62+
templatedUrl.resolve("param", "temp") shouldBe URI("https://example.com/api?param=temp")
63+
64+
// Original template should not have the param saved
65+
templatedUrl.set("param", "permanent")
66+
templatedUrl.resolve() shouldBe URI("https://example.com/api?param=permanent")
67+
}
68+
69+
should("throw an exception when not all requiredKeys are set") {
70+
val template = "https://example.com/{{path}}?param={{param}}"
71+
val templatedUrl = TemplatedUrl(template, requiredKeys = setOf("param"))
72+
templatedUrl.set("path", "api")
73+
74+
shouldThrow<IllegalArgumentException> {
75+
templatedUrl.resolve()
76+
}
77+
}
78+
should("throw an exception when the URI is invalid") {
79+
val templatedUrl = TemplatedUrl("https://example.com/{{}}")
80+
81+
shouldThrow<URISyntaxException> {
82+
templatedUrl.resolve()
83+
}
84+
}
85+
86+
should("handle keys that appear multiple times") {
87+
val template = "https://{{host}}/{{path}}/{{path}}"
88+
val templatedUrl = TemplatedUrl(template)
89+
templatedUrl.set("host", "example.com")
90+
templatedUrl.set("path", "resource")
91+
92+
templatedUrl.resolve() shouldBe URI("https://example.com/resource/resource")
93+
}
94+
}
95+
})

0 commit comments

Comments
 (0)