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

feat: improve LoxoneEndpoint constructors #14

Merged
merged 2 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion examples/java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repositories {
}

dependencies {
implementation("cz.smarteon.loxone:loxone-client-kotlin:0.1.0-SNAPSHOT")
implementation("cz.smarteon.loxone:loxone-client-kotlin:0.1.0")

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1")
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public String callRaw(@NotNull final String command) {
}

public void close() {
wrappedClient.close();
try {
BuildersKt.runBlocking(
EmptyCoroutineContext.INSTANCE,
(scope, continuation) -> wrappedClient.close(continuation)
);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
2 changes: 1 addition & 1 deletion examples/kotlin/src/main/kotlin/LoxoneClientExample.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import cz.smarteon.loxone.message.ApiInfo


suspend fun main(args: Array<String>) {
val endpoint = LoxoneEndpoint(args[0], useSsl = true)
val endpoint = LoxoneEndpoint.fromUrl(args[0])
val loxoneClient: LoxoneClient = HttpLoxoneClient(
endpoint,
LoxoneTokenAuthenticator(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import cz.smarteon.loxone.LoxoneEndpoint
import cz.smarteon.loxone.LoxoneProfile
import cz.smarteon.loxone.LoxoneTokenAuthenticator
import cz.smarteon.loxone.callForMsg
import cz.smarteon.loxone.ktor.HttpLoxoneClient
import cz.smarteon.loxone.ktor.WebsocketLoxoneClient
import cz.smarteon.loxone.message.ApiInfo


suspend fun main(args: Array<String>) {
val endpoint = LoxoneEndpoint(args[0], useSsl = false)
val endpoint = LoxoneEndpoint.fromUrl(args[0])
val loxoneClient: LoxoneClient = WebsocketLoxoneClient(
endpoint,
LoxoneTokenAuthenticator(
Expand Down
73 changes: 73 additions & 0 deletions src/commonMain/kotlin/LoxoneEndpoint.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cz.smarteon.loxone

import io.ktor.http.*
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic

/**
* Loxone connection endpoint representation.
*
* @param host Loxone host name or IP address, without protocol prefix, port or path.
* @param port Loxone port, default is 443 (HTTPS).
* @param useSsl Whether to use SSL, default is true.
* @param path Loxone path, url encoded, default is empty string.
*/
data class LoxoneEndpoint @JvmOverloads constructor(
val host: String,
val port: Int = HTTPS_PORT,
val useSsl: Boolean = true,
val path: String = ""
) {

init {
require(host.isNotBlank()) { "Host must not be blank" }
require(!host.contains(":")) { "Host must not contain port or protocol" }
}
companion object {
private const val HTTP_PORT = 80
private const val HTTPS_PORT = 443

/**
* Creates Loxone endpoint by parsing URL.
* @param url Loxone URL.
*/
@JvmStatic
fun fromUrl(url: String): LoxoneEndpoint {
val parsed = URLBuilder().takeFrom(url)
val port = when {
parsed.port > 0 -> parsed.port
parsed.protocol.isSecure() -> HTTPS_PORT
else -> HTTP_PORT
}
println(parsed)
println(parsed.encodedPath)
return LoxoneEndpoint(parsed.host, port, parsed.protocol.isSecure(), parsed.encodedPath)
}

/**
* Creates Loxone endpoint for local connection.
*
* @param address Loxone IP address.
* @param port Loxone port, default is 80 (HTTP).
* @param path Loxone path, url encoded, default is empty string.
*/
@JvmStatic @JvmOverloads
fun local(address: String, port: Int = HTTP_PORT, path: String = ""): LoxoneEndpoint {
require(hostIsIp(address)) { "Local address must be IP" }
return LoxoneEndpoint(address, port, false, path)
}

/**
* Creates Loxone endpoint for public domain connection.
*
* @param domain Loxone host domain.
* @param port Loxone port, default is 443 (HTTPS).
* @param path Loxone path, url encoded, default is empty string.
*/
@JvmStatic @JvmOverloads
fun public(domain: String, port: Int = HTTPS_PORT, path: String = ""): LoxoneEndpoint {
require(!hostIsIp(domain)) { "Public domain must not be IP" }
return LoxoneEndpoint(domain, port, true, path)
}
}
}
7 changes: 0 additions & 7 deletions src/commonMain/kotlin/LoxoneProfile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ package cz.smarteon.loxone

import kotlin.jvm.JvmOverloads

data class LoxoneEndpoint @JvmOverloads constructor(
val host: String,
val port: Int = 80,
val useSsl: Boolean = true,
val path: String = ""
)

data class LoxoneCredentials @JvmOverloads constructor(
val username: String,
val password: String,
Expand Down
78 changes: 78 additions & 0 deletions src/commonTest/kotlin/LoxoneEndpointTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cz.smarteon.loxone

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.WordSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe

class LoxoneEndpointTest : WordSpec({

"Loxone endpoint constructed" When {

"with default values" should {
LoxoneEndpoint("somehost").apply {
"have host" { host shouldBe "somehost"}
"have port" { port shouldBe 443 }
"have useSsl" { useSsl shouldBe true }
"have path" { path shouldBe "" }
}
}

withData(
nameFn = { (case, _) -> "with $case host" },
"empty" to "",
"contains port" to "host:123",
"contains protocol" to "http://host"
) { (_, host) ->
shouldThrow<IllegalArgumentException> {
LoxoneEndpoint(host)
}
}
}

"FromUrl endpoint constructed" When {
withData(
nameFn = { (url, _) -> url },
"http://some:7780/testPath" to LoxoneEndpoint("some", 7780, false, "/testPath"),
"http://some/testPath" to LoxoneEndpoint("some", 80, false, "/testPath"),
"https://some.smarteon.cz:7743/testPath" to LoxoneEndpoint("some.smarteon.cz", 7743, true, "/testPath"),
"https://some.smarteon.cz/testPath" to LoxoneEndpoint("some.smarteon.cz", 443, true, "/testPath")
) { (url, expected) ->
LoxoneEndpoint.fromUrl(url) shouldBe expected
}
}

"Local endpoint constructed" When {
"with default values" should {
LoxoneEndpoint.local("192.168.9.77").apply {
"have host" { host shouldBe "192.168.9.77" }
"have port" { port shouldBe 80 }
"have useSsl" { useSsl shouldBe false }
"have path" { path shouldBe "" }
}
}

"with non-IP address" should {
shouldThrow<IllegalArgumentException> {
LoxoneEndpoint.local("somehost")
}
}
}

"Public domain endpoint constructed" When {
"with default values" should {
LoxoneEndpoint.public("test.smarteon.cz").apply {
"have host" { host shouldBe "test.smarteon.cz" }
"have port" { port shouldBe 443 }
"have useSsl" { useSsl shouldBe true }
"have path" { path shouldBe "" }
}
}

"with non-IP address" should {
shouldThrow<IllegalArgumentException> {
LoxoneEndpoint.public("192.168.9.77")
}
}
}
})
Loading