Merge pull request #286 from usbharu/feature/bean-validation

Bean Validationの導入
This commit is contained in:
usbharu 2024-02-21 15:58:36 +09:00 committed by GitHub
commit fd269a52e2
5 changed files with 72 additions and 14 deletions

View File

@ -197,9 +197,10 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server") implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
implementation("org.springframework.boot:spring-boot-starter-log4j2") implementation("org.springframework.boot:spring-boot-starter-log4j2")
compileOnly("jakarta.validation:jakarta.validation-api") implementation("org.springframework.boot:spring-boot-starter-validation")
compileOnly("jakarta.annotation:jakarta.annotation-api:2.1.0") implementation("jakarta.validation:jakarta.validation-api")
compileOnly("io.swagger.core.v3:swagger-annotations:2.2.6") implementation("jakarta.annotation:jakarta.annotation-api:2.1.0")
implementation("io.swagger.core.v3:swagger-annotations:2.2.6")
implementation("io.swagger.core.v3:swagger-models:2.2.6") implementation("io.swagger.core.v3:swagger-models:2.2.6")
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version") implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")

View File

@ -24,6 +24,7 @@ import org.assertj.core.api.Assertions.assertThat
import org.flywaydb.core.Flyway import org.flywaydb.core.Flyway
import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
@ -31,7 +32,6 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.security.core.authority.SimpleGrantedAuthority import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.test.context.support.WithAnonymousUser import org.springframework.security.test.context.support.WithAnonymousUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
@ -160,6 +160,7 @@ class AccountApiTest {
} }
@Test @Test
@Disabled("JSONでも作れるようにするため")
@WithAnonymousUser @WithAnonymousUser
fun apiV1AccountsPostでJSONで作ろうとしても400() { fun apiV1AccountsPostでJSONで作ろうとしても400() {
mockMvc mockMvc

View File

@ -19,6 +19,7 @@ package dev.usbharu.hideout.generate
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.core.MethodParameter import org.springframework.core.MethodParameter
import org.springframework.validation.BindException
import org.springframework.web.bind.support.WebDataBinderFactory import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor import org.springframework.web.method.annotation.ModelAttributeMethodProcessor
@ -56,12 +57,17 @@ class JsonOrFormModelMethodProcessor(
return try { return try {
modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory) modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
} catch (e: BindException) {
throw e
} catch (exception: Exception) { } catch (exception: Exception) {
try { try {
requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory) requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
} catch (e: BindException) {
throw e
} catch (e: Exception) { } catch (e: Exception) {
logger.warn("Failed to bind request (1)", exception) logger.warn("Failed to bind request (1)", exception)
logger.warn("Failed to bind request (2)", e) logger.warn("Failed to bind request (2)", e)
throw IllegalArgumentException("Failed to bind request.")
} }
} }
} }

View File

@ -59,19 +59,19 @@ class MastodonAccountApiController(
HttpStatus.OK HttpStatus.OK
) )
override suspend fun apiV1AccountsPost( override suspend fun apiV1AccountsPost(accountsCreateRequest: AccountsCreateRequest): ResponseEntity<Unit> {
username: String,
password: String,
email: String?,
agreement: Boolean?,
locale: Boolean?,
reason: String?
): ResponseEntity<Unit> {
transaction.transaction { transaction.transaction {
accountApiService.registerAccount(UserCreateDto(username, username, "", password)) accountApiService.registerAccount(
UserCreateDto(
accountsCreateRequest.username,
accountsCreateRequest.username,
"",
accountsCreateRequest.password
)
)
} }
val httpHeaders = HttpHeaders() val httpHeaders = HttpHeaders()
httpHeaders.location = URI("/users/$username") httpHeaders.location = URI("/users/${accountsCreateRequest.username}")
return ResponseEntity(Unit, httpHeaders, HttpStatus.FOUND) return ResponseEntity(Unit, httpHeaders, HttpStatus.FOUND)
} }

View File

@ -276,6 +276,9 @@ paths:
requestBody: requestBody:
required: true required: true
content: content:
application/json:
schema:
$ref: "#/components/schemas/AccountsCreateRequest"
application/x-www-form-urlencoded: application/x-www-form-urlencoded:
schema: schema:
$ref: "#/components/schemas/AccountsCreateRequest" $ref: "#/components/schemas/AccountsCreateRequest"
@ -1355,6 +1358,7 @@ components:
format: binary format: binary
description: description:
type: string type: string
maxLength: 4000
focus: focus:
type: string type: string
required: required:
@ -1365,12 +1369,18 @@ components:
properties: properties:
username: username:
type: string type: string
minLength: 1
maxLength: 300
pattern: '^[a-zA-Z0-9_-]{1,300}$'
email: email:
type: string type: string
format: email
password: password:
type: string type: string
format: password
agreement: agreement:
type: boolean type: boolean
default: false
locale: locale:
type: boolean type: boolean
reason: reason:
@ -1399,6 +1409,8 @@ components:
type: string type: string
username: username:
type: string type: string
minLength: 1
pattern: '^[a-zA-Z0-9_-]{1,300}$'
acct: acct:
type: string type: string
url: url:
@ -1968,8 +1980,10 @@ components:
properties: properties:
phrase: phrase:
type: string type: string
maxLength: 1000
context: context:
type: array type: array
maxItems: 10
items: items:
type: string type: string
enum: enum:
@ -1980,8 +1994,10 @@ components:
- account - account
irreversible: irreversible:
type: boolean type: boolean
default: false
whole_word: whole_word:
type: boolean type: boolean
default: false
expires_in: expires_in:
type: integer type: integer
required: required:
@ -1993,8 +2009,10 @@ components:
properties: properties:
phrase: phrase:
type: string type: string
maxLength: 1000
context: context:
type: array type: array
maxItems: 10
items: items:
type: string type: string
enum: enum:
@ -2015,8 +2033,10 @@ components:
properties: properties:
title: title:
type: string type: string
maxLength: 255
context: context:
type: array type: array
maxItems: 10
items: items:
type: string type: string
enum: enum:
@ -2033,6 +2053,7 @@ components:
expires_in: expires_in:
type: integer type: integer
keywords_attributes: keywords_attributes:
maxItems: 1000
type: array type: array
items: items:
$ref: "#/components/schemas/FilterPostRequestKeyword" $ref: "#/components/schemas/FilterPostRequestKeyword"
@ -2045,6 +2066,7 @@ components:
properties: properties:
keyword: keyword:
type: string type: string
maxLength: 1000
whole_word: whole_word:
type: boolean type: boolean
default: false default: false
@ -2059,6 +2081,7 @@ components:
properties: properties:
keyword: keyword:
type: string type: string
maxLength: 1000
whole_word: whole_word:
type: boolean type: boolean
default: false default: false
@ -2073,6 +2096,7 @@ components:
properties: properties:
keyword: keyword:
type: string type: string
maxLength: 1000
whole_word: whole_word:
type: boolean type: boolean
regex: regex:
@ -2083,8 +2107,10 @@ components:
properties: properties:
title: title:
type: string type: string
maxLength: 255
context: context:
type: array type: array
maxItems: 10
items: items:
type: string type: string
enum: enum:
@ -2101,6 +2127,7 @@ components:
expires_in: expires_in:
type: integer type: integer
keywords_attributes: keywords_attributes:
maxItems: 1000
type: array type: array
items: items:
$ref: "#/components/schemas/FilterPubRequestKeyword" $ref: "#/components/schemas/FilterPubRequestKeyword"
@ -2110,6 +2137,7 @@ components:
properties: properties:
keyword: keyword:
type: string type: string
maxLength: 1000
whole_word: whole_word:
type: boolean type: boolean
regex: regex:
@ -2118,6 +2146,9 @@ components:
type: string type: string
_destroy: _destroy:
type: boolean type: boolean
default: false
required:
- id
FilterStatusRequest: FilterStatusRequest:
type: object type: object
@ -2473,18 +2504,22 @@ components:
status: status:
type: string type: string
nullable: true nullable: true
maxLength: 3000
media_ids: media_ids:
type: array type: array
items: items:
type: string type: string
maxItems: 4
poll: poll:
$ref: "#/components/schemas/StatusesRequestPoll" $ref: "#/components/schemas/StatusesRequestPoll"
in_reply_to_id: in_reply_to_id:
type: string type: string
sensitive: sensitive:
type: boolean type: boolean
default: false
spoiler_text: spoiler_text:
type: string type: string
maxLength: 100
visibility: visibility:
type: string type: string
enum: enum:
@ -2494,22 +2529,29 @@ components:
- direct - direct
language: language:
type: string type: string
maxLength: 100
scheduled_at: scheduled_at:
type: string type: string
format: date-time
example: "2019-12-05T12:33:01.000Z"
StatusesRequestPoll: StatusesRequestPoll:
type: object type: object
properties: properties:
options: options:
type: array type: array
maxItems: 10
items: items:
type: string type: string
maxLength: 100
expires_in: expires_in:
type: integer type: integer
multiple: multiple:
type: boolean type: boolean
default: false
hide_totals: hide_totals:
type: boolean type: boolean
default: false
Application: Application:
type: object type: object
@ -2536,12 +2578,16 @@ components:
properties: properties:
client_name: client_name:
type: string type: string
maxLength: 200
redirect_uris: redirect_uris:
type: string type: string
maxLength: 1000
scopes: scopes:
type: string type: string
maxLength: 1000
website: website:
type: string type: string
maxLength: 1000
required: required:
- client_name - client_name
- redirect_uris - redirect_uris
@ -2602,16 +2648,20 @@ components:
default: false default: false
languages: languages:
type: array type: array
maxItems: 10
items: items:
type: string type: string
maxLength: 10
UpdateCredentials: UpdateCredentials:
type: object type: object
properties: properties:
display_name: display_name:
type: string type: string
maxLength: 300
note: note:
type: string type: string
maxLength: 2000
avatar: avatar:
type: string type: string
format: binary format: binary