Skip to content

Commit

Permalink
- Pointing to in/specmatic/examples/store/api_order_with_oauth_v3.yaml
Browse files Browse the repository at this point in the history
- Switching from specmatic.json to specmatic.yaml
- Updating all layers to work with in/specmatic/examples/store/api_order_with_oauth_v3.yaml
- Upgrading Specmatic to 1.3.25
  • Loading branch information
harikrishnan83 committed Jun 16, 2024
1 parent 9c2a6b4 commit dceae06
Show file tree
Hide file tree
Showing 14 changed files with 145 additions and 65 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ lib/*
*.iml
*.idea

.vscode/*
.vscode/*
.specmatic
15 changes: 7 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<properties>
<kotlin.version>1.9.24</kotlin.version>
<maven.compiler>1.8</maven.compiler>
<specmatic.version>0.81.0</specmatic.version>
<specmatic.version>1.3.25</specmatic.version>
<spring.boot.version>2.7.18</spring.boot.version>
</properties>

Expand All @@ -31,6 +31,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
Expand Down Expand Up @@ -121,13 +127,6 @@
<version>3.8.1</version>
</dependency>

<dependency>
<groupId>in.specmatic</groupId>
<artifactId>specmatic-core</artifactId>
<version>${specmatic.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>in.specmatic</groupId>
<artifactId>junit5-support</artifactId>
Expand Down
43 changes: 0 additions & 43 deletions specmatic.json

This file was deleted.

19 changes: 19 additions & 0 deletions specmatic.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
sources:
- provider: git
repository: https://github.com/znsio/specmatic-order-contracts.git
test:
- in/specmatic/examples/store/api_order_with_oauth_v3.yaml

report:
formatters:
- type: text
layout: table
types:
APICoverage:
OpenAPI:
successCriteria:
minThresholdPercentage: 70
maxMissedEndpointsInSpec: 4
enforce: true
excludedEndpoints:
- /internal/metrics
2 changes: 1 addition & 1 deletion src/main/java/com/store/controllers/Orders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class Orders {

@GetMapping("/orders")
fun search(
@RequestParam(name = "status", required = false) status: String?,
@RequestParam(name = "status", required = false) status: OrderStatus?,
@RequestParam(name = "productid", required = false) productid: Int?
): List<Order> = orderService.findOrders(status, productid)
}
12 changes: 11 additions & 1 deletion src/main/java/com/store/controllers/Products.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.store.controllers

import com.store.exceptions.NotFoundException
import com.store.exceptions.ValidationException
import com.store.model.Id
import com.store.model.Product
import com.store.model.User
Expand All @@ -13,6 +14,8 @@ import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import javax.validation.Valid

private val typesOfProducts = listOf("gadget", "book", "food", "other")

@RestController
open class Products {

Expand All @@ -26,6 +29,10 @@ open class Products {
@Valid @RequestBody product: Product,
@AuthenticationPrincipal user: User
): ResponseEntity<String> {
productService.addProduct(product.also {
if(product.type !in typesOfProducts)
throw ValidationException("type must be one of ${typesOfProducts.joinToString(", ")}")
})
productService.updateProduct(product)
return ResponseEntity(HttpStatus.OK)
}
Expand All @@ -41,7 +48,10 @@ open class Products {

@PostMapping("/products")
fun create(@Valid @RequestBody newProduct: Product, @AuthenticationPrincipal user: User): ResponseEntity<Id> {
val productId = productService.addProduct(newProduct)
val productId = productService.addProduct(newProduct.also {
if(newProduct.type !in typesOfProducts)
throw ValidationException("type must be one of ${typesOfProducts.joinToString(", ")}")
})
return ResponseEntity(productId, HttpStatus.OK)
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/store/exceptions/NotFoundException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@ResponseStatus(HttpStatus.NOT_FOUND)
class NotFoundException(validationErrorMessage: String = "") : RuntimeException(validationErrorMessage)
class NotFoundException(private val validationErrorMessage: String = "") : RuntimeException(validationErrorMessage) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.store.exceptions

import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@ResponseStatus(HttpStatus.BAD_REQUEST)
class UnrecognizedTypeException(type: String) : Throwable("Unrecognized type: $type")
3 changes: 2 additions & 1 deletion src/main/java/com/store/exceptions/ValidationException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus

@ResponseStatus(HttpStatus.BAD_REQUEST)
class ValidationException(validationErrorMessage: String = "") : RuntimeException(validationErrorMessage)
class ValidationException(private val validationErrorMessage: String = "") : RuntimeException(validationErrorMessage) {
}
59 changes: 59 additions & 0 deletions src/main/java/com/store/handlers/GlobalExceptionHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.store.handlers

import com.store.exceptions.NotFoundException
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import java.time.LocalDateTime

@ControllerAdvice
class GlobalExceptionHandler {

@ExceptionHandler(NotFoundException::class)
fun handleGenericException(ex: NotFoundException): ResponseEntity<ErrorResponse> {
val notFound = HttpStatus.NOT_FOUND
return ResponseEntity.status(notFound).body(
errorResponse(
notFound,
ex,
"Requested resource not found",
"resource not found"
)
)
}

@ExceptionHandler(Exception::class)
fun handleGenericException(ex: Exception): ResponseEntity<ErrorResponse> {
val badRequest = HttpStatus.BAD_REQUEST
return ResponseEntity.status(badRequest).body(
errorResponse(
badRequest,
ex,
"An error occurred while processing the request",
"Unknown error"
)
)
}

private fun errorResponse(
httpStatus: HttpStatus,
ex: Exception,
error: String,
message: String
): ErrorResponse {
return ErrorResponse(
LocalDateTime.now(),
httpStatus.value(),
error,
ex.message ?: message
)
}
}

data class ErrorResponse(
val timestamp: LocalDateTime,
val status: Int,
val error: String,
val message: String
)
24 changes: 18 additions & 6 deletions src/main/java/com/store/model/DB.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package com.store.model

import com.store.exceptions.UnrecognizedTypeException
import javax.validation.ValidationException

object DB {
private var PRODUCTS: MutableMap<Int, Product> = mutableMapOf(10 to Product("XYZ Phone", "gadget", 10, 10), 20 to Product("Gemini", "dog", 10, 20))
private var ORDERS: MutableMap<Int, Order> = mutableMapOf(10 to Order(10, 2, "pending", 10), 20 to Order(10, 1, "pending", 20))
private val USERS: Map<String, User> = mapOf("API-TOKEN-HARI" to User("Hari"))
private var PRODUCTS: MutableMap<Int, Product> =
mutableMapOf(10 to Product("XYZ Phone", "gadget", 10, 10), 20 to Product("Gemini", "dog", 10, 20))
private var ORDERS: MutableMap<Int, Order> =
mutableMapOf(10 to Order(10, 2, OrderStatus.pending, 10), 20 to Order(10, 1, OrderStatus.pending, 20))
private val USERS: Map<String, User> = mapOf("API-TOKEN-SPEC" to User("Hari"))

fun userCount(): Int {
return USERS.values.count()
}

fun resetDB() {
PRODUCTS = mutableMapOf(10 to Product("XYZ Phone", "gadget", 10, 10), 20 to Product("Gemini", "dog", 10, 20))
ORDERS = mutableMapOf(10 to Order(10, 2, "pending", 10), 20 to Order(10, 1, "pending", 20))
ORDERS = mutableMapOf(10 to Order(10, 2, OrderStatus.pending, 10), 20 to Order(10, 1, OrderStatus.pending, 20))
}

fun addProduct(product: Product) {
Expand All @@ -27,9 +32,14 @@ object DB {
}
}

fun deleteProduct(id: Int) { PRODUCTS.remove(id) }
fun deleteProduct(id: Int) {
PRODUCTS.remove(id)
}

fun findProducts(name: String?, type: String?, status: String?): List<Product> {
if (type != null && type !in listOf("book", "food", "gadget", "other"))
throw UnrecognizedTypeException(type)

return PRODUCTS.filter { (id, product) ->
product.name == name || product.type == type || inventoryStatus(id) == status
}.values.toList()
Expand All @@ -52,7 +62,7 @@ object DB {
ORDERS.remove(id)
}

fun findOrders(status: String?, productId: Int?): List<Order> {
fun findOrders(status: OrderStatus?, productId: Int?): List<Order> {
return ORDERS.filter { (_, order) ->
order.status == status || order.productid == productId
}.values.toList()
Expand All @@ -68,6 +78,8 @@ object DB {
}

fun reserveProductInventory(productId: Int, count: Int) {
if (productId !in PRODUCTS)
throw ValidationException("Product Id $productId does not exist")
val updatedProduct = PRODUCTS.getValue(productId).let {
it.copy(inventory = it.inventory - count)
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/com/store/model/Order.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import java.util.concurrent.atomic.AtomicInteger
import javax.validation.constraints.NotNull
import javax.validation.constraints.Positive

class Order(@field:Positive val productid: Int = 0, @field:Positive val count: Int = 0, @field:NotNull var status: String = "pending", val id: Int = idGenerator.getAndIncrement()) {
class Order(@field:Positive val productid: Int = 0, @field:Positive val count: Int = 0, @field:NotNull var status: OrderStatus = OrderStatus.pending, val id: Int = idGenerator.getAndIncrement()) {
companion object {
val idGenerator: AtomicInteger = AtomicInteger()
}
}

enum class OrderStatus {
pending,
fulfilled,
cancelled
}
9 changes: 8 additions & 1 deletion src/main/java/com/store/model/Product.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ import javax.validation.constraints.Positive
data class Product(
@field:NotNull @field:JsonDeserialize(using = StrictStringDeserializer::class) val name: String = "",
@field:NotNull val type: String = "gadget",
@field:Positive val inventory: Int = 0,
@field:NotNull @field:Positive val inventory: Int = 0,
val id: Int = idGenerator.getAndIncrement()
) {
companion object {
val idGenerator: AtomicInteger = AtomicInteger()
}
}

enum class ProductType {
book,
food,
gadget,
other
}
3 changes: 2 additions & 1 deletion src/main/java/com/store/services/OrderService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.store.exceptions.ValidationException
import com.store.model.DB
import com.store.model.Id
import com.store.model.Order
import com.store.model.OrderStatus
import org.springframework.stereotype.Service

@Service
Expand All @@ -28,7 +29,7 @@ class OrderService {
DB.updateOrder(order)
}

fun findOrders(status: String?, productid: Int?): List<Order> {
fun findOrders(status: OrderStatus?, productid: Int?): List<Order> {
return DB.findOrders(status, productid)
}
}

0 comments on commit dceae06

Please sign in to comment.