Skip to content

Commit 867f3e4

Browse files
sw-squaresvc-squareup-copybara
authored andcommitted
This PR marks the wisp tokens package as depreacted, and moves their
functionality over to their equivalent `misk.tokens.{Real,Fake,}TokenGenerator2` versions. (The `2` is because a migration was already in place; once the wisp -> misk migration is done, it should be able to be completed more smoothly) GitOrigin-RevId: 27d160f0016e8d1cee625d5803cd7dae78dfa87a
1 parent 41aaa0f commit 867f3e4

File tree

10 files changed

+247
-19
lines changed

10 files changed

+247
-19
lines changed

misk-core/api/misk-core.api

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,6 @@ public final class misk/tokens/FakeTokenGenerator : misk/testing/FakeFixture, wi
311311
public final class misk/tokens/FakeTokenGenerator2 : misk/testing/FakeFixture, misk/tokens/TokenGenerator2 {
312312
public fun <init> ()V
313313
public fun generate (Ljava/lang/String;I)Ljava/lang/String;
314-
public fun reset ()V
315314
}
316315

317316
public final class misk/tokens/FakeTokenGeneratorModule : misk/inject/KAbstractModule {
@@ -329,11 +328,15 @@ public final class misk/tokens/RealTokenGenerator2 : misk/tokens/TokenGenerator2
329328
}
330329

331330
public abstract interface class misk/tokens/TokenGenerator2 {
331+
public static final field CANONICALIZE_LENGTH_MAX I
332+
public static final field CANONICALIZE_LENGTH_MIN I
332333
public static final field Companion Lmisk/tokens/TokenGenerator2$Companion;
333334
public abstract fun generate (Ljava/lang/String;I)Ljava/lang/String;
334335
}
335336

336337
public final class misk/tokens/TokenGenerator2$Companion {
338+
public static final field CANONICALIZE_LENGTH_MAX I
339+
public static final field CANONICALIZE_LENGTH_MIN I
337340
public final fun canonicalize (Ljava/lang/String;)Ljava/lang/String;
338341
}
339342

misk-core/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ dependencies {
3737
testImplementation(project(":misk-core"))
3838
testImplementation(project(":misk-testing"))
3939
testImplementation(project(":misk-testing-api"))
40+
41+
testImplementation(libs.kotestAssertions)
42+
testImplementation(libs.kotestAssertionsShared)
43+
testImplementation(libs.kotestCommon)
44+
testImplementation(libs.kotestFrameworkApi)
45+
testRuntimeOnly(libs.junitEngine)
46+
testRuntimeOnly(libs.kotestJunitRunnerJvm)
47+
4048
}
4149

4250
mavenPublishing {

misk-core/src/main/kotlin/misk/tokens/FakeTokenGenerator.kt

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package misk.tokens
33
import jakarta.inject.Inject
44
import jakarta.inject.Singleton
55
import misk.testing.FakeFixture
6+
import misk.tokens.TokenGenerator2.Companion.CANONICALIZE_LENGTH_MAX
7+
import misk.tokens.TokenGenerator2.Companion.CANONICALIZE_LENGTH_MIN
8+
import misk.tokens.TokenGenerator2.Companion.canonicalize
9+
import java.util.*
10+
import java.util.concurrent.atomic.AtomicLong
611

712
@Singleton
813
class FakeTokenGenerator @Inject constructor() : FakeFixture(), TokenGenerator {
@@ -17,10 +22,30 @@ class FakeTokenGenerator @Inject constructor() : FakeFixture(), TokenGenerator {
1722

1823
@Singleton
1924
class FakeTokenGenerator2 @Inject constructor() : FakeFixture(), TokenGenerator2 {
20-
private val tokenGenerator = wisp.token.FakeTokenGenerator()
25+
internal val nextByLabel by resettable {
26+
Collections.synchronizedMap<String, AtomicLong>(
27+
mutableMapOf()
28+
)
29+
}
2130

22-
override fun generate(label: String?, length: Int) =
23-
tokenGenerator.generate(label, length)
31+
override fun generate(label: String?, length: Int): String {
32+
require(length in CANONICALIZE_LENGTH_MIN..CANONICALIZE_LENGTH_MAX) {
33+
"unexpected length: $length"
34+
}
35+
36+
// Strip 'u' characters which aren't used in Crockford Base32 (due to possible profanity).
37+
val effectiveLabel = (label ?: "").replace("u", "", ignoreCase = true)
38+
39+
val atomicLong = nextByLabel.computeIfAbsent(effectiveLabel) { AtomicLong(1L) }
40+
val suffix = atomicLong.getAndIncrement().toString()
41+
42+
val unpaddedLength = effectiveLabel.length + suffix.length
43+
val rawResult = when {
44+
unpaddedLength < length -> effectiveLabel + "0".repeat(length - unpaddedLength) + suffix
45+
suffix.length <= length -> effectiveLabel.substring(0, length - suffix.length) + suffix
46+
else -> suffix.substring(suffix.length - length) // Possible collision.
47+
}
2448

25-
override fun reset() = tokenGenerator.reset()
49+
return canonicalize(rawResult)
50+
}
2651
}

misk-core/src/main/kotlin/misk/tokens/RealTokenGenerator.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,33 @@ package misk.tokens
22

33
import jakarta.inject.Inject
44
import jakarta.inject.Singleton
5+
import misk.tokens.TokenGenerator2.Companion.CANONICALIZE_LENGTH_MAX
6+
import misk.tokens.TokenGenerator2.Companion.CANONICALIZE_LENGTH_MIN
7+
import misk.tokens.TokenGenerator2.Companion.indexToChar
8+
import java.security.SecureRandom
9+
import kotlin.experimental.and
510

611
@Singleton
712
class RealTokenGenerator @Inject constructor() : TokenGenerator by wisp.token.RealTokenGenerator()
813

14+
private const val REAL_TOKEN_GENERATOR_BIT_MASK = 31.toByte()
915
@Singleton
1016
class RealTokenGenerator2 @Inject constructor() : TokenGenerator2 {
11-
private val tokenGenerator = wisp.token.RealTokenGenerator()
17+
private val random = SecureRandom()
1218

1319
override fun generate(label: String?, length: Int): String {
14-
return tokenGenerator.generate(label, length)
20+
require(length in CANONICALIZE_LENGTH_MIN..CANONICALIZE_LENGTH_MAX) {
21+
"unexpected length: $length"
22+
}
23+
24+
val byteArray = ByteArray(length)
25+
random.nextBytes(byteArray)
26+
27+
val result = CharArray(length)
28+
for (i in 0 until length) {
29+
result[i] = indexToChar[(byteArray[i] and REAL_TOKEN_GENERATOR_BIT_MASK).toInt()]
30+
}
31+
32+
return String(result)
1533
}
1634
}

misk-core/src/main/kotlin/misk/tokens/TokenGenerator.kt

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,26 @@ interface TokenGenerator2 {
1313
companion object {
1414
internal const val alphabet = "0123456789abcdefghjkmnpqrstvwxyz"
1515
internal val indexToChar = alphabet.toCharArray()
16-
private val charToIndex = ByteArray(128)
16+
private const val CHAR_TO_INDEX_SIZE = 128
17+
private val charToIndex = ByteArray(CHAR_TO_INDEX_SIZE)
18+
const val CANONICALIZE_LENGTH_MIN = 4
19+
const val CANONICALIZE_LENGTH_MAX = 25
1720

1821
init {
1922
Arrays.fill(charToIndex, (-1).toByte())
2023
for (i in indexToChar.indices) {
2124
val c = indexToChar[i]
22-
charToIndex[Character.toLowerCase(c).toInt()] = i.toByte()
23-
charToIndex[Character.toUpperCase(c).toInt()] = i.toByte()
25+
charToIndex[Character.toLowerCase(c).code] = i.toByte()
26+
charToIndex[Character.toUpperCase(c).code] = i.toByte()
2427
if (c == '0') {
25-
charToIndex['o'.toInt()] = i.toByte()
26-
charToIndex['O'.toInt()] = i.toByte()
28+
charToIndex['o'.code] = i.toByte()
29+
charToIndex['O'.code] = i.toByte()
2730
}
2831
if (c == '1') {
29-
charToIndex['i'.toInt()] = i.toByte()
30-
charToIndex['I'.toInt()] = i.toByte()
31-
charToIndex['l'.toInt()] = i.toByte()
32-
charToIndex['L'.toInt()] = i.toByte()
32+
charToIndex['i'.code] = i.toByte()
33+
charToIndex['I'.code] = i.toByte()
34+
charToIndex['l'.code] = i.toByte()
35+
charToIndex['L'.code] = i.toByte()
3336
}
3437
}
3538
}
@@ -50,12 +53,12 @@ interface TokenGenerator2 {
5053
val result = StringBuilder()
5154
for (c in token) {
5255
if (c == ' ') continue
53-
require(c.toInt() in 0..127) { "unexpected token $token" }
54-
val index = charToIndex[c.toInt()].toInt()
56+
require(c.code in 0 until CHAR_TO_INDEX_SIZE) { "unexpected token $token" }
57+
val index = charToIndex[c.code].toInt()
5558
require(index != -1) { "unexpected token $token" }
5659
result.append(indexToChar[index])
5760
}
58-
require(result.length in 4..25)
61+
require(result.length in CANONICALIZE_LENGTH_MIN..CANONICALIZE_LENGTH_MAX)
5962
return result.toString()
6063
}
6164
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package misk.tokens
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.FreeSpec
5+
import io.kotest.matchers.shouldBe
6+
import java.util.concurrent.atomic.AtomicLong
7+
import kotlin.concurrent.thread
8+
9+
class FakeTokenGeneratorTest : FreeSpec({
10+
lateinit var tokenGenerator: TokenGenerator2
11+
12+
beforeTest {
13+
tokenGenerator = FakeTokenGenerator2()
14+
}
15+
16+
"Happy Path" {
17+
val token0 = tokenGenerator.generate()
18+
val token1 = tokenGenerator.generate()
19+
token0 shouldBe "0000000000000000000000001"
20+
token1 shouldBe "0000000000000000000000002"
21+
}
22+
23+
"Labels Are Prefixes" {
24+
val token0 = tokenGenerator.generate("payment")
25+
val token1 = tokenGenerator.generate("payment")
26+
token0 shouldBe "payment000000000000000001"
27+
token1 shouldBe "payment000000000000000002"
28+
}
29+
30+
"Labels Are Canonicalized" {
31+
val token0 = tokenGenerator.generate("customer")
32+
val token1 = tokenGenerator.generate("customer")
33+
token0 shouldBe "cst0mer000000000000000001"
34+
token1 shouldBe "cst0mer000000000000000002"
35+
}
36+
37+
"Labels Are Namespaces" {
38+
val token0 = tokenGenerator.generate("customer")
39+
val token1 = tokenGenerator.generate("payment")
40+
val token2 = tokenGenerator.generate("customer")
41+
val token3 = tokenGenerator.generate("payment")
42+
token0 shouldBe "cst0mer000000000000000001"
43+
token1 shouldBe "payment000000000000000001"
44+
token2 shouldBe "cst0mer000000000000000002"
45+
token3 shouldBe "payment000000000000000002"
46+
}
47+
48+
"Generation Is Thread Safe" {
49+
(1..30).map {
50+
thread { (1..100).forEach { _ -> tokenGenerator.generate() } }
51+
}.forEach { it.join() }
52+
tokenGenerator.generate() shouldBe "0000000000000000000003001"
53+
}
54+
55+
"Custom Length" {
56+
tokenGenerator.generate(label = "payment", length = 4) shouldBe "pay1"
57+
tokenGenerator.generate(label = "payment", length = 7) shouldBe "paymen2"
58+
tokenGenerator.generate(label = "payment", length = 8) shouldBe "payment3"
59+
tokenGenerator.generate(label = "payment", length = 9) shouldBe "payment04"
60+
tokenGenerator.generate(label = "payment", length = 12) shouldBe "payment00005"
61+
tokenGenerator.generate(label = "payment", length = 25) shouldBe "payment000000000000000006"
62+
}
63+
64+
"Custom Length With Large Suffix" {
65+
// Fast-forward the next token suffix.
66+
(tokenGenerator as FakeTokenGenerator2).nextByLabel["payment"] = AtomicLong(12345L)
67+
68+
tokenGenerator.generate(label = "payment", length = 4) shouldBe "2345"
69+
tokenGenerator.generate(label = "payment", length = 7) shouldBe "pa12346"
70+
tokenGenerator.generate(label = "payment", length = 8) shouldBe "pay12347"
71+
tokenGenerator.generate(label = "payment", length = 9) shouldBe "paym12348"
72+
tokenGenerator.generate(label = "payment", length = 12) shouldBe "payment12349"
73+
tokenGenerator.generate(label = "payment", length = 25) shouldBe "payment000000000000012350"
74+
}
75+
76+
"Length Out Of Bounds" {
77+
shouldThrow<IllegalArgumentException> {
78+
tokenGenerator.generate(length = 3)
79+
}
80+
shouldThrow<IllegalArgumentException> {
81+
tokenGenerator.generate(length = 26)
82+
}
83+
}
84+
})
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package misk.tokens
2+
3+
import io.kotest.assertions.throwables.shouldThrow
4+
import io.kotest.core.spec.style.FreeSpec
5+
import io.kotest.matchers.shouldBe
6+
import io.kotest.matchers.shouldNotBe
7+
import io.kotest.matchers.string.shouldMatch
8+
9+
class RealTokenGeneratorTest : FreeSpec({
10+
val tokenGenerator: TokenGenerator2 = RealTokenGenerator2()
11+
12+
"Happy Path" {
13+
val token0 = tokenGenerator.generate()
14+
val token1 = tokenGenerator.generate()
15+
16+
token0 shouldMatch "[${TokenGenerator2.alphabet}]{25}"
17+
token1 shouldMatch "[${TokenGenerator2.alphabet}]{25}"
18+
token0 shouldNotBe token1
19+
}
20+
21+
"Labels Are Ignored" {
22+
tokenGenerator.generate("payment").contains("payment") shouldNotBe true
23+
}
24+
25+
"Custom Length" {
26+
tokenGenerator.generate(length = 4) shouldMatch "[${TokenGenerator2.alphabet}]{4}"
27+
tokenGenerator.generate(length = 12) shouldMatch "[${TokenGenerator2.alphabet}]{12}"
28+
tokenGenerator.generate(length = 25) shouldMatch "[${TokenGenerator2.alphabet}]{25}"
29+
}
30+
31+
"Length Out Of Bounds" {
32+
shouldThrow<IllegalArgumentException> {
33+
tokenGenerator.generate(length = 3)
34+
}
35+
shouldThrow<IllegalArgumentException> {
36+
tokenGenerator.generate(length = 26)
37+
}
38+
}
39+
40+
"Canonicalize" {
41+
TokenGenerator2.canonicalize("iIlLoO") shouldBe "111100"
42+
TokenGenerator2.canonicalize("Pterodactyl") shouldBe "pter0dacty1"
43+
TokenGenerator2.canonicalize("Veloci Raptor") shouldBe "ve10c1rapt0r"
44+
}
45+
46+
"Canonicalize Unexpected Characters" {
47+
shouldThrow<IllegalArgumentException> {
48+
TokenGenerator2.canonicalize("Dinosaur") // u.
49+
}
50+
shouldThrow<IllegalArgumentException> {
51+
TokenGenerator2.canonicalize("Veloci_Raptor") // _.
52+
}
53+
shouldThrow<IllegalArgumentException> {
54+
TokenGenerator2.canonicalize("Velociräptor") // ä.
55+
}
56+
}
57+
58+
"Canonicalize Unexpected Length" {
59+
shouldThrow<IllegalArgumentException> {
60+
TokenGenerator2.canonicalize("a b c") // 3 characters after stripping spaces.
61+
}
62+
shouldThrow<IllegalArgumentException> {
63+
TokenGenerator2.canonicalize("12345678901234567890123456") // 26 characters.
64+
}
65+
}
66+
})

wisp/wisp-token-testing/src/main/kotlin/wisp/token/FakeTokenGenerator.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import java.util.*
88
import java.util.concurrent.atomic.AtomicLong
99
import jakarta.inject.Inject
1010

11+
@Deprecated(
12+
message = "Duplicate implementations in Wisp are being migrated to the unified type in Misk.",
13+
replaceWith = ReplaceWith(
14+
expression = "FakeTokenGenerator2()",
15+
imports = ["misk.tokens.Fake3TokenGenerator2"]
16+
)
17+
)
1118
class FakeTokenGenerator @Inject constructor(): FakeFixture(), TokenGenerator {
1219
internal val nextByLabel by resettable {
1320
Collections.synchronizedMap<String, AtomicLong>(

wisp/wisp-token/src/main/kotlin/wisp/token/RealTokenGenerator.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import kotlin.experimental.and
88

99
private const val REAL_TOKEN_GENERATOR_BIT_MASK = 31.toByte()
1010

11+
@Deprecated(
12+
message = "Duplicate implementations in Wisp are being migrated to the unified type in Misk.",
13+
replaceWith = ReplaceWith(
14+
expression = "RealTokenGenerator2()",
15+
imports = ["misk.tokens.RealTokenGenerator2"]
16+
)
17+
)
1118
class RealTokenGenerator : TokenGenerator {
1219
private val random = SecureRandom()
1320

wisp/wisp-token/src/main/kotlin/wisp/token/TokenGenerator.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ import java.util.*
3434
* payment000000000000000035
3535
* ```
3636
*/
37+
@Deprecated(
38+
message = "Duplicate implementations in Wisp are being migrated to the unified type in Misk.",
39+
replaceWith = ReplaceWith(
40+
expression = "TokenGenerator2()",
41+
imports = ["misk.tokens.TokenGenerator2"]
42+
)
43+
)
3744
interface TokenGenerator {
3845
fun generate(label: String? = null, length: Int = 25): String
3946

0 commit comments

Comments
 (0)