¿Cuál es la forma correcta de jerarquizar los correladores y manipuladores de ruta Ktor para una implementación típica de REST?

Estoy teniendo problemas para entender la forma correcta de usar el DSL de Ktor para el routing de requestes . El problema es que cuando pruebo mi API y trato de GET /nomenclature/articles/categories que deben devolver una list de todas las categorías de artículos, obtengo en su lugar el Invalid article specified que devuelvo un post para /nomenclature/articles/{articleId} ruta cuando el parámetro articleId no es válido.

Aquí está mi código:

 route("/nomenclature") { method(HttpMethod.Get) { handle { call.respondText("The resource you accessed is not a valid REST resource, but a parent node. Children nodes include articles, categories, subcategories and so on.") } } route("articles") { route("categories") { get("{categoryId?}") { val categoryIdParam = call.parameters["categoryId"] if (categoryIdParam != null) { val categoryId = categoryIdParam.toIntOrNull() if (categoryId != null) { val category = articlesDAO.findCategoryById(categoryId) if (category != null) call.respond(category) else call.respondText("Category not found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid category ID specified", status = HttpStatusCode.BadRequest) } else { val categories = articlesDAO.getCategories() if (categories != null) call.respond(categories) else call.respondText("No categories found", status = HttpStatusCode.NotFound) } } } route("subcategories") { get("{subcategoryId?}") { val subcategoryIdParam = call.parameters["subcategoryId"] if (subcategoryIdParam != null) { val subcategoryId = subcategoryIdParam.toIntOrNull() if (subcategoryId != null) { val subcategory = articlesDAO.findSubcategoryById(subcategoryId) if (subcategory != null) call.respond(subcategory) else call.respondText("Subcategory not found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid subcategory ID specified", status = HttpStatusCode.BadRequest) } else { val subcategories = articlesDAO.getCategories() if (subcategories != null) call.respond(subcategories) else call.respondText("No subcategories found", status = HttpStatusCode.NotFound) } } } get("types") { val articleTypes = articlesDAO.getArticleTypes() if (articleTypes != null) call.respond(articleTypes) else call.respondText("No article types found", status = HttpStatusCode.NotFound) } get("wholePackagings") { val wholePackagings = articlesDAO.getWholePackagings() if (wholePackagings != null) call.respond(wholePackagings) else call.respondText("No whole packagings found", status = HttpStatusCode.NotFound) } get("fractionsPackagings") { val fractionsPackagings = articlesDAO.getFractionPackagings() if (fractionsPackagings != null) call.respond(fractionsPackagings) else call.respondText("No fractions packagings found", status = HttpStatusCode.NotFound) } get("{articleId?}") { val articleIdParam = call.parameters["articleId"] if (articleIdParam != null) { val articleId = articleIdParam.toIntOrNull() if (articleId != null) { val article = articlesDAO.findArticleById(articleId) if (article != null) call.respond(article) else call.respondText("No article found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid article ID specified", status = HttpStatusCode.BadRequest) } else { val articles = articlesDAO.getArticles() if (articles != null) call.respond(articles) else call.respondText("No articles found", status = HttpStatusCode.NotFound) } } } } 

Sería realmente genial si alguien pudiera ayudarme brindando un ejemplo básico pero algo exhaustivo de cómo usar todos los adaptadores y manipuladores de ruta Ktor, incluso de manera anidada.

EDITAR: He reescrito el routing con un enfoque diferente, pero aún me gustaría saber por qué mi versión anterior no funcionó como se esperaba. Aquí está mi segundo enfoque que parece funcionar como se esperaba (lo he probado):

 routing { route("/v1") { route("stock") { get { val stock = stockDAO.getAllStock() if (stock != null) call.respond(stock) else call.respondText("No stock found", status = HttpStatusCode.NotFound) } get("{locationId}") { val locationIdParam = call.parameters["locationId"] if (locationIdParam != null) { val locationId = locationIdParam.toIntOrNull() if (locationId != null) { val stock = stockDAO.getStockByLocationId(locationId) if (stock != null) call.respond(stock) else call.respondText("No stock found", status = HttpStatusCode.NotFound) } else call.respondText("ERROR: Invalid location ID specified.", status = HttpStatusCode.BadRequest) } } } route("nomenclature") { get { call.respondText("The resource you accessed is not a valid REST resource, but a parent node. Children nodes include articles, categories, subcategories and so on.") } route("articles") { get { val articles = articlesDAO.getArticles() if (articles != null) call.respond(articles) else call.respondText("No articles found", status = HttpStatusCode.NotFound) } get("{articleId}") { val articleIdParam = call.parameters["articleId"] if (articleIdParam != null) { val articleId = articleIdParam.toIntOrNull() if (articleId != null) { val article = articlesDAO.findArticleById(articleId) if (article != null) call.respond(article) else call.respondText("No article found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid article ID specified", status = HttpStatusCode.BadRequest) } } route("categories") { get { val categories = articlesDAO.getCategories() if (categories != null) call.respond(categories) else call.respondText("No categories found", status = HttpStatusCode.NotFound) } get("{categoryId}") { val categoryIdParam = call.parameters["categoryId"] if (categoryIdParam != null) { val categoryId = categoryIdParam.toIntOrNull() if (categoryId != null) { val category = articlesDAO.findCategoryById(categoryId) if (category != null) call.respond(category) else call.respondText("Category not found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid category ID specified", status = HttpStatusCode.BadRequest) } } } route("subcategories") { get { val subcategories = articlesDAO.getSubcategories() if (subcategories != null) call.respond(subcategories) else call.respondText("No subcategories found", status = HttpStatusCode.NotFound) } get("{subcategoryId}") { val subcategoryIdParam = call.parameters["subcategoryId"] if (subcategoryIdParam != null) { val subcategoryId = subcategoryIdParam.toIntOrNull() if (subcategoryId != null) { val subcategory = articlesDAO.findSubcategoryById(subcategoryId) if (subcategory != null) call.respond(subcategory) else call.respondText("Subcategory not found", status = HttpStatusCode.NotFound) } else call.respondText("Invalid subcategory ID specified", status = HttpStatusCode.BadRequest) } } } get("types") { val articleTypes = articlesDAO.getArticleTypes() if (articleTypes != null) call.respond(articleTypes) else call.respondText("No article types found", status = HttpStatusCode.NotFound) } get("wholePackagings") { val wholePackagings = articlesDAO.getWholePackagings() if (wholePackagings != null) call.respond(wholePackagings) else call.respondText("No whole packagings found", status = HttpStatusCode.NotFound) } get("fractionsPackagings") { val fractionsPackagings = articlesDAO.getFractionPackagings() if (fractionsPackagings != null) call.respond(fractionsPackagings) else call.respondText("No fractions packagings found", status = HttpStatusCode.NotFound) } } } } }