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

See also #55

Merged
merged 5 commits into from
Mar 11, 2023
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ The format is based on [Keep a Changelog], and this project adheres to
[keep a changelog]: https://keepachangelog.com/en/1.0.0/
[semantic versioning]: https://semver.org/spec/v2.0.0.html

### Unreleased

### Added

- Added `SeeAlso()` method to all builders, which links to another variable for documentation purposes

## [0.4.2] - 2023-03-11

### Added
Expand Down
6 changes: 6 additions & 0 deletions bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ func (b *BoolBuilder[T]) WithDefault(v T) *BoolBuilder[T] {
return b
}

// SeeAlso creates a relationship between this variable and those used by i.
func (b *BoolBuilder[T]) SeeAlso(i Input, options ...SeeAlsoOption) *BoolBuilder[T] {
seeAlsoInput(&b.spec, i, options...)
return b
}

// Required completes the build process and registers a required variable with
// Ferrite's validation system.
func (b *BoolBuilder[T]) Required(options ...RequiredOption) Required[T] {
Expand Down
9 changes: 3 additions & 6 deletions builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ func undefinedError(v variable.Any) error {
}

// isBuilderOf makes a static assertion that B meats
type isBuilderOf[T any, B builderOf[T]] struct{}

// builderOf is an interface and type constriant common to all builders that
// produce a value of type T.
type builderOf[T any] interface {
type isBuilderOf[T any, B interface {
SeeAlso(input Input, options ...SeeAlsoOption) B
Required(options ...RequiredOption) Required[T]
Optional(options ...OptionalOption) Optional[T]
Deprecated(options ...DeprecatedOption) Deprecated[T]
}
}] struct{}
6 changes: 6 additions & 0 deletions duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func (b *DurationBuilder) WithMaximum(v time.Duration) *DurationBuilder {
return b
}

// SeeAlso creates a relationship between this variable and those used by i.
func (b *DurationBuilder) SeeAlso(i Input, options ...SeeAlsoOption) *DurationBuilder {
seeAlsoInput(&b.spec, i, options...)
return b
}

// Required completes the build process and registers a required variable with
// Ferrite's validation system.
func (b *DurationBuilder) Required(options ...RequiredOption) Required[time.Duration] {
Expand Down
6 changes: 6 additions & 0 deletions enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ func (b *EnumBuilder[T]) WithDefault(v T) *EnumBuilder[T] {
return b
}

// SeeAlso creates a relationship between this variable and those used by i.
func (b *EnumBuilder[T]) SeeAlso(i Input, options ...SeeAlsoOption) *EnumBuilder[T] {
seeAlsoInput(&b.spec, i, options...)
return b
}

// Required completes the build process and registers a required variable with
// Ferrite's validation system.
func (b *EnumBuilder[T]) Required(options ...RequiredOption) Required[T] {
Expand Down
6 changes: 6 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ func (b *FileBuilder) WithDefault(v string) *FileBuilder {
return b
}

// SeeAlso creates a relationship between this variable and those used by i.
func (b *FileBuilder) SeeAlso(i Input, options ...SeeAlsoOption) *FileBuilder {
seeAlsoInput(&b.spec, i, options...)
return b
}

// Required completes the build process and registers a required variable with
// Ferrite's validation system.
func (b *FileBuilder) Required(options ...RequiredOption) Required[FileName] {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/kr/pretty v0.1.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
8 changes: 7 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmalloc/gomegax v0.0.0-20200507221434-64fca4c0e03a h1:Gk7Gkwl1KUJII/FiAjvBjRgEz/lpvTV8kNYp+9jdpuk=
github.com/jmalloc/gomegax v0.0.0-20200507221434-64fca4c0e03a/go.mod h1:TZpc8ObQEKqTuy1/VXpPRfcMU80QFDU4zK3nchXts/k=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down Expand Up @@ -75,8 +80,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
Expand Down
15 changes: 15 additions & 0 deletions input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ferrite

import "github.com/dogmatiq/ferrite/variable"

// An Input is the application-facing interface for obtaining a value from
// environment variables.
//
// Typically each input is sourced from exactly one environment variable,
// however it is possible that a value collates values from multiple variables.
//
// An input is created using one of the various builder-functions, for example
// String().
type Input interface {
variables() []variable.Any
}
29 changes: 18 additions & 11 deletions builderdeprecated.go → inputdeprecated.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import (
"github.com/dogmatiq/ferrite/variable"
)

// Deprecated is the application-facing interface for a value that is sourced
// Deprecated is a specialization of the Input interface for values obtained
// from deprecated environment variables.
//
// It is obtained by calling Deprecated() on a variable builder.
type Deprecated[T any] interface {
// DeprecatedValue returns the parsed and validated value of the environment
// variable, if it is defined.
Input

// DeprecatedValue returns the parsed and validated value built from the
// environment variable(s).
//
// If the environment variable is not defined (and there is no default
// value), ok is false; otherwise, ok is true and v is the value.
// If the constituent environment variable(s) are not defined and there is
// no default value, ok is false; otherwise, ok is true and v is the value.
//
// It panics if the environment variable is defined but invalid.
// It panics if any of one of the constituent environment variable(s) has an
// invalid value.
DeprecatedValue() (T, bool)
}

Expand All @@ -41,6 +42,7 @@ func deprecated[T any, S variable.TypedSchema[T]](

// interface is currently empty so we don't need an implementation
return deprecatedFunc[T]{
[]variable.Any{v},
func() (T, bool, error) {
return v.NativeValue()
},
Expand All @@ -50,13 +52,18 @@ func deprecated[T any, S variable.TypedSchema[T]](
// deprecatedFunc is an implementation of Deprecated[T] that obtains the value
// from an arbitrary function.
type deprecatedFunc[T any] struct {
fn func() (T, bool, error)
vars []variable.Any
fn func() (T, bool, error)
}

func (d deprecatedFunc[T]) DeprecatedValue() (T, bool) {
n, ok, err := d.fn()
func (i deprecatedFunc[T]) DeprecatedValue() (T, bool) {
n, ok, err := i.fn()
if err != nil {
panic(err.Error())
}
return n, ok
}

func (i deprecatedFunc[T]) variables() []variable.Any {
return i.vars
}
35 changes: 21 additions & 14 deletions builderoptional.go → inputoptional.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import (
"github.com/dogmatiq/ferrite/variable"
)

// Optional is the application-facing interface for a value that is sourced
// from optional environment variables.
//
// It is obtained by calling Deprecated() on a variable builder.
// Optional is a specialization of the Input interface for values obtained
// from deprecated environment variables.
type Optional[T any] interface {
// Value returns the parsed and validated value of the environment variable,
// if it is defined.
Input

// Value returns the parsed and validated value built from the environment
// variable(s).
//
// If the environment variable is not defined (and there is no default
// value), ok is false; otherwise, ok is true and v is the value.
// If the constituent environment variable(s) are not defined and there is
// no default value, ok is false; otherwise, ok is true and v is the value.
//
// It panics if the environment variable is defined but invalid.
// It panics if any of one of the constituent environment variable(s) has an
// invalid value.
Value() (T, bool)
}

Expand All @@ -37,22 +38,28 @@ func optional[T any, S variable.TypedSchema[T]](
)

return optionalFunc[T]{
[]variable.Any{v},
func() (T, bool, error) {
return v.NativeValue()
},
}
}

// optionalFunc is an implementation of Optional[T] that obtains the value from an
// arbitrary function.
// optionalFunc is an implementation of Optional[T] that obtains the value from
// an arbitrary function.
type optionalFunc[T any] struct {
fn func() (T, bool, error)
vars []variable.Any
fn func() (T, bool, error)
}

func (d optionalFunc[T]) Value() (T, bool) {
n, ok, err := d.fn()
func (i optionalFunc[T]) Value() (T, bool) {
n, ok, err := i.fn()
if err != nil {
panic(err.Error())
}
return n, ok
}

func (i optionalFunc[T]) variables() []variable.Any {
return i.vars
}
27 changes: 17 additions & 10 deletions builderrequired.go → inputrequired.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import (
"github.com/dogmatiq/ferrite/variable"
)

// Required is the application-facing interface for a value that is sourced
// from required environment variables.
//
// It is obtained by calling Deprecated() on a variable builder.
// Required is a specialization of the Input interface for values obtained
// from required (mandatory) environment variables.
type Required[T any] interface {
Input

// Value returns the parsed and validated value of the environment variable.
//
// It panics if the environment variable is undefined or invalid.
// It panics if any of one of the constituent environment variable(s) is
// undefined or has an invalid value.
Value() T
}

Expand All @@ -35,6 +36,7 @@ func required[T any, S variable.TypedSchema[T]](
)

return requiredFunc[T]{
[]variable.Any{v},
func() (T, error) {
n, ok, err := v.NativeValue()
if ok || err != nil {
Expand All @@ -45,16 +47,21 @@ func required[T any, S variable.TypedSchema[T]](
}
}

// requiredFunc is an implementation of Required[T] that obtains the value
// from an arbitrary function.
// requiredFunc is an implementation of Required[T] that obtains the value from
// an arbitrary function.
type requiredFunc[T any] struct {
fn func() (T, error)
vars []variable.Any
fn func() (T, error)
}

func (d requiredFunc[T]) Value() T {
n, err := d.fn()
func (i requiredFunc[T]) Value() T {
n, err := i.fn()
if err != nil {
panic(err.Error())
}
return n
}

func (i requiredFunc[T]) variables() []variable.Any {
return i.vars
}
59 changes: 59 additions & 0 deletions internal/mode/usage/markdown/complete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package markdown_test

import (
"github.com/dogmatiq/ferrite"
"github.com/dogmatiq/ferrite/variable"
. "github.com/onsi/ginkgo/v2"
)

var _ = DescribeTable(
"func Run()",
tableTest("complete"),
Entry(
"no variables",
"empty.md",
func(reg *variable.Registry) {},
),
Entry(
"non-normative examples",
"non-normative.md",
func(reg *variable.Registry) {
ferrite.
String("READ_DSN", "database connection string for read-models").
Required(variable.WithRegistry(reg))
},
),
Entry(
"platform examples",
"platform-examples.md",
func(reg *variable.Registry) {
ferrite.
Bool("DEBUG", "enable or disable debugging features").
Optional(variable.WithRegistry(reg))
},
),
Entry(
"platform examples use default values as examples when available",
"platform-examples-use-defaults.md",
func(reg *variable.Registry) {
ferrite.
NetworkPort("PORT", "an environment variable that has a default value").
WithDefault("ftp").
Required(variable.WithRegistry(reg))
},
),
Entry(
"see also",
"see-also.md",
func(reg *variable.Registry) {
verbose := ferrite.
Bool("VERBOSE", "enable verbose logging").
Optional(variable.WithRegistry(reg))

ferrite.
Bool("DEBUG", "enable or disable debugging features").
SeeAlso(verbose).
Optional(variable.WithRegistry(reg))
},
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package markdown

import "github.com/dogmatiq/ferrite/variable"

func (r *renderer) renderUsage() {
func (r *renderer) renderPlatformExamples() {
r.line("## Usage Examples")
r.gap()
r.renderKubernetesUsage()
r.renderKubernetesExample()
r.gap()
r.renderDockerUsage()
r.renderDockerExample()
}

func (r *renderer) renderKubernetesUsage() {
func (r *renderer) renderKubernetesExample() {
r.line("<details>")
r.line("<summary>Kubernetes</summary>")

Expand Down Expand Up @@ -82,7 +82,7 @@ func (r *renderer) renderKubernetesUsage() {
r.line("</details>")
}

func (r *renderer) renderDockerUsage() {
func (r *renderer) renderDockerExample() {
r.line("<details>")
r.line("<summary>Docker</summary>")
r.gap()
Expand Down
Loading