im-kulikov/helium

View on GitHub
web/listener.go

Summary

Maintainability
A
0 mins
Test Coverage
package web

import (
    "context"
    "errors"
    "fmt"

    "go.uber.org/zap"

    "github.com/im-kulikov/helium/internal"
    "github.com/im-kulikov/helium/service"
)

type (
    // Listener service interface.
    Listener interface {
        ListenAndServe() error
        Shutdown(context.Context) error
    }

    listener struct {
        logger *zap.Logger

        name         string
        skipErrors   bool
        ignoreErrors []error
        server       Listener
    }

    // ListenerOption options that allows to change
    // default settings for Listener service.
    ListenerOption func(l *listener)
)

// ErrEmptyListener is raised when an empty Listener
// used in the NewListener function or Listener methods.
const ErrEmptyListener = internal.Error("empty listener")

// ListenerSkipErrors allows for ignoring all raised errors.
func ListenerSkipErrors() ListenerOption {
    return func(l *listener) {
        l.skipErrors = true
    }
}

// ListenerIgnoreError allows for ignoring all passed errors.
func ListenerIgnoreError(errors ...error) ListenerOption {
    return func(l *listener) {
        l.ignoreErrors = errors
    }
}

// ListenerName allows changing the default listener name.
func ListenerName(v string) ListenerOption {
    return func(l *listener) {
        l.name = v
    }
}

// ListenerWithLogger allows to set custom zap.Logger.
func ListenerWithLogger(log *zap.Logger) ListenerOption {
    return func(l *listener) {
        if log == nil {
            return
        }

        l.logger = log
    }
}

// NewListener creates new Listener service and applies passed options to it.
func NewListener(lis Listener, opts ...ListenerOption) (service.Service, error) {
    if lis == nil {
        return nil, ErrEmptyListener
    }

    s := &listener{
        server:     lis,
        skipErrors: false,

        // Default name
        name: fmt.Sprintf("listener %T", lis),
    }

    for i := range opts {
        opts[i](s)
    }

    return s, nil
}

// Name returns name of the service.
func (l *listener) Name() string { return l.name }

// Start tries to start the Listener and returns an error
// if the Listener is empty. If something went wrong
// returns an error.
func (l *listener) Start(context.Context) error {
    if l.server == nil {
        return l.catch(ErrEmptyListener)
    }

    return l.catch(l.server.ListenAndServe())
}

// Stop tries to stop the Listener and returns an error
// if something went wrong. Ignores errors that were passed
// by options and if used skip errors.
func (l *listener) Stop(ctx context.Context) {
    if l.server == nil {
        l.logger.Error(ErrEmptyListener.Error(),
            zap.String("name", l.name))

        return
    }

    if err := l.catch(l.server.Shutdown(ctx)); err != nil {
        l.logger.Error("could not stop listener",
            zap.String("name", l.name),
            zap.Error(err))
    }
}

func (l *listener) catch(err error) error {
    if l.skipErrors {
        return nil
    }

    for i := range l.ignoreErrors {
        if errors.Is(err, l.ignoreErrors[i]) {
            return nil
        }
    }

    return err
}