Introducing ezpkg.io: A Collection of Packages to Make Writing Go Code Easier | HackerNoon

As I work on various Go projects, I often find myself creating utility functions, extending existing packages, or developing packages to solve specific problems. Moving from one project to another, I usually have to copy or rewrite these solutions. So, I created ezpkg.io to have all these utilities and packages in one place. Hopefully, you’ll find them useful as well.

Let’s look at some problems that these packages are solving.


There Are Many Functions That Always Return Nil Errors, but We Still Need to Check.

For example, let’s take a look at this function using strings.Builderfrom the stdlib:

import "fmt"
import "strings"

func SliceToString1[T any](slice []T) string {
    var b strings.Builder
    for _, v := range slice {
        _, err := fmt.Fprint(&b, v)
        if err != nil {
            panic(err)
        }
    }
    return b.String()
)
func SliceToString2[T any](slice []T) (string, error) {
    var b strings.Builder
    for _, v := range slice {
        _, err := fmt.Fprint(&b, v)
        if err != nil {
            return err
        }
    }
    return b.String()
)
func SliceToString3[T any](slice []T) string {
    var b strings.Builder
    for _, v := range slice {
        _, _ = fmt.Fprint(&b, v) //nolint:errcheck
    }
    return b.String()
)
  • In SliceToString1, we add a panic check on the string despite the fact that strings.Builder will always return a nil error.
  • In SliceToString2, we correctly handle the returned error by making the caller worry about checking that never-happen error too!
  • In SliceToString3, we skip the check because they are nil anyway, but we still need to add _, _ = to make the IDE happy and //nolint:errcheck because our company blocks merging any PR that does not pass the golint CI check.

In another way, we could create our utility functions to simplify the code, and then copy or write those fprint and must from package to package and project to project:

func SliceToString4[T any](slice []T) string {
    var b strings.Builder
    for _, v := range slice {
        fprint(&b, v)
    }
    return b.String()
}
func fprint(w io.Writer, v any) {
    must(fmt.Fprint(w, v))
}
func must[T any](v T, err error) T {
    if err != nil {
        panic(err)
    }
    return v
}

Import “ezpkg.io/stringz

Here is how the example is rewritten with ezpkg.io/stringz using stringz.Builder:

import "ezpkg.io/stringz"

func SliceToString[T any](slice []T) string {
    var b stringz.Builder      // change to stringz
    for _, v := range slice {
        b.Print(v)             // look ma, no error!🔥
    }
    return b.String()
}

Other examples are bytez.Buffer and fmtz.State. They share the same interface and include various methods that are ready to use. Let’s look at WriteString and its sister WriteStringZ:

package stringz // import "ezpkg.io/stringz"

type Builder strings.Builder

func (b *Builder) unwrap() *strings.Builder {
    return (*strings.Builder)(b)
}
func (b *Builder) WriteString(s string) (int, error) {
    return b.unwrap().WriteString(s)
}
func (b *Builder) WriteStringZ(s string) int {
    n, _ := b.unwrap().WriteString(s)
    return n
}

The WriteString method exposes the original method and keeps the same signature, while the WriteStringZ variant eliminates the need for handling errors. Writing Go code is eazier now!🥰


Sometimes, We Just Want to Skip All the Errors to Quickly Write a Simple CLI Script

By using typez.CoalesceXerrorz.Musterrorz.Skiperrorz.Validate, and their variants:

import "ezpkg.io/errorz"

func main() {
    var err error
    projectDir := os.Getenv("PROJECT_DIR")
    errorz.Validatef(&err, projectDir != "", "no PROJECT_DIR")
    errorz.Validatef(&err, len(os.Args) > 1, "must at least 1 arg")

    // panic if any validation fails
    errorz.MustZ(err)

    // get the file path
    fileName := os.Args[1] // already check: len(os.Args)>1

    // panic if the file extension is not .json
    errorz.MustValidate(strings.HasSuffix(fileName, ".json"))

    // read the file, skip error if it does not exist
    data := errorz.Skip(os.ReadFile(fileName))

    // default to empty json object
    data = typez.CoalesceX(data, []byte("{}"))

    // process then print the formatted json
    object := errorz.Must(process(data))
    fmt.Print(errorz.Must(json.MarshalIndent(object, "", "t")))
}

Comparing Values in Tests With Diff That Can Ignore Spaces

Another day, we are making some changes to a SQL repository method using gorm.io/gorm and gomock. The test code looks like this:

var updateSQL = `UPDATE "company_channels"
SET "updated_at"=$1,"access_token"=$2
WHERE ("company_id" = $3 AND "channel_code" = $4 AND "channel_type" = $5) AND "company_channels"."deleted_at" IS NULL`

dbCtrl.SQLMock.ExpectExec(regexp.QuoteMeta(updateSQL)).
    WithArgs(
        sqlmock.AnyArg(),
        companyChannel.AccessToken,
        companyChannel.CompanyID,
        companyChannel.ChannelCode,
        companyChannel.ChannelType,
    ).WillReturnResult(sqlmock.NewResult(0, 1))

And we are getting this error, which is hard to read and see what is wrong:


Import “ezpkg.io/diffz,” and Rewrite the Assertion Function:

diffz.IgnoreSpace().DiffByChar(actualSQL, expectedSQL)

We can get a cleaner output and quickly spot the difference:

There Is Support for Tests With Random Values

With diffz.Placeholder().AndIgnoreSpaces().DiffByLine() or simply diffz.ByLineZ():

expect := `
color:
  id: ████████-████-████-████-████████████
  name: red
  code: #ff0000`
red := `
color:
  id: d56d5f0d-f05d-4d46-9ce2-af6396d25c55
  name: red
  code: #ff0000`
green := `
color:
  id: 5b01ec0b-0607-446e-8a25-aaef595902a9
  name: green
  code: #00ff00`

fmt.Println("no diff")
fmt.Println(diffz.ByLineZ(red, expect))

fmt.Println("diff")
fmt.Println(diffz.ByLineZ(red, green))

The first diffz.ByLineZ(red, expect) will be considered equal, because of the use of a placeholder . The second diffz.ByLineZ(red, green) will output:


ezpkg.io Is Still in Its Early Stage

Most packages are usable but the API may change over time. There are a lot of missing utilities that I will add sooner or later. If you need something, feel free to open an issue or post a new discussion. I’m happy to see that it’s useful for you! 👋

Author

I’m Oliver Nguyen. A software maker working mostly in Go and JavaScript. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects. Share knowledge and thoughts during my journey.