Feign QueryMap usage with POJO - kotlin

I've found in FEIGN-README that I can do stuff like:
interface MarketDataRestClient {
#RequestLine("GET /api/v1/depth")
fun getOrderBook(#QueryMap orderBookQuery: OrderBookQuery) : OrderBook
}
OrderBookQuery:
data class OrderBookQuery(val symbol: String, val limit: Int? = 100)
And Feign should generate query params: /api/v1/depth?symbol={symbol}&limit={limit}
Unfortunately all I'm getting is:
Exception in thread "main" java.lang.IllegalStateException: QueryMap parameter must be a Map: class OrderBookQuery
at feign.Util.checkState(Util.java:128)
at feign.Contract$BaseContract.parseAndValidateMetadata(Contract.java:126)
at feign.Contract$BaseContract.parseAndValidatateMetadata(Contract.java:64)
at feign.ReflectiveFeign$ParseHandlersByName.apply(ReflectiveFeign.java:146)
at feign.ReflectiveFeign.newInstance(ReflectiveFeign.java:53)
at feign.Feign$Builder.target(Feign.java:198)
at feign.Feign$Builder.target(Feign.java:194)

This feature will be available in 9.7. The current published version, as of the writing of this answer, is 9.6. If you do not want to wait, please clone the repository and run build the project.

Just add a QueryMapEncoder,likeļ¼š
return Feign
.builder()
.client(new OkHttpClient())
.logger(new Logger.ErrorLogger()).logLevel(Logger.Level.BASIC)
.queryMapEncoder(new BeanQueryMapEncoder())
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())

Related

kotest change environment variables

I am writing tests for Ktor app using Kotests, but stumbled into the problem how can I change env variables for tests preferably globally. I have tried adding withEnvironment but it throw quite strange error into me
Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module #3daa422a
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.Map java.util.Collections$UnmodifiableMap.m accessible: module java.base does not "opens java.util" to unnamed module #3daa422a
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
my test file looks like this
class VisitorSpec : FreeSpec({
val ds = createDataSourceTest()
val visitor = RegisterVisitorDTO(
email = TestConstants.VISITOR_EMAIL,
username = TestConstants.VISITOR_USERNAME,
password = TestConstants.PASSWORD,
firstName = TestConstants.VISITOR_FIRST_NAME,
lastName = TestConstants.VISITOR_LAST_NAME,
gender = TestConstants.VISITOR_GENDER,
birthday = TestConstants.VISITOR_BIRTHDAY,
)
"check visitor routes" - {
val loginData = LoginDTO(TestConstants.VISITOR_EMAIL + 0, TestConstants.PASSWORD)
"can get list of visitors with correct query" {
withEnvironment(
mapOf(
"POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
"POSTGRES_USERNAME" to "test_user",
"POSTGRES_PASSWORD" to "test_pass"
)
) {
testApplication {
val client = getClient(ds)
repeat(6) {
registerConfirmedUser(
client, visitor.copy(
email = "${TestConstants.VISITOR_EMAIL}$it",
username = "${TestConstants.VISITOR_USERNAME}$it",
)
)
}
val accessToken = loginUser(client, loginData).run { this.body<LoginResponseDTO>().accessToken }
client.get("/api/v1/visitors?page=1&count=5") {
header("Authorization", "Bearer $accessToken")
}.apply {
val response = this.body<VisitorPaginatedResponseDTO>()
response.data.size.shouldBe(5)
response.totalCount.shouldBe(6)
response.currentPage.shouldBe(1)
}
}
}
}
...
if I remove
withEnvironment(
mapOf(
"POSTGRES_URL" to "jdbc:postgresql://localhost:5432/test",
"POSTGRES_USERNAME" to "test_user",
"POSTGRES_PASSWORD" to "test_pass"
)
)
it will just work but with default db, any advice on this?
In some places, it was advised to use
override fun listeners() = listOf(
SystemEnvironmentTestListener("fooKeyEnv", "barValueEnv"),
SystemPropertyTestListener("fooKeyProp", "barValueProp")
)
but ide tells me that this method is deprecated.
Thanks in advance for any advice.
Recent Java versions prohibit modifying the environment variables with the default access settings (JEP 403: Strongly Encapsulate JDK Internals). Kotest and some other testing frameworks that manipulate the environment variables got affected by this, you can find the related issues:
https://github.com/kotest/kotest/issues/2849
https://github.com/stefanbirkner/system-lambda/issues/23
https://github.com/junit-pioneer/junit-pioneer/issues/509
One solution would be to add the arguments to the JVM running the tests that would make the Java Platform Module System allow the access to the API used by the test framework. Here's an answer that explains the arguments: How to set environment variable in Java without 'illegal reflective access'? How to use add-opens?
The simplest form of the argument, if you are not using Java modules in your code, would be:
--add-opens java.base/java.util=ALL-UNNAMED
If you are running the tests using Gradle, then you can pass this argument to the jvmArgs of the test task:
tasks.withType<Test>().named("jvmTest") {
jvmArgs("--add-opens", "java.base/java.util=ALL-UNNAMED")
}
Note: modifying the module access in this way could make the tests pass even if some of your code needs illegal access to the JDK internals. Make sure that your code doesn't do that or that you have other tests that check for this without modifying module access rights.
It seems that some other libraries, like system-stubs, provide a way to modify the environment variables in tests without illegal reflective access to the JDK internals.

uuidconfigurationexception in kmogo Kotlin

import org.litote.kmongo.KMongo
fun main() {
val client = Kmongo.createClient(/* connection string from mongodb */)
val database = client.getDatabase(/* databaseName */)
}
my code ^
this is what it returns:
Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Changing the default UuidRepresentation requires a CodecRegistry that also implements the CodecProvider interface
at org.litote.kmongo.KMongo.createRegistry(KMongo.kt:89)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:78)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:60)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:50)
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)
for security purposes, i have omitted the additional code that are irrelevant and the database names
Thanks for your help in advance!
From kmongo source here: https://github.com/Litote/kmongo/blob/master/kmongo-core/src/main/kotlin/org/litote/kmongo/KMongo.kt, it seems that this is happening because the UUID Representation is JAVA_LEGACY.
Hence, you should supply a MongoClientSettings object to createClient, which specifies a UUID representation other than JAVA_LEGACY. Since UuidRepresentation is an enum (https://mongodb.github.io/mongo-java-driver/3.5/javadoc/org/bson/UuidRepresentation.html), you can try using STANDARD.
Like this:
val settings = MongoClientSettings.builder()
.applyConnectionString(ConnectionString("Connection String here"))
.uuidRepresentation(UuidRepresentation.STANDARD)
.codecRegistry(KMongoUtil.defaultCodecRegistry)
.build()
val client = KMongo.createClient(settings)
// other code below...
This should get KMongo/MongoDB to use UuidRepresentation.STANDARD instead of UuidRepresentation.JAVA_LEGACY (in which KMongo will always throw an exception)
Thanks!

Extracting Nested POJO Object with Rest-Assured

I'm writing some tests using rest-assured and its Kotlin extensions to test some simple Spring MVC endpoints. I'm trying to understand how to extract values.
One endpoint returns a BookDetailsView POJO, the other returns a Page<BookDetailsView> (where Page is an interface provided by Spring for doing paging).
BookDetailsView is a really simple Kotlin data class with a single field:
data class BookDetailsView(val id: UUID)
For the single object endpoint, I have:
#Test
fun `single object`() {
val details = BookDetailsView(UUID.randomUUID())
whenever(bookDetailsService.getBookDetails(details.id)).thenReturn(details)
val result: BookDetailsView = Given {
mockMvc(mockMvc)
} When {
get("/book_details/${details.id}")
} Then {
statusCode(HttpStatus.SC_OK)
} Extract {
`as`(BookDetailsView::class.java)
}
assertEquals(details.id, result.id)
}
This works as expected, but trying to apply the same technique for the Page<BookDetailsView> runs afoul of all sorts of parsing challenges since Page is an interface, and even trying to use PageImpl isn't entirely straightforward. In the end, I don't even really care about the Page object, I just care about the nested list of POJOs inside it.
I've tried various permutations like the code below to just grab the bit I care about:
#Test
fun `extract nested`() {
val page = PageImpl(listOf(
BookDetailsView(UUID.randomUUID())
))
whenever(bookDetailsService.getBookDetailsPaged(any())).thenReturn(page)
val response = Given {
mockMvc(mockMvc)
} When {
get("/book_details")
} Then {
statusCode(HttpStatus.SC_OK)
body("content.size()", `is`(1))
body("content[0].id", equalTo(page.first().id.toString()))
} Extract {
path<List<BookDetailsView>>("content")
}
println(response[0].javaClass)
}
The final println spits out class java.util.LinkedHashMap. If instead I try to actually use the object, I get class java.util.LinkedHashMap cannot be cast to class BookDetailsView. There are lots of questions and answers related to this, and I understand it's ultimately an issue of the underlying JSON parser not knowing what to do, but I'm not clear on:
Why does the "simple" case parse without issue?
Shouldn't the type param passed to the path() function tell it what type to use?
What needs configuring to make the second case work, OR
Is there some other approach for grabbing a nested object that would make more sense?
Digging a bit into the code, it appears that the two cases may actually be using different json parsers/configurations (the former seems to stick to rest-assured JSON parsing, while the latter ends up in JsonPath's?)
I don't know kotlin but here is the thing:
path() doesn't know the Element in your List, so it'll be LinkedHashMap by default instead of BookDetailsView.class
to overcome it, you can provide TypeReference for this.
java example
List<BookDetailsView> response = ....then()
.extract().jsonPath()
.getObject("content", new TypeRef<List<BookDetailsView>>() {});
kotlin example
#Test
fun `extract nested`() {
var response = RestAssured.given().get("http://localhost:8000/req1")
.then()
.extract()
.jsonPath()
.getObject("content", object : TypeRef<List<BookDetailsView?>?>() {});
println(response)
//[{id=1}, {id=2}]
}

How to exclude mutations from Query root node with spqr?

I use io.leangen.graphql.spqr library version 0.9.6 and I need to exclude mutations from Query root node into the Doc.
My GraphQLController.kt looks this way:
#RestController
class GraphQLController {
private var graphQL: GraphQL? = null
#Autowired
fun GraphQLController(bookGraph: BookGraph) {
val schema = GraphQLSchemaGenerator()
.withResolverBuilders(
AnnotatedResolverBuilder(),
PublicResolverBuilder("com.example.graphql.spqr"))
.withOperationsFromSingleton(bookGraph)
.withValueMapperFactory(JacksonValueMapperFactory())
.generate()
graphQL = GraphQL.newGraphQL(schema)
.build()
}
#PostMapping(value = ["/graphql"], consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE])
#ResponseBody
fun execute(#RequestBody request: Map<String?, Any?>): ExecutionResult? {
return graphQL!!.execute(ExecutionInput.newExecutionInput()
.query(request["query"] as String?)
.variables(request["variables"] as Map<String, Object>?)
.operationName(request["operationName"] as String?)
.build())
and my BookGraph.kt looks this way:
#Component
class BookGraph {
#Autowired
private lateinit var bookService: BookService
#GraphQLQuery(name = "books")
fun books() : List<Book> {
return bookService.findAll()
}
#GraphQLMutation(name = "createBook")
fun createBook(#GraphQLInputField(name = "name") name: String) : Book {
return bookService.findAll()
}
}
How can I do it?
I searched for possible solutions both in StackOverflow and SPQR issues but cannot find a solution.
Example of Query root node below, I want to exclude createBook:
While I want Mutation root node to remain untouched:
It's bug. You're using a very old version of SPQR (Feb. 2018). This has been fixed a long long time ago. Please try to follow the releases as much as possible, as lots of things are getting fixed and improved.
It is possible to work around the bug by customizing the ResolverBuilders, but I wouldn't recommend going that route.
The Spring Starter (if even relevant to you) is currently lagging behind (not yet on the latest SPQR version) but I'm actively working on the new release. Should be out very soon.
Btw, your setup has a lot of redundancy. Can be simplified to:
val schema = GraphQLSchemaGenerator()
.withOperationsFromSingleton(bookGraph)
//replace with your own root package(s)
.withBasePackages("com.example.graphql.spqr")
.generate()

Http4s EntityDecoder not being auto derived for simple case class

I am getting this error:
Cannot decode into a value of type com.blah.rest.model.UserProfile,
because no EntityDecoder[cats.effect.IO, com.blah.rest.model.UserProfile]
instance could be found.
for the following case class:
case class UserProfile(id: Option[Int], firstName: String, lastName: String)
Encountered the error on POST code:
case req # POST -> Root / "v1" / "profiles" =>
req.as[UserProfile] flatMap {(up: UserProfile) =>
Ok(service.createProfile(up).asJson)
}
With the following POST body:
{
"firstName": "Jack",
"lastName": "Appleseed"
}
I think this happens when the body is being converted to UserProfile in req.as[UserProfile]!
But, this is a plain vanilla case class, the EntityDecoder should be auto-derived! I know akka-http does it!
Any ideas/suggestions?
Please Note: Http4sVersion = "0.18.0-M4" and circe version "0.9.0-M1"
The answer is:
req.decodeJson[UserProfile] flatMap {(up: UserProfile) =>
Ok(service.createProfile(up).asJson)
}
The reason you get that is the bridge from a Circe decoder to an http4s EntityDecoder is not implicit. You can make one if you're purely a JSON API, but that's not an assumption the library can generally make.
Adding this dependency:
"io.circe" %% "circe-generic" % "0.9.1"
resolved the auto-encoding of case classes to JSON for me.
It allows for the required import: import io.circe.generic.auto._