@@ -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,12 +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".
10791func Default [T any ](value T ) Optional [T ] {
108- err := errors .Join (assertStructKind [T ](), assertNoEnabledField [T ]())
109- if err != nil {
92+ if err := assertNoEnabledField [T ](); err != nil {
11093 panic (err )
11194 }
11295 return Optional [T ]{value : value , flavor : defaultFlavor }
@@ -149,8 +132,7 @@ func (o *Optional[T]) Get() *T {
149132// - T is not a struct OR
150133// - T has a field with the mapstructure tag "enabled".
151134func (o * Optional [T ]) GetOrInsertDefault () * T {
152- err := errors .Join (assertStructKind [T ](), assertNoEnabledField [T ]())
153- if err != nil {
135+ if err := assertNoEnabledField [T ](); err != nil {
154136 panic (err )
155137 }
156138
@@ -167,7 +149,10 @@ func (o *Optional[T]) GetOrInsertDefault() *T {
167149 return o .Get ()
168150}
169151
170- var _ confmap.Unmarshaler = (* Optional [any ])(nil )
152+ var (
153+ _ confmap.Unmarshaler = (* Optional [any ])(nil )
154+ _ confmap.ScalarUnmarshaler = (* Optional [any ])(nil )
155+ )
171156
172157// Unmarshal the configuration into the Optional value.
173158//
@@ -183,7 +168,9 @@ var _ confmap.Unmarshaler = (*Optional[any])(nil)
183168// - if enabled is false: the Optional becomes None regardless of other configuration values.
184169//
185170// T must be derefenceable to a type with struct kind and not have an 'enabled' field.
186- // Scalar values are not supported.
171+ // Scalar values are not supported, and will be handled by [UnmarshalScalar] instead.
172+ // We do not need to check this since the hook for [ScalarUnmarshaler] will be called
173+ // before the hook for [Unmarshaler].
187174func (o * Optional [T ]) Unmarshal (conf * confmap.Conf ) error {
188175 if err := assertNoEnabledField [T ](); err != nil {
189176 return err
@@ -221,19 +208,47 @@ 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 (scalarValue confmap.ScalarValue ) error {
217+ if scalarValue .GetRaw () == nil {
218+ if deref (reflect .TypeOf (o .value )).Kind () == reflect .Struct {
219+ // Defer to Unmarshal behavior
220+ return confmap .ErrValueNotApplicable
221+ }
222+ // For scalar types, a nil map represents `null` and clears to None.
223+ var zero T
224+ o .value = zero
225+ o .flavor = noneFlavor
226+
227+ return nil
228+ }
229+
230+ if err := scalarValue .Unmarshal (& o .value ); err != nil {
231+ return err
232+ }
233+ o .flavor = someFlavor
234+
235+ return nil
236+ }
237+
238+ var (
239+ _ confmap.Marshaler = (* Optional [any ])(nil )
240+ _ confmap.ScalarMarshaler = (* Optional [any ])(nil )
241+ )
225242
226243// Marshal the Optional value into the configuration.
227244// If the Optional is None or Default, it does not marshal anything.
228245// If the Optional is Some, it marshals the value into the configuration.
229246//
230247// T must be derefenceable to a type with struct kind.
231- // Scalar values are not supported.
248+ // Scalar values are not supported, and will be handled by [MarshalScalar] instead.
249+ // We do not need to check this since the hook for [ScalarMarshaler] will be called
250+ // before the hook for [Marshaler].
232251func (o Optional [T ]) Marshal (conf * confmap.Conf ) error {
233- if err := assertStructKind [T ](); err != nil {
234- return err
235- }
236-
237252 if o .flavor == noneFlavor || o .flavor == defaultFlavor {
238253 // Optional is None or Default, do not marshal anything.
239254 return conf .Marshal (map [string ]any (nil ))
@@ -246,6 +261,20 @@ func (o Optional[T]) Marshal(conf *confmap.Conf) error {
246261 return nil
247262}
248263
264+ func (o Optional [T ]) MarshalScalar (scalarValue confmap.ScalarValue ) error {
265+ if deref (reflect .TypeOf (o .value )).Kind () == reflect .Struct {
266+ // Defer to Marshal behavior
267+ return confmap .ErrValueNotApplicable
268+ }
269+
270+ if o .flavor == noneFlavor || o .flavor == defaultFlavor {
271+ // An Optional of type None or Default should marshal as nil.
272+ return scalarValue .Marshal (nil )
273+ }
274+
275+ return scalarValue .Marshal (o .value )
276+ }
277+
249278var _ confmap.Validator = (* Optional [any ])(nil )
250279
251280// Validate implements [confmap.Validator]. This is required because the
0 commit comments