Support one-of contraints and apply it for S3 source
This commit is contained in:
@@ -15,6 +15,7 @@ import static org.springframework.ide.vscode.commons.util.ExceptionUtil.getSimpl
|
||||
import static org.springframework.ide.vscode.commons.yaml.ast.NodeUtil.asScalar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -52,6 +53,8 @@ import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.ScalarNode;
|
||||
import org.yaml.snakeyaml.nodes.SequenceNode;
|
||||
|
||||
import com.google.gson.internal.Streams;
|
||||
|
||||
public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
|
||||
private final IProblemCollector problems;
|
||||
@@ -217,6 +220,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
boolean allPropertiesKnown = beanProperties.keySet().containsAll(foundProps);
|
||||
//Don't check for missing properties if some properties look like they might be spelled incorrectly.
|
||||
if (allPropertiesKnown) {
|
||||
//Check for missing required properties:
|
||||
Set<String> missingProps = beanProperties.values().stream()
|
||||
.filter(YTypedProperty::isRequired)
|
||||
.map(YTypedProperty::getName)
|
||||
@@ -233,6 +237,25 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
}
|
||||
problem(map, message);
|
||||
}
|
||||
|
||||
//Check for missing/extra 'one-of' constrained properties
|
||||
for (String[] _requiredProps : typeUtil.getOneOfConstraints(type)) {
|
||||
List<String> requiredProps = Arrays.asList(_requiredProps);
|
||||
long foundPropsCount = requiredProps.stream()
|
||||
.filter(foundProps::contains)
|
||||
.count();
|
||||
if (foundPropsCount==0) {
|
||||
problem(map, "One of "+requiredProps+" is required for '"+type+"'");
|
||||
} else if (foundPropsCount>1) {
|
||||
//Mark each of the found keys as a violation:
|
||||
for (NodeTuple entry : map.getValue()) {
|
||||
String key = NodeUtil.asScalar(entry.getKeyNode());
|
||||
if (key!=null && requiredProps.contains(key)) {
|
||||
problem(entry.getKeyNode(), "Only one of "+requiredProps+" should be defined for '"+type+"'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,20 +23,16 @@ import java.util.concurrent.Callable;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.EnumValueParser;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParser;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Multiset;
|
||||
|
||||
/**
|
||||
* Static utility method for creating YType objects representing either
|
||||
@@ -137,6 +133,11 @@ public class YTypeFactory {
|
||||
public YType inferMoreSpecificType(YType type, DynamicSchemaContext schemaContext) {
|
||||
return ((AbstractType)type).inferMoreSpecificType(schemaContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String[]> getOneOfConstraints(YType type) {
|
||||
return ((AbstractType)type).getOneOfConstraints();
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -152,6 +153,8 @@ public class YTypeFactory {
|
||||
private Map<String, YTypedProperty> cachedPropertyMap;
|
||||
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
||||
|
||||
private List<String[]> oneOfConstraints = new ArrayList<>(1);
|
||||
|
||||
public boolean isSequenceable() {
|
||||
return false;
|
||||
}
|
||||
@@ -212,6 +215,10 @@ public class YTypeFactory {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
public List<String[]> getOneOfConstraints() {
|
||||
return ImmutableList.copyOf(oneOfConstraints);
|
||||
}
|
||||
|
||||
public List<YTypedProperty> getProperties() {
|
||||
return Collections.unmodifiableList(propertyList);
|
||||
}
|
||||
@@ -282,6 +289,11 @@ public class YTypeFactory {
|
||||
private ValueParser getParser(DynamicSchemaContext dc) {
|
||||
return parser == null ? null : parser.withContext(dc);
|
||||
}
|
||||
|
||||
public void requireOneOf(String... properties) {
|
||||
Assert.isLegal(properties.length>1);
|
||||
this.oneOfConstraints.add(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,11 +430,6 @@ public class YTypeFactory {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void requireOneOf(String... properties) {
|
||||
Assert.isLegal(properties.length>1);
|
||||
//TODO: implement support for this.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class YAtomicType extends AbstractType {
|
||||
@@ -479,7 +486,7 @@ public class YTypeFactory {
|
||||
return null; //unreachable, but compiler doesn't know.
|
||||
}
|
||||
private boolean isUniqueFor(String name, AbstractType t, List<YBeanType> types) {
|
||||
for (YBeanType other : types) {
|
||||
for (AbstractType other : types) {
|
||||
if (other!=t) {
|
||||
//Note: passing null dynamic context below is okay, assuming the properties in YBeanType
|
||||
// do not care about dynamic context.
|
||||
|
||||
@@ -45,4 +45,5 @@ public interface YTypeUtil {
|
||||
* should be returned.
|
||||
*/
|
||||
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
|
||||
List<String[]> getOneOfConstraints(YType type);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
.collect(Collectors.toSet())
|
||||
.block();
|
||||
|
||||
private final YBeanType TOPLEVEL_TYPE;
|
||||
private final AbstractType TOPLEVEL_TYPE;
|
||||
private final YTypeUtil TYPE_UTIL;
|
||||
|
||||
public final YTypeFactory f = new YTypeFactory();
|
||||
@@ -99,7 +99,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
.parseWith(ValueParsers.NE_STRING)
|
||||
.addHints(MimeTypes.getKnownMimeTypes());
|
||||
|
||||
public final YBeanType task;
|
||||
public final AbstractType task;
|
||||
|
||||
private final ResourceTypeRegistry resourceTypes = new ResourceTypeRegistry();
|
||||
|
||||
@@ -156,13 +156,13 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
resourceTypes.getSourceType(getResourceTypeTag(models, dc))
|
||||
);
|
||||
|
||||
YBeanType resource = f.ybean("Resource");
|
||||
AbstractType resource = f.ybean("Resource");
|
||||
addProp(resource, "name", resourceNameDef).isRequired(true);
|
||||
addProp(resource, "type", t_resource_type_name).isRequired(true);
|
||||
addProp(resource, "source", resourceSource);
|
||||
addProp(resource, "check_every", t_duration);
|
||||
|
||||
YBeanType t_image_resource = f.ybean("ImageResource");
|
||||
AbstractType t_image_resource = f.ybean("ImageResource");
|
||||
for (YTypedProperty p : resource.getProperties()) {
|
||||
if (!"name".equals(p.getName())) {
|
||||
t_image_resource.addProperty(p);
|
||||
@@ -172,15 +172,15 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
YAtomicType t_platform = f.yenum("Platform", "windows", "linux", "darwin");
|
||||
t_platform.parseWith(ValueParsers.NE_STRING); //no errors because in theory platform are just strings.
|
||||
|
||||
YBeanType t_input = f.ybean("TaskInput");
|
||||
AbstractType t_input = f.ybean("TaskInput");
|
||||
addProp(t_input, "name", t_ne_string).isRequired(true);
|
||||
addProp(t_input, "path", t_ne_string);
|
||||
|
||||
YBeanType t_output = f.ybean("TaskOutput");
|
||||
AbstractType t_output = f.ybean("TaskOutput");
|
||||
addProp(t_output, "name", t_ne_string).isRequired(true);
|
||||
addProp(t_output, "path", t_ne_string);
|
||||
|
||||
YBeanType t_command = f.ybean("Command");
|
||||
AbstractType t_command = f.ybean("Command");
|
||||
addProp(t_command, "path", t_ne_string).isRequired(true);
|
||||
addProp(t_command, "args", t_strings);
|
||||
addProp(t_command, "dir", t_ne_string);
|
||||
@@ -242,7 +242,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(tryStep, "try", step);
|
||||
|
||||
// shared properties applicable for any subtype of Step:
|
||||
for (YBeanType subStep : stepTypes) {
|
||||
for (AbstractType subStep : stepTypes) {
|
||||
addProp(step, subStep, "on_success", step);
|
||||
addProp(step, subStep, "on_failure", step);
|
||||
addProp(step, subStep, "ensure", step);
|
||||
@@ -251,7 +251,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(step, subStep, "timeout", t_duration);
|
||||
}
|
||||
|
||||
YBeanType job = f.ybean("Job");
|
||||
AbstractType job = f.ybean("Job");
|
||||
addProp(job, "name", jobNameDef).isRequired(true);
|
||||
addProp(job, "plan", f.yseq(step)).isRequired(true);
|
||||
addProp(job, "serial", t_boolean);
|
||||
@@ -261,12 +261,12 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(job, "public", t_boolean);
|
||||
addProp(job, "disable_manual_trigger", t_boolean);
|
||||
|
||||
YBeanType resourceType = f.ybean("ResourceType");
|
||||
AbstractType resourceType = f.ybean("ResourceType");
|
||||
addProp(resourceType, "name", resourceTypeNameDef).isRequired(true);
|
||||
addProp(resourceType, "type", t_image_type).isRequired(true);
|
||||
addProp(resourceType, "source", resourceSource);
|
||||
|
||||
YBeanType group = f.ybean("Group");
|
||||
AbstractType group = f.ybean("Group");
|
||||
addProp(group, "name", t_ne_string).isRequired(true);
|
||||
addProp(group, "resources", f.yseq(t_resource_name));
|
||||
addProp(group, "jobs", f.yseq(t_job_name));
|
||||
@@ -286,7 +286,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
private void initializeDefaultResourceTypes() {
|
||||
// git :
|
||||
{
|
||||
YBeanType source = f.ybean("GitSource");
|
||||
AbstractType source = f.ybean("GitSource");
|
||||
addProp(source, "uri", t_string).isRequired(true);
|
||||
addProp(source, "branch", t_string).isRequired(true);
|
||||
addProp(source, "private_key", t_string);
|
||||
@@ -302,12 +302,12 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(source, "commit_verification_key_ids", t_strings);
|
||||
addProp(source, "gpg_keyserver", t_string);
|
||||
|
||||
YBeanType get = f.ybean("GitGetParams");
|
||||
AbstractType get = f.ybean("GitGetParams");
|
||||
addProp(get, "depth", t_pos_integer);
|
||||
addProp(get, "submodules", f.yany("GitSubmodules").addHints("all", "none"));
|
||||
addProp(get, "disable_git_lfs", t_boolean);
|
||||
|
||||
YBeanType put = f.ybean("GitPutParams");
|
||||
AbstractType put = f.ybean("GitPutParams");
|
||||
addProp(put, "repository", t_ne_string).isRequired(true);
|
||||
addProp(put, "rebase", t_boolean);
|
||||
addProp(put, "tag", t_ne_string);
|
||||
@@ -320,7 +320,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
}
|
||||
//docker-image:
|
||||
{
|
||||
YBeanType source = f.ybean("DockerImageSource");
|
||||
AbstractType source = f.ybean("DockerImageSource");
|
||||
addProp(source, "repository", t_ne_string).isRequired(true);
|
||||
addProp(source, "tag", t_ne_string);
|
||||
addProp(source, "username", t_ne_string);
|
||||
@@ -339,12 +339,12 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
f.yprop("cert", t_ne_string)
|
||||
)));
|
||||
|
||||
YBeanType get = f.ybean("DockerImageGetParams");
|
||||
AbstractType get = f.ybean("DockerImageGetParams");
|
||||
addProp(get, "save", t_boolean);
|
||||
addProp(get, "rootfs", t_boolean);
|
||||
addProp(get, "skip_download", t_boolean);
|
||||
|
||||
YBeanType put = f.ybean("DockerImagePutParams");
|
||||
AbstractType put = f.ybean("DockerImagePutParams");
|
||||
addProp(put, "build", t_ne_string);
|
||||
addProp(put, "load", t_ne_string);
|
||||
addProp(put, "dockerfile", t_ne_string);
|
||||
@@ -386,7 +386,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
"log-delivery-write"
|
||||
);
|
||||
|
||||
YBeanType source = f.ybean("S3Source");
|
||||
AbstractType source = f.ybean("S3Source");
|
||||
addProp(source, "bucket", t_ne_string).isRequired(true);
|
||||
addProp(source, "access_key_id", t_ne_string);
|
||||
addProp(source, "secret_access_key", t_ne_string);
|
||||
@@ -402,10 +402,10 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(source, "versioned_file", t_ne_string);
|
||||
source.requireOneOf("regexp", "versioned_file");
|
||||
|
||||
YBeanType get = f.ybean("S3GetParams");
|
||||
AbstractType get = f.ybean("S3GetParams");
|
||||
//Note: S3GetParams intentionally has no properties since no params are expected according to the docs.
|
||||
|
||||
YBeanType put = f.ybean("S3PutParams");
|
||||
AbstractType put = f.ybean("S3PutParams");
|
||||
addProp(put, "file", t_ne_string).isRequired(true);
|
||||
addProp(put, "acl", t_canned_acl);
|
||||
addProp(put, "content_type", t_mime_type);
|
||||
@@ -459,7 +459,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YBeanType getTopLevelType() {
|
||||
public AbstractType getTopLevelType() {
|
||||
return TOPLEVEL_TYPE;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
|
||||
|
||||
/**
|
||||
* Keeps track of known resource types.
|
||||
@@ -25,26 +25,26 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType
|
||||
public class ResourceTypeRegistry {
|
||||
|
||||
private static class ResourceTypeInfo {
|
||||
private final YBeanType source;
|
||||
private final YBeanType in;
|
||||
private final YBeanType out;
|
||||
private final AbstractType source;
|
||||
private final AbstractType in;
|
||||
private final AbstractType out;
|
||||
|
||||
public ResourceTypeInfo(YBeanType source, YBeanType in, YBeanType out) {
|
||||
public ResourceTypeInfo(AbstractType source, AbstractType in, AbstractType out) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public YBeanType getSource() {
|
||||
public AbstractType getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public YBeanType getIn() {
|
||||
public AbstractType getIn() {
|
||||
return in;
|
||||
}
|
||||
|
||||
public YBeanType getOut() {
|
||||
public AbstractType getOut() {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class ResourceTypeRegistry {
|
||||
public ResourceTypeRegistry() {
|
||||
}
|
||||
|
||||
public void def(String resourceTypeName, YBeanType source, YBeanType in, YBeanType out) {
|
||||
public void def(String resourceTypeName, AbstractType source, AbstractType in, AbstractType out) {
|
||||
Assert.isLegal(!resourceTypes.containsKey(resourceTypeName), "Multiple definitions for '"+resourceTypeName+"'");
|
||||
resourceTypes.put(resourceTypeName, new ResourceTypeInfo(source, in, out));
|
||||
}
|
||||
|
||||
@@ -1314,7 +1314,10 @@ public class ConcourseEditorTest {
|
||||
" source:\n" +
|
||||
" access_key_id: the-key"
|
||||
);
|
||||
editor.assertProblems("access_key_id: the-key|'bucket' is required");
|
||||
editor.assertProblems(
|
||||
"access_key_id: the-key|'bucket' is required",
|
||||
"access_key_id: the-key|One of [regexp, versioned_file] is required"
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
"resources:\n" +
|
||||
@@ -1339,7 +1342,9 @@ public class ConcourseEditorTest {
|
||||
"bogus-region|unknown 'S3Region'",
|
||||
"is-private|'boolean'",
|
||||
"no_ssl_checking|'boolean'",
|
||||
"should-use-v2|'boolean'"
|
||||
"should-use-v2|'boolean'",
|
||||
"regexp|Only one of [regexp, versioned_file] should be defined",
|
||||
"versioned_file|Only one of [regexp, versioned_file] should be defined"
|
||||
);
|
||||
|
||||
editor.assertHoverContains("bucket", "The name of the bucket");
|
||||
|
||||
@@ -20,8 +20,8 @@ import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.ValueParsers;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPropertyImpl;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
@@ -34,7 +34,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
*/
|
||||
public class ManifestYmlSchema implements YamlSchema {
|
||||
|
||||
private final YBeanType TOPLEVEL_TYPE;
|
||||
private final AbstractType TOPLEVEL_TYPE;
|
||||
private final YTypeUtil TYPE_UTIL;
|
||||
private final Callable<Collection<YValueHint>> buildpackProvider;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ManifestYmlSchema implements YamlSchema {
|
||||
// define schema types
|
||||
TOPLEVEL_TYPE = f.ybean("Cloudfoundry Manifest");
|
||||
|
||||
YBeanType application = f.ybean("Application");
|
||||
AbstractType application = f.ybean("Application");
|
||||
YAtomicType t_path = f.yatomic("Path");
|
||||
|
||||
YAtomicType t_buildpack = f.yatomic("Buildpack");
|
||||
@@ -136,7 +136,7 @@ public class ManifestYmlSchema implements YamlSchema {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YBeanType getTopLevelType() {
|
||||
public AbstractType getTopLevelType() {
|
||||
return TOPLEVEL_TYPE;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.junit.Test;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YSeqType;
|
||||
import org.springframework.ide.vscode.manifest.yaml.ManifestYmlSchema;
|
||||
|
||||
@@ -131,7 +131,7 @@ public class ManifestYmlSchemaTest {
|
||||
|
||||
private List<YTypedProperty> getNestedProps() {
|
||||
YSeqType applications = (YSeqType) schema.getTopLevelType().getPropertiesMap().get("applications").getType();
|
||||
YBeanType application = (YBeanType) applications.getDomainType();
|
||||
AbstractType application = (AbstractType) applications.getDomainType();
|
||||
return application.getProperties();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user