Skip to content

Commit

Permalink
[api][desktop]: Handle exception and show it as notification message. F…
Browse files Browse the repository at this point in the history
…ix #520
  • Loading branch information
tiagohm committed Oct 13, 2024
1 parent 4ad9bd9 commit 51b70e7
Show file tree
Hide file tree
Showing 25 changed files with 156 additions and 107 deletions.
16 changes: 16 additions & 0 deletions api/src/main/kotlin/nebulosa/api/Nebulosa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ import com.fasterxml.jackson.module.kotlin.jsonMapper
import com.github.rvesse.airline.annotations.Command
import com.github.rvesse.airline.annotations.Option
import io.javalin.Javalin
import io.javalin.http.Context
import io.javalin.http.HttpStatus.BAD_REQUEST
import io.javalin.json.JavalinJackson
import nebulosa.api.converters.modules.DeviceModule
import nebulosa.api.core.ErrorResponse
import nebulosa.api.inject.*
import nebulosa.json.PathModule
import nebulosa.log.i
import nebulosa.log.loggerFor
import org.koin.core.context.startKoin
import org.slf4j.LoggerFactory
import java.net.ConnectException

@Command(name = "nebulosa")
class Nebulosa : Runnable, AutoCloseable {
Expand Down Expand Up @@ -52,6 +56,8 @@ class Nebulosa : Runnable, AutoCloseable {
}
}.start(host, port)

app.exception(Exception::class.java, ::handleException)

koinApp.modules(appModule(app))
koinApp.modules(objectMapperModule(OBJECT_MAPPER))
koinApp.modules(servicesModule())
Expand All @@ -61,6 +67,16 @@ class Nebulosa : Runnable, AutoCloseable {
LOG.i("server is started at port: {}", app.port())
}

private fun handleException(ex: Exception, ctx: Context) {
val message = when (ex) {
is ConnectException -> "connection refused"
is NumberFormatException -> "invalid number: ${ex.message}"
else -> ex.message!!
}

ctx.status(BAD_REQUEST).json(ErrorResponse.error(message.lowercase()))
}

override fun close() {
app.stop()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class SkyAtlasController(
}

private fun searchMinorPlanet(ctx: Context) {
val text = ctx.queryParam("text").notNull().notBlank()
val text = ctx.queryParam("text").notNullOrBlank()
ctx.json(skyAtlasService.searchMinorPlanet(text))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package nebulosa.api.confirmation
import io.javalin.Javalin
import io.javalin.http.Context
import nebulosa.api.core.Controller
import nebulosa.api.validators.notBlank
import nebulosa.api.validators.notNull
import nebulosa.api.validators.notNullOrBlank

class ConfirmationController(
override val app: Javalin,
Expand All @@ -16,7 +16,7 @@ class ConfirmationController(
}

private fun confirm(ctx: Context) {
val idempotencyKey = ctx.pathParam("idempotencyKey").notNull().notBlank()
val idempotencyKey = ctx.pathParam("idempotencyKey").notNullOrBlank()
val accepted = ctx.queryParam("accepted").notNull().toBoolean()
confirmationService.confirm(idempotencyKey, accepted)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package nebulosa.api.connection
import io.javalin.Javalin
import io.javalin.http.Context
import nebulosa.api.core.Controller
import nebulosa.api.validators.notBlank
import nebulosa.api.validators.notNull
import nebulosa.api.validators.notNullOrBlank
import nebulosa.api.validators.range

class ConnectionController(
Expand All @@ -20,7 +20,7 @@ class ConnectionController(
}

private fun connect(ctx: Context) {
val host = ctx.queryParam("host").notNull().notBlank()
val host = ctx.queryParam("host").notNullOrBlank()
val port = ctx.queryParam("port").notNull().toInt().range(1, 65535)
val type = ctx.queryParam("type").notNull().let(ConnectionType::valueOf)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package nebulosa.api.connection

import io.javalin.http.InternalServerErrorResponse
import nebulosa.alpaca.indi.client.AlpacaClient
import nebulosa.api.message.MessageService
import nebulosa.indi.client.INDIClient
Expand Down Expand Up @@ -81,7 +80,7 @@ class ConnectionService(
return provider.id
} catch (e: Throwable) {
LOG.e("failed to connect", e)
throw InternalServerErrorResponse("Connection Failed")
throw e
}
}

Expand Down
20 changes: 20 additions & 0 deletions api/src/main/kotlin/nebulosa/api/core/ErrorResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package nebulosa.api.core

import nebulosa.api.notification.Severity

data class ErrorResponse(
@JvmField val type: Severity,
@JvmField val message: String,
) {

companion object {

fun success(message: String) = ErrorResponse(Severity.SUCCESS, message)

fun info(message: String) = ErrorResponse(Severity.INFO, message)

fun warn(message: String) = ErrorResponse(Severity.WARNING, message)

fun error(message: String) = ErrorResponse(Severity.ERROR, message)
}
}
7 changes: 3 additions & 4 deletions api/src/main/kotlin/nebulosa/api/framing/FramingController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import io.javalin.Javalin
import io.javalin.http.Context
import nebulosa.api.core.Controller
import nebulosa.api.image.ImageService
import nebulosa.api.validators.notBlank
import nebulosa.api.validators.notNull
import nebulosa.api.validators.notNullOrBlank
import nebulosa.api.validators.range
import nebulosa.math.deg
import nebulosa.math.hours
Expand All @@ -26,8 +25,8 @@ class FramingController(
}

private fun frame(ctx: Context) {
val rightAscension = ctx.queryParam("rightAscension").notNull().notBlank()
val declination = ctx.queryParam("declination").notNull().notBlank()
val rightAscension = ctx.queryParam("rightAscension").notNullOrBlank()
val declination = ctx.queryParam("declination").notNullOrBlank()
val width = ctx.queryParam("width")?.toInt()?.range(1, 7680) ?: 1280
val height = ctx.queryParam("height")?.toInt()?.range(1, 4320) ?: 720
val fov = ctx.queryParam("fov")?.toDouble()?.range(0.0, 90.0) ?: 1.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import io.javalin.Javalin
import io.javalin.http.Context
import nebulosa.api.connection.ConnectionService
import nebulosa.api.core.Controller
import nebulosa.api.validators.notBlank
import nebulosa.api.validators.notNull
import nebulosa.api.validators.notNullOrBlank
import nebulosa.api.validators.range
import nebulosa.guiding.GuideDirection
import java.time.Duration
Expand Down Expand Up @@ -49,7 +49,7 @@ class GuideOutputController(
private fun pulse(ctx: Context) {
val id = ctx.pathParam("id")
val guideOutput = connectionService.guideOutput(id) ?: return
val direction = ctx.queryParam("direction").notNull().notBlank().let(GuideDirection::valueOf)
val direction = ctx.queryParam("direction").notNullOrBlank().let(GuideDirection::valueOf)
val duration = ctx.queryParam("duration").notNull().toLong().range(0L, 1800000000L).times(1000L).let(Duration::ofNanos)
guideOutputService.pulse(guideOutput, direction, duration)
}
Expand Down
3 changes: 1 addition & 2 deletions api/src/main/kotlin/nebulosa/api/image/ImageService.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package nebulosa.api.image

import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.NotFoundResponse
import jakarta.servlet.http.HttpServletResponse
import nebulosa.api.atlas.Location
import nebulosa.api.atlas.SimbadEntityRepository
Expand Down Expand Up @@ -280,7 +279,7 @@ class ImageService(
require(save.path != null)

var (image) = imageBucket.open(path).image?.transform(save.shouldBeTransformed, save.transformation, ImageOperation.SAVE)
?: throw NotFoundResponse("Image not found")
?: throw IllegalArgumentException("image not found")

val (x, y, width, height) = save.subFrame.constrained(image.width, image.height)

Expand Down
32 changes: 16 additions & 16 deletions api/src/main/kotlin/nebulosa/api/mounts/MountController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,17 @@ class MountController(
private fun sync(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val rightAscension = ctx.queryParam("rightAscension").notNull().notBlank()
val declination = ctx.queryParam("declination").notNull().notBlank()
val rightAscension = ctx.queryParam("rightAscension").notNullOrBlank()
val declination = ctx.queryParam("declination").notNullOrBlank()
val j2000 = ctx.queryParam("j2000")?.toBoolean() ?: false
mountService.sync(mount, rightAscension.hours, declination.deg, j2000)
}

private fun slew(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val rightAscension = ctx.queryParam("rightAscension").notNull().notBlank()
val declination = ctx.queryParam("declination").notNull().notBlank()
val rightAscension = ctx.queryParam("rightAscension").notNullOrBlank()
val declination = ctx.queryParam("declination").notNullOrBlank()
val j2000 = ctx.queryParam("j2000")?.toBoolean() ?: false
val idempotencyKey = ctx.idempotencyKey()
mountService.slewTo(mount, rightAscension.hours, declination.deg, j2000, idempotencyKey)
Expand All @@ -98,8 +98,8 @@ class MountController(
private fun goTo(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val rightAscension = ctx.queryParam("rightAscension").notNull().notBlank()
val declination = ctx.queryParam("declination").notNull().notBlank()
val rightAscension = ctx.queryParam("rightAscension").notNullOrBlank()
val declination = ctx.queryParam("declination").notNullOrBlank()
val j2000 = ctx.queryParam("j2000")?.toBoolean() ?: false
val idempotencyKey = ctx.idempotencyKey()
mountService.goTo(mount, rightAscension.hours, declination.deg, j2000, idempotencyKey)
Expand All @@ -120,21 +120,21 @@ class MountController(
private fun trackMode(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val mode = ctx.queryParam("mode").notNull().notBlank().let(TrackMode::valueOf)
val mode = ctx.queryParam("mode").notNullOrBlank().let(TrackMode::valueOf)
mountService.trackMode(mount, mode)
}

private fun slewRate(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val rate = ctx.queryParam("rate").notNull().notBlank()
val rate = ctx.queryParam("rate").notNullOrBlank()
mountService.slewRate(mount, mount.slewRates.first { it.name == rate })
}

private fun move(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val direction = ctx.queryParam("direction").notNull().notBlank().let(GuideDirection::valueOf)
val direction = ctx.queryParam("direction").notNullOrBlank().let(GuideDirection::valueOf)
val enabled = ctx.queryParam("enabled").notNull().toBoolean()
mountService.move(mount, direction, enabled)
}
Expand All @@ -154,8 +154,8 @@ class MountController(
private fun coordinates(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val longitude = ctx.queryParam("longitude").notNull().notBlank()
val latitude = ctx.queryParam("latitude").notNull().notBlank()
val longitude = ctx.queryParam("longitude").notNullOrBlank()
val latitude = ctx.queryParam("latitude").notNullOrBlank()
val elevation = ctx.queryParam("elevation")?.toDouble() ?: 0.0
mountService.coordinates(mount, longitude.deg, latitude.deg, elevation.m)
}
Expand All @@ -173,7 +173,7 @@ class MountController(
private fun celestialLocation(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val type = ctx.pathParam("type").notNull().notBlank().let(CelestialLocationType::valueOf)
val type = ctx.pathParam("type").notNullOrBlank().let(CelestialLocationType::valueOf)

val location = when (type) {
CelestialLocationType.ZENITH -> mountService.computeZenithLocation(mount)
Expand All @@ -191,8 +191,8 @@ class MountController(
private fun location(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val rightAscension = ctx.queryParam("rightAscension").notNull().notBlank()
val declination = ctx.queryParam("declination").notNull().notBlank()
val rightAscension = ctx.queryParam("rightAscension").notNullOrBlank()
val declination = ctx.queryParam("declination").notNullOrBlank()
val j2000 = ctx.queryParam("j2000")?.toBoolean() ?: false
val equatorial = ctx.queryParam("equatorial")?.toBoolean() ?: true
val horizontal = ctx.queryParam("horizontal")?.toBoolean() ?: true
Expand All @@ -212,7 +212,7 @@ class MountController(
private fun remoteControlStart(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val protocol = ctx.queryParam("protocol").notNull().notBlank().let(MountRemoteControlProtocol::valueOf)
val protocol = ctx.queryParam("protocol").notNullOrBlank().let(MountRemoteControlProtocol::valueOf)
val host = ctx.queryParam("host")?.ifBlank { null } ?: "0.0.0.0"
val port = ctx.queryParam("port")?.toInt()?.positive() ?: 10001
mountService.remoteControlStart(mount, protocol, host, port)
Expand All @@ -221,7 +221,7 @@ class MountController(
private fun remoteControlStop(ctx: Context) {
val id = ctx.pathParam("id")
val mount = connectionService.mount(id) ?: return
val protocol = ctx.queryParam("protocol").notNull().notBlank().let(MountRemoteControlProtocol::valueOf)
val protocol = ctx.queryParam("protocol").notNullOrBlank().let(MountRemoteControlProtocol::valueOf)
mountService.remoteControlStop(mount, protocol)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.javalin.http.Context
import io.javalin.http.bodyAsClass
import nebulosa.api.core.Controller
import nebulosa.api.validators.exists
import nebulosa.api.validators.notNull
import nebulosa.api.validators.notNullOrBlank
import nebulosa.api.validators.path
import nebulosa.api.validators.valid

Expand All @@ -20,7 +20,7 @@ class PlateSolverController(
}

private fun start(ctx: Context) {
val path = ctx.queryParam("path")?.path().notNull().exists()
val path = ctx.queryParam("path").notNullOrBlank().path().exists()
val solver = ctx.bodyAsClass<PlateSolverRequest>().valid()
ctx.json(plateSolverService.solveImage(solver, path))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import nebulosa.api.converters.angle.DegreesDeserializer
import nebulosa.api.converters.angle.RightAscensionDeserializer
import nebulosa.api.converters.time.DurationUnit
import nebulosa.api.inject.Named
import nebulosa.api.validators.Validatable
import nebulosa.api.validators.max
import nebulosa.api.validators.positiveOrZero
import nebulosa.api.validators.*
import nebulosa.astap.platesolver.AstapPlateSolver
import nebulosa.astrometrynet.nova.NovaAstrometryNetService
import nebulosa.astrometrynet.platesolver.LocalAstrometryNetPlateSolver
Expand Down Expand Up @@ -44,6 +42,7 @@ data class PlateSolverRequest(
) : Validatable, KoinComponent, Supplier<PlateSolver> {

override fun validate() {
executablePath.notNull(PLATE_SOLVER_IS_NOT_CONFIGURED).notBlank(PLATE_SOLVER_IS_NOT_CONFIGURED)
timeout.positiveOrZero().max(5, TimeUnit.MINUTES)
downsampleFactor.positiveOrZero()
focalLength.positiveOrZero()
Expand All @@ -70,6 +69,8 @@ data class PlateSolverRequest(

companion object {

const val PLATE_SOLVER_IS_NOT_CONFIGURED = "plate solver is not configured"

@JvmStatic val EMPTY = PlateSolverRequest()
@JvmStatic private val NOVA_ASTROMETRY_NET_CACHE = HashMap<String, NovaAstrometryNetService>()
}
Expand Down
Loading

0 comments on commit 51b70e7

Please sign in to comment.