{"id":13428865,"url":"https://github.com/kohesive/kovert","last_synced_at":"2026-01-14T02:28:58.864Z","repository":{"id":36897560,"uuid":"41204568","full_name":"kohesive/kovert","owner":"kohesive","description":"The invisible REST and web framework","archived":false,"fork":false,"pushed_at":"2018-11-15T18:16:49.000Z","size":412,"stargazers_count":157,"open_issues_count":9,"forks_count":10,"subscribers_count":11,"default_branch":"master","last_synced_at":"2026-01-12T15:58:54.745Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kohesive.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-22T11:41:42.000Z","updated_at":"2025-11-29T09:24:31.000Z","dependencies_parsed_at":"2022-09-01T00:30:56.216Z","dependency_job_id":null,"html_url":"https://github.com/kohesive/kovert","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kohesive/kovert","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kohesive%2Fkovert","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kohesive%2Fkovert/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kohesive%2Fkovert/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kohesive%2Fkovert/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kohesive","download_url":"https://codeload.github.com/kohesive/kovert/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kohesive%2Fkovert/sbom","scorecard":{"id":566000,"data":{"date":"2025-08-11","repo":{"name":"github.com/kohesive/kovert","commit":"c093fe21135b1e8ea61a2d54c3371898e231605a"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}}]},"last_synced_at":"2025-08-20T14:59:11.160Z","repository_id":36897560,"created_at":"2025-08-20T14:59:11.160Z","updated_at":"2025-08-20T14:59:11.160Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408711,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T01:01:07.266Z","updated_at":"2026-01-14T02:28:58.848Z","avatar_url":"https://github.com/kohesive.png","language":"Kotlin","funding_links":[],"categories":["Libraries","开源库和框架","Web Frameworks","开发框架"],"sub_categories":["Web 开发","Web","Web框架"],"readme":"[![Kotlin](https://img.shields.io/badge/kotlin-1.3.x-blue.svg)](http://kotlinlang.org) [![Maven Central](https://img.shields.io/maven-central/v/uy.kohesive.kovert/kovert-core.svg)](https://mvnrepository.com/artifact/uy.kohesive.kovert) [![CircleCI branch](https://img.shields.io/circleci/project/kohesive/kovert/master.svg)](https://circleci.com/gh/kohesive/kovert/tree/master) [![Issues](https://img.shields.io/github/issues/kohesive/kovert.svg)](https://github.com/kohesive/kovert/issues?q=is%3Aopen) [![DUB](https://img.shields.io/dub/l/vibe-d.svg)](https://github.com/kohesive/kovert/blob/master/LICENSE) [![Kotlin Slack #kohesive](https://img.shields.io/badge/chat-kotlin%20slack%20%23kohesive-orange.svg)](http://kotlinslackin.herokuapp.com)\n\n# Kovert\n\nThe invisible REST (and WEB) framework.  It is \"invisible\" since it does not invade your code, and only uses annotations for exception cases (or view rendering).\n\nKovert is a simple framework that binds your Kotlin classes into your Vert.x 3 (and soon Undertow) routers.  It does not try to replace or rebuild these frameworks and only handles the task of providing the \"last mile\" binding to your controllers.  From a fairly normal looking Kotlin class, Kovert can infer the route path and parameters.  \n\nThis framework is opinionated and optimized for easy coding and default behavior typical of most apps.  If you want more control, please use [Vertx-Nubes](https://github.com/aesteve/vertx-nubes) for Vert-x and [Kikaha](http://kikaha.skullabs.io) for Undertow, or take a peek at [SparkJava](http://sparkjava.com).\n\nFor starting an application with Kovert, you have two options:\n\n* Configure, Startup Vert-x, deploy a Vert-x verticle, add your routes with Vert-x Web, and _then_ ask Kovert to bind a controller to an existing route.  For that sentence to make sense, you should be familiar with [Vertx-Web](http://vertx.io/docs/vertx-web/java/) and the basics of [Vertx](http://vertx.io/docs/vertx-core/java/)\n* Alternatively, if you just want to get started without knowing too much, [Kovert provides `KovertVertx` and `KovertVerticle` classes](#vertx-and-kovertverticle-startup) that can bootstrap a base application, but this acts more as an example starting point from which you should build your own.\n\nIn addition, Kovert uses [Klutter/Vertx3](https://github.com/kohesive/klutter/tree/master/vertx3) module which contains helper classes for working with Vert-x that use [Kovenant](http://kovenant.komponents.nl) promises -- including ensuring that the dispatcher for Kovenant is unified with the thread dispatching in Vert.x so that Vert.x context is maintained on dispatch threads, and callbacks come as expected by Vert.x as well.  There are additional helpers for Vert.x JSON objects, the logging facade, web, and integration with [Injekt](#injekt), and more.\n\n#### Maven Dependnecy (Vert.x Version, requires JDK 8 or newer)\n\nInclude the dependency in your Gradle / Maven projects that are setup with Kotlin.\n\n**Gradle:**\n```\ncompile \"uy.kohesive.kovert:kovert-vertx:1.5.+\"\n```\n\n**Maven:**\n```\n\u003cdependency\u003e\n    \u003cgroupId\u003euy.kohesive.kovert\u003c/groupId\u003e\n    \u003cartifactId\u003ekovert-vertx\u003c/artifactId\u003e\n    \u003cversion\u003e[1.5.0,1.6.0)\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Learn by Example\n\nFor a full sample, view the [sample REST application](vertx-example/src/main/kotlin/uy/kohesive/kovert/vertx/sample/) to see an example that uses the helper classes `KovertVertx` and `KovertVerticle` to startup and run a vertx server using a configuration file.  \n\nIt helps if you are familiar with [Injekt](https://github.com/kohesive/injekt) to completely understand the sample.  Injekt is not required for use of Kovert and can be bypassed, but it helps!  The sample also uses small libraries from [Klutter](https://github.com/kohesive/klutter) such as to control configuration loading with Typesafe Config.\n\n### Binding a Controller\n\nTo bind a controller is simple.  When you have a route object, call the extension method `bindController`:\n\n```kotlin\nroute.bindController(MyControllerClass(), \"/api/mystuff\")\n```\n\nA controller class is any class that contains methods that are extension functions on a class that you wish to be\nyour dispatch context.  So you can either decide that dispatch context is the raw `RoutingContext` of Vert.x, or you can isolate your code from the raw Vert.x and use simple classes that wrap elements of the `RoutingContext` to make\nthem available in a type-safe way.  Any class that has a 1 parameter constructor of type `RoutingContext` can be a context class, or `RoutingContext` itself.  An example of a custom context:\n\n```kotlin\nclass RestContext(private val routingContext: RoutingContext) {\n    public val user: User by Delegates.lazy { routingContext.user() }\n}\n```\n\nOr one that uses an API key to validate the request using an Auth service provided by Injekt (really you should build a Vert.x auth service, this is just for an example of a context that can reject a request instead of using an intercept):\n\n```kotlin\nclass ApiKeySecured(private val routingContext: RoutingContext) {\n    public val user: User =  Injekt.get\u003cAuthService\u003e()\n                 .apiKeyToUser(routingContext.request().getHeader(HttpHeaders.AUTHORIZATION.toString()) ?: \"\") \n                 ?: throw HttpErrorUnauthorized()\n}\n```\n\nWith the dispatch object selected, write your controller.  Here is the CompanyRestController from the sample app:\n\n```kotlin\nclass CompanyRestController(val companyService: CompanyService = Injekt.get()) {\n    public fun ApiKeySecured.getCompanyByName(name: String): Company = companyService.findCompanyByName(name) ?: throw HttpErrorNotFound()\n\n    public fun ApiKeySecured.putCompanyByName(name: String, company: Company): Company {\n        if (!name.equals(company.name, ignoreCase = true)) {\n            throw HttpErrorBadRequest()\n        }\n        companyService.upsertCompany(company)\n        return company\n    }\n\n    public fun ApiKeySecured.listCompanyByNameEmployees(name: String): List\u003cPerson\u003e {\n        return companyService.listEmployeesOfCompany(name) ?: throw HttpErrorNotFound()\n    }\n\n    public fun ApiKeySecured.findCompaniesNamedByName(name: String): Company = companyService.findCompanyByName(name) ?: throw HttpErrorNotFound()\n\n    public fun ApiKeySecured.findCompaniesLocatedInCountry(country: String): List\u003cCompany\u003e {\n        val found = companyService.findCompaniesByCountry(country)\n        if (found.isEmpty()) throw HttpErrorNotFound()\n        return found\n    }\n\n    public fun ApiKeySecured.getCompaniesSearch(name: String?, country: String?): Promise\u003cSet\u003cCompany\u003e, Exception\u003e {\n        return task {\n            val byName: List\u003cCompany\u003e = name.whenNotNull { companyService.findCompanyByName(name!!) }.whenNotNull { listOf(it) } ?: emptyList()\n            val byCountry: List\u003cCompany\u003e = country.whenNotNull { companyService.findCompaniesByCountry(country!!) } ?: emptyList()\n            (byName + byCountry).toSet()\n        }\n    }\n}\n```\n\nGreat, that class looks like it contains normal Kotlin methods, just that they are extension methods on `RestContext` class.  That means each method has access to values of the context and only those values.  You can actually have a different context class for each method if you want, and the correct context will be created for dispatching to that method.  A context for public methods, one for logged in, another for temporary state during a process flow, etc.\n\nWhen binding the controller class, HTTP verb and path names are derived from method names unless you use [special annotations](#annotations) to override the path, path parameters, HTTP verb and success status code.  Kovert is designed to avoid using those annotations altogether, and they should appear ONLY in special cases.  \n\n### Infering HTTP Verb and Path\n\nSo without any annotations, how does Kovert decide the path names and parameters?!?\n\nFirst, if the name contains underscores it is split on each underscore maintaining case of each fragment in between.  Otherwise it camelCase parses the name lower casing each fragment.\n\nAfter parsing the first fragment indicates the HTTP verb, and the rest are segments of the path.  When it encounters special words, it creates path parameters.\n\nThe parsing looks like:\n\n```kotlin\n// thisIsATestOfSplitting = this is a test of splitting\n// AndWhatAboutThis = and what about this\n// aURIIsPresent = a uri is present\n// SomethingBySomething = something :something\n// something20BySomething30 = something20 :something30\n// 20ThisAndThat = 20 this and that\n// 20thisAndThat = 20this and that\n// What_about_underscores = What about underscores\n// 20_ThisAndThat_And_What = 20 ThisAndThat And What\n// 20________thisAndThat__What = 20 thisAndThat What\n```\n\nUsing the prefix part of the method, a HTTP verb is inferred.  Obviously prefixes of \"get\", \"put\", \"post\", \"delete\", \"patch\" will generate a route that is for the HTTP verb of the same name.  You can see in `KovertConfig` that other aliases are defined such as \"list\" and \"view\" for HTTP GET, and \"remove\" also works same as HTTP DELETE.  You can change the alias list in `KovertConfig` using the `addVerbAlias` or `removeVerbAlias` methods.  You can also specify aliases in the `bindController` method as an optional parameter, or as annotations `@VerbAliases` and `@VerbAlias` on your controller class.  The sample application modifies `KovertConfig` to add \"find\" as an alias to HTPT GET:\n\n```kotlin\nKovertConfig.addVerbAlias(\"find\", HttpVerb.GET)\n```\n\n[Other annotations can be used on classes](#annotations) to tune their behavior and override the path, path parameters and HTTP verbs.\n\nAll routing path and parameter decisions are logged to the current logger, so  you can easily see the results of the `bindController` method.  The example above, would generate these paths when bound at \"/api\" route:\n\n|Method|Verb|Path (w/parameters)|\n|------|----|-------------------|\n|`getCompanyByName(name: String)`|GET|`api/company/:name`|\n|`putCompanyByName(name: String)`|PUT|`api/company/:name`|\n|`listCompanyByNameEmployees(name: String)`|GET|`api/company/:name/employees`|\n|`findCompaniesNamedByName(name: String)`|GET|`api/companies/named/:name`|\n|`findCompaniesLocatedInCountry(country: String)`|GET|`api/companies/located/:country`|\n|`getCompaniesSearch(name: String, country: String)`|GET|`api/companies/search?name=xyz\u0026country=abc`|\n\nWhich I can confirm by viewing my log output (notice it logs from my controller class):\n\n```\n11:41:20.880 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding getCompanyByName to HTTP GET:200 /api/company/:name w/context ApiKeySecured\n11:41:20.882 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding listCompanyByNameEmployees to HTTP GET:200 /api/company/:name/employees w/context ApiKeySecured\n11:41:20.883 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding findCompaniesNamedByName to HTTP GET:200 /api/companies/named/:name w/context ApiKeySecured\n11:41:20.884 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding putCompanyByName to HTTP PUT:200 /api/company/:name w/context ApiKeySecured\n11:41:20.885 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding findCompaniesLocatedInCountry to HTTP GET:200 /api/companies/located/:country w/context ApiKeySecured\n11:41:20.886 [vert.x-eventloop-thread-2] INFO  u.k.k.vertx.sample.CompanyRestController - Binding getCompaniesSearch to HTTP GET:200 /api/companies/search w/context ApiKeySecured\n```\n\n### Path Parameters\n\nPreviously, we mentioned that you can use special words to create path parameters, here they are:\n\n|word|description|example|result|\n|----|-----------|-------|------|\n|By|next word is path parameter|`getCompanyByName(name:String)`|HTTP\u0026nbsp;GET\u0026nbsp;company/:name|\n|In|same as By|`getCompaniesInCountry(country:String)`|HTTP\u0026nbsp;GET\u0026nbsp;companies/:country|\n|With|next word is path segment and then repeated as path parameter|`getPersonWithName(name:String)`|HTTP\u0026nbsp;GET\u0026nbsp;person/name/:name|\n\nThe parameter name will then be bound into your method parameters if one of them has a mathing name.  Optional parameters should be nullable.\n\nSoon, we will allow configuring additional replacement rules for word patterns so you can customize the behavior.\n\n### Query and Form Parameters\n\nJust add the parameter to your method signature, and it is looked for in the path parameters, query and form parameters.  Nothing is needed.  You can do simple parameters such as:\n\n```kotlin\npublic fun MyContext.getPeopleByName(name: String): List\u003cPeople\u003e\n```\n\nOr you can use complex parameter such as an object:\n\n```kotlin\npublic fun MyContext.getPeopleByQuery(query: Query): List\u003cPeople\u003e\n```\n\nIn this case, it must receive parameters prefixed by `query.` such as `query.text`, `query.name`, `query.country` to fill in the values of the `Query` object in this example.\n\n### Body as JSON\n\nIf a parameter is not satisfied from path, query, or form parameters, and it has `Content-Type` of `application/json` then if it is a complex parameter type it will bound from the body of the request using Jackson data binding.  \n\nYou can freely mix all parameter types, and the body will only be used if the others do not provide values for a parameter and it will only be used once.  An error will result if a complex parameter exists that cannot be satsified from the request.\n\n### JSON Response\n\nAny non-String return type becomes JSON automatically using Jackson to serialize the result.  This includes classes, lists, maps, and anything else Jackson can detect and serialize as JSON.\n\n### Async and Longer Running Handlers \n\nReturning a Kovenant Promise will unwrap the promise when completed and use the resulting value as the response object.  This allows async methods.  EVERYTHING that isn't immediately resonsive should use Promises otherwise you block Vert.x IO thread, whereas a Promise dispathces on the Vert.x worker thread.  In the sample application, you can see in the company controller that the query method uses a promise return type and returns an `task {}` block of code.  You can also create a `Deferred` instead with more control over your Promise.\n\n```kotlin\npublic fun RestContext.getCompaniesSearch(name: String?, country: String?): Promise\u003cSet\u003cCompany\u003e, Exception\u003e {\n    return task {\n        val byName: List\u003cCompany\u003e = name.whenNotNull { companyService.findCompanyByName(name!!) }.whenNotNull { listOf(it) } ?: emptyList()\n        val byCountry: List\u003cCompany\u003e = country.whenNotNull { companyService.findCompaniesByCountry(country!!) } ?: emptyList()\n        (byName + byCountry).toSet()\n    }\n}\n```\n\nWhen using Kovenant promises, please see the section below about Vert.x + Kovenant.\n\n### Authorization\n\nControllers, context classes and controller methods can be protected by a list of Authorities that must be present on\nthe current user or the controller method(s) cannot be called.  These are set using the ``@Authority` annotation and\nprovides one or more authorities to match with mode `ANY` or `ALL`.  If authorities are set on the controller then they\napply to all methods fo the controller and must match along with any added directly to the context or methods.  Same\nfor adding authorities to a context, it must match and any method specific authorities.\n\nAuthorization handlers must be set when creating the routes to which the controllers are bound.  The authority annotations\nbasically cause the `user.isAuthorised(authoriy)` method to be called for each authority specified, then combined based\non the mode of `ANY` or `ALL`.  See the [Vert.x documentation for Authentication and Authorisation](http://vertx.io/docs/vertx-auth-common/java/)\nfor more on configuration of Vert.x.\n\nAn empty `@Authority()` annotation indicates soley that a valid user must be present, they must be authenticated.\n\nAn example:\n\n```kotlin\n@Authority(\"role:admin\")\nclass AdminApiController {\n    @Authority(\"resource:users:read\") // requires both role:admin and resource:users:read\n    fun SecuredContext.listUsers(): Promise\u003cPageableList\u003cUser\u003e, Exception\u003e { ... }\n\n    @Authority(\"resource:users:write\") // requires both role:admin and resource:users:write\n    fun SecuredContext.postUser(user: User): Promise\u003cUnit, Exception\u003e { ... }\n\n    // requires only role:admin\n    fun SecuredContext.getAdminStats(): Promise\u003cAdminStats, Exception\u003e { ... }\n\n    @Authority(\"resource:secrets\")  // requires role:admin from controller, and role:root from the context, and resource:secrets\n    fun RootContext.getSecretThing(): Promise\u003cSuperSecret, Exception\u003e { ... }\n}\n\n@Authority(\"role:root\")\nclass RootContext(val routingContext: RoutingContext) { ... }\n```\n\n### HTML Views (or rendering any text based content type)\n\nFor rendering HTML or other rendered content you have two options:\n\n* Return anything of type `String` and it will set `Content-Type` of `text/html`\n* Setup a renderer for any controller method\n\nTo use a renderer, add the `@Rendered` annotation to any controller method, and provide the template name to render.   The\nreturn value of the method will be used as the model and will be provided to the template.  This looks like:\n\n```kotlin\n@Rendered(\"search-results.ftl\")\npublic fun UserContext.getCompaniesSearch(name: String?, country: String?): Promise\u003cCompanySearchResults, Exception\u003e {\n    ...\n}\n```\n\nIn this example, the template engine will be selected by the file extension \".ftl\", and the rendering will be passed a model containing and instance of `CompanySearchResults`.\n\nIf the code dynamically changes the template to be used, it can provide a empty `@Rendered` annotation and return a `ModelAndRenderTemplate` instance which provides the information about how to render the results.\n\n```kotlin\n@Rendered\npublic fun UserContext.getCompaniesSearch(name: String?, country: String?): Promise\u003cModelAndRenderTemplate, Exception\u003e {\n    return task {\n        ...\n        ModelAndRenderTemplate(searchResults, \"search-${searchType}-results.ftl\")\n    }\n}\n```\n\nBefore using rendering, add the dependency for your rendering engine, and register an instance of `TemplateEngine` via the `KovertConfig.registerTemplateEngine()` method.\n\n```kotlin\nval freemarker = KovertFreemarkerTemplateEngine(configuredFreemarker)\nKovertConfig.registerTemplateEngine(freemarker, \".html.ftl\", \"text/html\") // content-type is optional for text/html\nKovertConfig.registerTemplateEngine(freemarker, \".xml.ftl\", \"application/xml\")\nKovertConfig.registerTemplateEngine(freemarker, \".json.ftl\", \"application/json\")\n```\n\nTemplate engines are matched from the longest extension to the shortest, so the  most specific wins.  Note that each extension is registered separately with its appropriate Content-Type type.  You can override the content type on any controller method in the `@Rendered` annotation:\n\n```kotlin\n@Rendered(\"search-results.ftl\", \"application/xml\")\npublic fun UserContext.getCompaniesSearch(name: String?, country: String?): Promise\u003cCompanySearchResults, Exception\u003e {\n  ...\n}\n```\n\n**WARNING:** *content type expressed in registration of template engine, or in the @Rendered annotation does not currently affect route matching, but might in the future when it is known at binding time.*\n\nCreating your own template engine is simple, just implement a simple interface `TemplateEngine`, and register an instance of your new engine.  The template engine is always run async so it does not block.  Most implementations are a few lines of code.\n\n```kotlin\npublic interface TemplateEngine {\n    fun render(template: String, model: Any): String\n    // throw unknown exception causes error 500,\n    // intentionally throw HttpErrorNotFound if you want a 404\n}\n```\n\n**NOTE:** *Some engines may need to wrap the model and the use of the model will vary by engine (some allow it to be at the \"root\", others always require it to be named).*\n\nAvailable template engines:\n\n* [Apache FreeMarker](http://freemarker.org/) - KovertFreemarkerTemplateEngine ([source](template-engine-freemarker/src/main/kotlin/uy/kohesive/kovert/template/freemarker/KovertFreemarkerTemplateEngine.kt), [tests](template-engine-freemarker/src/test/kotlin/uy/kohesive/kovert/template/freemarker/TestKovertFreemarkerTemplateEngine.kt))\n\n```\ncompile group: 'uy.kohesive.kovert', name: 'kovert-template-engine-freemarker', version: \"${versionKovert}\"\n```\n\n* [Handlebars.java](https://github.com/jknack/handlebars.java) - KovertHandlebarsTemplateEngine ([source](template-engine-handlebars/src/main/kotlin/uy/kohesive/kovert/template/handlebars/KovertHandlebarsTemplateEngine.kt), [tests](template-engine-handlebars/src/test/kotlin/uy/kohesive/kovert/template/handlebars/TestKovertHandlebarsTemplateEngine.kt))\n\n```\ncompile group: 'uy.kohesive.kovert', name: 'kovert-template-engine-handlebars', version: \"${versionKovert}\"\n```\n\nIt is up to you to configure the raw template system that the engine uses, see the samples above for ideas.\n\n\n### Redirects\n\nFor a redirect, just throw an `HttpRedirect(toUrl)` exception from anywhere in your controller to cause a redirect.  Our rational is that redirects are rare, or exception cases so we didn't want to force a standardized return type (such as ActionResult) just for these special cases, which typically never occur in REST api's and rarely in web frontend controllers.\n\n### HTTP Errors\n\nThe following prebuilt exceptions are available to return HTTP errors.  Any other exception will always result in an HTTP status code 500.\n\n```kotlin\nopen class HttpErrorUnauthorized() : HttpErrorCode(\"unauthorized\", 401)\nopen class HttpErrorForbidden() : HttpErrorCode(\"forbidden\", 403)\nopen class HttpErrorBadRequest() : HttpErrorCode(\"bad request\", 400)\nopen class HttpErrorNotFound() : HttpErrorCode(\"not found\", 404)\n\nopen class HttpErrorCode(message: String, val code: Int = 500, causedBy: Throwable? = null)\nopen class HttpErrorCodeWithBody(message: String, code: Int = 500, val body: Any, causedBy: Throwable? = null) : HttpErrorCode(message, code, causedBy)\n```\n\nThe `HttpErrorCodeWithBody` allows returning a body (String as HTML, complex object as JSON) with the error code.\n\n### Intercepts\n\nYou can intercept by putting another Vert.x handler before you bind the controller and that handler will be called before the controller, and it can decide whether the next handler is called or not.  Or, a controller can implement traits to intercept requests, failures, dispatching and also to create a custom context object factory.  See [VertxTraits.kt](vertx-jdk8/src/main/kotlin/uy/kohesive/kovert/vertx/VertxTraits.kt) for more information.\n\n### Annotations\n\n|Name|Where|Purpose|\n|----|-----|-------|\n|@VerbAlias|Controller|Set one method prefix alias to be used only by this controller|\n|@VerbAliases|Controller|Set a list of method perfix aliases to be used only by this controller|\n|@Location|Method|Set a specific path for a method, ignoring the method name other than for the prefix to infer the HTTP Verb.  Path parameters should be prefixed by a `:` such as `my/path/with/:param`|\n|@Verb|Method|Set the HTTP Verb and default status success code for a method, optionally skipping part of the method name when infering the path|\n|@Authority|Controller, Method, Context|Set the authorities (role, permission, etc) required for the controller, context or method.  And the mode to match ANY or ALL|\n\nIf is typical to use `@Location` and `@Verb` together on a method, although they can be used individually.\n\nIf you use the `@Verb` annotation on a method, by default the prefix of the method name is parsed and thrown away so it really can be anything.  Or if you want to use the prefix as the first path segment you may use the skipPrefix parameter with value `false` such as `@Verb(HttpVerb.GET, skipPrefix = false) public fun SomeContext.someHappyMethod(): MyResult` would bind to `some/happy/method` whereas `skipPrefix = true` would bind to `happy/method`.\n\n### Kovert Helpers\n\n#### Vert.x + Kovenant Promises\n\nFor using Vert.x with [Kovenant](http://kovenant.komponents.nl) promises, you should launch Vert.x using one of the [Klutter/Vertx3](https://github.com/kohesive/klutter/tree/master/vertx3) helper functions.  If you are NOT using these methods, then call `VertxInit.ensure()` before using your first Kovenant promise, and before using anything that involves data binding with Kovert.  Otherwise, using a helper startup function will do this for you automatically.  Note that you can also use the prettier `task { }` instead of Vert.x `executeBlocking()` when using Kovenant integration.\n\nSee [Klutter/Vertx3](https://github.com/kohesive/klutter/tree/master/vertx3) for all Vert.x helper functions include JSON, Vertx-Web, Logging and Injekt modules.\n\n#### Vert.x and KovertVerticle startup\n\nReally, you should configure and launch Vert.x yourself (use helpers above, Klutter for config loading, etc.).  But to act as both a sample, and a quick start helper, There are two classes you can use to startup Vert.x enabled for everything described in this documentation.  Or use these as a samples to write your own:\n\n*  [KovertVertx.kt](vertx-jdk8/src/main/kotlin/uy/kohesive/kovert/vertx/boot/KovertVertx.kt) \n*  [KovertVerticle.kt](vertx-jdk8/src/main/kotlin/uy/kohesive/kovert/vertx/boot/KovertVerticle.kt) \n\nThe sample application [App.kt](vertx-example/src/main/kotlin/uy/kohesive/kovert/vertx/sample/App.kt) shows one use of these classes.\n\n#### Injekt\n\nBoth for setting up JSON integrated with Kotlin, JDK 8 and Vert.x; and for integrated logging you may import Injekt modules.\n\nImporting module `VertxInjektables` will provide an `ObjectMapper` singleton available for Jackson data binding that is shared with Vert.x, and Kovenant will be initialized correctly so that promises and async calls work in conjunction with Vert.x thread dispathcing, and you will have a logger factory configured routing any of your injected logging calls through the Vertx logging Facade.  Alterantively you can import the `VertxWithSlf4jInjektables` module for the same benefits, although your logging factory will be setup to be direct to SLF4j for application code.\n\nSee the sample application [App.kt](vertx-example/src/main/kotlin/uy/kohesive/kovert/vertx/sample/App.kt) which uses Injekt for configuration, `KovertVertx` and `KovertVerticle` classes to launch Vert.x and Kovert, data binding, logging and providing services.\n\n### More Examples\n\nView the [sample application](vertx-example/src/main/kotlin/uy/kohesive/kovert/vertx/sample/), and the [unit tests](vertx-jdk8/src/test/kotlin/uy/kohesive/kovert/vertx/) for more combinations of the previously described topics.  \n\n### Road Map (random order)\n\n* Undertow support as alternative to Vert.x\n* SparkJava support as alternative to Vert.x and Undertow\n* Configurable clauses in method names for substitution patterns (i.e. \"By\", \"In\", \"With\" are substitution patterns)\n* With View support, people will want to ask for the HREF from a given controller method, should be able to provide that in Kotlin M13, or can provide using the `val getSomeThing = fun MyContext.(param: String): MyObject { ... }` form of declaring a controller method already since `MyClass::getSomething` can reference that value, whereas in the other form, it is not referenceable in M12.\n* Ignore annotation for extension methods in controller that are not desired\n\n\n## Special Thanks\n\n![YourKit logo](https://www.yourkit.com/images/yklogo.png)\n\nYourKit supports open source projects with its full-featured Java Profiler.\nYourKit, LLC is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/)\nand [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/),\ninnovative and intelligent tools for profiling Java and .NET applications.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkohesive%2Fkovert","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkohesive%2Fkovert","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkohesive%2Fkovert/lists"}