Skip to content

Commit

Permalink
Initial commit for MicronautConverter (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
cc-jhr committed Apr 18, 2019
1 parent 56620ec commit 6f5b610
Show file tree
Hide file tree
Showing 29 changed files with 592 additions and 12 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The aim of this project is to meet this need and offer a mechanism to check spec
+ [WADL](wadl/README.md)
+ **Implementations**
+ [Spring MVC 5.1.X](spring/README.md)
+ [Micronaut](micronaut/README.md)
+ [JAX-RS 2.1.X](jax-rs/README.md)
+ [Apache CXF](http://cxf.apache.org)
+ [Dropwizard](https://www.dropwizard.io)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.codecentric.hikaku.converters.jaxrs
package de.codecentric.hikaku.converters

import de.codecentric.hikaku.extensions.extension
import de.codecentric.hikaku.extensions.nameWithoutExtension
Expand All @@ -10,7 +10,7 @@ import java.nio.file.Paths
/**
* Original code snippet found at [dzone](https://dzone.com/articles/get-all-classes-within-package) posted by [Victor Tatai](https://dzone.com/users/74061/vtatai.html).
*/
internal object ClassLocator {
object ClassLocator {

fun getClasses(packageName: String): List<Class<*>> {
val classLoader = Thread.currentThread().contextClassLoader
Expand Down Expand Up @@ -42,7 +42,7 @@ internal object ClassLocator {
Files.list(directory)
.forEach {
if (Files.isDirectory(it)) {
assert(!it.fileName.toString().contains("."))
assert(!it.fileName.toString().contains(""))
classes.addAll(findClasses(it, "$packageName.${it.fileName}"))
} else if (it.extension() == "class") {
classes.add(Class.forName("$packageName.${it.nameWithoutExtension()}"))
Expand Down
16 changes: 8 additions & 8 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ The following table gives an overview of all features and which converter suppor
The check for endpoint paths and http methods are base functions that every converter has to support. Those are not listed in the table below.
There might be various ways to declare or use a feature, so check each converter for unsupported features as well.

| Feature Name | Description | [OpenApi Converter](../openapi/README.md)| [Spring Converter](../spring/README.md) | [WADL Converter](../wadl/README.md) | [RAML Converter](../raml/README.md) | [JAX-RS Converter](../jax-rs/README.md) |
| --- | --- | --- | --- | --- | --- | --- |
| QueryParameters | Name of a query parameter and whether the parameter is required or not. _Example:_ `/todos?filter=all`|_(1.0.0)_ |_(1.0.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ |
| PathParameters | Name of a path parameter. _Example:_ `/todos/{id}`|_(1.0.0)_ |_(1.0.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ |
| HeaderParameters | Name of a header parameter and whether the parameter is required or not. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ |
| MatrixParameters | Name of a matrix parameter and whether the parameter is required or not. _Example:_ `/todos;param=value` ||_(2.1.0)_ |_(2.1.0)_ ||_(2.1.0)_ |
| Produces | Checks the supported media types of the response. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ |
| Consumes | Checks the supported media types of the request. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ |
| Feature Name | Description | [OpenApi Converter](../openapi/README.md)| [Spring Converter](../spring/README.md) | [WADL Converter](../wadl/README.md) | [RAML Converter](../raml/README.md) | [JAX-RS Converter](../jax-rs/README.md) | [Micronaut Converter](../micronaut/README.md) |
| --- | --- | --- | --- | --- | --- | --- | --- |
| QueryParameters | Name of a query parameter and whether the parameter is required or not. _Example:_ `/todos?filter=all`|_(1.0.0)_ |_(1.0.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ ||
| PathParameters | Name of a path parameter. _Example:_ `/todos/{id}`|_(1.0.0)_ |_(1.0.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ ||
| HeaderParameters | Name of a header parameter and whether the parameter is required or not. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ ||
| MatrixParameters | Name of a matrix parameter and whether the parameter is required or not. _Example:_ `/todos;param=value` ||_(2.1.0)_ |_(2.1.0)_ ||_(2.1.0)_ ||
| Produces | Checks the supported media types of the response. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ ||
| Consumes | Checks the supported media types of the request. |_(1.1.0)_ |_(1.1.0)_ |_(1.1.0)_ |_(2.0.0)_ |_(2.1.0)_ ||
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.codecentric.hikaku.converters.jaxrs
import de.codecentric.hikaku.SupportedFeatures
import de.codecentric.hikaku.SupportedFeatures.Feature
import de.codecentric.hikaku.converters.AbstractEndpointConverter
import de.codecentric.hikaku.converters.ClassLocator
import de.codecentric.hikaku.converters.EndpointConverterException
import de.codecentric.hikaku.endpoints.*
import de.codecentric.hikaku.endpoints.HttpMethod
Expand Down
1 change: 1 addition & 0 deletions micronaut/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# hikaku - micronaut
18 changes: 18 additions & 0 deletions micronaut/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
group = 'de.codecentric.hikaku'
archivesBaseName = 'hikaku-micronaut'

dependencies {
api project(':core')
api "io.micronaut:micronaut-http:1.1.0"
}

uploadArchives {
repositories {
mavenDeployer {
pom.project {
name = 'hikaku-micronaut'
description = 'A library that tests if the implementation of a REST-API meets its specification. This module contains a converter for micronaut implementations.'
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package de.codecentric.hikaku.converters.micronaut

import de.codecentric.hikaku.SupportedFeatures
import de.codecentric.hikaku.converters.AbstractEndpointConverter
import de.codecentric.hikaku.converters.ClassLocator
import de.codecentric.hikaku.converters.EndpointConverterException
import de.codecentric.hikaku.endpoints.Endpoint
import de.codecentric.hikaku.endpoints.HttpMethod
import io.micronaut.http.annotation.*
import java.lang.reflect.Method

class MicronautConverter(private val packageName: String) : AbstractEndpointConverter() {

override val supportedFeatures = SupportedFeatures()

override fun convert(): Set<Endpoint> {
if (packageName.isBlank()) {
throw EndpointConverterException("Package name must not be blank.")
}

return ClassLocator.getClasses(packageName)
.filter { it.getAnnotation(Controller::class.java) != null }
.flatMap { extractEndpoints(it) }
.toSet()
}

private fun extractEndpoints(resource: Class<*>): List<Endpoint> {
return resource.methods
.filter { isHttpMethodAnnotationPresent(it) }
.map { createEndpoint(resource, it) }
}

private fun isHttpMethodAnnotationPresent(method: Method): Boolean {
return when {
method.isAnnotationPresent(Delete::class.java) -> true
method.isAnnotationPresent(Get::class.java) -> true
method.isAnnotationPresent(Head::class.java) -> true
method.isAnnotationPresent(Options::class.java) -> true
method.isAnnotationPresent(Patch::class.java) -> true
method.isAnnotationPresent(Post::class.java) -> true
method.isAnnotationPresent(Put::class.java) -> true
else -> false
}
}

private fun createEndpoint(resource: Class<*>, method: Method) = Endpoint(
path = extractPath(resource, method),
httpMethod = extractHttpMethod(method)
)

private fun extractPath(resource: Class<*>, method: Method): String {
var pathOnClass = resource.getAnnotation(Controller::class.java).value
val pathOnFunction = when {
method.isAnnotationPresent(Delete::class.java) -> method.getAnnotation(Delete::class.java).value
method.isAnnotationPresent(Get::class.java) -> method.getAnnotation(Get::class.java).value
method.isAnnotationPresent(Head::class.java) -> method.getAnnotation(Head::class.java).value
method.isAnnotationPresent(Options::class.java) -> method.getAnnotation(Options::class.java).value
method.isAnnotationPresent(Patch::class.java) -> method.getAnnotation(Patch::class.java).value
method.isAnnotationPresent(Post::class.java) -> method.getAnnotation(Post::class.java).value
method.isAnnotationPresent(Put::class.java) -> method.getAnnotation(Put::class.java).value
else -> ""
}

if (!pathOnClass.startsWith("/")) {
pathOnClass = "/$pathOnClass"
}

val combinedPath = "$pathOnClass/$pathOnFunction".replace(Regex("/+"), "/")

return if (combinedPath.endsWith('/')) {
combinedPath.substringBeforeLast('/')
} else {
combinedPath
}
}

private fun extractHttpMethod(method: Method): HttpMethod {
return when {
method.isAnnotationPresent(Delete::class.java) -> HttpMethod.DELETE
method.isAnnotationPresent(Get::class.java) -> HttpMethod.GET
method.isAnnotationPresent(Head::class.java) -> HttpMethod.HEAD
method.isAnnotationPresent(Options::class.java) -> HttpMethod.OPTIONS
method.isAnnotationPresent(Patch::class.java) -> HttpMethod.PATCH
method.isAnnotationPresent(Post::class.java) -> HttpMethod.POST
method.isAnnotationPresent(Put::class.java) -> HttpMethod.PUT
else -> throw IllegalStateException("Unable to determine http method. Valid annotation not found.")
}
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package de.codecentric.hikaku.converters.micronaut

import de.codecentric.hikaku.converters.EndpointConverterException
import org.junit.jupiter.api.Test
import kotlin.test.assertFailsWith

class MicronautConverterPackageDefinitionTest {

@Test
fun `invoking converter with empty string leads to EndpointConverterException`() {
assertFailsWith<EndpointConverterException> {
MicronautConverter("").conversionResult
}
}

@Test
fun `invoking converter with blank string leads to EndpointConverterException`() {
assertFailsWith<EndpointConverterException> {
MicronautConverter(" ").conversionResult
}
}
}
Loading

0 comments on commit 6f5b610

Please sign in to comment.