Skip to content

Instantly share code, notes, and snippets.

@LeMikaelF
Created December 9, 2024 02:15
Show Gist options
  • Save LeMikaelF/79dc7d3d6998695db039fee33f4dd875 to your computer and use it in GitHub Desktop.
Save LeMikaelF/79dc7d3d6998695db039fee33f4dd875 to your computer and use it in GitHub Desktop.
Proposition SB

Title: Allow collections with multiple types in Configuration Properties

Motivation

It's not uncommon for libraries or applications that accept configuration properties to need to extend their model. For example, here is a configuration for zalando/logbook, where the logbook.obfuscate.headers property is a list of strings, and is mapped to a List<String> in a @ConfigurationProperties-annotated class:

logbook:
  obfuscate:
    headers:
      - Authorization
      - Cookie

The problem is, if we decide that these strings aren't enough, and decide to also accept regexes or prefix matches without a breaking change in the library, we have to resort to either using an alternate property, or using magic strings like regex->.*secret.*.

A better pattern would be to accept either strings or objects in the list of headers, so that clients can specify header names if that's all they need, or specify more complex objects if they need particular behaviour. This satisfies both readability and extensibility:

logbook:
  obfuscate:
    headers:
      - Authorization
      - Cookie
      - type: pattern
        value: .*secret.*
      - type: startsWith
        value: x-secret

Possible implementation

With the introduction of sealed interfaces, it would be possible to bind these properties in a type-safe manner. There are a several ways of implementing this, but one would be to expose interfaces for primitives, and to let clients specify how one type or the other should be selected for binding:

import lombok.Getter;


// Hypothetical Spring Boot API
// Extending this would let users bind Strings to objects, and 
// there could be corresponding IntegerProperty, BooleanProperty, etc. 
public interface StringProperty {
	String getProp();
}

// start non-boot library code
sealed interface HeaderMatcher permits SimpleHeaderMatcher, PatternHeaderMatcher {
}

@Getter
@RequiredArgsConstructor
final class SimpleHeaderMatcher implements StringProperty, HeaderMatcher {
	private final String prop;
}

// Here, `context` would be 
@DiscriminatedBy("#{context.type == 'pattern'}")
record PatternHeaderMatcher(String value) implements HeaderMatcher {
}

@ConfigurationProperties("logbook.obfuscate.headers")
record LogbookProperties(List<HeaderMatcher> headers) {
}

Note: I'm not a zalando/logbook maintainer or contributor, it just made for a good example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment