"throws" section is not intuitive
See original GitHub issueContinuing #17.
I’ve just tried it (using 0d4f589ea49e91f98f8c981d4c011f43578b7796 revision) and I’ve come up with the following:
Normal version
data class SuccessResult(val ok: Boolean) {
companion object {
val NOT_OK = SuccessResult(ok = false)
val OK = SuccessResult(ok = true)
}
}
data class ProductUsable(val name: String, val id: Int, val type: Int)
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
"Throws" version
data class SuccessResult(val ok: Boolean) {
companion object {
val NOT_OK = SuccessResult(ok = false)
val OK = SuccessResult(ok = true)
}
}
data class ProductUsable(val name: String, val id: Int, val type: Int)
private fun <T : OpenAPIRoute<T>> T.throwsSuccessResultNotOk(fn: T.() -> Unit) {
throws(
APIException.apiException(
status = HttpStatusCode.BadRequest,
example = SuccessResult.NOT_OK,
gen = { _: Throwable -> SuccessResult.NOT_OK }
),
fn = fn
)
}
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
throwsSuccessResultNotOk {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
}
The usability problems I see:
- The generated exceptional response example doesn’t match my
NOT_OK
(which isfalse
):
See the image
-
When the request is missing some primitive fields, zeros are inserted. Here are some examples:
- Request:
ProductUsable(name = "a", id = 5, type = 42)
Expected response: 200 OKSuccessResult.OK
Actual behavior in normal version: as expected Actual behavior in “throws” version: as expected - Request:
ProductUsable(name = "a", type = 42)
Expected response: 400 Bad RequestSuccessResult.NOT_OK
(becauseid
is missing) Actual behavior in normal version: 500 internal server error Actual behavior in “throws” version: 200 OKSuccessResult.OK
(transformed asProductUsable(name = "a", id = 0, type = 42)
) - Request:
ProductUsable(id = 5, type = 42)
Expected response: 400 Bad RequestSuccessResult.NOT_OK
Actual behavior in normal version: 500 internal server error Actual behavior in “throws” version: as expected (maybe because a string is not a primitive type)
- Request:
-
“Gen” function looks duplicating logic of the “example”.
-
I don’t understand which exception I should specify in “gen” function.
Finally, I propose a bit different design. It’s inspired by exception
block and I think it solves the 3 and the 4:
// inspired by:
//exception<JsonMappingException, Error>(HttpStatusCode.BadRequest) {
// it.printStackTrace()
// Error("mapping.json", it.localizedMessage)
//}
fun NormalOpenAPIRoute.addRoutes() {
route("product") {
catches<JsonMappingException, SuccessResult>( // means that when JsonMappingException happens (can't generate the request body object), "generator" is called
status = HttpStatusCode.BadRequest,
example = SuccessResult.NOT_OK,
generator = null // means that the example is returned
) {
post<Unit, SuccessResult, ProductUsable>(
info(
summary = "Create a product.",
description = "The product is created only if a product with the same ID does not exist. Returns `${SuccessResult::class.simpleName}` saying whether the product has been created."
),
exampleResponse = SuccessResult.OK,
exampleRequest = exampleProductUsable,
body = { _, body -> createProduct(body, this::respond) }
)
// ...
}
}
}
Issue Analytics
- State:
- Created 4 years ago
- Comments:9 (4 by maintainers)
Top Results From Across the Web
Throwing Errors - cs.wisc.edu
When you are aiming by direct sight, twisting your feet can change the point of your body just right. But, it can be...
Read more >Throwing exceptions from constructors - c++ - Stack Overflow
Yes, throwing an exception from the failed constructor is the standard way of doing this. Read this FAQ about Handling a constructor that ......
Read more >WebAPICallResult and the error handling is not intuitive #775
When the client receives a non-OK HTTP response, it simply wraps this into an Error and throw instead. Please correct me if I...
Read more >10 not so intuitive things about programming with R
While library() throws an error when the package is not available to be loaded into active memory, require() throw a warning and return...
Read more >9 Best Practices to Handle Java Exceptions - Stackify
Handling Java exceptions isn't easy, especially for beginners. Read this post to understand exceptions and best practices for using them.
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I indeed messed up, i missed a processing step that aggregated the information of multiple throw clauses: fixed in 0.2-beta.1-experimental
Hmm, can reproduce 500 error anymore… It really does insert 0 instead of absent key-values. Sorry…
However, there is one more issue mentioned:
Yes, it’s wrong:
It can be reproduced here: https://github.com/SerVB/e-shop/tree/a71b923d270c500ed68a6c9663f51a4b5af608bc . For a bad POST request, the response body should be
{"ok": false}
.Could you investigate?