I'm working on a RESTful app in Kotlin and for the router, I'm using a when statement, as it's the most readable and good looking conditional.
Is there a way to use Regex or a wildcard in the when statement for a string?
(So that URIs like "/article/get/" would all be passed to the same controller)
The structure of my router is as follows:
when(uri) {
"some/url" -> return SomeController(config).someAction(session)
}
Yes.
import kotlin.text.regex
val regex1 = Regex( /* pattern */ )
val regex2 = Regex( /* pattern */ )
/* etc */
when {
regex1.matches(uri) -> /* do stuff */
regex2.matches(uri) -> /* do stuff */
/* etc */
}
You could also use containsMatchIn if that suits your needs better than matches.
Explanation:
The test expression of a when statement is optional. If no test expression is included, then the when statement functions like an if-else if chain, where the whenCondition of each whenEntry shall independently evaluate to a boolean.
EDIT:
So I thought about it for awhile, and I came up with a different approach that might be closer to what you want.
import kotlin.text.regex
when (RegexWhenArgument(uri)) {
Regex(/* pattern */) -> /* do stuff */
Regex(/* pattern */) -> /* do stuff */
/* etc */
}
Where RegexWhenArgument is minimally defined as:
class RegexWhenArgument (val whenArgument: CharSequence) {
operator fun equals(whenEntry: Regex) = whenEntry.matches(whenArgument)
override operator fun equals(whenEntry: Any?) = (whenArgument == whenEntry)
}
This approach lets you get as close as possible to the "argument-ful" when expression syntax. I think it's about as streamlined and readable as it can be (assuming that you define the RegexWhenArgument class elsewhere).
This approach uses something similar to the visitor design pattern in combination with Kotlin's operator overloading to redefine what constitutes a "match" between a when expression argument and a whenEntry. If you really wanted to, I suppose you could take this approach a step further and generify RegexWhenArgument into a general-purpose WhenArgument and WhenArgumentDecorator that allows you to specify custom "match" criteria in a when expression for any sort of type, not just Regex.
The typing of the when statement enforces to have compatible types between the whenSubject and the whenEntries. So we cannot compare a String whenSubject with a Regex directly.
We can use when with no subject, then branch conditions may be simply boolean expressions.
fun main() {
val uri: String? = "http://my.site.com/a/b/c"
val res = when {
uri == null -> "NULL"
uri == "http://my.site.com/" -> "ROOT"
uri.startsWith("http://my.site.com/a/") -> "A STUFF"
uri.matches(Regex("http://my.site.com/b/.*")) -> "B STUFF"
else -> "DEFAULT"
}
/* do stuff */
}
Alternatively, we can emulate a kind of when+regex with a dedicated class and few helper functions.
fun main() {
val uri: String? = "http://my.site.com/a/b/c"
val res2 = when(matching(uri)) {
null -> "NULL"
matchesLiteral("http://my.site.com/") -> "ROOT"
matchesRegex("http://my.site.com/a/.*") -> "A STUFF"
else -> "DEFAULT"
}
/* do stuff */
}
class MatchLiteralOrPattern(val value: String, val isPattern: Boolean) {
override fun equals(other: Any?): Boolean {
if (other !is MatchLiteralOrPattern) return false
if (isPattern && !other.isPattern) return Regex(this.value).matches(other.value)
if (!isPattern && other.isPattern) return Regex(other.value).matches(this.value)
return value == other.value
}
}
fun matching(whenSubject: String?) = whenSubject?.let { MatchLiteralOrPattern(it, false) }
fun matchesLiteral(value: String) = MatchLiteralOrPattern(value, false)
fun matchesRegex(value: String) = MatchLiteralOrPattern(value, true)
I tried the following on the kotlin playground and it seems to work as expected.
class WhenArgument (val whenArg: CharSequence) {
override operator fun equals(other: Any?): Boolean {
return when (other) {
is Regex -> other.matches(whenArg)
else -> whenArg.equals(other)
}
}
}
fun what(target: String): String {
return when (WhenArgument(target) as Any) {
Regex("source-.*") -> "${target} is-a-source"
Regex(".*-foo") -> "${target} is-a-foo"
"target-fool" -> "${target} is-the-target-fool"
else -> "nothing"
}
}
fun main() {
println(what("target-foo"))
println(what("source-foo"))
println(what("target-bar"))
println(what("target-fool"))
}
It works around the type compatibility problem by making the 'when' argument of type Any.
Related
I am trying to verify that .shuffled() on a list is called, but get an error on running because of a prior .take(6) call on the list, and I cannot see a way around this.
Here is some code that gets the same error:
val mockList =
mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
val choiceList = spyk(listOf("String1", "String2")) { every { take(6) } returns mockList }
val tmp = choiceList.take(6)
val tmp2 = tmp.shuffled()
verify {mockList.shuffled())
On line 4, I get the following error:
class io.mockk.renamed.java.util.List$Subclass0 cannot be cast to class java.lang.Integer (io.mockk.renamed.java.util.List$Subclass0 is in unnamed module of loader 'app'; java.lang.Integer is in module java.base of loader 'bootstrap')
Attempting to go around by directly verifying on choiceList.take(6).shuffled() and combining the two tmp vals into one has had no success, as it gets true whether or not .shuffled() gets called. Also, switching from a spy to a mock for choiceList has also not worked.
Edit: Note, since this is a toy example, the take() is completely necessary, and cannot be removed, as it has real use in the actual code.
Interesting one!
I think in current implementation it is not possible. The easy answer would be "this test misses the declaration of wrapping static class" (as extension methods are just the same as java static methods for JVM). But if we add it...
#Test
fun test() {
mockkStatic("kotlin.reflect.jvm.internal.impl.utils.CollectionsKt")
val iterClass = mockkClass(Iterable::class)
val mockList = mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
with(iterClass) {
every { take(6) } returns mockList
val tmp = take(6)
val tmp2 = tmp.shuffled()
verify {
mockList.shuffled()
}
}
}
we have a Recursion detected in a lazy value under LockBasedStorageManager#1d2ad266 (DeserializationComponentsForJava.ModuleData) which is understandable - we just mocked the whole extensions package. And it is not possible to mock only one extension method leaving others intact. (source: https://github.com/mockk/mockk#extension-functions)
However, I'd do the following. Why not make our own extension functions which call the original and mock those?
It would go like this:
Main.kt:
package root
...
fun <T> Iterable<T>.take(n: Int): Iterable<T> {
val m = Iterable<T>::take
return m.call(this)
}
fun <T> Iterable<T>.shuffled(): Iterable<T> {
val m = Iterable<T>::shuffled
return m.call(this)
}
Test.kt:
package root
...
#Test
fun test() {
// note this changed
mockkStatic("root.MainKt")
val iterClass = mockkClass(Iterable::class)
val mockList = mockk<List<String>> { every { shuffled() } returns mockk(relaxed = true) }
with(iterClass) {
every { take(6) } returns mockList
val tmp = take(6)
val tmp2 = tmp.shuffled()
verify {
mockList.shuffled()
}
}
}
The only downside here I think is that it's reflection (duh!) So, this can possibly affect performance and has the requirement to have implementation(kotlin("reflect")) in the dependencies (to use call()). If it is not feasible I think there's no clean solution.
val mockList: List<String> = mockk(relaxed = true)
mockList.shuffled()
verify { mockList.shuffled() }
This works for me. The problem is that take of choiceList cannot be mocked somehow. Is that really necessary?
I'm trying to test that Permission.REVEAL_NOW returns the method isFeatureRevealNowAvailable.
Heres some code I already tried but didn't succeed with. Any help would be hugely appreciated as always!
Function to test:
class PermissionRepository(private val permissionApi: PermissionApi,
private val appPreferences: AppPreferences) {
fun checkPermission(permission: PermissionType, onPermissionResponse: (Boolean) -> Unit) {
Log.i("Permission", "Checking permission")
when (permission) {
PermissionType.REVEAL_NOW -> {
isFeatureRevealNowAvailable(onPermissionResponse, ::errorHandler)
}
}
}
Attempted solution:
#RunWith(MockitoJUnitRunner::class)
class PermissionRepositoryTest{
#Test
fun checkPermissionTest() {
val mockPermissionRepository = mock(PermissionRepository::class.java)
val mockPermissionApi = mock(PermissionApi::class.java)
val result = mockPermissionRepository.checkPermission(PermissionType.REVEAL_NOW, onPermissionResponse = null)
//Unsure of what to use here AssertThat or Mockito's "when" function
}
}
private fun isFeatureRevealNowAvailable(permissionResponseHandler: (Boolean) -> Unit, permissionError: (Throwable) -> Unit) {
permissionApi.getRevealNowPermission().enqueue(object : Callback<PermissionResponse> {
override fun onFailure(call: Call<PermissionResponse>, t: Throwable) {
permissionResponseHandler(false)
permissionError(t)
}
override fun onResponse(call: Call<PermissionResponse>, response: Response<PermissionResponse>) {
val permissionResult = response.body()?.isRevealNow ?: false
updateUserLocalPermission(PermissionType.REVEAL_NOW, permissionResult)
permissionResponseHandler(permissionResult)
}
})
}
(TL;DR - go to Example)
Since you don't define specifically what you want to test or achieve, I will give some overall tips:
Mocking
Never mock the class you want to test
val mockPermissionRepository = mock(PermissionRepository::class.java)
Mock only what you want to exclude from your test but rely on
Try to avoid mocks whenever it is possible and makes sence, because they simulate a perfect world, not the real behaviour
Test naming
Use proper names to describe what you want to achive with your test. My personal favourite is to start the sentence with the word "should ..."
Example
Two tests I could image to write for your function:
#Test
fun `should invoke reveal permission on PermissionApi when type is REVEAL_NOW`(){
val mockPermissionApi = mock(PermissionApi::class.java)
val permissionRepository = PermissionRepository(mockPermissionApi, mock())
permissionRepository.checkPermission(PermissionType.REVEAL_NOW, onPermissionResponse = {})
verify(mockPermissionApi, times(1)).getRevealNowPermission()
}
#Test
fun `should do nothing when type is not REVEAL_NOW`() {
val mockPermissionApi = mock(PermissionApi::class.java)
val permissionRepository = PermissionRepository(mockPermissionApi, mock())
permissionRepository.checkPermission(PermissionType.ELSE, onPermissionResponse = {})
verify(mockPermissionApi, times(0)).getRevealNowPermission()
}
Currently I am working on one of the BLE communication-related app, Where BLE device sending data in JSON format. I have one requirement to make sure that received data should be in JSON format before passing to the caller method.
So, I have created one JSON Validator which is going to validate received response should have expected field name and JSON Object and Array symbols. Here is JSONValidator Kotlin code which is testing JSON Object and Array symbol in response.
class JSONValidator: StringValidator() {
private val jsonArray = "(^\\[|]\$)"
private val jsonObject = "([{}])"
private val jsonArrayAndObject = "(^\\[\\{|}]\$)"
override fun isStringValid(s: String?): Boolean {
return if (s == null || s.isEmpty()) {
false
} else {
doesPacketContainJson(s)
}
}
private fun doesPacketContainJson(s: String): Boolean {
return Regex(jsonObject).containsMatchIn(s) ||
Regex(jsonArray).containsMatchIn(s) ||
Regex(jsonArrayAndObject).containsMatchIn(s)
}
}
JSONValidator Test Class
class JSONValidatorTest {
private val packetValidator: StringValidator = JSONValidator()
// Other Test cases
#Test
fun `validate contains json`() {
val partialJson1 = "{"
val partialJson2 = "}"
val partialJson3 = "[{"
val partialJson4 = "]}"
val partialJson5 = "]"
//Issue: These test cases always failed
assert(packetValidator.isStringValid(partialJson1))
assert(packetValidator.isStringValid(partialJson2))
assert(packetValidator.isStringValid(partialJson3))
assert(packetValidator.isStringValid(partialJson4))
assert(packetValidator.isStringValid(partialJson5))
}
}
I have cross verified Regular expression on below sites and It's working fine over there. I hope I am doing things right. :
https://regex101.com/
https://www.freeformatter.com/java-regex-tester.html
I did google but unable to find any help around my use-case. Can anyone have any experience or idea around this issue? Thanks in advance.
I am currently having a list of obeject defined as:
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
... code below
}
the Tool data class is defined as:
data class Tool(
var id: String = ""
var description: String = ""
var assignedTo: String = ""
)
the Updated data class is defined as:
data class Updated(
var id: String = ""
var assignedTo: String = ""
)
Basically, I parse the list updateTools and if I found a id match in tools, I update the assignedTo field from the Tool type object from tools by the one from updateTools
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
updateTools.forEach{
val idToSearch = it.id
val nameToReplace = it.name
tools.find(){
if(it.id == idToSearch){it.name=nameToReplace}
}
}
return tools
}
it's not working but I do not see how to make it easier to work. I just started kotlin and I feel that it's not the good way to do it
any idea ?
Thanks
First of all:
you're not assigning assignedTo, you're assigning name...
in the predicate passed to find, which
should only return a Boolean value to filter elements, and
should probably not have any side effects,
those should be done later with a call to i.e. forEach.
Additionally, your constructor parameters to the data class are normal parameters, and as such, need commas between them!
Your last code block, corrected, would be:
updateTools.forEach {
val idToSearch = it.id
val nameToReplace = it.name
tools.find { it.id == idToSearch }.forEach { it.assignedTo = nameToReplace }
}
return tools
I'd do it like this (shorter):
updateTools.forEach { u -> tools.filter { it.id == u.id }.forEach { it.assignedTo = u.name } }
This loops through each update, filters tools for tools with the right ID, and sets the name of each of these tools.
I use forEach as filter returns a List<Tool>.
If you can guarantee that id is unique, you can do it like this instead:
updateTools.forEach { u -> tools.find { it.id == u.id }?.assignedTo = u.name }
firstOrNull returns the first element matching the condition, or null if there is none. Edit: it seems find is firstOrNull - its implementation just calls firstOrNull.
The ?. safe call operator returns null if the left operand is null, otherwise, it calls the method.
For = and other operators which return Unit (i.e. void, nothing), using the safe call operator simply does nothing if the left operand is null.
If we combine these, it effectively sets the name of the first element which matches this condition.
First, you're missing comma after properties in your data classes, so it should be:
data class Tool(
var id: String = "",
var description: String = "",
var assignedTo: String = ""
)
data class Updated(
var id: String = "",
var assignedTo: String = ""
)
As for second problem, there're probably number of ways to do that, but I've only corrected your idea:
fun updateList(tools: List<Tool>, updateTools: List<Updated>): List<Tool> {
updateTools.forEach{ ut ->
tools.find { it.id == ut.id }?.assignedTo = ut.assignedTo
}
return tools
}
Instead of assigning values to variables, you can name parameter for forEach and use it in rest of the loop.
I have found successes using ideas of immutable List but I am stumped when come to this piece of code here. I find myself has written something more Java than of Scala style. I would prefer to use List(...) instead of Buffer(...) but I don't see how I can pass the same modified immutable List to the next function. guesses is also modified within eliminate(...).
Any suggestions to help me to make this the Scala way of doing this is appreciated. Thanks
val randomGuesses = List(...) // some long list of random integers
val guesses = randomGuesses.zipWithIndex.toBuffer
for ( s <- loop()) {
val results = alphaSearch(guesses)
if (results.size == 1) {
guesses(resultes.head._2) = results.head._1
eliminate(guesses, resultes.head._2)
}
else {
val results = betaSearch(guesses)
if (results.size == 1) {
guesses(resultes.head._2) = results.head._1
eliminate(guesses, resultes.head._2)
} else {
val results = betaSearch(guesses)
if (results.size == 1) {
guesses(resultes.head._2) = results.head._1
eliminate(guesses, resultes.head._2)
}
}
}
}
Here are some general tips since this might be better suited for codereview and the code posted is incomplete with no samples.
You can use pattern matching instead of if and else for checking the size.
results.size match{
case 1 => ... //Code in the if block
case _ => ... //Code in the else block
}
Instead of mutating guesses create a new List.
val newGuesses = ...
Then pass newGuesses into eliminate.
Lastly, it looks like eliminate modifies guesses. Change this to return a new list. e.g.
def eliminate(list: List[Int]) = {
//Eliminate something from list and return a new `List`
}