-
Notifications
You must be signed in to change notification settings - Fork 1
/
bedrock.go
157 lines (129 loc) · 3.74 KB
/
bedrock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright (c) 2023 Z5Labs and Contributors
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package bedrock
import (
"context"
"errors"
"fmt"
"github.com/z5labs/bedrock/config"
)
// App represents the entry point for user specific code.
type App interface {
Run(context.Context) error
}
// AppBuilder represents anything which can initialize a Runtime.
type AppBuilder[T any] interface {
Build(ctx context.Context, cfg T) (App, error)
}
// AppBuilderFunc is a functional implementation of
// the AppBuilder interface.
type AppBuilderFunc[T any] func(context.Context, T) (App, error)
// Build implements the RuntimeBuilder interface.
func (f AppBuilderFunc[T]) Build(ctx context.Context, cfg T) (App, error) {
return f(ctx, cfg)
}
// PanicError represents a value that was recovered from a panic.
type PanicError struct {
Value any
}
// Error implements the [error] interface.
func (e PanicError) Error() string {
return fmt.Sprintf("recovered from panic: %v", e.Value)
}
// Unwrap implements the interface used by [errors.Unwrap], [errors.Is] and [errors.As].
func (e PanicError) Unwrap() error {
if e.Value == nil {
return nil
}
if err, ok := e.Value.(error); ok {
return err
}
return nil
}
// Recover calls [recover] and if a value is captured it will be wrapped
// into a [PanicError]. The [PanicError] will then be joined with any
// value, err, may reference. The joining is performed using [errors.Join].
func Recover(err *error) {
r := recover()
if r == nil {
return
}
*err = errors.Join(*err, PanicError{
Value: r,
})
}
// Run executes the application. It's responsible for reading the provided
// config sources, unmarshalling them into the generic config type, using
// the config and builder to build the users [App] and, lastly, running the
// returned [App].
func Run[T any](ctx context.Context, builder AppBuilder[T], srcs ...config.Source) (err error) {
defer Recover(&err)
m, err := config.Read(srcs...)
if err != nil {
return ConfigReadError{Cause: err}
}
var cfg T
err = m.Unmarshal(&cfg)
if err != nil {
return ConfigUnmarshalError{Cause: err}
}
app, err := builder.Build(ctx, cfg)
if err != nil {
return AppBuildError{Cause: err}
}
err = app.Run(ctx)
if err != nil {
return AppRunError{Cause: err}
}
return nil
}
// ConfigReadError
type ConfigReadError struct {
Cause error
}
// Error implements the [builtin.error] interface.
func (e ConfigReadError) Error() string {
return fmt.Sprintf("failed to read config source(s): %s", e.Cause)
}
// Unwrap implements the implicit interface used by [errors.Is] and [errors.As].
func (e ConfigReadError) Unwrap() error {
return e.Cause
}
// ConfigUnmarshalError
type ConfigUnmarshalError struct {
Cause error
}
// Error implements the [builtin.error] interface.
func (e ConfigUnmarshalError) Error() string {
return fmt.Sprintf("failed to unmarshal read config source(s) into custom type: %s", e.Cause)
}
// Unwrap implements the implicit interface used by [errors.Is] and [errors.As].
func (e ConfigUnmarshalError) Unwrap() error {
return e.Cause
}
// AppBuildError
type AppBuildError struct {
Cause error
}
// Error implements the [builtin.error] interface.
func (e AppBuildError) Error() string {
return fmt.Sprintf("failed to build app: %s", e.Cause)
}
// Unwrap implements the implicit interface used by [errors.Is] and [errors.As].
func (e AppBuildError) Unwrap() error {
return e.Cause
}
// AppRunError
type AppRunError struct {
Cause error
}
// Error implements the [builtin.error] interface.
func (e AppRunError) Error() string {
return fmt.Sprintf("failed to run app: %s", e.Cause)
}
// Unwrap implements the implicit interface used by [errors.Is] and [errors.As].
func (e AppRunError) Unwrap() error {
return e.Cause
}