-
Notifications
You must be signed in to change notification settings - Fork 0
fix: 도서 검색 DTO 변경사항에 따른 LastPage 로직 수정 및 빈 ISBN 대응 로직 추가 #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,14 +6,14 @@ import androidx.compose.runtime.Stable | |
| data class BookSearchModel( | ||
| val version: String = "", | ||
| val title: String = "", | ||
| val link: String = "", | ||
| val pubDate: String = "", | ||
| val totalResults: Int = 0, | ||
| val startIndex: Int = 0, | ||
| val itemsPerPage: Int = 0, | ||
| val query: String = "", | ||
| val searchCategoryId: Int = 0, | ||
| val searchCategoryName: String = "", | ||
| val lastPage: Boolean = false, | ||
| val books: List<BookSummaryModel> = emptyList(), | ||
| ) | ||
|
|
||
|
|
@@ -24,5 +24,7 @@ data class BookSummaryModel( | |
| val author: String = "", | ||
| val publisher: String = "", | ||
| val coverImageUrl: String = "", | ||
| val link: String = "", | ||
| val userBookStatus: String = "", | ||
| val key: String = "", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainkey의 고유성/안정성 보장 필요(빈 ISBN 시 충돌 가능성)
권장 주석 추가 예시: - val key: String = "",
+ // UI 리스트 key(고유/불변). isbn13 우선, 없으면 link, 그마저 없으면 대표 특성 조합으로 생성.
+ val key: String = "",매퍼 예시(외부 파일 참고용): // core/data/impl/.../ResponseToModel.kt
fun BookSummary.toModel(): BookSummaryModel = BookSummaryModel(
isbn13 = isbn13.orEmpty(),
title = title.orEmpty(),
author = author.orEmpty(),
publisher = publisher.orEmpty(),
coverImageUrl = coverImageUrl.orEmpty(),
link = link.orEmpty(),
userBookStatus = userBookStatus.orEmpty(),
key = when {
!isbn13.isNullOrBlank() -> isbn13
!link.isNullOrBlank() -> link
else -> listOf(title, author, publisher, coverImageUrl).joinToString("|") { it.orEmpty() }
}
)빠른 확인 스크립트: 필요하시면 테스트 케이스(빈 ISBN/중복 title 시 key 충돌 방지)도 함께 작성해 드리겠습니다. 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== key 생성 로직 확인(ResponseToModel) =="
fd -p "ResponseToModel.kt" | xargs -I{} rg -n "key\\s*=" "{}" -n -A 3 -B 3 || true
echo
echo "== Compose 리스트에서 key 사용 확인 =="
rg -n "key\\s*=\\s*\\{\\s*it\\.key\\s*\\}" -A 1 -B 1 || trueLength of output: 471 UI 리스트 key 고유성/불변성 보장 필요: key 생성 로직 및 Compose key 지정 검토 현재 확인 결과
◆ 제안 사항
fun BookSummary.toModel(): BookSummaryModel = BookSummaryModel(
// …기존 필드…
- key = "$title-$isbn13",
+ key = when {
+ !isbn13.isNullOrBlank() -> isbn13
+ !link.isNullOrBlank() -> link
+ else -> listOf(title, author, publisher, coverImageUrl)
+ .joinToString("|") { it.orEmpty() }
+ }
)
- val key: String = "",
+ // UI 리스트 key(고유/불변): isbn13 우선, 없으면 link, 그마저 없으면 주요 특성 결합
+ val key: String = "",
- items(bookList) { item -> … }
+ items(bookList, key = { it.key }) { item -> … }테스트 케이스(빈 ISBN·동명 도서 충돌 방지) 추가 작성도 권장드립니다. 🤖 Prompt for AI Agents |
||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,6 @@ data class BookSearchResponse( | |
| val version: String, | ||
| @SerialName("title") | ||
| val title: String, | ||
| @SerialName("link") | ||
| val link: String, | ||
| @SerialName("pubDate") | ||
| val pubDate: String, | ||
| @SerialName("totalResults") | ||
|
|
@@ -25,6 +23,8 @@ data class BookSearchResponse( | |
| val searchCategoryId: Int, | ||
| @SerialName("searchCategoryName") | ||
| val searchCategoryName: String, | ||
| @SerialName("lastPage") | ||
| val lastPage: Boolean, | ||
|
Comment on lines
+26
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent ❓ Verification inconclusivelastPage 추가 시 백워드 호환성 고려(기본값 지정 권장) kotlinx.serialization 사용 시, 구버전 서버 응답에 lastPage가 없으면 디코딩 실패할 수 있습니다. 기본값을 두면 안전합니다. 서버/클라이언트 배포 타이밍이 완전히 동기화되어 있다면 스킵 가능하지만, 그렇지 않다면 아래처럼 기본값을 권장합니다. - @SerialName("lastPage")
- val lastPage: Boolean,
+ @SerialName("lastPage")
+ val lastPage: Boolean = false,lastPage 및 link 필드에 기본값 추가(백워드 호환성 확보) kotlinx.serialization 사용 시, 서버 응답에 해당 키가 없으면 MissingFieldException이 발생합니다. 서버·클라이언트 배포 시점이 완전히 동기화되지 않을 경우를 대비해 다음과 같이 기본값을 지정하세요:
@@ core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt
- @SerialName("lastPage")
- val lastPage: Boolean,
+ @SerialName("lastPage")
+ val lastPage: Boolean = false,
- @SerialName("link")
- val link: String,
+ @SerialName("link")
+ val link: String = "",이렇게 설정하면 구버전 서버 응답에서도 안전하게 디코딩할 수 있습니다. 🤖 Prompt for AI Agents |
||
| @SerialName("books") | ||
| val books: List<BookSummary>, | ||
| ) | ||
|
|
@@ -41,6 +41,8 @@ data class BookSummary( | |
| val publisher: String, | ||
| @SerialName("coverImageUrl") | ||
| val coverImageUrl: String, | ||
| @SerialName("link") | ||
| val link: String, | ||
| @SerialName("userBookStatus") | ||
| val userBookStatus: String, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -40,7 +40,6 @@ class BookSearchPresenter @AssistedInject constructor( | |||||||||||||||||||||||||||||
| private val repository: BookRepository, | ||||||||||||||||||||||||||||||
| ) : Presenter<BookSearchUiState> { | ||||||||||||||||||||||||||||||
| companion object { | ||||||||||||||||||||||||||||||
| private const val PAGE_SIZE = 20 | ||||||||||||||||||||||||||||||
| private const val START_INDEX = 1 | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
|
@@ -80,7 +79,7 @@ class BookSearchPresenter @AssistedInject constructor( | |||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| currentStartIndex = startIndex | ||||||||||||||||||||||||||||||
| isLastPage = result.books.size < PAGE_SIZE | ||||||||||||||||||||||||||||||
| isLastPage = result.lastPage | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (startIndex == START_INDEX) { | ||||||||||||||||||||||||||||||
| uiState = UiState.Success | ||||||||||||||||||||||||||||||
|
|
@@ -185,7 +184,12 @@ class BookSearchPresenter @AssistedInject constructor( | |||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| is BookSearchUiEvent.OnBookClick -> { | ||||||||||||||||||||||||||||||
| selectedBookIsbn = event.isbn13 | ||||||||||||||||||||||||||||||
| isBookRegisterBottomSheetVisible = true | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if (selectedBookIsbn.isEmpty()) { | ||||||||||||||||||||||||||||||
| sideEffect = BookSearchSideEffect.ShowToast("isbn이 없는 도서는 등록할 수 없습니다") | ||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||
| isBookRegisterBottomSheetVisible = true | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
Comment on lines
186
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 빈 ISBN 처리 시 상태 오염 방지 및 견고성 개선(isEmpty→isBlank, 할당 위치 이동) 빈 ISBN이면 하단시트를 열지 않으므로 selectedBookIsbn을 미리 세팅하지 않는 편이 상태 일관성에 유리합니다. 공백 포함 케이스도 방지하려면 isBlank 사용을 권장합니다. - selectedBookIsbn = event.isbn13
-
- if (selectedBookIsbn.isEmpty()) {
+ if (event.isbn13.isBlank()) {
sideEffect = BookSearchSideEffect.ShowToast("isbn이 없는 도서는 등록할 수 없습니다")
} else {
+ selectedBookIsbn = event.isbn13
isBookRegisterBottomSheetVisible = true
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| is BookSearchUiEvent.OnBookRegisterBottomSheetDismiss -> { | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
마지막 페이지 플래그 추가 LGTM — 네이밍(nit)과 하위호환 확인 요청
isLastPage네이밍도 고려 가능하나, API와의 정합성이 더 중요하다면 현 상태 유지도 무방합니다. (nit)lastPage누락 시 fallback 계산(예:books.size < itemsPerPage)을 두었는지 확인 부탁드립니다.lastPage기반으로 전환되었는지 잔존 로직 점검 권장.다음 스크립트로 매핑 및 사용처를 빠르게 확인할 수 있습니다:
🏁 Script executed:
Length of output: 2808
마지막 페이지 플래그 누락 시 안전 대체 로직 추가 필요
lastPage를 단순 매핑만 하고 있어, JSON 응답에 해당 필드가 없으면 파싱 오류가 발생할 수 있습니다.→
lastPage를 nullable로 변경하거나, 매퍼에서 누락 시books.size < itemsPerPage기반으로 fallback 처리 로직을 추가해야 합니다.isLastPage = result.lastPage로 전환되어 있으며, 개수(size) 기반 판별 로직은 더 이상 남아 있지 않습니다.예시 수정안 (ResponseToModel.kt):
fun BookSearchResponse.toModel(): BookSearchModel = BookSearchModel( version = version, title = title, pubDate = pubDate, totalResults = totalResults, startIndex = startIndex, itemsPerPage = itemsPerPage, query = query, searchCategoryId = searchCategoryId, searchCategoryName = searchCategoryName, - lastPage = lastPage, + lastPage = lastPage ?: (books.size < itemsPerPage), books = books.map { it.toModel() }, )위와 같이 매퍼 레벨에서 하위 호환과 필드 미존재 시에도 정상 동작을 보장하도록 수정해주세요.
🤖 Prompt for AI Agents