package util
Axiell Utilities Project
A set of classes and traits that provide a simple mechanism for the internationalisation of strings and error handling. A mechanism is also supplied to read an application's configuration resource. All methods have been designed to be referentially transparent without the introduction of an effectful library (e.g. cats).
Imports
The utility classes/traits can be imported in the usual way, however
it is necessary to include the Implicits object if the showLocal
and jsonLocal
type class methods are used, similarly for the read
method on Configure.
Example:
import com.axiell.util.Implicits._
A shorthand way to gain access to all utility classes is to import:
import com.axiell.util._ import com.axiell.util.Implicits._
For more detail on project see the root
package page.
- Source
- package.scala
- Alphabetic
- By Inheritance
- util
- AnyRef
- Any
- Hide All
- Show All
- Public
- Protected
Package Members
- package instances
Contains implicit values used by type classes.
Contains implicit values used by type classes.
The implicit values are used by the ShowLocal, JsonLocal and ConfigureReader type classes to print out language dependent strings, language dependent JSON strings and read configuration values respectively.
Implicit values exist for the following classes:
- ConfigureReader (read)
- Error (showLocal, jsonLocal)
- Int (showLocal, jsonLocal)
- List (showLocal, jsonLocal)
- Message (showLocal, jsonLocal)
java.time.OffsetDateTime
(showLocal, jsonLocal)java.lang.String
(showLocal, jsonLocal)java.lang.Throwable (showLocal, jsonLocal)
The method names appended in brackets are the extension methods added by the implicit values.
- package syntax
Contains implicit classes used to add extension methods.
Contains implicit classes used to add extension methods.
The implicit classes are used to add the following extension methods:
showLocal
displays a language dependent version of an objectjsonLocal
generates a JSON string with language dependent stringsmasonLocal
generates a Mason Draft 2 compliant JSON string for Errorfold
functionality for Boolean type
All traits found in the package are for the implementation of extension methods. The traits are mixed into the Implicits object so they can be easily imported into clients when required.
Type Members
- final class Configure extends AnyRef
Reads values from application configuration resource
Reads values from application configuration resource
A configuration resource consists of a hierarchical set of properties. The resource uses a superset of the JSON format called HOCON to specify settings.
Example configuration file:
server { # The interface the server should bind to for servicing requests. # The default value of 0.0.0.0 forces the server to bind to all # interfaces on the machine. interface = 0.0.0.0 interface = ${?SERVER_INTERFACE} # The port the server should bind to when accepting requests. The # default port used is 8000. port = 8000 port = ${?SERVER_PORT} }
Property values are read using the
read
method defined in this class:val result: Either[Error, (String, Int)] = for { config <- Configure() interface <- config.read[String] port <- config.read[Int] } yield (interface, port)
The
read
method returns an Error if the property cannot be read as the type requested. - trait ConfigureReader[A] extends AnyRef
Reads a configuration value
Reads a configuration value
ConfigureReader
is a type class that implements theread
method on Configure. The method takes a configuration and key and extracts the key's value. The type of the key's value is determined by the type of the constructedConfigureReader
. A set of implicit instances implement common return types (e.g. String, Int, List[_]). It is possible to define your own reader, however the most common will already be available as part of thecom.axiell.util.Implicits._
import.Example:
implicit val stringForConfigureReader = new ConfigureReader[String] { def read[String](config: Configure, key: String): Result[String] = Try { configure.config.getString(key) } .fold( { exc => Left(Error("BadString", key).withException(exc)) }, { Right(_) } )
- A
return type of key value
- final case class Error extends Product with Serializable
Contains a language independent error.
Contains a language independent error.
When an error occurs in a method it is useful to be able to return a description of what the error was, along with some context values associated with the error. In order to support internationalisation the error description should not contain a language specific string, but rather a code used to identify the error. Fortunately we can use the Message class to describe the error in a language independent way.
The context for an error may include an optional exception, if the error was due to an exception, but it may also contain many other context specific values (e.g. file paths, error codes, counts, etc). In order to handle arbitrary context data Error contains an internal map where the key is an arbitrary label identifying the context and the value is the context data itself.
The
withProperty
method allows an entry to be added to the internal map. For example, if an exception and a status code are to be associated with the error then the following code is required:Error("BadInt", value) .withProperty("exception", exc) .withProperty("status", 404)
The labels
exception
andstatus
are arbitrary and identify the kind of context data. The data itself can be any type that has an implicit ShowLocal and JsonLocal defined and in scope. The labelsmessage
andcode
are set to the error message and error code when the error is created.It is possible to retrieve context data by using the
get
method. The method takes the label to retrieve and the expected type of the data:Error("BadInt", value) .withProperty("status", 404) .get[Int]("status")
will return the Int 404.
A common paradigm, especially in functional programming is to have methods that may generate errors return either an
Error
or the return value. So it is common to see method return values like:def method[A]: Either[Error, A]
The use of Either for return values is encouraged.
Example:
def method(value: String): Either[Error, Int] = Try { value.toInt } .fold( { exc => Left(Error("BadInt", value).withProperty("exception", exc) }, { Right(_) } )
The method tries to convert a string to an integer value, which may result in an exception being thrown if the conversion fails.
The
showLocal
method allows a language specific string for an Error to be output as a string. The method takes an implicitjava.util.Locale
identifying the language to be shown. The method is an extension method added by the ShowLocal type class.The
jsonLocal
method outputs the error as a JSON object where the map labels are the property names and the map values the property values. An implicitjava.util.Locale
is required for language specific values (e.g. Message). The method is an extension method added by the JsonLocal type class.The
masonLocal
method outputs the error as a JSON object conforming to the Mason Draft 2 specification. The method is an extension method.A number of extension methods are provided to set specific values with predefined labels, these include:
withException
(label: exception)withId
(label: id)withTime
(label: time)withHttpStatusCode
(label: httpStatusCode)withMessages
(label: messages)
- final case class ErrorException(error: Error) extends Exception with Product with Serializable
Wraps an Error with an
Exception
Wraps an Error with an
Exception
In some instances an exception needs to be thrown that contains a language independent error message. The language independent error message is represented by an Error. The
getMessage()
method returns the error message using the system defaultLocale
. In most instances the catching code will unpack the error and use it.Example:
def method(value: String): Int = Try { value.toInt } .fold( { _ => throw ErrorException(Error("BadInt", value)) }, { _ } )
The
showLocal
method can be used to generate aString
containing the error properties along with a stack trace. ThejsonLocal
method produces a JSON object consisting of the error properties and the stack trace of where the exception was created.- error
Error to be wrapped in a
Exception
- final class Formatter extends AnyRef
Provides access to a set of language specific strings.
Provides access to a set of language specific strings.
A
code
is used support language independent strings. Thecode
is effectively a label used to identify a language specified string in a resource bundle loaded when the class was created. The string extracted from the bundle may containprintf()
style formatting markers. In order to satisfy the formatting markers a variable number of arguments are passed along with the string code. - trait JsonLocal[-A] extends AnyRef
Displays locale specific JSON string for any object.
Displays locale specific JSON string for any object.
JsonLocal
is a type class that adds thejsonLocal
extension method to any object. The method takes an implicitjava.util.Locale
defining the language to use for language specific strings. An implicit implementation of the JsonLocal trait must exist in scope for the method to be available.Example:
val error = Error(MessageBundle("Errors"), "Bad Error") implicit val locale = Locale.ENGLISH error.jsonLocal error.jsonLocal(Locale.GERMAN)
Output:
{"message":"A bad error","code":"BadError"} {"message":"Ein schlimmer Fehler","code":"BadError"}
The
jsonLocal
extension method can be defined for any classA
provided an implementation ofJsonLocal[A]
exists implicitly and is in scope. The code below shows the implementation ofJsonLocal[Error]
:implicit val utilJsonLocalForError: JsonLocal[Error] = new JsonLocal[Error] { def jsonLocal(error: Error)(implicit locale: Locale): String = { error.map.toList.map { case (key, (value, format, _)) => s""""${escape(key)}":${format.jsonLocal(value)}""" } .mkString("{", ",", "}") } }
- A
type for which a language specific JSON string is generated
- final case class Message extends Product with Serializable
Contains a language independent message.
Contains a language independent message.
In order to support internationalisation of applications it is necessary to provide a mechanism that allows a string to be generated in a language independent way. The Message class provides such a mechanism.
A Message contains information used to extract a string and format it when it needs to be displayed. In order to display the string it is necessary to invoke the
showLocal
method passing in an implicitjava.util.Locale
. The locale determines what language is used to extract the string.A language independent string is identified by three things, namely:
bundle
identifies a group of strings stored within the one resourcecode
indicates which string to extract from the bundleargs
that are interpolated into the string when displayed
Message objects may be passed around and the
showLocal
method only invoked when it is necessary to display the string in a language dependent way.Let's look at an example:
class Messages_en extends java.util.ListResourceBundle { final override val getContents: Array[Array[AnyRef]] = Array( Array("BadValue", "The bad value is %s") ... ) } class Messages_de extends java.util.ListResourceBundle { final override val getContents: Array[Array[AnyRef]] = Array( Array("BadValue", "Der falsche Wert ist %s") ... ) } implicit val bundle = MessageBundle("Messages") val message = Message("BadValue", "abc123") implicit val locale = Locale.ENGLISH message.showLocal message.showLocal(Locale.GERMAN)
Output:
The bad value is abc123 Der falsche Wert ist abc123
The
Messages_en
class contains a mapping between thecode
used to identify a string and its English message, similarlyMessages_de
contains the same codes with the equivalent German message.The Message is created using an implicit MessageBundle identifying the base class name containing the translated strings. Since the translated strings contain a format marker (
%s
) an argument must be supplied along with the code when theMessage
is instantiated.The
showLocal
method is used when a Message is to be displayed. The method takes an implicitjava.util.Locale
identifying which language is to be shown. The method is an extension method added by the ShowLocal type class. - final case class MessageBundle(name: String) extends Product with Serializable
Identifies the class name containing string resources.
Identifies the class name containing string resources.
A bundle is a set of classes containing string resources. Each class in the set begins with the
MessageBundle
name and has a locale appended to it identifying the language of the strings it contains. A special case is the class name without any locale appended. If a locale specific class cannot be located then the class without the locale is appended.A bundle extends the
java.util.ListResoruceBundle
class and overrides thegetContents
method. The method returns an Array where each element is an array containing two elements. The first element is acode
used to identify a string in a language independent way and the second is a language dependent string.Example:
class com.acme.Messages extends java.util.ListResourceBundle { final override val getContents: Array[Array[Object]] = Array( Array("BadValue", "The bad value is %s") ... ) } class com.acme.Messages_de extends java.util.ListResourceBundle { final override val getContents: Array[Array[Object]] = Array( Array("BadValue", "Der falsche Wert ist %s") ... ) }
The name of the class to supply to the
MessageBundle
to access the above string resources is"com.acme.Messages"
. Hence theMessageBundle
is instantiated with:MessageBundle("com.acme.Messages")
Best practice would dictate that each resource bundle class is placed in a separate file so that only the strings for a given language are loaded when required.
- name
of the class containing the resource strings
- trait ShowLocal[A] extends AnyRef
Displays locale specific string for any object.
Displays locale specific string for any object.
ShowLocal
is a type class that extends classes with theshowLocal
method. The method takes an implicitjava.util.Locale
defining in what language the returned string should be. In order to add the extension method to an object it is necessary to define an implicit implementation of the ShowLocal trait.Example:
case class Person(name: String, age: Int) implicit val showLocalForPerson: ShowLocal[Person] = new ShowLocal[Person] { def showLocal(person: Person)(implicit locale: Locale): String = { locale.getLanguage match { case "en" => s"Name: ${person.name}, Age: ${person.age}" case "fr" => s"prénom: ${person.name}, âge: ${person.age}" } } } val person = Person("John Smith", 32) implicit val locale = Locale.ENGLISH person.showLocal person.showLocal(Locale.FRENCH)
Output:
Name: John Smith, Age: 32 prénom: John Smith, âge: 32
The
showLocal
extension method can be defined for any classA
provided an implementation ofShowLocal[A]
exists implicitly and is in scope. The implicit definitions are generally defined in separate classes in a "instances" sub-directory enclosed within a trait. The trait is then mixed into an Implicits object allowing user to only importImplicits._
.For the example above the trait class would look like:
trait PersonInstances { implicit val showLocalForPerson: ShowLocal[Person] = new ShowLocal[Person] { def showLocal(person: Person)(implicit locale: Locale): String = { locale.getLanguage match { case "en" => s"Name: ${person.name}, Age: ${person.age}" case "fr" => s"prénom: ${person.name}, âge: ${person.age}" } } } }
stored in the file instances/PersonInstances.scala. All implicits would be mixed in a file called Implicits.scala that looks like:
object Implicits extends instances.PersonInstances
- A
type for which a language specific string is generated
Value Members
- object Configure
Provides access to application configuration resource
Provides access to application configuration resource
The application configuration is defined in the
application.conf
file stored in the application resource directory. The configuration file is a superset of the JSON format. All JSON constructs are supported (e.g. string, number, array, object, etc) along with extensions like comments and environment variable substitution.The
Typesafe Config
library is wrapped by this class and full details on the configuration file format can be found in their documentation. The reason to wrap an existing library is to provide an interface supporting a consistent return type using Either[Error, A] .Example configuration file:
server { # The interface the server should bind to for servicing requests. # The default value of 0.0.0.0 forces the server to bind to all # interfaces on the machine. interface = 0.0.0.0 interface = ${?SERVER_INTERFACE} # The port the server should bind to when accepting requests. The # default port used is 8000. port = 8000 port = ${?SERVER_PORT} }
The
${?variable}
construct indicates an environment variable should be consulted for a value. If a value is found it overrides the default setting.Referential Transparency
The application configuration (stored in the
resources/application.conf
file) is read once first accessed. It is then cached for the life of the application. Hence the configuration definition itself is immutable. When a setting is consulted and the setting depends on an environment variable the environment is only read once and cached (viaSystem.getEnv()
). Since it is not possible change an environment variable value (as the values are cached and Java does not provide a mechanism to do so) the values are effectively immutable. Hence referential transparency is enforced. - object ConfigureReader
Contains factory method to extract reader from type bound definition
Contains factory method to extract reader from type bound definition
The
read
method on Configure is type bound, meaning an unnamed implicit argument is passed to the method (aConfigureReader[A]
in this case). In order to access the nameless implicit a factory method is defined that extracts a named implicit and returns it's value, allowing the value to be used. - object Error extends Serializable
Constructs Error objects
Constructs Error objects
An error may be created using either an explicit or implicit MessageBundle. The bundle defines the resource (class) containing the language specific error strings.
Example:
implicit bundle MessageBundle("Errors") val err1 = Error("ErrorCode") val err2 = Error(MessageBundle("Errors"), "ErrorCode")
The
MessageBundle
is generally defined implicitly inpackage.scala
making it available to all code within the enclosed package. Error strings may be placed in a separate resource bundle from general messages.When and error is created the
message
label is set to the error message (Message) andcode
is set to the error code (java.lang.String
). - object Formatter
Creates a Formatter used to access strings for a given
locale
stored in a givenbundle
.Creates a Formatter used to access strings for a given
locale
stored in a givenbundle
.The support for language specific strings involves two values. The first is a MessageBundle that defines the name of a class containing language dependent strings. The second value is the
java.util.Locale
used to select the set of language strings to be accessed. When a Formatter is created an attempt is made to load the supplied bundle with the given locale. If the loading succeeds then the strings may be accessed via the returned Formatter.The current class loader is used to try and load the bundle class. The bundle class must extend
java.util.ListResourceBundle
. The class name may have a language code (e.g. _en) appended indicating the language whose strings are defined in the class. A class name without a language extension is the default bundle, used when a language specific version cannot be found.Example:
package com.acme class Resources extends java.util.ListResourceBundle { final override val getContents: Array[Array[AnyRef]] = Array( Array("BadValue", "The bad value is %s") ... ) } class Resources_de extends java.util.ListResourceBundle { final override val getContents: Array[Array[AnyRef]] = Array( Array("BadValue", "Der falsche Wert ist %s") ... ) }
The above bundle classes define strings for German (
Resources_de
) and the default bundle (Resources
). When defining bundles it makes sense to define all languages within a single file.Referential Transparency
The string resources are stored in a class and read as a
java.util.ListResourceBundle
. The strings are constants defined in a class ensuring that the same value is always returned for the same arguments. The read-only nature of the resource class ensures the Formatter methods are referentially transparent and so not effectful. - object Implicits extends ConfigureReaderInstances with ErrorInstances with ErrorExceptionInstances with IntInstances with ListInstances with MessageInstances with OffsetDateTimeInstances with StringInstances with ThrowableInstances with BooleanSyntax with ErrorSyntax with JsonLocalSyntax with MasonLocalSyntax with ShowLocalSyntax
Groups all implicits into a single object
Groups all implicits into a single object
Method extension implicits (Syntax suffix) and implicit variables (Instances Suffix) are mixed into a single object allowing a single import statement to be used to bring them all into scope.
The required import statement is:
import com.axiell.util.Implicits._
- object JsonLocal
Implements type class extension method for
JsonLocal[A]
- object Message extends Serializable
Constructs Message objects.
Constructs Message objects.
Two ways of creating
Message
objects are supported. The first method requires the MessageBundle to be supplied explicitly while the second method accepts theMessageBundle
implicitly.Example:
implicit bundle = MessageBundle("Resources") val mess1 = Message("BadCode", "abc123") val mess2 = Message(MessageBundle("Resources"), "BadCode", "abc123")
The
mess1
assignment uses the implicitMessageBundle
to determine where to locate the message strings, while the assignment tomess2
states theMessageBundle
explicitly. In practice the implicit version is generally used, with the implicitMessageBundle
declared in apackage.scala
file. - object ShowLocal
Implements type class extension method for
ShowLocal[A]
Documentation for the Axiell Utilities Project
Overview
The Axiell Utilities Project attempts to provide a small library of classes and object applicable to most Axiell developments. The library is not geared towards any one project and so should be useful for new projects as well as existing ones.
The library is based on a few simple principles, namely:
Package structure
The
package consists of a number of utilities, where each utility provides a single piece of functionality.com.axiell.util
Notable utilities are:
Configure
generic configuration reader allowing application settings defined as resources to be interrogated.Error
generic error handling class defining a locale independent error message along with details relating to the context of the error. The context may include an exception and other relevant values.Formatter
retrieves and formats a locale specific string from a specified resource bundle. The formatting is specified in aC
printf style.Implicits
implements implicit methods and classes used to pimp methods onto existing classes (e.g.fold()
method toBoolean
class) and to provide implicit implementations for the JsonLocal and ShowLocal type classes.JsonLocal
type class used to output locale specific JSON structures. Implementations exist for Message and Error.Message
provides a locale independent mechanism for handling strings. The strings for a given locale are stored in resource bundles that are accessed when the message is to be displayed.ShowLocal
type class used to show locale specific strings. Allows Message and Error to be converted to a given language string.Dependencies
The list of dependencies are:
Typesafe Config
a generic configuration file reader. The library is used by theConfigure
class to access settings stored as resources within an application.