question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

"throws" section is not intuitive

See original GitHub issue

Continuing #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:

  1. The generated exceptional response example doesn’t match my NOT_OK (which is false):
See the image

image

  1. When the request is missing some primitive fields, zeros are inserted. Here are some examples:

    1. Request: ProductUsable(name = "a", id = 5, type = 42)
      Expected response: 200 OK SuccessResult.OK Actual behavior in normal version: as expected Actual behavior in “throws” version: as expected
    2. Request: ProductUsable(name = "a", type = 42)
      Expected response: 400 Bad Request SuccessResult.NOT_OK (because id is missing) Actual behavior in normal version: 500 internal server error Actual behavior in “throws” version: 200 OK SuccessResult.OK (transformed as ProductUsable(name = "a", id = 0, type = 42))
    3. Request: ProductUsable(id = 5, type = 42)
      Expected response: 400 Bad Request SuccessResult.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)
  2. “Gen” function looks duplicating logic of the “example”.

  3. 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:closed
  • Created 4 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
Wicparcommented, Mar 20, 2020

I indeed messed up, i missed a processing step that aggregated the information of multiple throw clauses: fixed in 0.2-beta.1-experimental

0reactions
SerVBcommented, Mar 19, 2020

I just tested with your code, the throw clause really doesn’t change parsing behavior…

Hmm, can reproduce 500 error anymore… It really does insert 0 instead of absent key-values. Sorry…

However, there is one more issue mentioned:

I do not understand what you mean with 1. , is the generated model using the wrong example?

Yes, it’s wrong:

image

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?

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found