/go-core

My core stuff with types, code, etc that I use almost everywhere

Primary LanguageGoMIT LicenseMIT

go-core

My core stuff with types, code, etc that I use almost everywhere

Slice generics

Contains

You can check if a slice contains a value with Contains method:

slice := []int{1, 2, 3, 4, 5}

fmt.Println(core.Contains(slice, 4)) // true
fmt.Println(core.Contains(slice, 6)) // false

Contains works with all comparable types.

If the slice type is more complex, you can use ContainsWithFunc method:

type User struct {
  ID int
  Name string
}

slice := []User{
  {ID: 1, Name: "John"},
  {ID: 2, Name: "Jane"},
  {ID: 3, Name: "Bob"},
}

fmt.Println(core.ContainsWithFunc(slice, User{Name: "John"}, func(a, b User) bool {
  return a.Name == b.Name
})) // true

EqualSlices

You can check if two slices are equal with EqualSlices method:

slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{1, 2, 3, 4, 5}

fmt.Println(core.EqualSlices(slice1, slice2)) // true

EqualSlices works with all comparable types.

If the slice type is more complex, you can use EqualSlicesWithFunc method:

type User struct {
  ID int
  Name string
}

slice1 := []User{
  {ID: 1, Name: "John"},
  {ID: 2, Name: "Jane"},
  {ID: 3, Name: "Bob"},
}

slice2 := []User{
  {ID: 1, Name: "John"},
  {ID: 2, Name: "Jane"},
  {ID: 3, Name: "Bob"},
}

fmt.Println(core.EqualSlicesWithFunc(slice1, slice2, func(a, b User) bool {
  return a.Name == b.Name
})) // true

Filter

You can filter a slice with Filter method:

slice := []int{1, 2, 3, 4, 5}

fmt.Println(core.Filter(slice, func(element int) bool {
  return element > 3
})) // [4 5]

Join

You can join a slice with Join method:

slice := []int{1, 2, 3, 4, 5}

fmt.Println(core.Join(slice, ",")) // 1,2,3,4,5

If the slice type is more complex, you can use JoinWithFunc method:

type User struct {
  ID int
  Name string
}

slice := []User{
  {ID: 1, Name: "John"},
  {ID: 2, Name: "Jane"},
  {ID: 3, Name: "Bob"},
}

fmt.Println(core.JoinWithFunc(slice, ",", func(element User) string {
  return element.Name
})) // John,Jane,Bob

Map

You can map a slice with Map method:

slice := []int{1, 2, 3, 4, 5}

fmt.Println(core.Map(slice, func(element int) int {
  return element * 2
})) // [2 4 6 8 10]

The returned slice is a new slice, the original slice is not modified.

Reduce

You can reduce a slice with Reduce method:

slice := []int{1, 2, 3, 4, 5}

fmt.Println(core.Reduce(slice, func(accumulator, element int) int {
  return accumulator + element
})) // 15

Sort

You can sort a slice with Sort method:

slice := []int{5, 2, 3, 1, 4}

fmt.Println(core.Sort(slice, func(a, b int) {
  return a < b
})) // [1 2 3 4 5]

The Sort method uses a simple Quick Sort algorithm.

Time and Duration helpers

The core.Time mimics the time.Time and adds JSON serialization support to and from RFC 3339 time strings.

The core.Duration mimics the time.Duration and adds JSON serialization support to and from duration strings. Its core.ParseDuration also understands most of the ISO 8601 duration formats. It marshals to milliseconds. It can unmarshal from milliseconds, GO duration strings, and ISO 8601 duration strings.

Example:

type User struct {
  Name      string
  CreatedAt core.Time
  Duration  core.Duration
}

user := User{
  Name:      "John",
  CreatedAt: core.Now(),
  Duration:  core.Duration(5 * time.Second),
}

data, err := json.Marshal(user)
if err != nil {
  panic(err)
}

fmt.Println(string(data))
// {"Name":"John","CreatedAt":"2021-01-01T00:00:00Z","Duration":"5000"}

string.Replace(string(data), "5000", "PT5S", 1)

var user2 User
err = json.Unmarshal(data, &user2)
if err != nil {
  panic(err)
}
fmt.Println(user2.Duration) // 5s

The core.Timestamp type is an alias for core.Time and it is used to represent timestamps in milliseconds. It marshals into milliseconds and unmarshals from milliseconds (string or integer).

Environment Variable helpers

You can get an environment variable with GetEnvAsX method, where X is one of bool, time.Duration, string, time.Time, url.URL, uuid.UUID, if the environment variable is not set or the conversion fails, the default value is returned.

Example:

// GetEnvAsBool
v := core.GetEnvAsBool("ENV_VAR", false)
// GetEnvAsDuration
v := core.GetEnvAsDuration("ENV_VAR", 5 * time.Second)
// GetEnvAsString
v := core.GetEnvAsString("ENV_VAR", "default")
// GetEnvAsTime
v := core.GetEnvAsTime("ENV_VAR", time.Now())
// GetEnvAsURL
v := core.GetEnvAsURL("ENV_VAR", &url.URL{Scheme: "https", Host: "example.com"})
// GetEnvAsUUID
v := core.GetEnvAsUUID("ENV_VAR", uuid.New())

Notes:

  • GetEnvAsBool returns true if the environment variable is set to true, 1, on or yes, otherwise it returns false. It is also case-insensitive.
  • GetEnvAsDuration accepts any duration string that can be parsed by core.ParseDuration.
  • GetEnvAsTime accepts an RFC 3339 time string.
  • GetEnvAsURL fallback can be a url.URL, a *url.URL, or a string.

Common Interfaces

The core.Identifiable interface is used to represent an object that has an ID in the form of a uuid.UUID.

The core.Nameable interface is used to represent an object that has a name in the form of a string.

The core.IsZeroer interface is used to represent an object that can be checked if it is zero.

The core.GoString interface is used to represent an object that can be converted to a Go string.

HTTP Response helpers

core.RespondWithJSON is a helper function that marshals a payload into an http.ResponseWriter as JSON. It also sets the Content-Type header to application/json.

core.RespondWithHTMLTemplate is a helper function that executes a template on a given data and writes the result into an http.ResponseWriter. It also sets the Content-Type header to text/html.

core.RespondWithError is a helper function that marshals an error into an http.ResponseWriter as JSON. It also sets the Content-Type header to application/json.

Miscelaneous

ExecEvery executes a function every duration:

stop, ping, change := core.ExecEvery(5 * time.Second, func() {
  fmt.Println("ping")
})

time.Sleep(15 * time.Second)
change <- 10 * time.Second
time.Sleep(15 * time.Second)
stop <- true

Notes:

  • stop is a channel that can be used to stop the execution.
  • ping is a channel that can be used to force the execution of the func at any time.
  • change is a channel that can be used to change the execution duration.

FlexInt, FlexInt8, FlexInt16, FlexInt32, FlexInt64 are types that can be unmarshalled from a string or an integer:

type User struct {
  ID core.FlexInt
}

user := User{}
json.Unmarshal([]byte(`{"ID": 1}`), &user)
json.Unmarshal([]byte(`{"ID": "1"}`), &user)

core.Must is a helper function that panics if the error is not nil from a function that returns a value and an error:

func DoSomething() (string, error) {
  return "", errors.New("error")
}

func main() {
  value := core.Must(DoSomething()).(string)
}

core.URL is an alias for url.URL that marshals as a string and unmarshals from a string. When unmarshaling, if the value is nil or empty, the unmarshaled value is nil (it is not considered as an error).

core.UUID is an alias for uuid.UUID that marshals as a string and unmarshals from a string. When unmarshaling, if the value is nil or empty, the unmarshaled value is uuid.Nil (it is not considered as an error).

core.TypeRegistry is a type registry that can be used to unmarshal JSON core.TypeCarrier objects into the correct type:

type User struct {
  ID   uuid.UUID
  Name string
}

func (user User) GetType() string {
  return "user"
}

type Product struct {
  ID   uuid.UUID
  Name string
}

func (product Product) GetType() string {
  return "product"
}

registry := core.NewTypeRegistry()

registry.Add(User{}, Product{})

var user User

err := registry.UnmarshalJSON([]byte(`{"type": "user", "ID": "00000000-0000-0000-0000-000000000000", "Name": "John"}`), &user)
if err != nil {
  panic(err)
}

fmt.Println(user)

Notes:

  • The default JSON property name for the type is type, but it can be changed by adding strings to the UnmarshalJSON method.