Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

github.com/serhiy-t/errf

Package Overview
Dependencies
Alerts
File Explorer
Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

github.com/serhiy-t/errf

  • v0.4.0
  • Source
  • Go
  • Socket score

Version published
Created
Source

Build Status Go Report Card Documentation

ErrorFlow

Declarative error handling for Go.

Motivation

Reading list:

ErrorFlow goal is to provide a library solution to the issues raised in articles above.

Library solution (as opposed to a language change), although less clean, has a very important benefit: it is optional. Many language proposals for addressing this issue have been rejected because language change is required to be universally applicable. Library solution can be used only for use cases where it works well.

Features

  • 'err'-variable-free type-safe branchless business logic
reader := errf.Io.CheckReadCloser(os.Open(srcFilename))
	/* vs */
reader, err := os.Open(srcFilename)
if err != nil {
	return err
}
  • Declarative multiple errors handling logic
defer errf.IfError().ReturnFirst().ThenAssignTo(&err)
defer errf.CheckDeferErr(writer.Close)
	/* vs */
defer func() {
	closeErr := writer.Close()
	if closeErr != nil and err == nil {
		err = closeErr
	}
}()
  • Declarative errors logging logic
defer errf.IfError().LogIfSuppressed().ThenAssignTo(&err)
defer errf.CheckDeferErr(writer.Close)
	/* vs */
defer func() {
	closeErr := writer.Close()
	if closeErr != nil {
		if err == nil {
			err = closeErr
		} else {
			log.Printf("error closing writer: %w", err)
		}
	}
}()
  • Doesn't affect APIs
    • Every use of ErrorFlow is scoped to a single function and doesn't leak into its API
  • Extendable
    • Custom return types for type safety
    • Custom ErrorFlow config functions (e.g. creating a wrapper that converts errors from a third-party libraries into standard error types for an internal codebase)

Example: error handling for a file gzip function

Error handling requirements for function:

  • Returns error only in case of error that affects result file correctness.
  • Cleans up dst file in case of the error instead of leaving it in inconsistent state.
  • Logs all internal errors that it didn't return.
  • Wraps returned errors with "error compressing file: " prefix.
  • Performs input parameters validation.

ErrorFlow style error handling

func GzipFile(dstFilename string, srcFilename string) (err error) {
	// defer IfError()... creates and configures
	// ErrorFlow error handler for this function.
	// When any of Check* functions encounters non-nil error
	// it immediately sends error to this handler
	// unwinding all stacked defers.
	errWrapper := errf.WrapperFmtErrorw("error compressing file")
	defer errf.IfError().ReturnFirst().LogIfSuppressed().Apply(errWrapper).ThenAssignTo(&err)

	errf.CheckAssert(len(dstFilename) > 0, "dst file should be specified")
	errf.CheckAssert(len(srcFilename) > 0, "src file should be specified")

	reader := errf.Io.CheckReadCloser(os.Open(srcFilename))
	defer errf.With(errWrapper).LogDefer(reader.Close)

	writer := errf.Io.CheckWriteCloser(os.Create(dstFilename))
	defer errf.Handle().OnAnyErrOrPanic(func() { os.Remove(dstFilename) })
	defer errf.CheckDeferErr(writer.Close)

	gzipWriter := gzip.NewWriter(writer)
	defer errf.CheckDeferErr(gzipWriter.Close)

	return errf.CheckDiscard(io.Copy(gzipWriter, reader)).IfOkReturnNil
}

Compare with

Plain Go implementation without any error handling
func GzipFile(dstFilename string, srcFilename string) error {
	reader, _ := os.Open(srcFilename)
	defer reader.Close()

	writer, _ := os.Create(dstFilename)
	defer writer.Close()

	gzipWriter := gzip.NewWriter(writer)
	defer gzipWriter.Close()

	_, _ = io.Copy(gzipWriter, reader)

	return nil
}
Plain Go implementation (functionally roughly equivalent to ErrorFlow example above; not using helper functions)
func GzipFile(dstFilename string, srcFilename string) (err error) {
	if len(dstFilename) == 0 {
		return fmt.Errorf("error compressing file: dst file should be specified")
	}
	if len(srcFilename) == 0 {
		return fmt.Errorf("error compressing file: src file should be specified")
	}

	reader, err := os.Open(srcFilename)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}
	defer func() {
		closeErr := reader.Close()
		if closeErr != nil {
			log.Println(closeErr)
		}
	}()

	writer, err := os.Create(dstFilename)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}
	defer func() {
		if err != nil {
			os.Remove(dstFilename)
		}
	}()
	defer func() {
		closeErr := writer.Close()
		if closeErr != nil {
			if err == nil {
				err = fmt.Errorf("error compressing file: %w", closeErr)
			} else {
				log.Println(fmt.Errorf("[suppressed] error compressing file: %w", closeErr))
			}
		}
	}()

	gzipWriter := gzip.NewWriter(writer)
	defer func() {
		closeErr := gzipWriter.Close()
		if closeErr != nil {
			if err == nil {
				err = fmt.Errorf("error compressing file: %w", closeErr)
			} else {
				log.Println(fmt.Errorf("[suppressed] error compressing file: %w", closeErr))
			}
		}
	}()

	_, err = io.Copy(gzipWriter, reader)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}

	return nil
}
ErrorFlow-Lite style error handling (using only defer helper functions, but not IfError/Check* handler)
func GzipFile(dstFilename string, srcFilename string) (err error) {
	errflow := errf.With(
		errf.LogStrategyIfSuppressed,
		errf.WrapperFmtErrorw("error compressing file"),
	)

	if len(dstFilename) == 0 {
		return fmt.Errorf("error compressing file: dst file should be specified")
	}
	if len(srcFilename) == 0 {
		return fmt.Errorf("error compressing file: src file should be specified")
	}

	reader, err := os.Open(srcFilename)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}
	defer errflow.LogDefer(reader.Close)

	writer, err := os.Create(dstFilename)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}
	defer func() {
		if err != nil {
			os.Remove(dstFilename)
		}
	}()
	defer errflow.IfErrorAssignTo(&err, writer.Close)

	gzipWriter := gzip.NewWriter(writer)
	defer errflow.IfErrorAssignTo(&err, gzipWriter.Close)

	_, err = io.Copy(gzipWriter, reader)
	if err != nil {
		return fmt.Errorf("error compressing file: %w", err)
	}

	return nil
}

FAQs

Package last updated on 28 Mar 2021

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc