Closed jcustenborder closed 1 year ago
I refactored the code to no longer use reflection. The annotations are now part of the annotation processor and only require a compile dependency. Now the validators directly emit code which is added into the Impl class.
@Tag(value = "exampleConfig")
public class ExampleConfigImpl implements ExampleConfig {
@Override()
public <T> Output<T> debug(Output<T> output) {
DebugFormatter formatter = DebugFormatter.of(ExampleConfig.class);
formatter.add("hostName", this.hostName);
formatter.add("port", this.port);
formatter.add("limitedPort", this.limitedPort);
formatter.add("portWithDefault", this.portWithDefault);
formatter.add("additionalProperties", this.additionalProperties);
formatter.add("relaySchema", this.relaySchema);
formatter.add("elements", this.elements);
formatter.add("testLocation", this.testLocation);
return formatter.to(output);
}
/**
* Method is used to validate the supplied configuration.
*/
@Override()
public void validate() throws ConfigException {
List<ConfigError> errors = new ArrayList<ConfigError>();
if (this.hostName == null) {
errors.add(new ConfigError("hostName", this.hostName, "value cannot be null."));
}
if (this.port < 1024 || this.port > 65535) {
errors.add(new ConfigError("port", this.port, "value must be between 1024 and 65535."));
}
if (this.limitedPort < 3000 || this.limitedPort > 5000) {
errors.add(new ConfigError("limitedPort", this.limitedPort, "value must be between 3000 and 5000."));
}
if (this.portWithDefault < 1024 || this.portWithDefault > 65535) {
errors.add(new ConfigError("portWithDefault", this.portWithDefault, "value must be between 1024 and 65535."));
}
if (this.additionalProperties == null) {
errors.add(new ConfigError("additionalProperties", this.additionalProperties, "value cannot be null."));
} else {
if (!this.additionalProperties.containsKey("batch.size")) {
errors.add(new ConfigError("additionalProperties", this.additionalProperties, "value must contain 'batch.size'"));
}
}
if (!errors.isEmpty()) {
throw new ConfigException(errors);
}
}
@Override()
public void configure(Value value) {
if (!value.containsKey("portWithDefault")) {
this.portWithDefault = ExampleConfig.super.portWithDefault();
}
if (!value.containsKey("testLocation")) {
this.testLocation = ExampleConfig.super.testLocation();
}
}
public static final Form<ExampleConfigImpl> FORM = Form.forClass(ExampleConfigImpl.class);
public static ExampleConfig load(Value value) {
ExampleConfig result = ExampleConfigImpl.FORM.cast(value);
result.configure(value);
return result;
}
/**
* <p>This is the hostname that the adapter should connect to</p>
*
* @return
*/
protected java.lang.String hostName;
/**
* <p>This is the hostname that the adapter should connect to</p>
*
* @return
*/
public java.lang.String hostName() {
return this.hostName;
}
/**
* <p>Port to connect to</p>
*
* @return
*/
protected int port;
/**
* <p>Port to connect to</p>
*
* @return
*/
public int port() {
return this.port;
}
/**
* <p>This is a port that is limited to 3000 or 5000</p>
*
* @return
*/
protected int limitedPort;
/**
* <p>This is a port that is limited to 3000 or 5000</p>
*
* @return
*/
public int limitedPort() {
return this.limitedPort;
}
/**
* <p>This parameter will use a default value if none is specified.</p>
*
* @return
*/
protected int portWithDefault;
/**
* <p>This parameter will use a default value if none is specified.</p>
*
* @return
*/
public int portWithDefault() {
return this.portWithDefault;
}
/**
* <p>This is an example that contains multiple paragraphs.</p>
* <p>This should be the start of the second paragraph.</p>
* <p>This should be the start of the third paragraph.</p>
*
* @return
*/
protected java.util.Map<java.lang.String, java.lang.String> additionalProperties;
/**
* <p>This is an example that contains multiple paragraphs.</p>
* <p>This should be the start of the second paragraph.</p>
* <p>This should be the start of the third paragraph.</p>
*
* @return
*/
public java.util.Map<java.lang.String, java.lang.String> additionalProperties() {
return this.additionalProperties;
}
/**
* <p class='code-recon'>
* {@literal @}command($value) {
* nodeUri: {
* "/dynamic/",
* $val + 1
* },
* laneUri: "unused"
* value:
* }
* }
* </p>
*
* @return
*/
protected swim.structure.Value relaySchema;
/**
* <p class='code-recon'>
* {@literal @}command($value) {
* nodeUri: {
* "/dynamic/",
* $val + 1
* },
* laneUri: "unused"
* value:
* }
* }
* </p>
*
* @return
*/
public swim.structure.Value relaySchema() {
return this.relaySchema;
}
protected java.util.List<java.lang.String> elements;
public java.util.List<java.lang.String> elements() {
return this.elements;
}
/**
* <p>This example uses an enum and recommendations of values will be provided.</p>
*
* @return
*/
protected processor.TestLocation testLocation;
/**
* <p>This example uses an enum and recommendations of values will be provided.</p>
*
* @return
*/
public processor.TestLocation testLocation() {
return this.testLocation;
}
}
@jcustenborder please look at comments in pr #119
This is a WIP for adding configuration validation and generation to swim. The idea being that we create simple configuration interfaces and an annotation processor fills in the implementation while extracting documentation.
For example this is a configuration class.
This is an adapter and it's configuration.
The annotation processor reads this to extract documentation and generate configuration classes for validating the configuration. The idea is minimizing the code required for configs while getting valuable documentation.
A generated implementation of the config class looks like this.
The extracted documentation looks like this. Given this is now generated we can use it to generate some usage documentation.
Using the config class is as easy as this:
In the case there are problems with the configuration it will throw an exception that looks similar to this. An exception will be generated highlighting all of the parameters that did not pass validation.