feat: Actorのインスタンス化時にチェックするように

This commit is contained in:
usbharu 2024-02-26 15:58:32 +09:00
parent 15b3cf6796
commit 7719863b80
4 changed files with 78 additions and 30 deletions

View File

@ -18,37 +18,56 @@ package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.application.config.ApplicationConfig import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.config.CharacterLimit
import jakarta.validation.Validator
import jakarta.validation.constraints.*
import org.hibernate.validator.constraints.URL
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Instant import java.time.Instant
import kotlin.math.max import kotlin.math.max
data class Actor private constructor( data class Actor private constructor(
@get:NotNull
@get:Positive
val id: Long, val id: Long,
@get:Pattern(regexp = "^[a-zA-Z0-9_-]{1,300}\$")
@get:Size(min = 1)
val name: String, val name: String,
@get:Pattern(regexp = "^([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\\.){1,255}[a-zA-Z]{2,}\$")
val domain: String, val domain: String,
val screenName: String, val screenName: String,
val description: String, val description: String,
@get:URL
val inbox: String, val inbox: String,
@get:URL
val outbox: String, val outbox: String,
@get:URL
val url: String, val url: String,
@get:NotBlank
val publicKey: String, val publicKey: String,
val privateKey: String? = null, val privateKey: String? = null,
@get:PastOrPresent
val createdAt: Instant, val createdAt: Instant,
@get:NotBlank
val keyId: String, val keyId: String,
val followers: String? = null, val followers: String? = null,
val following: String? = null, val following: String? = null,
@get:Positive
val instance: Long? = null, val instance: Long? = null,
val locked: Boolean, val locked: Boolean,
val followersCount: Int = 0, val followersCount: Int = 0,
val followingCount: Int = 0, val followingCount: Int = 0,
val postsCount: Int = 0, val postsCount: Int = 0,
val lastPostDate: Instant? = null, val lastPostDate: Instant? = null,
val emojis: List<Long> = emptyList() val emojis: List<Long> = emptyList(),
) { ) {
@Component @Component
class UserBuilder(private val characterLimit: CharacterLimit, private val applicationConfig: ApplicationConfig) { class UserBuilder(
private val characterLimit: CharacterLimit,
private val applicationConfig: ApplicationConfig,
private val validator: Validator,
) {
private val logger = LoggerFactory.getLogger(UserBuilder::class.java) private val logger = LoggerFactory.getLogger(UserBuilder::class.java)
@ -74,7 +93,7 @@ data class Actor private constructor(
followingCount: Int = 0, followingCount: Int = 0,
postsCount: Int = 0, postsCount: Int = 0,
lastPostDate: Instant? = null, lastPostDate: Instant? = null,
emojis: List<Long> = emptyList() emojis: List<Long> = emptyList(),
): Actor { ): Actor {
if (id == 0L) { if (id == 0L) {
return Actor( return Actor(
@ -176,7 +195,7 @@ data class Actor private constructor(
"keyId must contain non-blank characters." "keyId must contain non-blank characters."
} }
return Actor( val actor = Actor(
id = id, id = id,
name = limitedName, name = limitedName,
domain = domain, domain = domain,
@ -199,6 +218,13 @@ data class Actor private constructor(
lastPostDate = lastPostDate, lastPostDate = lastPostDate,
emojis = emojis emojis = emojis
) )
val validate = validator.validate(actor)
for (constraintViolation in validate) {
throw IllegalArgumentException("${constraintViolation.propertyPath} : ${constraintViolation.message}")
}
return actor
} }
} }
@ -217,27 +243,27 @@ data class Actor private constructor(
fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate) fun withLastPostAt(lastPostDate: Instant): Actor = this.copy(lastPostDate = lastPostDate)
override fun toString(): String { override fun toString(): String {
return "Actor(" + return "Actor(" +
"id=$id, " + "id=$id, " +
"name='$name', " + "name='$name', " +
"domain='$domain', " + "domain='$domain', " +
"screenName='$screenName', " + "screenName='$screenName', " +
"description='$description', " + "description='$description', " +
"inbox='$inbox', " + "inbox='$inbox', " +
"outbox='$outbox', " + "outbox='$outbox', " +
"url='$url', " + "url='$url', " +
"publicKey='$publicKey', " + "publicKey='$publicKey', " +
"privateKey=$privateKey, " + "privateKey=$privateKey, " +
"createdAt=$createdAt, " + "createdAt=$createdAt, " +
"keyId='$keyId', " + "keyId='$keyId', " +
"followers=$followers, " + "followers=$followers, " +
"following=$following, " + "following=$following, " +
"instance=$instance, " + "instance=$instance, " +
"locked=$locked, " + "locked=$locked, " +
"followersCount=$followersCount, " + "followersCount=$followersCount, " +
"followingCount=$followingCount, " + "followingCount=$followingCount, " +
"postsCount=$postsCount, " + "postsCount=$postsCount, " +
"lastPostDate=$lastPostDate, " + "lastPostDate=$lastPostDate, " +
"emojis=$emojis" + "emojis=$emojis" +
")" ")"
} }
} }

View File

@ -0,0 +1,13 @@
package dev.usbharu.hideout.core.domain.model.actor
import org.junit.jupiter.api.Test
import utils.UserBuilder
class ActorTest {
@Test
fun validator() {
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
UserBuilder.localUserOf(name = "うんこ")
}
}
}

View File

@ -25,6 +25,7 @@ import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.post.Post import dev.usbharu.hideout.core.domain.model.post.Post
import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter import dev.usbharu.hideout.core.service.post.DefaultPostContentFormatter
import jakarta.validation.Validation
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -37,7 +38,11 @@ import kotlin.test.assertEquals
import kotlin.test.assertNull import kotlin.test.assertNull
class ActorServiceTest { class ActorServiceTest {
val actorBuilder = Actor.UserBuilder(CharacterLimit(), ApplicationConfig(URL("https://example.com"))) val actorBuilder = Actor.UserBuilder(
CharacterLimit(),
ApplicationConfig(URL("https://example.com")),
Validation.buildDefaultValidatorFactory().validator
)
val postBuilder = Post.PostBuilder(CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy())) val postBuilder = Post.PostBuilder(CharacterLimit(), DefaultPostContentFormatter(HtmlSanitizeConfig().policy()))
@Test @Test
fun `createLocalUser ローカルユーザーを作成できる`() = runTest { fun `createLocalUser ローカルユーザーを作成できる`() = runTest {

View File

@ -20,12 +20,16 @@ import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.application.config.CharacterLimit import dev.usbharu.hideout.application.config.CharacterLimit
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
import dev.usbharu.hideout.core.domain.model.actor.Actor import dev.usbharu.hideout.core.domain.model.actor.Actor
import jakarta.validation.Validation
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.net.URL import java.net.URL
import java.time.Instant import java.time.Instant
object UserBuilder { object UserBuilder {
private val actorBuilder = Actor.UserBuilder(CharacterLimit(), ApplicationConfig(URL("https://example.com"))) private val actorBuilder = Actor.UserBuilder(
CharacterLimit(), ApplicationConfig(URL("https://example.com")),
Validation.buildDefaultValidatorFactory().validator
)
private val idGenerator = TwitterSnowflakeIdGenerateService private val idGenerator = TwitterSnowflakeIdGenerateService
@ -43,7 +47,7 @@ object UserBuilder {
createdAt: Instant = Instant.now(), createdAt: Instant = Instant.now(),
keyId: String = "https://$domain/users/$id#pubkey", keyId: String = "https://$domain/users/$id#pubkey",
followers: String = "https://$domain/users/$id/followers", followers: String = "https://$domain/users/$id/followers",
following: String = "https://$domain/users/$id/following" following: String = "https://$domain/users/$id/following",
): Actor { ): Actor {
return actorBuilder.of( return actorBuilder.of(
id = id, id = id,
@ -77,7 +81,7 @@ object UserBuilder {
createdAt: Instant = Instant.now(), createdAt: Instant = Instant.now(),
keyId: String = "https://$domain/$id#pubkey", keyId: String = "https://$domain/$id#pubkey",
followers: String = "https://$domain/$id/followers", followers: String = "https://$domain/$id/followers",
following: String = "https://$domain/$id/following" following: String = "https://$domain/$id/following",
): Actor { ): Actor {
return actorBuilder.of( return actorBuilder.of(
id = id, id = id,