Skip to content
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

Error refactor #440

Merged
merged 13 commits into from
Apr 14, 2020
4 changes: 2 additions & 2 deletions docs/docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Any type with a `cats.effect.Sync` instance can be used with this example, such
object ProgramF {
import cats.effect.Sync
import github4s.Github
import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain.User
import org.http4s.client.Client

Expand Down Expand Up @@ -185,7 +185,7 @@ object ProgramEvalWithHeaders {
## Using github4s with GitHub Enterprise

By default `Github` instances are configured for the [public GitHub][public-github] endpoints via a fallback
`GithubConfig` instance which is picked up by the `Github` constructor if there's no other `GithubConfig` in the scope.
`GithubConfig` instance which is picked up by the `Github` constructor if there's no other `GithubConfig` in the scope.

It is also possible to pass a custom GitHub configuration (e.g. for a particular [GitHub Enterprise][github-enterprise]
server). To override the default configuration values declare a custom `GithubConfig` instance in an appropriate
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ See [the API doc](https://developer.github.com/v3/issues/milestones/#update-a-mi

[milestone-scala]: https://github.com/47degrees/github4s/blob/master/github4s/src/main/scala/github4s/domain/Milestone.scala

### Delete milestone
### Delete a milestone

You can delete a milestone for a particular organization and repository with `deleteMilestone`; it takes arguments:

Expand Down
216 changes: 216 additions & 0 deletions github4s/src/main/scala/github4s/GHError.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright 2016-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s

import io.circe.{Decoder, Encoder}
import io.circe.generic.semiauto._
import org.http4s.DecodeFailure
import io.circe.HCursor

/**
* Top-level exception returned by github4s when an error occurred.
* @param message that is common to all exceptions
*/
sealed abstract class GHError(
message: String
) extends Exception {
final override def fillInStackTrace(): Throwable = this
final override def getMessage(): String = message
}

object GHError {

/**
* Corresponds to a response for which the status code couldn't be handled.
* @param message indicating what happened
* @param body of the response
*/
final case class UnhandledResponseError(
message: String,
body: String
) extends GHError(message) {
final override def toString(): String = s"UnhandledResponseError($message, $body)"
}

/**
* Corresponds to the case when an issue occurred during JSON parsing.
* @param message indicating what happened
* @param cause root cause
*/
final case class JsonParsingError(
message: String,
cause: Option[Throwable]
) extends GHError(message) {
final override def toString(): String = s"JsonParsingError($message, $cause)"
final override def getCause(): Throwable = cause.orNull
}
object JsonParsingError {
def apply(df: DecodeFailure): JsonParsingError = JsonParsingError(df.message, df.cause)
}

/**
* Exotic error sometimes used by GitHub.
* @param message indicating what happened
*/
final case class BasicError(
message: String
) extends GHError(message) {
final override def toString(): String = s"BasicError($message)"
}
object BasicError {
private[github4s] implicit val basicErrorDecoder: Decoder[BasicError] =
new Decoder[BasicError] {
final override def apply(c: HCursor): Decoder.Result[BasicError] =
c.downField("error").as[String].map(BasicError.apply)
}
}

/**
* Corresponds to a 400 status code.
* @param message that was given in the response body
*/
final case class BadRequestError(
message: String
) extends GHError(message) {
final override def toString(): String = s"BadRequestError($message)"
}
object BadRequestError {
private[github4s] implicit val badRequestErrorDecoder: Decoder[BadRequestError] =
deriveDecoder[BadRequestError]
}

/**
* Corresponds to a 401 status code
* @param message that was given in the response body
* @param documentation_url associated documentation URL for this endpoint
*/
final case class UnauthorizedError(
message: String,
documentation_url: String
) extends GHError(message) {
final override def toString(): String = s"UnauthorizedError($message, $documentation_url)"
}
object UnauthorizedError {
private[github4s] implicit val unauthorizedErrorDecoder: Decoder[UnauthorizedError] =
deriveDecoder[UnauthorizedError]
}

/**
* Corresponds to a 403 status code
* @param message that was given in the response body
* @param documentation_url associated documentation URL for this endpoint
*/
final case class ForbiddenError(
message: String,
documentation_url: String
) extends GHError(message) {
final override def toString(): String = s"ForbiddenError($message, $documentation_url)"
}
object ForbiddenError {
private[github4s] implicit val forbiddenErrorDecoder: Decoder[ForbiddenError] =
deriveDecoder[ForbiddenError]
}

/**
* Corresponds to a 404 status code
* @param message that was given in the response body
* @param documentation_url associated documentation URL for this endpoint
*/
final case class NotFoundError(
message: String,
documentation_url: String
) extends GHError(message) {
final override def toString(): String = s"NotFoundError($message, $documentation_url)"
}
object NotFoundError {
private[github4s] implicit val notFoundErrorDecoder: Decoder[NotFoundError] =
deriveDecoder[NotFoundError]
}

sealed trait ErrorCode
object ErrorCode {
final case object MissingResource extends ErrorCode
final case object MissingField extends ErrorCode
final case object InvalidFormatting extends ErrorCode
final case object ResourceAlreadyExists extends ErrorCode
final case object Custom extends ErrorCode
private[github4s] implicit val errorCodeDecoder: Decoder[ErrorCode] =
Decoder.decodeString.map {
case "missing" => MissingResource
case "missing_field" => MissingField
case "invalid" => InvalidFormatting
case "already_exists" => ResourceAlreadyExists
case _ => Custom
}
private[github4s] implicit val errorCodeEncoder: Encoder[ErrorCode] =
Encoder.encodeString.contramap[ErrorCode] {
case ErrorCode.MissingResource => "missing"
case ErrorCode.MissingField => "missing_field"
case ErrorCode.InvalidFormatting => "invalid"
case ErrorCode.ResourceAlreadyExists => "already_exists"
case ErrorCode.Custom => "custom"
}
}

/**
* Error given when a 422 status code is returned
* @param resource github resource on which the error occurred (issue, pull request, etc)
* @param field for which the error occurred
* @param code error code to debug the problem
*/
final case class UnprocessableEntity(
resource: String,
field: String,
code: ErrorCode
)
object UnprocessableEntity {
private[github4s] implicit val unprocessableEntityDecoder: Decoder[UnprocessableEntity] =
deriveDecoder[UnprocessableEntity]
}

/**
* Corresponds to a 422 status code
* @param message that was given in the response body
* @param errors list of validation errors
*/
final case class UnprocessableEntityError(
message: String,
errors: List[UnprocessableEntity]
) extends GHError(message) {
final override def toString(): String = s"UnprocessableEntityError($message, $errors)"
}
object UnprocessableEntityError {
private[github4s] implicit val uEntityErrorDecoder: Decoder[UnprocessableEntityError] =
deriveDecoder[UnprocessableEntityError]
}

/**
* Corresponds to a 423 status code
* @param message that was given in the response body
* @param documentation_url associated documentation URL for this endpoint
*/
final case class RateLimitExceededError(
message: String,
documentation_url: String
) extends GHError(message) {
final override def toString(): String = s"RateLimitExceededError($message, $documentation_url)"
}
object RateLimitExceededError {
private[github4s] implicit val rleErrorDecoder: Decoder[RateLimitExceededError] =
deriveDecoder[RateLimitExceededError]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,8 @@

package github4s

object GithubResponses {

final case class GHResponse[A](
result: Either[GHException, A],
statusCode: Int,
headers: Map[String, String]
)

sealed abstract class GHException extends Exception {
final override def fillInStackTrace(): Throwable = this
}

final case class JsonParsingException(
msg: String,
json: String
) extends GHException {
final override def getMessage: String = msg
}

}
final case class GHResponse[A](
result: Either[GHError, A],
statusCode: Int,
headers: Map[String, String]
)
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Activities.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Activities[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Auth[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Gists.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Gists[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/GitData.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package github4s.algebras

import cats.data.NonEmptyList
import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait GitData[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Issues.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package github4s.algebras

import java.time.ZonedDateTime

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Issues[F[_]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Organizations[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Projects.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Projects[F[_]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait PullRequests[F[_]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package github4s.algebras

import cats.data.NonEmptyList
import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Repositories[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Teams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Teams[F[_]] {
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/main/scala/github4s/algebras/Users.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package github4s.algebras

import github4s.GithubResponses.GHResponse
import github4s.GHResponse
import github4s.domain._

trait Users[F[_]] {
Expand Down