Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.deletefp

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/resources"])
@RestController
open class HttpOracleDeleteFPApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(HttpOracleDeleteFPApplication::class.java, *args)
}

private val data = mutableMapOf<Int, String>()

fun reset(){
data.clear()
}
}


@PutMapping(path = ["/{id}"])
open fun put(
@PathVariable("id") id: Int
): ResponseEntity<Any> {

data[id] = "Data for $id"
return ResponseEntity.status(200).build()
}

@GetMapping(path = ["/{id}"])
open fun get(@PathVariable("id") id: Int): ResponseEntity<String> {

if(!data.containsKey(id)){
return ResponseEntity.status(404).build()
}

return ResponseEntity.status(200).body(data[id])
}

@DeleteMapping(path = ["/{id}"])
open fun delete(@PathVariable("id") id: Int): ResponseEntity<String> {

if(!data.containsKey(id)){
return ResponseEntity.status(404).build()
}

//not wrong, as not stating we are done
return ResponseEntity.status(202).build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.failmodificationfp

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/resources"])
@RestController
open class FailModificationFPApplication {


@PutMapping
open fun put(
@RequestBody body: FailModificationFPDto,
): ResponseEntity<Any> {

return ResponseEntity.status(400).build()
}


@GetMapping
open fun get(): ResponseEntity<FailModificationFPDto> {

return ResponseEntity.status(200).body(
FailModificationFPDto(timestamp = System.currentTimeMillis(), message = null)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.failmodificationfp

class FailModificationFPDto(
var timestamp: Long? = null,
var message: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.invalidallowfp

import io.swagger.v3.oas.annotations.Hidden
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/invalid-allow-fp"])
@RestController
open class InvalidAllowFPApplication {


@GetMapping
open fun get(): ResponseEntity<String> {

return ResponseEntity.status(200).body("Hello")
}

@Hidden
@DeleteMapping
open fun deleteProduct(): ResponseEntity<Any> {
return ResponseEntity.status(204).build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.invalidallowfp

import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.FilterConfig
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
import javax.servlet.http.HttpServletResponse
import javax.servlet.http.HttpServletResponseWrapper
import org.springframework.stereotype.Component

@Component
class RemoveAllowHeaderFilter : Filter {

override fun init(filterConfig: FilterConfig?) {
// no-op
}

override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
chain.doFilter(request, object : HttpServletResponseWrapper(response as HttpServletResponse) {
override fun setHeader(name: String, value: String) {
if (!name.equals("Allow", ignoreCase = true)) {
super.setHeader(name, value)
}
}

override fun addHeader(name: String, value: String) {
if (!name.equals("Allow", ignoreCase = true)) {
super.addHeader(name, value)
}
}
})
}

override fun destroy() {
// no-op
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.invalidlocationfp

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/invalid-location-fp"])
@RestController
open class InvalidLocationFPApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(InvalidLocationFPApplication::class.java, *args)
}

private val data = mutableMapOf<Int, String>()

fun reset(){
data.clear()
}
}


@DeleteMapping(path = ["/{id}"])
open fun delete(@PathVariable("id") id: Int): ResponseEntity<Any> {
if(! data.containsKey(id)){
return ResponseEntity.status(404).build()
}
data.remove(id)
return ResponseEntity.status(204).build()
}


@PutMapping(path = ["/{id}"])
open fun put(
@PathVariable("id") id: Int
): ResponseEntity<Any> {

val isNew = !data.containsKey(id)
data[id] = "$id"

val status = if (isNew) 201 else 200
return ResponseEntity.status(status)
.header("Location", "/api/invalid-location-fp/$id")
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.invalidmergepatchfp

import java.net.URI
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/merge-patch-fp"])
@RestController
open class InvalidMergePatchFPApplication {

companion object {

const val MERGE_PATCH = "application/merge-patch+json"

@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(InvalidMergePatchFPApplication::class.java, *args)
}

private val data = mutableMapOf<Int, MergePatchResourceFP>()

fun reset() {
data.clear()
}
}

data class MergePatchResourceFP(
var name: String? = null,
var value: Int? = null
)

class MergeRequestFP(
var name: String? = null,
var value: Int? = null
)


@PostMapping
open fun create(@RequestBody body: MergePatchResourceFP): ResponseEntity<MergePatchResourceFP> {
val id = data.size + 1
val stored = body.copy()
data[id] = stored
return ResponseEntity.created(URI.create("/api/merge-patch-fp/$id")).body(stored)
}

@GetMapping("/{id}")
open fun get(@PathVariable("id") id: Int): ResponseEntity<MergePatchResourceFP> {
val resource = data[id] ?: return ResponseEntity.status(404).build()
return ResponseEntity.status(200).body(resource)
}

@PatchMapping("/{id}", consumes = [MERGE_PATCH])
open fun patch(
@PathVariable("id") id: Int,
@RequestBody body: MergeRequestFP
): ResponseEntity<MergePatchResourceFP> {

val resource = data[id] ?: return ResponseEntity.status(404).build()

// BUG: overwrites every field unconditionally. A body of {"name":"x"} makes
// 'value' arrive as null and wipes the stored value -> PATCH acts like PUT.
resource.name = body.name
resource.value = body.value

//but, it returns 202, which means it has not completed yet
return ResponseEntity.status(202).body(resource)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.foo.rest.examples.spring.openapi.v3.httporaclefp.nonidempotentputfp

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.net.URI


@SpringBootApplication(exclude = [SecurityAutoConfiguration::class])
@RequestMapping(path = ["/api/accounts"])
@RestController
open class NonIdempotentPutFPApplication {

companion object {
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(NonIdempotentPutFPApplication::class.java, *args)
}

private val data = mutableMapOf<Int, AccountData>()

fun reset(){
data.clear()
}
}

data class AccountData(
var balance: Int
)

data class DepositRequest(
val amount: Int
)


@PostMapping()
open fun create(@RequestBody body: AccountData): ResponseEntity<AccountData> {
val id = data.size + 1
data[id] = body.copy()
return ResponseEntity.created(URI("/api/accounts/$id")).body(data[id])
}

@GetMapping(path = ["/{id}"])
open fun get(@PathVariable("id") id: Int): ResponseEntity<AccountData> {
val resource = data[id]
?: return ResponseEntity.status(404).build()
return ResponseEntity.status(200).body(resource)
}

@PutMapping(path = ["/{id}/deposit"])
open fun deposit(
@PathVariable("id") id: Int,
@RequestBody body: DepositRequest
): ResponseEntity<AccountData> {

val resource = data[id]
?: return ResponseEntity.status(404).build()

// wrong: PUT must be idempotent, but each call accumulates the deposit
resource.balance += body.amount

//but not a bug, as returning 202, meaning not finished yet
return ResponseEntity.status(202).body(resource)
}
}
Loading
Loading