Spring Webflux Mono is ignoring ResponseEntity's status and returning 200 OK - kotlin

I have a RestController which looks like this
#RestController
#RequestMapping("/user")
class UserController(val userService: UserService) {
#PostMapping("/register")
fun register(#RequestBody body: UsernamePasswordResource): Mono<*> {
return userService.createUser(body.username, body.password)
.map {ResponseEntity(it, HttpStatus.CREATED)}
.doOnError {
if (it is DuplicateUserException){
ResponseEntity(ErrorResource(it.message!!), HttpStatus.BAD_REQUEST)
} else {
ResponseEntity(ErrorResource("Internal server error"), HttpStatus.INTERNAL_SERVER_ERROR)
}
}
}
}
When I create a using using this endpoint 200 OK in response and this as body:
{
"headers": {},
"body": {
"username": "test7",
"passwordEncoded": "4HxMMUI09pltEr9pyKIxsQ==",
"description": ""
},
"statusCode": "CREATED",
"statusCodeValue": 201
}
Why is the status of my ResponseEntity ignored and 200 is returned? How do i fix this? Thanks in advance.

Spring provides an HTTP 200 (OK) response by default when an endpoint returns successfully. To return a custom HTTP status, use the #ReponseStatus annotation above your function and pass your status code in the parenthesis. Your code snippet should look like:
#RestController
#RequestMapping("/user")
class UserController(val userService: UserService) {
#PostMapping("/register")
#ResponseStatus(HttpStatus.CREATED)
fun register(#RequestBody body: UsernamePasswordResource): Mono<*> {
return userService.createUser(body.username, body.password)
.map {ResponseEntity(it, HttpStatus.CREATED)}
.doOnError {
if (it is DuplicateUserException){
ResponseEntity(ErrorResource(it.message!!), HttpStatus.BAD_REQUEST)
} else {
ResponseEntity(ErrorResource("Internal server error"), HttpStatus.INTERNAL_SERVER_ERROR)
}
}
}
}

I figured it out. The endpoint was returning Mono<*>
After I changed my service to return AuthenticationResource instead of UserResource
I could also make the endpoint return Mono<ResponseEntity<AuthenticationResource>>.
After that the ResponseEntity was respected and used as normal.

Related

Ktor-server RequestValidation not working

I am using ktor (2.1.0) to create a small API. While doing so, I am trying to leverage Ktor-server cool functionalities, including RequestValidation.
That being said, it is not working and I can't figure out why since it looks to me very close to the examples in the documentation.
This is my server config:
embeddedServer(Netty, port = 8080) {
routing {
post("/stock") {
val dto = call.receive<CreateStockRequest>()
call.respond(HttpStatusCode.NoContent)
}
}
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
install(StatusPages) {
exception<RequestValidationException> { call, cause ->
call.respond(HttpStatusCode.BadRequest, cause.reasons.joinToString())
}
}
install(RequestValidation) {
validate<CreateStockRequest> { request ->
if (request.name.isBlank())
ValidationResult.Invalid("Stock must have a name")
if (request.symbol.isBlank())
ValidationResult.Invalid("Symbol must have a name")
ValidationResult.Valid
}
}
}
This is the request object being "received":
#Serializable
data class CreateStockRequest(val name: String, val symbol: String)
And this is the body being sent:
{
"name": "",
"symbol": "something"
}
I was expecting to get a BadRequest response, but I am getting a NoContent response as if everything was fine with the request.
Am I doing something wrong?
In your example, the server responds with BadRequest only if both "name" and "symbol" are blank. You can replace your validation logic with the when expression:
validate<CreateStockRequest> { request ->
when {
request.name.isBlank() -> ValidationResult.Invalid("Stock must have a name")
request.symbol.isBlank() -> ValidationResult.Invalid("Symbol must have a name")
else -> ValidationResult.Valid
}
}
I found the accepted answer to be a cleaner code solution, and that's why it is the accepted one.
That being said, the actual problem I was having is better explained by understanding that the compiler was getting confused with scopes.
On each "if" clause, I needed to return the validation result, but the compiler was not having it.
The reason for that was the fact that it got confused with the context to which my return statement referred.
My Solution was to use the '#' notation to specify the scope for the return:
validate<CreateStockRequest> { request ->
if (request.name.isBlank())
return#validate ValidationResult.Invalid("Stock must have a name")
if (request.symbol.isBlank())
return#validate ValidationResult.Invalid("Symbol must have a name")
return#validate ValidationResult.Valid
}
}

How do I annotate an endpoint in NestJS for OpenAPI that takes Multipart Form Data

My NestJS server has an endpoint that accepts files and also additional form data
For example I pass a file and a user_id of the file creator in the form.
NestJS Swagger needs to be told explicitly that body contains the file and that the endpoint consumes multipart/form-data this is not documented in the NestJS docs https://docs.nestjs.com/openapi/types-and-parameters#types-and-parameters.
Luckily some bugs led to discussion about how to handle this use case
looking at these two discussions
https://github.com/nestjs/swagger/issues/167
https://github.com/nestjs/swagger/issues/417
I was able to put together the following
I have added annotation using a DTO:
the two critical parts are:
in the DTO add
#ApiProperty({
type: 'file',
properties: {
file: {
type: 'string',
format: 'binary',
},
},
})
public readonly file: any;
#IsString()
public readonly user_id: string;
in the controller add
#ApiConsumes('multipart/form-data')
this gets me a working endpoint
and this OpenAPI Json
{
"/users/files":{
"post":{
"operationId":"UsersController_addPrivateFile",
"summary":"...",
"parameters":[
],
"requestBody":{
"required":true,
"content":{
"multipart/form-data":{
"schema":{
"$ref":"#/components/schemas/UploadFileDto"
}
}
}
}
}
}
}
...
{
"UploadFileDto":{
"type":"object",
"properties":{
"file":{
"type":"file",
"properties":{
"file":{
"type":"string",
"format":"binary"
}
},
"description":"...",
"example":"'file': <any-kind-of-binary-file>"
},
"user_id":{
"type":"string",
"description":"...",
"example":"cus_IPqRS333voIGbS"
}
},
"required":[
"file",
"user_id"
]
}
}
Here is what I find a cleaner Approach:
#Injectable()
class FileToBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest();
if(req.body && req.file?.fieldname) {
const { fieldname } = req.file;
if(!req.body[fieldname]) {
req.body[fieldname] = req.file;
}
}
return next
.handle();
}
}
const ApiFile = (options?: ApiPropertyOptions): PropertyDecorator => (
target: Object, propertyKey: string | symbol
) => {
ApiProperty({
type: 'file',
properties: {
[propertyKey]: {
type: 'string',
format: 'binary',
},
},
})(target, propertyKey);
};
class UserImageDTO {
#ApiFile()
file: Express.Multer.File; // you can name it something else like image or photo
#ApiProperty()
user_id: string;
}
#Controller('users')
export class UsersController {
#ApiBody({ type: UserImageDTO })
// #ApiResponse( { type: ... } ) // some dto to annotate the response
#Post('files')
#ApiConsumes('multipart/form-data')
#UseInterceptors(
FileInterceptor('file'), //this should match the file property name
FileToBodyInterceptor, // this is to inject the file into the body object
)
async addFile(#Body() userImage: UserImageDTO): Promise<void> { // if you return something to the client put it here
console.log({modelImage}); // all the fields and the file
console.log(userImage.file); // the file is here
// ... your logic
}
}
FileToBodyInterceptor and ApiFile are general, I wish they where in the NestJs
You probably need to install #types/multer to have to Express.Multer.File

Http Headers vue-apollo

👩‍💻,
I have see many articles of how to use headers in apollo with context options but I don't know how to make it work in vue-apollo , is already other isssue in github about these (https://github.com/vuejs/vue-apollo/issues/713) config the vue-apollo.js, but I want to use headers to individuals querys, ¿Is these possible?.
My Query with vue-apollo:
apollo:{
getPipedrivePersons: {
query: gql`query{
getPipedrivePersons(
term:"email"
){
id
name
detail{
firstName
lastName
}
email{
value
}
phone{
value
}
}
}
`,
pollInterval: 950,
},
}
solved by using context:
context: {
headers: { foo: this.fooHeader }
}

How to send Http Form with parameters by ktor-client

I've found nearly everywhere in ktor-client documentation and examples they use empty formData to show how the client works
formParameters: Parameters = Parameters.Empty
So what's the kotlin/ktor way to fill it with parameters?
Ktor uses this approach to fill the parameters:
client.submitForm<HttpResponse>(
url = "https://foo.com/login",
formParameters = Parameters.build {
append("_username", username)
append("_password", password)
})
Alternatively, you can also simply pass the form data using formData, e.g.:
client.post<HttpResponse>("https://example.com/login") {
formData {
parameter("username", username)
parameter("password", password)
}
}
I've found at least three ways to post www-urlencoded form:
return httpClient.submitForm("url") {
parameter("key", "value")
}
return httpClient.post("url") {
FormDataContent(Parameters.build {
parameter("key", "value")
})
}
return httpClient.post("url") {
formData {
parameter("key", "value")
}
}
append() method is marked as internal and not working with ktor 1.6.4
client.get{
url("https://api.yelp.com/v3/businesses/search")
contentType(ContentType.Application.Json)
headers {
append(HttpHeaders.Authorization, "Bearer $API_KEY")
}
formData {
parameter("location","NYC") //use this wey
}
}

Go Unmarshalling Response from API

I have been working with Go and the Bitmex API.
I have the following Bitmex api endpoint:
https://www.bitmex.com/api/v1/leaderboard
This returns an array of JSON objects structured as follows.
[
{
"name": "string",
"isRealName": true,
"profit": 0
}
]
However I get the following incorrect representation of the JSON when I marshal it.
[{0 false } {0 false } ... ]
I know my HTTP request is going through, as when I print the response.Body I get the following
[{"profit":256915996199,"isRealName":true,"name":"angelobtc"} ... ]
Here is the struct I am using to store the marshaled data.
type LeaderboardResponse struct {
profit float64 `json:"profit"`
isRealName bool `json:"isRealName"`
name string `json:"name"`
}
And my main method
func main() {
response, errorResponse := http.Get("https://www.bitmex.com/api/v1/leaderboard")
if response.Status == "200 OK"{
if errorResponse != nil {
fmt.Printf("The HTTP request failed with error %s\n",errorResponse)
} else {
body, errorBody := ioutil.ReadAll(response.Body)
if errorBody != nil {
fmt.Println("There was an error retrieving the body", errorBody)
} else {
leaderboard := []LeaderboardResponse{}
json.Unmarshal([]byte(body),&leaderboard)
if leaderboard != nil {
fmt.Println(leaderboard);
//The result of the statement above and the one below are different
fmt.Println(string(body))
} else {
fmt.Println("leaderboard array is undefined")
}
defer response.Body.Close()
}
}
} else {
fmt.Println("Response received with status ", string(response.Status))
}
}
It appears that the values of struct have not been modifies despite it being assigned the marshaled JSON body
How do I fix this issue?
Furthermore,
How do I add my API credentials to the HTTP request?
How do I access the response header and marshal it?