Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Version #2

Merged
merged 5 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .assets/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .assets/banner.psd
Binary file not shown.
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: release
on:
- push
jobs:
release:
uses: blugnu/.reusable/.github/workflows/[email protected]
secrets: inherit
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# IDE settings and macOS Finder
# (with some exclusions)
.DS_Store
.vscode
.idea

# Binaries for programs and plugins
*.exe
*.exe~
Expand All @@ -14,8 +17,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
3 changes: 3 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
version: 2
builds:
- skip: true
6 changes: 6 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"MD013": { "line_length": 100 },
"MD014": false, // allow $ prompts in backtick blocks (shell command examples)
"MD033": false, // allow inline html
"MD041": false // allow non heading first line
}
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 blugnu
Copyright (c) 2024 blugnu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -14,7 +14,7 @@ copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
Expand Down
114 changes: 112 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,112 @@
# env
Assists with creating strongly-typed environment variables.
<div align="center" style="margin-bottom:20px">
<img src=".assets/banner.png" alt="env" />
<!-- <hr> -->
<div align="center">
<h3>streamline and simplify the way you work with environment variables</h3>
</div>
<hr>
<div align="center">
<a href="https://github.com/blugnu/env/actions/workflows/release.yml">
<img alt="build-status" src="https://github.com/blugnu/env/actions/workflows/release.yml/badge.svg"/>
</a>
<a href="https://goreportcard.com/report/github.com/blugnu/env" >
<img alt="go report" src="https://goreportcard.com/badge/github.com/blugnu/env"/>
</a>
<a>
<img alt="go version >= 1.14" src="https://img.shields.io/github/go-mod/go-version/blugnu/env?style=flat-square"/>
</a>
<a href="https://github.com/blugnu/env/blob/master/LICENSE">
<img alt="MIT License" src="https://img.shields.io/github/license/blugnu/env?color=%234275f5&style=flat-square"/>
</a>
<a href="https://coveralls.io/github/blugnu/env?branch=master">
<img alt="coverage" src="https://img.shields.io/coveralls/github/blugnu/env?style=flat-square"/>
</a>
<a href="https://pkg.go.dev/github.com/blugnu/env">
<img alt="docs" src="https://pkg.go.dev/badge/github.com/blugnu/env"/>
</a>
</div>
</div>

## Features

- [ ] **.env File Support**: Load variables from a `.env` file and/or any other file(s)
- [ ] **Type Conversions**: Easily convert environment variables to Go types
- [ ] **Validation**: Check common configuration errors (e.g. `as.PortNo` to enforce 0 <= X <= 65535)
- [ ] **Testing**: Convenient testing utilities

## Installation

```bash
go get github.com/blugnu/env
```

## Example Usage

### Override Default Configuration

Demonstrates the use of the `env.Override` function to replace a default
configuration value with a value parsed from an environment variable:

```go
port := 8080
if _, err := env.Override(&port, "SERVICE_PORT", as.PortNo); err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil))
```

### Parse a Required Configuration Value

Demonstrates the use of the `env.Parse` function to parse a required
configuration value from an environment variable:

```go
authURL, err := env.Parse("AUTH_SERVICE_URL", as.AbsoluteURL)
if err != nil {
log.Fatal(err)
}
```

### Load Configuration from a File

Demonstrates the use of the `env.Load` function to load configuration:

> by default, with no filename(s) specified, the `Load()` function
> loads configuration from a `.env` file.

```go
if err := env.Load(); err != nil {
log.Fatal(err)
}
```

### Preserve Environment Variables in a Test

Demonstrates the use of `defer env.State().Reset()` to preserve environment
variables during a test:

```go
func TestSomething(t *testing.T) {
// ARRANGE
defer env.State().Reset()
env.Vars{
"SOME_VAR": "some value",
"ANOTHER_VAR": "another value",
}.Set()

// ACT
SomeFuncUsingEnvVars()

// ASSERT
...
}
```

## Contributing

Contributions are welcome! Please feel free to submit a pull request.

## License

This project is licensed under the MIT License - see the [LICENSE file](LICENSE)
for details.
157 changes: 157 additions & 0 deletions as/as_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package as

import (
"errors"
"net/url"
"strconv"
"testing"
"time"

"github.com/blugnu/env"
"github.com/blugnu/test"
)

func TestDuration(t *testing.T) {
// ARRANGE
var sut = "1s"

// ACT
result, err := Duration(sut)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals(time.Second)
}

func TestDuration_WithSpecifiedUnit(t *testing.T) {
// ARRANGE
var sut = "10"

// ACT
result, err := Duration(sut, time.Second)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals(10 * time.Second)
}

func TestDuration_DurationWithSpecifiedUnit(t *testing.T) {
// ARRANGE
var sut = "1h"

// ACT
result, err := Duration(sut, time.Second)

// ASSERT
test.Error(t, err).Is(strconv.ErrSyntax)
test.That(t, result).Equals(0)
}

func TestInt(t *testing.T) {
// ARRANGE
var sut = "123"

// ACT
result, err := Int(sut)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals(123)
}

func TestInt_WhenConversionFails(t *testing.T) {
// ARRANGE
var sut = "not-a-number"

// ACT
result, err := Int(sut)

// ASSERT
test.Error(t, err).Is(strconv.ErrSyntax)
test.That(t, result).Equals(0)
}

func TestPortNo(t *testing.T) {
// ARRANGE
var sut = "123"

// ACT
result, err := PortNo(sut)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals(123)
}

func TestPortNo_WhenConversionFails(t *testing.T) {
// ARRANGE
var sut = "not-a-number"

// ACT
result, err := PortNo(sut)

// ASSERT
test.Error(t, err).Is(strconv.ErrSyntax)
test.That(t, result).Equals(0)
}

func TestPortNo_WhenOutOfRange(t *testing.T) {
// ARRANGE
var sut = "65536"

// ACT
result, err := PortNo(sut)

// ASSERT
test.Error(t, err).Is(env.RangeError[int]{Min: 0, Max: 65535})
test.That(t, result).Equals(0)
}

func TestString(t *testing.T) {
// ARRANGE
var sut = "value"

// ACT
result, err := String(sut)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals("value")
}

func TestAbsoluteURL(t *testing.T) {
// ARRANGE
var sut = "http://example.com"

// ACT
result, err := AbsoluteURL(sut)

// ASSERT
test.That(t, err).IsNil()
test.That(t, result).Equals(&url.URL{Scheme: "http", Host: "example.com"})
}

func TestAbsoluteURL_WhenNotAValidURI(t *testing.T) {
// ARRANGE
converr := errors.New("url error")
defer test.Using(&urlParse, func(string) (*url.URL, error) { return nil, converr })()

// ACT
result, err := AbsoluteURL("any")

// ASSERT
test.Error(t, err).Is(converr)
test.That(t, result).IsNil()
}

func TestAbsoluteURL_WhenNotAnAbsoluteURL(t *testing.T) {
// ARRANGE
var sut = "/path"

// ACT
result, err := AbsoluteURL(sut)

// ASSERT
test.Error(t, err).Is(ErrNotAnAbsoluteURL)
test.That(t, result).IsNil()
}
48 changes: 48 additions & 0 deletions as/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package as

import (
"time"
)

// Duration parses a string into a time.Duration. If a unit is provided, the string is first
// converted to an integer and then multiplied by the unit.
//
// Attempting to convert a duration string using a specified unit will fail due to the
// duration string contain non-numeric characters.
//
// # parameters
//
// s string // the string to convert
//
// u ...time.Duration // the unit to multiply the duration by; if no unit is provided
// // the string is parsed as a duration. If multiple units are
// // provided, only the first is used
//
// # returns
//
// time.Duration // the converted value
//
// error // any error that occurs during conversion
//
// # example: parse a duration string
//
// d, err := as.Duration("1h30m")
//
// # example: parse a duration string with a unit
//
// d, err := as.Duration("1", time.Hour)
//
// # example: parse a duration string with a unit
//
// // this will fail, returning an integer conversion error
// d, err := as.Duration("1h", time.Hour)
func Duration(s string, u ...time.Duration) (time.Duration, error) {
if len(u) == 0 {
return time.ParseDuration(s)
}
i, err := Int(s)
if err != nil {
return 0, err
}
return time.Duration(i) * u[0], nil
}
7 changes: 7 additions & 0 deletions as/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package as

import "errors"

var (
ErrNotAnAbsoluteURL = errors.New("not an absolute URI")
)
8 changes: 8 additions & 0 deletions as/funcs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package as

import "net/url"

// function variables to facilitate testing
var (
urlParse = url.Parse
)
Loading
Loading