Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: introduce conditional annotation for schema generation #4934

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.opensearch.dataprepper.model.schemas;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should put this in the package org.opensearch.dataprepper.model.annotations.


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marker annotation used in schema generation to define the names and corresponding values of other required
* properties to be returned, if the property represented by the annotated field/method is present.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface IfPresentAlsoRequire {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Schemas and validations are tightly related. So we should aim to have a common language for both schemas and validations. Otherwise the developers will need to track schemas and validations independently and this may introduce misses.

I think a few modifications to the documentation and package will help here.

What do you think about using the name @AlsoRequires()?

Copy link
Collaborator Author

@chenqi0805 chenqi0805 Sep 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I am not able to find an out-of-the-box annotation from Jakarta or JsonProperty to achieve this schema property. Thus the custom annotation. I agree the tradeoff would be to maintain a set of custom annotation for schema only.

So we should aim to have a common language for both schemas and validations.

Could you specify what you mean here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you are correct - Jakarta does not have this annotation.

However, you are adding an annotation that you expect to be useful for schema generation. I suggest that we take a different approach. You should be adding annotations that let plugin developers express constraints in one place. Data Prepper should be able to use these annotations for both schema generation and for validation.

So:

  1. We need a new annotation because there is not one available.
  2. We should design it to support validation.
  3. We should document it as being able to express configuration constraints, not as simply a schema generation tool.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I will open a separate issue for that. To incorporate validation requires additional work in pipeline parsing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/**
* Array of strings in which each string should represent property(:value) where :value can be optional.
*/
String[] values();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart;
import com.github.victools.jsonschema.generator.SchemaVersion;
import org.opensearch.dataprepper.model.schemas.IfPresentAlsoRequire;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class JsonSchemaConverter {
static final String DEPRECATED_SINCE_KEY = "deprecated";
Expand All @@ -31,9 +35,11 @@ public ObjectNode convertIntoJsonSchema(
final SchemaGeneratorConfigPart<FieldScope> scopeSchemaGeneratorConfigPart = configBuilder.forFields();
overrideInstanceAttributeWithDeprecated(scopeSchemaGeneratorConfigPart);
resolveDefaultValueFromJsonProperty(scopeSchemaGeneratorConfigPart);
resolveDependentRequiresFields(scopeSchemaGeneratorConfigPart);

final SchemaGeneratorConfig config = configBuilder.build();
final SchemaGenerator generator = new SchemaGenerator(config);

return generator.generateSchema(clazz);
}

Expand All @@ -59,4 +65,13 @@ private void resolveDefaultValueFromJsonProperty(
return annotation == null || annotation.defaultValue().isEmpty() ? null : annotation.defaultValue();
});
}

private void resolveDependentRequiresFields(
final SchemaGeneratorConfigPart<FieldScope> scopeSchemaGeneratorConfigPart) {
scopeSchemaGeneratorConfigPart.withDependentRequiresResolver(field -> Optional
.ofNullable(field.getAnnotationConsideringFieldAndGetter(IfPresentAlsoRequire.class))
.map(IfPresentAlsoRequire::values)
.map(Arrays::asList)
.orElse(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.github.victools.jsonschema.generator.Module;
import com.github.victools.jsonschema.generator.OptionPreset;
import com.github.victools.jsonschema.generator.SchemaVersion;
import org.junit.jupiter.api.Test;
import org.opensearch.dataprepper.model.schemas.IfPresentAlsoRequire;
import org.opensearch.dataprepper.schemas.module.CustomJacksonModule;

import java.util.Collections;
Expand Down Expand Up @@ -53,6 +55,16 @@ void testConvertIntoJsonSchemaWithCustomJacksonModule() throws JsonProcessingExc
assertThat(propertiesNode, instanceOf(ObjectNode.class));
assertThat(propertiesNode.has("test_attribute_with_getter"), is(true));
assertThat(propertiesNode.has("custom_test_attribute"), is(true));
final JsonNode dependentRequiredNode = jsonSchemaNode.at("/dependentRequired");
assertThat(dependentRequiredNode, instanceOf(ObjectNode.class));
assertThat(dependentRequiredNode.has("test_mutually_exclusive_attribute_a"), is(true));
assertThat(dependentRequiredNode.at("/test_mutually_exclusive_attribute_a"),
instanceOf(ArrayNode.class));
final ArrayNode dependentRequiredProperties = (ArrayNode) dependentRequiredNode.at(
"/test_mutually_exclusive_attribute_a");
assertThat(dependentRequiredProperties.size(), equalTo(1));
assertThat(dependentRequiredProperties.get(0),
equalTo(TextNode.valueOf("test_mutually_exclusive_attribute_b:null")));
}

@JsonClassDescription("test config")
Expand All @@ -65,6 +77,12 @@ static class TestConfig {
@JsonProperty(defaultValue = "default_value")
private String testAttributeWithDefaultValue;

@JsonProperty
@IfPresentAlsoRequire(values = {"test_mutually_exclusive_attribute_b:null"})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this :null part for? Should we have a distinct model to represent the additional requirement?

e.g.

@IfPresentAlsoRequire({@Required(name = "test_mutually_exclusive_attribute", allowedValues = {null}))

private String testMutuallyExclusiveAttributeA;

private String testMutuallyExclusiveAttributeB;

public String getTestAttributeWithGetter() {
return testAttributeWithGetter;
}
Expand Down
Loading