Skip to content
This repository has been archived by the owner on Aug 10, 2024. It is now read-only.

Commit

Permalink
url parser working, still needs docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sanity committed Apr 15, 2017
1 parent 4104185 commit 8bfec6a
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 33 deletions.
12 changes: 12 additions & 0 deletions src/main/kotlin/io/kweb/misc.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
import kotlin.reflect.jvm.jvmName

/**
Expand Down Expand Up @@ -49,4 +50,15 @@ fun Array<StackTraceElement>.pruneAndDumpStackTo(logStatementBuilder: StringBuil
this.filter { ste -> ste.lineNumber >= 0 && !disregardClassPrefixes.any { ste.className.startsWith(it) } }.forEach { stackTraceElement ->
logStatementBuilder.appendln(" at ${stackTraceElement.className}.${stackTraceElement.methodName}(${stackTraceElement.fileName}:${stackTraceElement.lineNumber})")
}
}

val <T : Any> KClass<T>.pkg : String get() {
val packageName = qualifiedName
val className = simpleName
return if (packageName != null && className != null) {
val endIndex = packageName.length - className.length - 1
packageName.substring(0, endIndex)
} else {
throw RuntimeException("Cannot determine package for $this because it may be local or an anonymous object literal")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.kweb.routing
/**
* Created by @jmdesprez, modified by @sanity
*/
import io.kweb.pkg
import io.kweb.routing.ParsingResult.NoValue
import io.kweb.routing.ParsingResult.ValueExtracted
import org.reflections.Reflections
Expand All @@ -12,11 +13,20 @@ import org.reflections.util.ConfigurationBuilder
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.KVisibility.PUBLIC
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSuperclassOf
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaType

class UrlPathParser(val contextProvider: (KClass<*>)-> Set<KClass<out Any>>) {
/**
* Translates between URL paths and objects.
*/



class UrlPathTranslator(val contextProvider: ((KClass<*>) -> Set<KClass<out Any>>)?) {

constructor(vararg pathObjectPackage: String) : this(if (pathObjectPackage.isEmpty()) null else ClasspathScanner(*pathObjectPackage)::getContext)

/**
* Build the an Entity using the given data list. Rules are:
Expand All @@ -33,7 +43,13 @@ class UrlPathParser(val contextProvider: (KClass<*>)-> Set<KClass<out Any>>) {

val entityName = if (dataList.isNotEmpty()) dataList[0] else null

val candidates = this.contextProvider(entityClass)
val candidates = contextProvider.let {
if (it != null) {
it(entityClass)
} else {
ClasspathScanner(entityClass.pkg).getContext(entityClass)
}
}

return candidates.filter { candidateClass ->
entityClass.isSuperclassOf(candidateClass) && isEntityNameMatch(candidateClass, entityName)
Expand All @@ -46,7 +62,6 @@ class UrlPathParser(val contextProvider: (KClass<*>)-> Set<KClass<out Any>>) {
constructor!! // safe because the visibility test will fail if the method is null
}
}.map { constructor ->
try {
val values = constructor.parameters.map { param ->

// dataList[0] is the entity name so add one to the index
Expand Down Expand Up @@ -81,7 +96,7 @@ class UrlPathParser(val contextProvider: (KClass<*>)-> Set<KClass<out Any>>) {
// Else, if it's nullable then set to null
param.type.isMarkedNullable -> ValueExtracted(param, null)
// Else, there nothing we can decide here, throw to someone else
else -> throw IllegalArgumentException("Unable to build the entity, null value for $param")
else -> throw UrlParseException("Unable to parse url part $dataList because no value can be found for $param")
}
} else {
ValueExtracted(param, value)
Expand All @@ -97,24 +112,44 @@ class UrlPathParser(val contextProvider: (KClass<*>)-> Set<KClass<out Any>>) {
val args = mapOf(*values.toTypedArray())
constructor.callBy(args)

} catch(e: Exception) {
// TODO better exception handling
e.printStackTrace()
null
}.firstOrNull() as T?
}

fun toPath(obj: Any): String = "/" + toPathList(obj).joinToString(separator = "/")

fun toPathList(obj: Any): List<String> {
val entityPathElement = obj::class.simpleName!!.toLowerCase().let {
if (it == "root") {
emptyList()
} else {
listOf(it)
}
}.filter { it != null }.firstOrNull() as T?
}
val declaredMemberProperties = obj::class.declaredMemberProperties
val params = declaredMemberProperties.map { property ->
val parameterValue = property.call(obj)
val parameterValueAsPathElements: List<String> = when (parameterValue) {
is Int -> listOf<String>(parameterValue.toString())
is Long -> listOf<String>(parameterValue.toString())
is Boolean -> listOf<String>(parameterValue.toString())
is String -> listOf<String>(parameterValue.toString())
else -> if (parameterValue != null) toPathList(parameterValue) else emptyList<String>()
}
parameterValueAsPathElements
}.flatMap { it }
return entityPathElement + params
}
}

inline fun <reified T : Any> UrlPathParser.parse(url: String): T {
inline fun <reified T : Any> UrlPathTranslator.parse(url: String): T {
val parts = url.trim('/').split('/').filter { it.isNotEmpty() }

return buildEntity(parts, T::class) { kClass, entityName ->
val realEntityName = entityName ?: "root"
val name = kClass.simpleName?.toLowerCase()
name == realEntityName
}.let { entity ->
entity ?: throw UrlParseException("Unable to parse the URL")
entity ?: throw UrlParseException("Unable to parse URL path `$url`")
}
}

Expand Down Expand Up @@ -143,20 +178,3 @@ internal sealed class ParsingResult {
data class ValueExtracted(val parameter: KParameter, val value: Any?) : ParsingResult()
object NoValue : ParsingResult()
}

fun Any.toPath(): String {
val entityPath = javaClass.simpleName.toLowerCase().takeUnless { it == "root" }?.let { "/$it" } ?: "/"
val members = javaClass.kotlin.members
val params = javaClass.kotlin.primaryConstructor?.parameters?.joinToString(separator = "") { parameter ->
val value = members.find { it.name == parameter.name }?.call(this)
val convertedValue = when (parameter.type.javaType) {
Int::class.java,
Long::class.java,
Boolean::class.java,
String::class.java -> value
else -> value?.toPath()
}
"/${convertedValue ?: ""}"
} ?: throw IllegalArgumentException("No primary constructor found")
gi return "$entityPath$params"
}
14 changes: 14 additions & 0 deletions src/test/kotlin/io/kweb/MiscSpec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.kweb

import io.kotlintest.specs.FreeSpec

/**
* Created by ian on 4/15/17.
*/
class MiscSpec : FreeSpec() {
init {
"test .pkg" {
MiscSpec::class.pkg shouldBe "io.kweb"
}
}
}
10 changes: 5 additions & 5 deletions src/test/kotlin/io/kweb/routing/UrlPathParserSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ sealed class UserEntity {

class UrlPathParserSpec : FreeSpec() {
init {
// TODO choose one solution
// val contextProvider = KClass<*>::meAndNested
val urlPathParser = UrlPathParser(ClasspathScanner("io.kweb.routing")::getContext)
val urlPathParser = UrlPathTranslator()

"test URL path parsing" - {
val parseableUrls = table(
Expand All @@ -64,14 +62,16 @@ class UrlPathParserSpec : FreeSpec() {
row<String, Entity>("/", Entity.Root()),
row<String, Entity>("/users/152", Entity.Users(152, UserEntity.Root())),
row<String, Entity>("/squares/152/22/44", Entity.Squares(152, 22, 44)),
row<String, Entity>("/squares/142/23", Entity.Squares(142, 23)),
row<String, Entity>("/users/152/friend/51", Entity.Users(152, UserEntity.Friend(51)))
)
forAll(parseableUrls) { url, parsedUrl ->
"verify that $parsedUrl is translated back to $url correctly" {
parsedUrl.toPath() shouldEqual url
urlPathParser.toPath(parsedUrl) shouldEqual url
}
}
"verify that a default value is specified explicity" {
urlPathParser.toPath(Entity.Squares(512, 11)) shouldEqual "/squares/512/11/42"
}
}

"should handle null value when property has no default" {
Expand Down

0 comments on commit 8bfec6a

Please sign in to comment.