Skip to content

Commit 521f5fd

Browse files
committed
Allow configoptional to wrap scalar values
1 parent b1b3b63 commit 521f5fd

11 files changed

Lines changed: 1139 additions & 236 deletions

File tree

config/configoptional/optional.go

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,6 @@ func deref(t reflect.Type) reflect.Type {
4848
return t
4949
}
5050

51-
// assertStructKind checks if T can be dereferenced into a type with struct kind.
52-
//
53-
// We assert this because our unmarshaling logic currently only supports structs.
54-
// This can be removed if we ever support scalar values.
55-
func assertStructKind[T any]() error {
56-
var instance T
57-
t := deref(reflect.TypeOf(instance))
58-
if t.Kind() != reflect.Struct {
59-
return fmt.Errorf("configoptional: %q does not have a struct kind", t)
60-
}
61-
62-
return nil
63-
}
64-
6551
// assertNoEnabledField checks that a struct type
6652
// does not have a field with a mapstructure tag "enabled".
6753
//
@@ -101,11 +87,9 @@ func Some[T any](value T) Optional[T] {
10187

10288
// Default creates an Optional with a default value for unmarshaling.
10389
//
104-
// It panics if
105-
// - T is not a struct OR
106-
// - T has a field with the mapstructure tag "enabled".
90+
// It panics if T has a field with the mapstructure tag "enabled".
10791
func Default[T any](value T) Optional[T] {
108-
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
92+
err := errors.Join(assertNoEnabledField[T]())
10993
if err != nil {
11094
panic(err)
11195
}
@@ -149,7 +133,7 @@ func (o *Optional[T]) Get() *T {
149133
// - T is not a struct OR
150134
// - T has a field with the mapstructure tag "enabled".
151135
func (o *Optional[T]) GetOrInsertDefault() *T {
152-
err := errors.Join(assertStructKind[T](), assertNoEnabledField[T]())
136+
err := errors.Join(assertNoEnabledField[T]())
153137
if err != nil {
154138
panic(err)
155139
}
@@ -167,7 +151,10 @@ func (o *Optional[T]) GetOrInsertDefault() *T {
167151
return o.Get()
168152
}
169153

170-
var _ confmap.Unmarshaler = (*Optional[any])(nil)
154+
var (
155+
_ confmap.Unmarshaler = (*Optional[any])(nil)
156+
_ xconfmap.ScalarUnmarshaler = (*Optional[any])(nil)
157+
)
171158

172159
// Unmarshal the configuration into the Optional value.
173160
//
@@ -205,7 +192,7 @@ func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error {
205192
}
206193
}
207194

208-
if err := conf.Unmarshal(&o.value, xconfmap.WithForceUnmarshaler()); err != nil {
195+
if err := conf.Unmarshal(&o.value, xconfmap.WithForceUnmarshaler(), xconfmap.WithScalarMarshaler()); err != nil {
209196
return err
210197
}
211198

@@ -221,7 +208,36 @@ func (o *Optional[T]) Unmarshal(conf *confmap.Conf) error {
221208
return nil
222209
}
223210

224-
var _ confmap.Marshaler = (*Optional[any])(nil)
211+
// UnmarshalScalar unmarshals a scalar value into the Optional.
212+
//
213+
// A `nil` value will set the Optional to None, disabling it as setting
214+
// `enabled: false` for a struct-type Optional or `null` for a pointer field
215+
// would.
216+
func (o *Optional[T]) UnmarshalScalar(val any) error {
217+
if val == nil {
218+
var zero T
219+
o.value = zero
220+
o.flavor = noneFlavor
221+
return nil
222+
}
223+
224+
v, ok := val.(T)
225+
if !ok {
226+
return fmt.Errorf("val is %T, not %T", val, v)
227+
}
228+
o.value = v
229+
o.flavor = someFlavor
230+
return nil
231+
}
232+
233+
func (o *Optional[T]) ScalarType() any {
234+
return o.value
235+
}
236+
237+
var (
238+
_ confmap.Marshaler = (*Optional[any])(nil)
239+
_ xconfmap.ScalarMarshaler = (*Optional[any])(nil)
240+
)
225241

226242
// Marshal the Optional value into the configuration.
227243
// If the Optional is None or Default, it does not marshal anything.
@@ -230,22 +246,26 @@ var _ confmap.Marshaler = (*Optional[any])(nil)
230246
// T must be derefenceable to a type with struct kind.
231247
// Scalar values are not supported.
232248
func (o Optional[T]) Marshal(conf *confmap.Conf) error {
233-
if err := assertStructKind[T](); err != nil {
234-
return err
235-
}
236-
237249
if o.flavor == noneFlavor || o.flavor == defaultFlavor {
238250
// Optional is None or Default, do not marshal anything.
239251
return conf.Marshal(map[string]any(nil))
240252
}
241253

242-
if err := conf.Marshal(o.value); err != nil {
254+
if err := conf.Marshal(o.value, xconfmap.WithScalarMarshaler()); err != nil {
243255
return fmt.Errorf("configoptional: failed to marshal Optional value: %w", err)
244256
}
245257

246258
return nil
247259
}
248260

261+
func (o Optional[T]) GetScalarValue() (any, error) {
262+
if o.flavor == noneFlavor || o.flavor == defaultFlavor {
263+
return nil, nil
264+
}
265+
266+
return o.value, nil
267+
}
268+
249269
var _ xconfmap.Validator = (*Optional[any])(nil)
250270

251271
// Validate implements [xconfmap.Validator]. This is required because the

0 commit comments

Comments
 (0)