OpenAPI Generation not respecting field name for multipart multifile upload - kotlin

I have an API I'm describing with OpenAPI, it will be implemented in Java/Kotlin so I'm doing the code generation using kotlin-spring generation, using OpenAPI 3.0.2 and the plugin "org.openapi.generator" version "6.2.1"
My problem is that the field in the schema which contains the files that will be uploaded is always named file instead of respecting the name attachments i'm specifying, maybe it's a standard that I am not aware of, but this is an existing API that is already using attachments and I'd like to keep that name.
This is the schema:
/emails/send:
post:
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/EmailSendResponse'
description: Response from the send email request.
operationId: sendEmail
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/EmailSendRequest'
And the request schema:
EmailSendRequest:
title: Email send request
description: Contains the data required to send an email.
required:
- success
- failureReason
type: object
properties:
from:
type: string
to:
type: string
subject:
type: string
content:
type: string
contentType:
type: string
enum:
- HTML
- TEXT
bcc:
type: string
tags:
type: array
items:
type: string
category:
type: string
attachments:
type: array
items:
type: string
format: binary
And this is the generated API method in Java:
#RequestMapping(
method = [RequestMethod.POST],
value = ["/emails/send"],
produces = ["application/json"],
consumes = ["multipart/form-data"]
)
fun sendEmail(#Parameter(description = "") #RequestParam(value = "from", required = false) from: kotlin.String? ,#Parameter(description = "") #RequestParam(value = "to", required = false) to: kotlin.String? ,#Parameter(description = "") #RequestParam(value = "subject", required = false) subject: kotlin.String? ,#Parameter(description = "") #RequestParam(value = "content", required = false) content: kotlin.String? ,#Parameter(description = "", schema = Schema(allowableValues = ["HTML", "TEXT"])) #RequestParam(value = "contentType", required = false) contentType: kotlin.String? ,#Parameter(description = "") #RequestParam(value = "bcc", required = false) bcc: kotlin.String? ,#Parameter(description = "") #RequestParam(value = "tags", required = false) tags: kotlin.collections.List<kotlin.String>? ,#Parameter(description = "") #RequestParam(value = "category", required = false) category: kotlin.String? ,#Parameter(description = "file detail") #RequestPart("file") attachments: kotlin.collections.List<org.springframework.core.io.Resource>?): ResponseEntity<EmailSendResponse> {
return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
}
As you can see, it generates with a #RequestPart("file") instead of #RequestPart("attachments")
Any clues? I've been looking at the documentation but I couldn't find anything.
Thank you!

this appears to be an issue with the kotlin generator. I ran the generator using the schema you provided with both the kotlin-spring generated and the spring generator. The java method has the correct value for #RequestPart
default ResponseEntity<String> sendEmail(
#Parameter(name = "from", description = "") #Valid #RequestParam(value = "from", required = false) String from,
#Parameter(name = "to", description = "") #Valid #RequestParam(value = "to", required = false) String to,
#Parameter(name = "subject", description = "") #Valid #RequestParam(value = "subject", required = false) String subject,
#Parameter(name = "content", description = "") #Valid #RequestParam(value = "content", required = false) String content,
#Parameter(name = "contentType", description = "") #Valid #RequestParam(value = "contentType", required = false) String contentType,
#Parameter(name = "bcc", description = "") #Valid #RequestParam(value = "bcc", required = false) String bcc,
#Parameter(name = "tags", description = "") #Valid #RequestParam(value = "tags", required = false) List<String> tags,
#Parameter(name = "category", description = "") #Valid #RequestParam(value = "category", required = false) String category,
#Parameter(name = "attachments", description = "") #RequestPart(value = "attachments", required = false) List<MultipartFile> attachments
) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
but the generated kotlin method does not
fun sendEmail(#Parameter(description = "") #RequestParam(value = "from", required = false) from: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "to", required = false) to: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "subject", required = false) subject: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "content", required = false) content: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "contentType", required = false)
contentType: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "bcc", required = false) bcc: kotlin.String?,
#Parameter(description = "") #RequestParam(value = "tags", required = false)
tags: kotlin.collections.List<kotlin.String>?,
#Parameter(description = "") #RequestParam(value = "category", required = false)
category: kotlin.String?, #Parameter(description = "file detail") #Valid #RequestPart("file")
attachments: kotlin.collections.List<org.springframework.core.io.Resource>?): ResponseEntity<kotlin.String> {
return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
}
If this is a deal breaker for you, then I'd recommend generating the code with the java generator. Kotlin and java are fully compatible, so you can still use the generated classes and methods in your kotlin classes.
I'd also recommend filing a bug report on the openapi generator wiki to bring attention to the issue.

Related

Adding dynamic maps in DynamoDB with Kotlin

I'm using Spring Boot, Kotlin and CrudRepository to add items to my Dynamo Table.
The map I'm trying to add is dynamic, and can change attributes every single time.
I add the date of the object (delta) and save it, but I am having several errors:
When I save:
#DynamoDBTable(tableName = "delta_computers_inventory")
class DeltaComputersInventory(
#DynamoDBHashKey
#DynamoDBAttribute(attributeName = "delta_computers_inventory_id")
var id: String = UUID.randomUUID().toString(),
#DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.M)
#DynamoDBAttribute(attributeName = "delta")
var delta: Map<String, Any?> = mapOf(),
) {
#DynamoDBAttribute(attributeName = "date")
var date: String = OffsetDateTime.now(ZoneOffset.UTC).format(
DateTimeFormatter.ISO_DATE_TIME
)
}
and I do:
.doOnSuccess { listOfDocuments ->
deltaComputersRepository.saveAll(
listOfDocuments.map {
DeltaComputersInventory(
delta = it,
)
}
)
}
I get:
reactor.core.Exceptions$ErrorCallbackNotImplemented: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
instead, if I do it through an Item (Item.fromMap(it))
#DynamoDBTable(tableName = "delta_computers_inventory")
class DeltaComputersInventory(
#DynamoDBHashKey
#DynamoDBAttribute(attributeName = "delta_computers_inventory_id")
var id: String = UUID.randomUUID().toString(),
#DynamoDBTyped(DynamoDBMapperFieldModel.DynamoDBAttributeType.M)
#DynamoDBAttribute(attributeName = "delta")
var delta: Item = Item(),
) {
#DynamoDBAttribute(attributeName = "date")
var date: String = OffsetDateTime.now(ZoneOffset.UTC).format(
DateTimeFormatter.ISO_DATE_TIME
)
}
I get no error, but my item in my DynamoDB shows empty:
{
"delta_computers_inventory_id": {
"S": "d389d63e-8e93-4b08-b576-e37fae9a4d58"
},
"date": {
"S": "2023-01-24T12:00:33.620015Z"
},
"delta": {
"M": {}
},
}
What am I doing wrong?

Include a lambda inside an if..else statement

I have the following function:
fun ValueListItem(
label: String,
value: String,
onClick: (() -> Unit)? = null
) {
}
But calling it like this is not allowed:
var edited = false
ValueListItem(
label = "my label",
value = "some value",
onClick = if (viewmodel.canEdit) { edited = true } else null
)
But calling it like this is allowed:
var edited = false
val onEditDateTime = {
edited = true
}
ValueListItem(
label = "my label",
value = "some value",
onClick = if (viewmodel.canEdit) onEditDateTime else null
)
How can I pass the lambda in a conditional statement rather than having to declare the lambda as an extra construct.
By writing
ValueListItem(
label = "my label",
value = "some value",
onClick = if (viewmodel.canEdit) { edited = true } else null
)
The compiler sees the braces as part of the if-statement syntax.
Simply adding another pair of braces like this should do the trick I think
ValueListItem(
label = "my label",
value = "some value",
onClick = if (viewmodel.canEdit) {{ edited = true }} else null
)

Dynamic block with optional data

let say we have this locals:
locals = {
schemas = [
{
name = "is_cool"
attribute_data_type = "Boolean"
mutable = true
required = false
},
{
name = "firstname"
attribute_data_type = "String"
mutable = true
required = false
min_length = 1
max_length = 256
}
]
}
What I would like to achieve is to use dynamic to build schemas and when the schema is a string I would like to add the string_attribute_constraints block.
This is what I did so far but it adds an empty string_attribute_constraints block when the schema is Boolean
dynamic "schema" {
for_each = var.schemas
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
string_attribute_constraints {
min_length = lookup(schema.value, "min_length", null)
max_length = lookup(schema.value, "max_length", null)
}
}
}
terraform plan:
+ schema {
+ attribute_data_type = "Boolean"
+ mutable = true
+ name = "is_cool"
+ required = false
+ string_attribute_constraints {}
}
You can use a second nested dynamic block to tell Terraform how many string_attribute_constraints blocks to generate based on your rule:
dynamic "schema" {
for_each = var.schemas
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
dynamic "string_attribute_constraints" {
for_each = schema.attribute_data_type == "String" ? [1] : []
content {
min_length = lookup(schema.value, "min_length", null)
max_length = lookup(schema.value, "max_length", null)
}
}
}
}
This works by making the for_each for the nested dynamic be an empty list in the case where we don't want to generate any blocks, and making it a single-element list in the case where we do. Since we need no references to string_attribute_constraints.key or string_attribute_constraints.value inside the block, we can set the value of the single element to anything, and so I just set it to 1 as an arbitrary placeholder.
dynamic "schema" {
for_each = local.my_schema
content {
name = schema.value.name
attribute_data_type = schema.value.attribute_data_type
mutable = schema.value.mutable
required = schema.value.required
dynamic "string_attribute_constraints" {
for_each = schema.value.attribute_data_type == "String" ? [1] : []
content {
min_length = lookup(schema.value, "min_length", 0)
max_length = lookup(schema.value, "max_length", 2048)
}
}
dynamic "number_attribute_constraints" {
for_each = schema.value.attribute_data_type == "Number" ? [1] : []
content {
min_value = lookup(schema.value, "min_value", 0)
max_value = lookup(schema.value, "max_value", 2048)
}
}
}
}

'NoneType' object is not subscriptable pymongo

So i am trying make it to where it checks the database and if they are in the database it sends a embed that say "User is already whitelisted" and if they aren't in the database then it adds them to the database. I keep getting the error NoneType' object is not subscriptable and i am lost at this point, any help will be useful.
#client.command(aliases=['wl'])
#commands.has_role("Database")
async def Whitelist(ctx, member: discord.Member):
member_id = member.id
result = Collection.find_one({"User Id": member_id },{ "_id": 0, "Key": 1})
KeyAdd = random.randint(23525623, 23623423426)
displayname = member.display_name
role = discord.utils.get(member.guild.roles, name="Whitelisted")
remove_role = discord.utils.get(member.guild.roles, name="Blacklisted")
AddWhitelist = {"User Id":member_id, "Display Name": displayname, "Key": f"Zelly_{KeyAdd}"}
embed2 = discord.Embed(
title = f"Whitelist Error",
color = discord.Colour(color)
)
embed2.add_field(name = "Content", value = f"{member.mention} is already whitelisted", inline = False)
embed2.set_footer(text = f'{bot_name}')
embed2.timestamp = datetime.datetime.utcnow()
embed = discord.Embed(
title = f"Whitelist Successful",
color = discord.Colour(color)
)
embed.add_field(name = "Content", value = f"Successfully whitelisted {member.mention}", inline = False)
embed.set_footer(text = f'{bot_name}')
embed.timestamp = datetime.datetime.utcnow()
embed1 = discord.Embed(
title = f"Whitelist",
color = discord.Colour(color)
)
embed1.add_field(name = "Content", value = f"You have been whitelisted for Zelly", inline = False)
embed1.add_field(name = "Key", value = result["Key"], inline = False)
embed1.set_footer(text = f'{bot_name}')
embed1.timestamp = datetime.datetime.utcnow()
if Collection.find_one({"User Id": member_id },{ "_id": 0, "Key": 1}) == None:
Added = Collection.insert_one(AddWhitelist)
await ctx.send(embed = embed)
elif Collection:
await ctx.send(embed = embed2)

How to make post with data class

I have this endpoint with this structure:
uri = http://127.0.0.1:9090/tables/mask
and this payload:
{
"_id" : "5d66c9b6d5ccf30bd5b6b541",
"connectionId" : "1967c072-b5cf-4e9e-1c92-c2b49eb771c4",
"name" : "Customer",
"columns" : [
{
"name" : "FirstName",
"mask" : true
},
{
"name" : "LastName",
"mask" : false
},
{
"name" : "City",
"mask" : false
},
{
"name" : "Phone",
"mask" : false
}
],
"parentId" : null
}
in my Kotlin code I have this structure to deserialize:
data class ColumnsMaskModel (val name:String, val mask:Boolean )
data class TablesMaskModel (val _id:String, val name:String, val connectionId:String, val columns:MutableList<ColumnsMaskModel?> )
and how can I use TablesMaskModel to make a HTTP post in Kotlin
You'll need an HTTP client to do that. Data classes themselves has nothing to do with HTTP, they are just data structures. There are a lot of HTTP clients available on Kotlin for JVM:
java.net.HttpURLConnection
Java 9's HttpClient
Apache HttpComponents
OkHttp
Ktor
Let's see how to make HTTP requests in Ktor:
data class ColumnsMaskModel(val name: String, val mask: Boolean)
data class TablesMaskModel(val _id: String, val name: String, val connectionId: String, val columns: MutableList<ColumnsMaskModel?>)
fun main() = runBlocking {
val client = HttpClient {
install(JsonFeature) {
serializer = JacksonSerializer()
}
}
val result = client.post<String> {
url("http://httpbin.org/post")
contentType(ContentType.Application.Json)
body = TablesMaskModel(
_id = "5d66c9b6d5ccf30bd5b6b541",
connectionId = "1967c072-b5cf-4e9e-1c92-c2b49eb771c4",
name = "Customer",
columns = mutableListOf(
ColumnsMaskModel(name = "FirstName", mask = true),
ColumnsMaskModel(name = "LastName", mask = false),
ColumnsMaskModel(name = "City", mask = false),
ColumnsMaskModel(name = "Phone", mask = false)
)
)
}
println(result)
client.close()
}
Note that Ktor uses suspending functions for HTTP requests, so you'll need a coroutine scope, runBlocking in this example.
Ktor supports various "backends" for HTTP clients – Apache, Coroutines IO, curl. It also has different "features" to enable on-the-flight payloads serializations and de-serializations, like in the example above.