Support one-of contraints and apply it for S3 source

This commit is contained in:
Kris De Volder
2017-01-28 18:35:06 -08:00
parent 928817a6b5
commit 4254f38e95
8 changed files with 84 additions and 48 deletions

View File

@@ -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+"'");
}
}
}
}
}
}

View File

@@ -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.

View File

@@ -45,4 +45,5 @@ public interface YTypeUtil {
* should be returned.
*/
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
List<String[]> getOneOfConstraints(YType type);
}

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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();
}