makiftutuncu / errors   1.2

MIT License GitHub

An easy-to-use library written in Scala for providing immutable, lightweight, extensible way to represent errors in your project

Scala versions: 2.12 2.11

Errors Build Status

Errors is an easy-to-use library written in Scala for providing immutable, lightweight, extensible way to represent errors in your project.

How to Include in Your Project

Add following to your build.sbt if you are using SBT

libraryDependencies += "com.github.mehmetakiftutuncu" %% "errors" % "1.2"

Or add following to your pom.xml if you are using Maven (use errors_2.11 for Scala 2.11)

<dependency>
	<groupId>com.github.mehmetakiftutuncu</groupId>
	<artifactId>errors_2.12</artifactId>
	<version>1.2</version>
</dependency>

Or add following to your build.gradle if you are using Gradle (use errors_2.11 for Scala 2.11)

compile 'com.github.mehmetakiftutuncu:errors_2.12:1.2'

Examples

Since Errors is immutable, any kind of update operation (i.e. any method returning Errors or any error extending ErrorBase) return a new copy with new data.

Creating an Errors Instance

You can easily create both empty and non-empty Errors instances. Just use empty or apply methods.

Example

// Empty errors
val errors1 = Errors.empty
val errors2 = Errors()

// Non-empty errors already containing given errors
val errors3 = Errors(CommonError.timeout, SimpleError.authorization)

Creating Error Objects

You can create error objects to add to or remove from or even perform checks on an Errors instance. There are 2 types of errors already defined; CommonError and SimpleError. However, you can use any type of errors as long as they extend from ErrorBase.

Example

// You can provide all info.
val error1 = CommonError(name = "invalidData", reason = "Value must be a positive integer.", data = "-5")

// You can provide some info and update reason and data of a CommonError later.
val error2 = CommonError("timeout").reason("Network might be down.").data("30 seconds")

// There are even some predefined helper methods to give you appropriate instances easily. 
val error3 = CommonError.authorization.reason("Username or password is invalid!")

// Literally "simple", just an error name
val error4 = SimpleError("database")
val error5 = SimpleError.timeout

Adding Errors

You can add a single error, multiple errors or the errors in an existing Errors instance to an Errors instance.

Example

val errors1 = Errors.empty
val errors2 = Errors(SimpleError.authorization)

val errors3 = errors1 + CommonError.timeout

val errors4 = errors1.addAll(CommonError.timeout, SimpleError.authorization)

val errors5 = errors3 ++ Errors(SimpleError.authorization)

Removing Errors

You can remove a single error, multiple errors or the errors in an existing Errors instance from an Errors instance.

Example

val errors1 = Errors(CommonError.timeout, SimpleError.authorization)

val errors2 = errors1 - CommonError.timeout

// Database error will be ignored here since it does not exist in errors1 anyway.
val errors3 = errors1.removeAll(SimpleError.authorization, CommonError.database)

val errors4 = errors1 -- Errors(SimpleError.authorization)

Checking Errors Instance for Errors

You can perform several checks on an Errors instance.

Example

val errors1 = Errors(CommonError.timeout, SimpleError.authorization)
val errors2 = Errors.empty

errors1.isEmpty // false
errors2.isEmpty // true

errors1.nonEmpty // true
errors2.nonEmpty // false

errors1.hasErrors // true
errors2.hasErrors // false

errors1.size // 2
errors2.size // 0

errors1.numberOfErrors // 2
errors2.numberOfErrors // 0

errors1.contains(CommonError.timeout)  // true
errors1.contains(CommonError.database) // false
errors2.contains(CommonError.timeout)  // false

// true
errors1.exists {
  case CommonError(name, _, _) if name == "timeout" => true
  case _ => false
}

Using Maybe

There is a type alias for Either[Errors, V] defined as Maybe. It can be useful for error handling while accessing a value.

Example

// Method will either return some Errors or an Int
def divide(n1: Int, n2: Int): Maybe[Int] = {
  if (n2 == 0) {
    // Create maybe with Errors
    Maybe(Errors(CommonError.invalidData.reason("Cannot divide by 0!")))
  } else {
    // Create Maybe with a value
    Maybe(n1 / n2)
  }
}

val result1: Maybe[Int] = divide(3, 0)
val result2: Maybe[Int] = divide(4, 2)

result1.maybeErrors // Returns Some(Errors(CommonError.invalidData.reason("Cannot divide by 0!")))
result2.maybeErrors // Returns None

result1.maybeValue // Returns None
result2.maybeValue // Returns Some(2)

result1.hasErrors // Returns true
result2.hasErrors // Returns false

result1.hasValue // Returns false
result2.hasValue // Returns true

// Methods below throw exceptions unless their criteria are met. So, use them with caution!

result1.errors // Returns Errors(CommonError.invalidData.reason("Cannot divide by 0!"))
result2.errors // Throws HasNoErrorsException!

result1.value // Throws HasNoValueException!
result2.value // Returns 2

Representing Errors

As default implementation, Errors will use JsonStringErrorRepresenter and give a Json formatted string representation of all errors as an array. You may also provide your own error representer extending ErrorRepresenter so you can represent your errors in any way you want.

Example

val error1 = CommonError(name = "foo")
val error2 = CommonError(name = "foo", reason = "bar")
val error3 = CommonError(name = "foo", data = "bar")
val error4 = CommonError(name = "foo", reason = "bar", data = "baz")
val error5 = SimpleError(name = "goo")
val errors = Errors(error1, error2, error3, error4, error5)

// Output will be
// [{"name":"foo"},{"name":"foo","reason":"bar"},{"name":"foo","data":"bar"},{"name":"foo","reason":"bar","data":"baz"},{"name":"goo"}]
errors.represent(includeWhen = false)
errors.toString

// Output will be
// [{"name":"foo","when":1454271634493},{"name":"foo","reason":"bar","when":1454271634557},{"name":"foo","data":"bar","when":1454271634621},{"name":"foo","reason":"bar","data":"baz","when":1454271634680},{"name":"goo","when":1454271634737}]
errors.represent(includeWhen = true)

// Custom error representer giving true unless Errors is empty, for demonstration purposes
val customBooleanRepresenter = new ErrorRepresenter[Boolean] {
  override def represent(error: ErrorBase, includeWhen: Boolean): Boolean = true
  override def represent(errors: List[ErrorBase], includeWhen: Boolean): Boolean = if (errors.isEmpty) false else true
  override def asString(representation: Boolean): String = representation.toString
}

Errors.empty.represent(customBooleanRepresenter, includeWhen = false) // Returns false
errors.represent(customBooleanRepresenter, includeWhen = true)        // Returns true

You can see a real error representer example in JsonErrorRepresenter.

Contributing

I'd appreciate if you comment, file an issue, send pull requests. Please feel free to and do contribute.

Contributors

  • @forthy - Cross compilation for Scala 2.11 and 2.12

License

The MIT License (MIT), Copyright (c) 2016 Mehmet Akif Tütüncü

See LICENSE.md for details.