Skip to content

Commit

Permalink
Merge pull request #371 from hossain-khan/370-handle-missing-user
Browse files Browse the repository at this point in the history
[#370] Added error message parsing for missing user
  • Loading branch information
hossain-khan authored Dec 22, 2024
2 parents ed1815c + 34f91ac commit 2bf27ee
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import dev.hossain.githubstats.logging.Log
import dev.hossain.githubstats.model.Issue
import dev.hossain.githubstats.model.IssueSearchResult
import dev.hossain.githubstats.service.GithubApiService.Companion.DEFAULT_PAGE_SIZE
import dev.hossain.githubstats.util.ErrorInfo
import dev.hossain.githubstats.util.ErrorProcessor
import kotlinx.coroutines.delay
import kotlin.math.ceil
Expand Down Expand Up @@ -33,8 +34,14 @@ class IssueSearchPagerService constructor(
size = pageSize,
)
} catch (exception: Exception) {
val errorInfo = errorProcessor.getDetailedError(exception)
throw errorInfo.exception
val errorInfo: ErrorInfo = errorProcessor.getDetailedError(exception)

if (errorInfo.isUserNotFound()) {
Log.w("❌ User not found. Skipping the PR list search for the user.\n")
return emptyList()
} else {
throw errorInfo.exception
}
}

val totalItemCount: Int = issueSearchResult.total_count
Expand Down
30 changes: 28 additions & 2 deletions src/main/kotlin/dev/hossain/githubstats/util/ErrorInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ data class ErrorInfo(
val exception: Exception,
val debugGuideMessage: String = "",
val githubError: GithubError? = null,
)
) {
fun isUserNotFound(): Boolean = ErrorProcessor.isUserMissingError(githubError)
}

/**
* Error threshold information.
Expand All @@ -24,18 +26,42 @@ data class ErrorThreshold(
/**
* Error response from GitHub API.
*
* Sample error message.
* Sample error messages.
* ```json
* {"message":"Bad credentials","documentation_url":"https://docs.github.com/rest"}
*
* {"message":"Validation Failed","errors":[{"resource":"Issue","code":"missing_field","field":"title"}],"documentation_url":"https://docs.github.com/rest/reference/issues#create-an-issue"}
*
* {"message":"Not Found","documentation_url":"https://docs.github.com/rest/pulls/pulls#get-a-pull-request","status":"404"}
*
* {"message":"Validation Failed","errors":[{"message":"The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.","resource":"Search","field":"q","code":"invalid"}],"documentation_url":"https://docs.github.com/v3/search/","status":"422"}
* ```
*/
@JsonClass(generateAdapter = true)
data class GithubError(
@Json(name = "message") val message: String,
@Json(name = "documentation_url") val documentationUrl: String,
@Json(name = "status") val status: Int? = null,
@Json(name = "errors") val errors: List<GithubErrorDetail> = emptyList(),
)

/**
* Error details from GitHub API.
*
* Example error detail:
* ```json
* {
* "resource": "Search",
* "code": "invalid",
* "message": "The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.",
* "field": "q"
* }
* ```
*/
@JsonClass(generateAdapter = true)
data class GithubErrorDetail(
@Json(name = "resource") val resource: String,
@Json(name = "code") val code: String,
@Json(name = "field") val field: String,
@Json(name = "message") val message: String,
)
30 changes: 30 additions & 0 deletions src/main/kotlin/dev/hossain/githubstats/util/ErrorProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,36 @@ class ErrorProcessor {
* ```
*/
private const val TOKEN_ERROR_MESSAGE = "Bad credentials"

/**
* Error message when search query is invalid.
*
* Sample error message.
* ```json
* {"message":"Validation Failed","errors":[{"message":"The listed users cannot be searched.","resource":"Search","field":"q","code":"invalid"}],"documentation_url":"https://docs.github.com/v3/search/","status":"422"}
* ```
*/
private const val VALIDATION_FAILED_ERROR_MESSAGE = "Validation Failed"

/**
* Resource type for search error.
*/
private const val RESOURCE_TYPE_SEARCH = "Search"

/**
* Check if user is missing in the search query.
*/
fun isUserMissingError(githubError: GithubError?): Boolean {
if (githubError == null || githubError.message != VALIDATION_FAILED_ERROR_MESSAGE) {
return false
}

return githubError.errors.any {
it.resource == RESOURCE_TYPE_SEARCH &&
// Yes, hardcoding the server message string to avoid any false positive
it.message.contains("users cannot be searched")
}
}
}

/**
Expand Down
68 changes: 68 additions & 0 deletions src/test/kotlin/dev/hossain/githubstats/util/ErrorProcessorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,72 @@ class ErrorProcessorTest {
assertThat(errorInfo.errorMessage).contains("HTTP 404")
assertThat(errorInfo.githubError).isNull()
}

@Test
fun `getDetailedError - given HttpException with JSON github errors list - returns ErrorInfo processed data with errors`() {
// language=JSON
val jsonErrorBody =
"""
{
"message": "Validation Failed",
"errors": [
{
"message": "The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.",
"resource": "Search",
"field": "q",
"code": "invalid"
}
],
"documentation_url": "https://docs.github.com/v3/search/",
"status": "422"
}
""".trimIndent()
val httpException = HttpException(Response.error<Any>(422, jsonErrorBody.toResponseBody("application/json".toMediaTypeOrNull())))
val errorProcessor = ErrorProcessor()

val errorInfo = errorProcessor.getDetailedError(httpException)

assertThat(errorInfo).isInstanceOf(ErrorInfo::class.java)
assertThat(errorInfo.githubError?.message).isEqualTo("Validation Failed")
assertThat(errorInfo.githubError?.status).isEqualTo(422)
assertThat(errorInfo.githubError?.errors).isNotEmpty()

val githubErrorDetail = errorInfo.githubError?.errors?.get(0)!!
assertThat(
githubErrorDetail.message,
).isEqualTo(
"The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.",
)
assertThat(githubErrorDetail.resource).isEqualTo("Search")
assertThat(githubErrorDetail.field).isEqualTo("q")
assertThat(githubErrorDetail.code).isEqualTo("invalid")
}

@Test
fun `getDetailedError - given HttpException with JSON github error user not found - validates user not found`() {
// language=JSON
val jsonErrorBody =
"""
{
"message": "Validation Failed",
"errors": [
{
"message": "The listed users cannot be searched either because the users do not exist or you do not have permission to view the users.",
"resource": "Search",
"field": "q",
"code": "invalid"
}
],
"documentation_url": "https://docs.github.com/v3/search/",
"status": "422"
}
""".trimIndent()
val httpException = HttpException(Response.error<Any>(422, jsonErrorBody.toResponseBody("application/json".toMediaTypeOrNull())))
val errorProcessor = ErrorProcessor()

val errorInfo = errorProcessor.getDetailedError(httpException)

assertThat(errorInfo).isInstanceOf(ErrorInfo::class.java)
assertThat(ErrorProcessor.isUserMissingError(errorInfo.githubError)).isTrue()
}
}

0 comments on commit 2bf27ee

Please sign in to comment.