Sandymcp
10/05/2021, 1:48 PMSandymcp
10/06/2021, 9:01 AMfun testDataGenerator(sse: Sse, start: Long = 0, delay: Long = 0, dataSource: (sequence: Int) -> SseMessage) {
<http://logger.info|logger.info> { "Request from ${sse.connectRequest.uri}" }
val closed = AtomicBoolean(false)
sse.onClose { <http://logger.info|logger.info> { "Response body closed by client"; closed.set(true) } }
Thread.sleep(start)
for (i in 1..Int.MAX_VALUE) {
if (closed.get())
break
try {
val m = dataSource(i)
if (m.event == null) {
sse.send(Data(data = m.data.joinToString(" ")))
} else {
sse.send(Event(event = m.event!!, data = m.data.joinToString(" "), id = m.id))
}
logger.debug { "sent ${Clock.System.now().toEpochMilliseconds()}" }
Thread.sleep(delay)
} catch (_: EOFException) {
<http://logger.info|logger.info> { "Response body closed by client" }
break
} catch (e: Throwable) {
logger.error(e) { "Unexpected error writing to response body" }
break
}
}
}
Sandymcp
10/06/2021, 9:02 AMMatt
10/06/2021, 9:36 AMCould not find locale-lang in MyClass(localeLang=en)
org.http4k.contract.openapi.v3.NoFieldFound: Could not find locale-lang in MyClass(localeLang=en)
at app//org.http4k.contract.openapi.v3.FieldRetrieval$Companion.compose$lambda-0(FieldRetrieval.kt:17)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectSchema(AutoJsonToJsonSchema.kt:99)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectOrMapSchema(AutoJsonToJsonSchema.kt:83)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:36)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toArraySchema(AutoJsonToJsonSchema.kt:53)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.makePropertySchemaFor(AutoJsonToJsonSchema.kt:146)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toMapSchema(AutoJsonToJsonSchema.kt:124)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toObjectOrMapSchema(AutoJsonToJsonSchema.kt:83)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:36)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toArraySchema(AutoJsonToJsonSchema.kt:53)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:35)
at app//org.http4k.contract.openapi.v3.AutoJsonToJsonSchema.toSchema(AutoJsonToJsonSchema.kt:26)
at app//org.http4k.contract.openapi.ApiRenderer$Companion$Auto$fallbackSchema$1.toSchema(ApiRenderer.kt:30)
at app//org.http4k.contract.openapi.ApiRenderer$Companion$Auto$1.toSchema(ApiRenderer.kt)
at app//org.http4k.contract.openapi.v3.OpenApi3.toSchemaContent(OpenApi3.kt:205)
at app//org.http4k.contract.openapi.v3.OpenApi3.collectSchemas(OpenApi3.kt:141)
at app//org.http4k.contract.openapi.v3.OpenApi3.responses(OpenApi3.kt:134)
at app//org.http4k.contract.openapi.v3.OpenApi3.apiPath(OpenApi3.kt:108)
at app//org.http4k.contract.openapi.v3.OpenApi3.asPath(OpenApi3.kt:94)
at app//org.http4k.contract.openapi.v3.OpenApi3.description(OpenApi3.kt:70)
at app//org.http4k.contract.ContractRoutingHttpHandler$descriptionRoute$2$1.invoke(ContractRoutingHttpHandler.kt:66)
at app//org.http4k.contract.ContractRoutingHttpHandler$descriptionRoute$2$1.invoke(ContractRoutingHttpHandler.kt:66)
at app//org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
at app//org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//org.http4k.core.Http4kKt$NoOp$1$1.invoke(Http4k.kt:11)
at app//com.pret.food.extensions.ServerFilterExtensions$BasicForwardedAuth$invoke$1$1.invoke(ServerFilterExtensions.kt:43)
at app//com.pret.food.extensions.ServerFilterExtensions$BasicForwardedAuth$invoke$1$1.invoke(ServerFilterExtensions.kt:36)
at app//org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:105)
at app//org.http4k.contract.ContractRoutingHttpHandler$identify$1$1$1.invoke(ContractRoutingHttpHandler.kt:103)
at app//org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt:15)
at app//org.http4k.core.Http4kKt$then$2.invoke(Http4k.kt:15)
at app//org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:47)
at app//org.http4k.routing.RouterMatch$MatchingHandler.invoke(Router.kt:46)
at app//org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:19)
at app//org.http4k.routing.RouterBasedHttpHandler.invoke(RouterBasedHttpHandler.kt:13)
at app//AppTest$3.invokeSuspend(AppTest.kt:31)
at app//AppTest$3.invoke(AppTest.kt)
at app//AppTest$3.invoke(AppTest.kt)
Moritz Platt
10/07/2021, 10:00 AMbody.auto
my Gson config is not used:
val auto: CustomerCreateModel = Body.auto<CustomerCreateModel>().toLens().extract(it)
val manual: CustomerCreateModel = MyGson.mapper.fromJson(it.bodyString(), CustomerCreateModel::class.java)
assert(auto == manual) //fails
I have the following on my classpath:
object MyGson : ConfigurableGson(
GsonBuilder()
.registerTypeAdapter(Country::class.java, CountryDeserializer())
.registerTypeAdapter(Country::class.java, CountrySerializer())
.setDateFormat("yyyy MM-dd'T'HH:mm:ss.SSS'Z'")
.asConfigurable()
.withStandardMappings()
.done()
)
I'm on a fairly old version of http4k (3.261.0), btwErich
10/11/2021, 4:21 PM"/book/{title}" bind GET to { req -> Response.invoke(Status.OK).body(GetBookDetails(req.path("title")) }
"/author/{name}/latest" bind GET to { req -> Response.invoke(Status.OK).body(GetAllBooks(author = req.path("name")).last()) }
But I’m looking to be able to take a list of Strings at runtime and create paths dynamically (with param) for each...
["book","author","character",..., "n"]
"/book/{key}"
"/author/{key}"
"/character/{key}"
...
"/{n}/{key}"
Is this possible?Andrew O'Hara
10/12/2021, 8:26 PMOPTIONS
call. The call works fine with a core http4k router, but if I wrap it in a contract
, then the contract router never calls my handler and returns an empty 200. Presumably, this was done with the assumption that OPTIONS
doesn't make much sense in a RESTP API, but that isn't always the case.
Is this phenomenon intentional? Is there a way to prevent it?
@Test
fun options() {
val handler = contract {
routes += "/v1/things" bindContract Method.OPTIONS to { _ ->
Response(Status.OK).body("lolcats")
}
}
val resp = handler(Request(Method.OPTIONS, "/v1/things"))
resp shouldHaveStatus Status.OK
resp shouldHaveBody "lolcats"
}
Body: expected:<"lolcats"> but was:<<empty string>>
Jim
10/15/2021, 1:19 PMspierce7
10/19/2021, 3:42 AMSean Abbott
10/29/2021, 7:03 PMMatt
11/08/2021, 2:57 PMdave
11/11/2021, 11:56 AMEduardo Narros
11/16/2021, 6:09 PMfun main() {
val counter = AtomicLong()
routes(
"/sleep" bind Method.GET to {
Thread.sleep(20000).body("woke up")
Response(Status.OK)
},
"/info" bind Method.GET to { Response(Status.OK).body(counter.addAndGet(1L).toString() + " ") },
).asServer(Netty(7000)).start()
}
If you run it and then using a terminal you run:
curl localhost:7000/sleep& for run in {1..20}; do curl localhost:7000/info; done
you would expect to see all the 20 /info responses and then the initial /sleep response after a few seconds.
However, what happens is that you get around 15 /info responses, then the next request blocks until the /sleep request completes and then the remaining requests are completed. i.e.
curl localhost:7000/sleep& for run in {1..20}; do curl localhost:7000/info; done
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 woke up
16 17 18 19 20
We have tried in different machines and we most of the time you get up to around 15 before the request blocks so we don’t know if that number is driven by a configuration parameter that we aren’t aware of.dave
11/17/2021, 7:40 AMAlexander Weickmann
11/22/2021, 9:01 PMDanielZ
11/24/2021, 8:59 AMBen Tilford
11/29/2021, 11:47 PMRequestContext
for unit tests that are using a mocked request?dmcg
12/03/2021, 8:41 AMhttps://youtu.be/MxzAr4UBOs0▾
Razvan
12/04/2021, 8:37 PMdata class TestData(
val s1: Int? = null,
val sXe: Int? = null,
)
object TestRoute {
val bodyTest = Body.auto<TestData>().toLens()
operator fun invoke(): ContractRoute {
val metaData: RouteMetaDsl.() -> Unit = {
tags += Tag("aaa")
returning(
Status.OK,
bodyTest to TestData(s1 = 1, sXe = 22)
)
}
return "/test" meta metaData bindContract Method.GET to { _: Request -> Response(Status.OK).with(bodyTest of TestData(s1 = 1, sXe = 12)) }
}
}
if in the example sXe
has a value the /api/swagger.json fails to load:
Could not find sxe in TestData(s1=1, sXe=22)
if i set it to null in the example it loads but when i run the query I get as response body
{
"s1": 1,
"sxe": 12
}
Looks like the uppercase X
is no longer in uppercase...
In 4.17.2.0
MrNiamh
12/06/2021, 3:11 PMval imageFile = MultipartFormFile.required("inputFile")
val visibility = MultipartFormField.string().required("visibility")
val signatureRequired = MultipartFormField.string().required("signatureRequired")
val strictFormBody = Body.multipartForm(Validator.Strict, imageFile, visibility, signatureRequired, diskThreshold = 5).toLens()
And if that’s the case, is there no way to show through the api docs that signatureRequired
is a boolean?
Just want to make sure i’m doing it correctlyLuca Battistelli
12/16/2021, 4:05 PMhttp4k generate project -o folder-name -r dD1BQU1BWlFES0FTOEI5Z09FQS1nRDZRU3dCUlEmYz1NYWluJnA9Y29tLmJhdHRpc3RlbGxpLmx1Y2E
but I don't seem to be able to run tests. What am I missing?
$ ./gradlew test
Starting a Gradle Daemon, 8 stopped Daemons could not be reused, use --status for details
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///Users/lucabattistelli/repos/backend-test-kotlin-2/Main/build/reports/tests/test/index.html
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at <https://help.gradle.org>
BUILD FAILED in 6s
3 actionable tasks: 1 executed, 2 up-to-date
Andrew O'Hara
12/16/2021, 8:49 PMmap
on a BiDiLensSpec
to convert between a DTO and a domain model, but I don't if or how I can use that to help here. I want the lens to be backed by an enum so that the openapi spec shows the options; simply validating a string isn't good enough.
enum class MyEnumDto(val value: String) {
A("aaaa"),
B("bbbb"),
C("cccc")
}
val lens: BiDiLens<HttpMessage, MyEnumDto> = Header
.mapWithNewMeta(StringBiDiMappings.enum<MyEnumDto>(), ParamMeta.EnumParam(MyEnumDto::class))
.defaulted("my-enum", MyEnumDto.A)
@Test
fun `parse enum lens`() {
val request = Request(Method.GET, "/").header("my-enum", "bbbb")
lens(request) shouldBe MyEnumDto.B
/*
header 'my-enum' must be string
Caused by: java.lang.IllegalArgumentException: No enum constant com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest.MyEnumDto.bbbb
at java.base/java.lang.Enum.valueOf(Enum.java:273)
at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$MyEnumDto.valueOf(LegacyLensesTest.kt)
at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$special$$inlined$enum$1.invoke(BiDiMapping.kt:87)
at com.cacmloud.config.camcapi.api.legacy.LegacyLensesTest$special$$inlined$enum$1.invoke(BiDiMapping.kt:87)
at org.http4k.lens.BiDiMapping.asOut(BiDiMapping.kt:47)
at org.http4k.lens.LensSpecKt$mapWithNewMeta$1.invoke(lensSpec.kt:249)
at org.http4k.lens.LensGet$map$1.invoke(lensSpec.kt:270)
at org.http4k.lens.LensGet$map$1.invoke(lensSpec.kt:19)
at org.http4k.lens.LensGet$invoke$1.invoke(lensSpec.kt:17)
at org.http4k.lens.LensGet$invoke$1.invoke(lensSpec.kt:17)
at org.http4k.lens.BiDiLensSpec$defaulted$2.invoke(lensSpec.kt:165)
at org.http4k.lens.Lens.invoke(lens.kt:15)
... 109 more
*/
}
@Test
fun `write enum lens`() {
val request = Request(Method.GET, "/")
.with(lens of MyEnumDto.C)
request.shouldHaveHeader("my-enum", "cccc")
/*
Header "my-enum": expected:<"cccc"> but was:<"C">
Expected :"cccc"
Actual :"C"
*/
}
Andrew O'Hara
12/17/2021, 9:14 PMxml
and json
content types available for a body in my contract meta, the sample responses aren't honoring the xml-specific jackson annotations (e.g. for element wrappers and root elements). The actual responses are fine. Presumably this is because the OpenApi3
class uses a json converter and ignores the xml annotations.
Is there any way to get this to work?dave
12/18/2021, 11:48 AMandyg
12/28/2021, 3:01 AM.with(CONTENT_TYPE of TEXT_HTML)
or similar)? Must be an easy way but I'm missing it. Thanks!Michael Grigoryan
12/29/2021, 5:49 AMBiDiBodyLens<T>
) to send and I guess receive JSON but isn't there any other way of sending JSON as a response in http4k except for I guess "injecting" the response? Also is there any place where I can find all the terminology used in http4k?
My code currently looks like this:
// Will be used for sending JSON encoded emoji as a response
val lensEmojiResponse: BiDiBodyLens<Emoji> = Body.auto<Emoji>().toLens()
/**
* Entrypoint of the application. We can consider
* this as the main router to which all other routes
* are attached to.
*/
val app: HttpHandler = { _: Request ->
val emoji = Emoji("Sob", "😭")
// Setting the response value. It will be already
// encoded as JSON by kotlinx.serialization
lensEmojiResponse.inject(emoji, Response(Status.OK))
}
MrNiamh
01/02/2022, 1:02 PMstartDate
is a date. Currently in my swagger.json it has:
"startDate": {
"example": "2021-01-01",
"description": null,
"type": "string"
},
but (i think) i'm expecting:
"startDate": {
"example": "2021-01-01",
"description": null,
"type": "string",
"format": "date"
},
I've defined my bidi lens as:
BiDiMapping<String, LocalDate>({ LocalDate.parse(it) }, { it.format(DateTimeFormatter.ISO_LOCAL_DATE) })
MrNiamh
01/04/2022, 1:05 PMfun downloadDocument(documentService: DocumentService): ContractRoute =
"document" / Path.uuid().of("documentId") meta {
summary = "Download document"
returning(OK, formBody to formExample)
} bindContract Method.GET to
{ documentId ->
{
val doc = documentService.getDocument(documentId))
Response(OK)
.with(
formBody of MultipartForm()
.with(outputFile of MultipartFormFile(filename = doc.filename, contentType = ContentType(doc.mimeType), content = doc.data.inputStream()))
)
}
}
private val outputFile = MultipartFormFile.required("outputFile")
private val formBody = Body.multipartForm(Validator.Strict, outputFile).toLens()
private val formExample = MultipartForm()
.with(outputFile of MultipartFormFile(filename = "test_file.txt", contentType = ContentType.TEXT_PLAIN, content = createTempFileWithName("byteBufferExample", suffix = ".txt")
.also { it.writeText("example") }
.inputStream()))
Alen Mujezinovic
01/12/2022, 12:16 PMkotlinx.serialization
with the contract
module?
The data classes in contract
are not market with any annotations that the kotlinx
lib needs. Would I have to create custom serialisers?Philipp Mayer
01/19/2022, 10:21 PMPhilipp Mayer
01/19/2022, 10:21 PMdave
01/19/2022, 10:25 PMPhilipp Mayer
01/19/2022, 10:35 PMdave
01/19/2022, 10:44 PMPhilipp Mayer
01/19/2022, 10:47 PMpabl0rg
01/21/2022, 11:13 AM