Verify constraints around 'health-check-http-endpoint' prop

This commit is contained in:
Kris De Volder
2017-04-27 16:50:23 -07:00
parent f7bde8559b
commit 1966affe05
8 changed files with 151 additions and 31 deletions

View File

@@ -43,6 +43,16 @@ public interface YamlTraversal {
*/
<T extends YamlNavigable<T>> T traverse(T startNode);
default Node traverseNode(Node root) {
if (root!=null) {
ASTCursor cursor = traverse(new NodeCursor(root));
if (cursor instanceof NodeCursor) {
return ((NodeCursor)cursor).getNode();
}
}
return null;
}
default Node traverseToNode(YamlFileAST root) {
ASTCursor cursor = traverse(new ASTRootCursor(root));
if (cursor instanceof NodeCursor) {

View File

@@ -95,7 +95,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
if (nodes!=null && !nodes.isEmpty()) {
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
reconcile(ast.getDocument(), new YamlPath(YamlPathSegment.valueAt(i)), /*parent*/null, node, schema.getTopLevelType());
reconcile(ast, new YamlPath(YamlPathSegment.valueAt(i)), /*parent*/null, node, schema.getTopLevelType());
}
}
} finally {
@@ -121,9 +121,10 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
return allOf(ast, node);
}
private void reconcile(IDocument doc, YamlPath path, Node parent, Node node, YType type) {
private void reconcile(YamlFileAST ast, YamlPath path, Node parent, Node node, YType type) {
// IDocument doc = ast.getDocument();
if (type!=null) {
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(doc, path, node);
DynamicSchemaContext schemaContext = new ASTDynamicSchemaContext(ast, path, node);
type = typeUtil.inferMoreSpecificType(type, schemaContext);
if (typeCollector!=null) {
typeCollector.accept(node, type);
@@ -136,8 +137,8 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
if (typeUtil.isMap(type)) {
for (NodeTuple entry : map.getValue()) {
String key = NodeUtil.asScalar(entry.getKeyNode());
reconcile(doc, keyAt(path, key), map, entry.getKeyNode(), typeUtil.getKeyType(type));
reconcile(doc, valueAt(path, key), map, entry.getValueNode(), typeUtil.getDomainType(type));
reconcile(ast, keyAt(path, key), map, entry.getKeyNode(), typeUtil.getKeyType(type));
reconcile(ast, valueAt(path, key), map, entry.getValueNode(), typeUtil.getDomainType(type));
}
} else if (typeUtil.isBean(type)) {
Map<String, YTypedProperty> beanProperties = typeUtil.getPropertiesMap(type);
@@ -155,7 +156,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
if (prop.isDeprecated()) {
problems.accept(YamlSchemaProblems.deprecatedProperty(keyNode, type, prop));
}
reconcile(doc, valueAt(path, key), map, entry.getValueNode(), prop.getType());
reconcile(ast, valueAt(path, key), map, entry.getValueNode(), prop.getType());
}
}
}
@@ -168,7 +169,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
if (typeUtil.isSequencable(type)) {
for (int i = 0; i < seq.getValue().size(); i++) {
Node el = seq.getValue().get(i);
reconcile(doc, valueAt(path, i), seq, el, typeUtil.getDomainType(type));
reconcile(ast, valueAt(path, i), seq, el, typeUtil.getDomainType(type));
}
} else {
expectTypeButFoundSequence(type, node);
@@ -185,7 +186,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
}
} catch (Exception e) {
ProblemType problemType = getProblemType(e);
DocumentRegion region = getRegion(e, doc, node);
DocumentRegion region = getRegion(e, ast.getDocument(), node);
String msg = getMessage(e);
valueParseError(type, region, msg, problemType, getValueReplacement(e));
}

View File

@@ -14,6 +14,7 @@ import java.util.Set;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
@@ -27,11 +28,11 @@ import org.yaml.snakeyaml.nodes.Node;
public class ASTDynamicSchemaContext extends CachingSchemaContext {
private MappingNode mapNode;
private IDocument doc;
private YamlPath path;
private YamlFileAST ast;
public ASTDynamicSchemaContext(IDocument doc, YamlPath path, Node node) {
this.doc = doc;
public ASTDynamicSchemaContext(YamlFileAST ast, YamlPath path, Node node) {
this.ast = ast;
this.path = path;
this.mapNode = as(MappingNode.class, node);
}
@@ -51,11 +52,16 @@ public class ASTDynamicSchemaContext extends CachingSchemaContext {
@Override
public IDocument getDocument() {
return doc;
return ast.getDocument();
}
@Override
public YamlPath getPath() {
return path;
}
@Override
public YamlFileAST getAST() {
return ast;
}
}

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.schema;
import java.util.Set;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import com.google.common.collect.ImmutableSet;
@@ -30,6 +31,7 @@ import com.google.common.collect.ImmutableSet;
public interface DynamicSchemaContext {
DynamicSchemaContext NULL = new DynamicSchemaContext() {
@Override
public Set<String> getDefinedProperties() {
return ImmutableSet.of();
@@ -47,6 +49,12 @@ public interface DynamicSchemaContext {
};
/**
* Returns the enitre AST of the current document. May be null if the AST is not
* available (e.g. because of parsing errors)
*/
default YamlFileAST getAST() { return null; }
/**
* Returns the set of property names that are already defined in the current context.
* <p>

View File

@@ -10,13 +10,9 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.yaml.schema.constraints;
import java.util.Set;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
/**
@@ -28,10 +24,10 @@ import org.yaml.snakeyaml.nodes.Node;
public interface Constraint {
/**
* Implemetors gain access to various bits of context information passed as parameters and
* Implementors gain access to various bits of context information passed as parameters and
* are supposed to use this information in whatever way they like to check if the
* constraint is satisfied. When the constrain is not satisfied they should report any
* violations by adding problems to the provide {@link IProblemCollector}.
* constraint is satisfied. When the constraint is not satisfied they should report any
* violations by adding problems to the provided {@link IProblemCollector}.
*
* @param node The node being validated
* @param type The inferred type of the node.

View File

@@ -17,13 +17,9 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
public class ManifestYamlSchemaProblemsTypes {
public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem",
ProblemSeverity.WARNING);
public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem",
ProblemSeverity.WARNING);
public static final ProblemType UNKNOWN_STACK_PROBLEM = problemType("UnknownStackProblem",
ProblemSeverity.WARNING);;
public static final ProblemType UNKNOWN_SERVICES_PROBLEM = problemType("UnknownServicesProblem", ProblemSeverity.WARNING);
public static final ProblemType UNKNOWN_DOMAIN_PROBLEM = problemType("UnknownDomainProblem", ProblemSeverity.WARNING);
public static final ProblemType UNKNOWN_STACK_PROBLEM = problemType("UnknownStackProblem", ProblemSeverity.WARNING);
public static final ProblemType IGNORED_PROPERTY = problemType("IgnoredProperty", ProblemSeverity.WARNING);
}

View File

@@ -14,10 +14,17 @@ import java.util.Collection;
import java.util.Set;
import java.util.concurrent.Callable;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.IntegerRange;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
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;
@@ -27,6 +34,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YTypedPro
import org.springframework.ide.vscode.commons.yaml.schema.YTypeUtil;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableSet;
@@ -35,6 +43,9 @@ import com.google.common.collect.ImmutableSet;
*/
public class ManifestYmlSchema implements YamlSchema {
private static final String HEALTH_CHECK_HTTP_ENDPOINT_PROP = "health-check-http-endpoint";
private static final String HEALTH_CHECK_TYPE_PROP = "health-check-type";
private final AbstractType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
@@ -47,6 +58,40 @@ public class ManifestYmlSchema implements YamlSchema {
return IntegerRange.exactly(1);
}
private void verify_heatth_check_http_end_point_constraint(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
YamlFileAST ast = dc.getAST();
if (ast!=null) {
Node markerNode = YamlPathSegment.keyAt(HEALTH_CHECK_HTTP_ENDPOINT_PROP).traverseNode(node);
if (markerNode != null) {
String healthCheckType = getEffectiveHealthCheckType(ast, dc.getPath(), node);
if (!"http".equals(healthCheckType)) {
problems.accept(YamlSchemaProblems.problem(ManifestYamlSchemaProblemsTypes.IGNORED_PROPERTY,
"This has no effect unless `"+HEALTH_CHECK_TYPE_PROP+"` is `http` (but it is currently set to `"+healthCheckType+"`)", markerNode));
}
}
}
}
/**
* Determines the actual health-check-type that applies to a given node, taking into account
* inheritance from parent node, and default value.
*/
private String getEffectiveHealthCheckType(YamlFileAST ast, YamlPath path, Node node) {
String explicit = NodeUtil.getScalarProperty(node, HEALTH_CHECK_TYPE_PROP);
if (explicit!=null) {
return explicit;
}
if (path.size()>2) {
//Must consider inherited props!
YamlPath parentPath = path.dropLast(2);
Node parent = parentPath.traverseToNode(ast);
String inherited = NodeUtil.getScalarProperty(parent, HEALTH_CHECK_TYPE_PROP);
if (inherited!=null) {
return inherited;
}
}
return "port";
}
public ManifestYmlSchema(ManifestYmlHintProviders providers) {
Callable<Collection<YValueHint>> buildpackProvider = providers.getBuildpackProviders();
@@ -60,8 +105,10 @@ public class ManifestYmlSchema implements YamlSchema {
// define schema types
TOPLEVEL_TYPE = f.ybean("Cloudfoundry Manifest");
TOPLEVEL_TYPE.require(this::verify_heatth_check_http_end_point_constraint);
AbstractType application = f.ybean("Application");
application.require(this::verify_heatth_check_http_end_point_constraint);
YAtomicType t_path = f.yatomic("Path");
YAtomicType t_buildpack = f.yatomic("Buildpack");
@@ -144,8 +191,8 @@ public class ManifestYmlSchema implements YamlSchema {
f.yprop("services", f.yseq(t_service)),
f.yprop("stack", t_stack),
f.yprop("timeout", t_pos_integer),
f.yprop("health-check-type", t_health_check_type),
f.yprop("health-check-http-endpoint", t_ne_string)
f.yprop(HEALTH_CHECK_TYPE_PROP, t_health_check_type),
f.yprop(HEALTH_CHECK_HTTP_ENDPOINT_PROP, t_ne_string)
};
for (YTypedPropertyImpl prop : props) {

View File

@@ -429,8 +429,7 @@ public class ManifestYamlEditorTest {
);
}
@Test
public void reconcileHealthCheckType() throws Exception {
@Test public void reconcileHealthCheckType() throws Exception {
Editor editor;
Diagnostic problem;
@@ -464,6 +463,63 @@ public class ManifestYamlEditorTest {
);
editor.assertProblems(/*NONE*/);
}
@Test public void reconcileHealthHttpEndPointIgnoredWarning() throws Exception {
Editor editor;
Diagnostic problem;
editor = harness.newEditor(
"applications:\n" +
"- name: my-app\n" +
" health-check-type: process\n" +
" health-check-http-endpoint: /health"
);
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
editor = harness.newEditor(
"health-check-type: http\n" +
"applications:\n" +
"- name: my-app\n" +
" health-check-http-endpoint: /health"
);
editor.assertProblems(/*NONE*/);
editor = harness.newEditor(
"applications:\n" +
"- name: my-app\n" +
" health-check-http-endpoint: /health"
);
problem = editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)").get(0);
assertEquals(DiagnosticSeverity.Warning, problem.getSeverity());
editor = harness.newEditor(
"applications:\n" +
"- name: my-app\n" +
" health-check-type: http\n" +
" health-check-http-endpoint: /health"
);
editor.assertProblems(/*NONE*/);
editor = harness.newEditor(
"health-check-type: http\n" +
"applications:\n" +
"- name: my-app\n" +
" health-check-type: process\n" +
" health-check-http-endpoint: /health"
);
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
editor = harness.newEditor(
"health-check-http-endpoint: /health"
);
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `port`)");
editor = harness.newEditor(
"health-check-type: process\n" +
"health-check-http-endpoint: /health"
);
editor.assertProblems("health-check-http-endpoint|This has no effect unless `health-check-type` is `http` (but it is currently set to `process`)");
}
@Test public void deprecatedHealthCheckTypeQuickfix() throws Exception {
Editor editor = harness.newEditor(