Guard and LogError

Jorge running while opening a drink that unexpectedly releases confetti. The speech bubble says GUARD and LOGERROR.

Table of contents


Problem

In multiple sections of my code, I need to execute three operations every time I handle an Optional:

  1. Unwrap the Optional content
  2. Log an error in the logging system if the value is nil
  3. Throw an exception when the value doesn’t exist

This pattern repeats frequently, generating duplicate code and reducing maintainability.


Solution

The strategy consists of encapsulating the three operations in reusable functions that work in a coordinated manner.

Helper function: logError

func logError(
    _ message: String, 
    status: HTTPResponseStatus
) throws -> Never {
    self.logMessage(message, level: .error)
    throw Abort(status, reason: message)
}

This function executes the error logging in the logging system and subsequently terminates execution by throwing an Abort exception. The Never return type is fundamental, as it indicates to the compiler that this function never returns normally, ensuring that execution is completely interrupted.

Main function: guardAndLogError

func guardAndLogError<T>(
    _ optional: T?,
    message: String,
    status: HTTPResponseStatus = .noContent
) throws -> T {
    guard let optional else {
        try logError(message, status: status)
    }
    return optional
}

This generic function implements the complete pattern:

  • Uses guard let to safely unwrap the Optional
  • If the value is nil, invokes logError() to log the failure and terminate execution
  • If it contains a value, returns it successfully

The genericity T allows using this function with any type of optional data.


Result

With this implementation, a single line of code executes the three required operations: safe unwrapping, error logging, and exception handling.

let fileName = try guardAndLogError(
    fileName, 
    message: "fileName value not found"
)

Benefits

βœ… Reduction of duplicate code

βœ… Consistent error handling

βœ… Centralized and structured logs

βœ… Reusability through genericity


Extensibility

This pattern can be extended for more specific use cases:

// For boolean validations
func guardAndLogError(
    _ condition: Bool, 
    message: String
) throws { ... }

// For arrays
func guardAndLogError<T>(
    _ optionals: T?..., 
    message: String
) throws -> [T] { ... }

// For tuples
func guardAndLogError<T, U>(
    _ first: T?, 
    _ second: U?
    , message: String
) throws -> (T, U) { ... }

Keep coding, keep running πŸƒβ€β™‚οΈ