Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HTTP] Call controller actions using URL from _publicApi instead of _query endpoint #73

Merged
merged 37 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b5e846f
Build and Call controller actions using URL from _publicApi
Shiranuit Apr 19, 2022
e7b8a5d
use built HTTP Endpoint
Shiranuit Apr 20, 2022
cb1aa32
lint
Shiranuit Apr 20, 2022
971f50e
fix route builder
Shiranuit Apr 20, 2022
53fe55a
lint
Shiranuit Apr 20, 2022
ec77f11
enhance error messages, use proper serialization for header
Shiranuit Apr 20, 2022
1a87c45
lint
Shiranuit Apr 20, 2022
2c9788a
lint
Shiranuit Apr 20, 2022
206cd35
remove toString
Shiranuit Apr 20, 2022
6306341
remove toString
Shiranuit Apr 20, 2022
ed1e26d
remove unused import
Shiranuit Apr 20, 2022
ab72a5d
add custom headers
Shiranuit Apr 20, 2022
d8470a2
LINT
Shiranuit Apr 20, 2022
4b0a1b1
add index and collection in querystring
Shiranuit Apr 20, 2022
e4aa3d5
add comments to explain what has been done in the code
Shiranuit Apr 20, 2022
153b25f
add headers to response object and RequestPayload
Shiranuit Apr 25, 2022
b6115ea
add documentation for headers field in HTTP Request
Shiranuit Apr 25, 2022
242fc0c
add headers field in response
Shiranuit Apr 25, 2022
7c33d83
ensure RawJson is properly stringified
Shiranuit Apr 25, 2022
3cb8d81
remove weird behaviour copied from SDK-JS
Shiranuit Apr 25, 2022
7494137
add other payload properties to skip
Shiranuit Apr 25, 2022
0aec276
lint
Shiranuit Apr 25, 2022
101715a
remove unused import
Shiranuit Apr 25, 2022
3712719
add default headers
Shiranuit Apr 25, 2022
05af4e1
fix body properties not transfered
Shiranuit Apr 25, 2022
366642d
rollback
Apr 25, 2022
e3bc10b
fix tests
Apr 25, 2022
140944c
Merge branch 'call-proper-http-routes' of https://github.com/kuzzleio…
Shiranuit Apr 26, 2022
ddd5752
apply requested changes
Shiranuit Apr 26, 2022
3a5611b
lint
Shiranuit Apr 26, 2022
b840f3d
fix query string
Apr 26, 2022
71b25b4
Merge branch 'call-proper-http-routes' of github.com:kuzzleio/sdk-jvm…
Apr 26, 2022
569b9f1
add better error when failed to parse JSON response
Shiranuit Apr 26, 2022
7d4c70c
remove debug print
Shiranuit Apr 26, 2022
84c3e53
lint
Shiranuit Apr 26, 2022
109d78b
lint
Shiranuit Apr 26, 2022
aeedba3
update compatibility matrix
Shiranuit Apr 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/kotlin/io/kuzzle/sdk/Kuzzle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ open class Kuzzle {
}

queries[requestId]?.completeExceptionally(ApiErrorException(response))
queries.remove(requestId)
protocol.trigger(TokenExpiredEvent())
}

Expand Down
6 changes: 5 additions & 1 deletion src/main/kotlin/io/kuzzle/sdk/controllers/AuthController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.kuzzle.sdk.coreClasses.lang.Lang
import io.kuzzle.sdk.coreClasses.maps.KuzzleMap
import io.kuzzle.sdk.coreClasses.responses.Response
import io.kuzzle.sdk.events.LoginAttemptEvent
import io.kuzzle.sdk.events.LogoutAttemptEvent
import java.util.concurrent.CompletableFuture

class AuthController(kuzzle: Kuzzle) : BaseController(kuzzle) {
Expand Down Expand Up @@ -164,7 +165,10 @@ class AuthController(kuzzle: Kuzzle) : BaseController(kuzzle) {
put("controller", "auth")
put("action", "logout")
}
return kuzzle.query(query)
return kuzzle.query(query).thenApplyAsync { response ->
kuzzle.protocol.trigger(LogoutAttemptEvent())
response
}
}

@JvmOverloads
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class ApiErrorException : KuzzleException {
constructor(response: Response) :
super(response.error?.message, response.status) {
if (response.error != null) {
this.stack = response.error?.stack
this.id = response.error?.id
this.stack = response.error!!.stack
this.id = response.error!!.id
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package io.kuzzle.sdk.coreClasses.exceptions

enum class KuzzleExceptionCode {
MISSING_REQUESTID(0, "Missing field requestId"), MISSING_QUERY(400, "You must provide a query"), NOT_CONNECTED(500, "Not connected."), CONNECTION_LOST(500, "Connection lost"), WRONG_VOLATILE_TYPE(
MISSING_REQUESTID(0, "Missing field requestId"),
MISSING_CONTROLLER(0, "Missing field controller"),
MISSING_ACTION(0, "Missing field action"),
MISSING_QUERY(400, "You must provide a query"),
NOT_CONNECTED(500, "Not connected."),
CONNECTION_LOST(500, "Connection lost"),
WRONG_VOLATILE_TYPE(
400,
"Volatile data must be a Map<String, Object>"
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.kuzzle.sdk.coreClasses.exceptions

open class MissingActionException : KuzzleException {
constructor() : super(KuzzleExceptionCode.MISSING_ACTION)
}
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.kuzzle.sdk.coreClasses.exceptions

open class MissingControllerException : KuzzleException {
constructor() : super(KuzzleExceptionCode.MISSING_CONTROLLER)
}
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.kuzzle.sdk.coreClasses.exceptions

/**
* Thrown when attempting to interact with the network while not connected.
*/
class MissingURLParamException : KuzzleException {
constructor(templateParam: String, baseURL: String) : super("Missing URL Param $templateParam in $baseURL", 0)
}
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.kuzzle.sdk.coreClasses.exceptions

class URLNotFoundException : KuzzleException {
constructor(controller: String, action: String) : super("No URL found for \"$controller:$action\".", 400)
}
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 10 additions & 0 deletions src/main/kotlin/io/kuzzle/sdk/coreClasses/http/HttpRequest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.kuzzle.sdk.coreClasses.http

import io.kuzzle.sdk.coreClasses.maps.KuzzleMap

data class HttpRequest(
val verb: String,
val url: String,
val body: KuzzleMap,
val headers: KuzzleMap,
)
178 changes: 178 additions & 0 deletions src/main/kotlin/io/kuzzle/sdk/coreClasses/http/Route.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package io.kuzzle.sdk.coreClasses.http

import io.kuzzle.sdk.coreClasses.exceptions.MissingURLParamException
import io.kuzzle.sdk.coreClasses.json.JsonSerializer
import io.kuzzle.sdk.coreClasses.maps.KuzzleMap
import java.net.URLEncoder

enum class PartType {
STATIC,
TEMPLATE
}

private class RoutePart {
val type: PartType
val value: String

constructor(type: PartType, value: String) {
this.type = type
this.value = value
}
}

val PayloadProperties = setOf<String>(
"controller",
"action",
"meta",
"volatile",
)

class Route {
private val partType: PartType
private val routeParts: ArrayList<RoutePart>
private var staticURL: String = ""
private var baseURL: String
private var verb: String

private constructor(verb: String, baseUrl: String, routeParts: ArrayList<RoutePart>) {
OlivierCavadenti marked this conversation as resolved.
Show resolved Hide resolved
this.verb = verb.uppercase()
this.baseURL = baseUrl
this.routeParts = routeParts
val hasOnePartTemplated = routeParts.indexOfFirst { it.type == PartType.TEMPLATE } > -1
this.partType = if (hasOnePartTemplated) PartType.TEMPLATE else PartType.STATIC
if (! hasOnePartTemplated) {
staticURL = routeParts.joinToString("") {
it.value
}
}
}

/**
* Given map containing the properties of the request, the buildRequest method
* will create an instance of HttpRequest which contains the information needed to make
* the request later on.
*
* The method has to:
* - Generate the URL using the properties of the request if there are part of the URL that are templated
* - Generate the query string
* - Generate the list of headers
*/
fun buildRequest(request: KuzzleMap): HttpRequest {
val headers = request.optMap("headers", KuzzleMap())

val queryArgs = KuzzleMap()
for (key: String? in request.keys) {
// Skip if a key or value is null
if (key == null || request[key] == null) {
continue
}

when (key) {
"jwt" -> headers["authorization"] = "Bearer ${request["jwt"]}"
Shiranuit marked this conversation as resolved.
Show resolved Hide resolved
"volatile" -> {
headers["x-kuzzle-volatile"] = request["volatile"]
}
"headers" -> headers.putAll(request.optMap("headers", KuzzleMap()))
"body" -> {
Aschen marked this conversation as resolved.
Show resolved Hide resolved
if (verb == "GET") {
queryArgs.putAll(request.optMap("body", KuzzleMap()))
}
}
else -> {
if (! PayloadProperties.contains(key)) {
queryArgs[key] = request[key]
}
}
}
}

/**
* Build the query string
*/
val queryString: String = queryArgs.keys.filterNotNull().joinToString("&") {
val encodedKey = URLEncoder.encode(it, "utf-8")

if (queryArgs.isArrayList(it)) {
Aschen marked this conversation as resolved.
Show resolved Hide resolved
val value = queryArgs.getArrayList(it)!!.joinToString(",")
"$encodedKey=${URLEncoder.encode(value, "utf-8")}"
} else if (queryArgs.optBoolean(it, false) == true) {
encodedKey
} else {
val value = if (queryArgs.isMap(it)) JsonSerializer.serialize(queryArgs.getMap(it)!!) else queryArgs[it].toString()
Aschen marked this conversation as resolved.
Show resolved Hide resolved
"$encodedKey=${URLEncoder.encode(value, "utf-8")}"
}
}

/**
* If the partType is STATIC it means that there is no template in the url
* So we can use the staticURL instead of building the URL
*/
if (partType == PartType.STATIC) {
return HttpRequest(
verb,
if (queryArgs.isEmpty()) staticURL else "$staticURL?$queryString",
request.optMap("body", KuzzleMap()),
headers
)
}

/**
* Each STATIC part will be appended to the string builder without further processing,
* TEMPLATE parts however will be used to find their corresponding value in the request map
* and the retrieve value will be put in the URL
*/
val urlBuilder = StringBuilder()
for (routePart: RoutePart in routeParts) {
if (routePart.type == PartType.STATIC) {
urlBuilder.append(routePart.value)
} else {
if (! request.containsKey(routePart.value) || request[routePart.value] == null) {
throw MissingURLParamException(routePart.value, baseURL)
}
val serializedValue = request[routePart.value]!!.toString()
urlBuilder.append(URLEncoder.encode(serializedValue, "utf-8"))
}
}
return HttpRequest(
verb,
if (queryArgs.isEmpty()) urlBuilder.toString() else "$urlBuilder?$queryString",
request.optMap("body", KuzzleMap()),
headers
)
}

companion object {
/**
* Parse Kuzzle url with the format /:index/:collection
* Split each section of the url and tag them either as STATIC or TEMPLATE
* Static parts are static as the name suggest, they can be combined without further processing
* Template parts however need to be replaced with the appropriate value
* Each sections starting with : is considered to be a template part
*/
fun parse(verb: String, url: String): Route {
val routeParts = ArrayList<RoutePart>()

// Sanitize path in case of double /
val parts = url.split('/').filter { it.isNotEmpty() }

routeParts.add(RoutePart(PartType.STATIC, "/"))

for (part: String in parts) {
if (part.startsWith(":")) {
routeParts.add(RoutePart(PartType.TEMPLATE, part.substring(1)))
} else {
routeParts.add(RoutePart(PartType.STATIC, part))
}
// Add static / to separate each parts
routeParts.add(RoutePart(PartType.STATIC, "/"))
}

// Remove trailing / if there was none in the url
if (! url.endsWith("/")) {
routeParts.removeLast()
}

return Route(verb, url, routeParts)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.kuzzle.sdk.coreClasses.serializer

import io.kuzzle.sdk.coreClasses.json.JsonSerializer

object StringSerializer {
fun serialize(obj: Any): String {
if (obj == null) {
return ""
}
if (obj is Map<*, *> || obj is List<*>) {
return JsonSerializer.serialize(obj)
}
return obj.toString()
}
}
3 changes: 3 additions & 0 deletions src/main/kotlin/io/kuzzle/sdk/events/LogoutAttemptEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package io.kuzzle.sdk.events

class LogoutAttemptEvent
Loading