diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlTraversal.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlTraversal.java index b806d0222..e9e357c4a 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlTraversal.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/path/YamlTraversal.java @@ -43,6 +43,16 @@ public interface YamlTraversal { */ > 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) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java index 7097c365b..9a0056140 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/reconcile/SchemaBasedYamlASTReconciler.java @@ -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 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)); } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java index aa1af750f..86b19137d 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/ASTDynamicSchemaContext.java @@ -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; + } } diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java index 211e93f41..67e271f6c 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/DynamicSchemaContext.java @@ -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 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. *

diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java index 3d768b06d..dd5fb666a 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/constraints/Constraint.java @@ -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. diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java index 807f01e83..f944594e5 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlSchemaProblemsTypes.java @@ -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); } diff --git a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java index d65218a71..50fd4d213 100644 --- a/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java +++ b/headless-services/manifest-yaml-language-server/src/main/java/org/springframework/ide/vscode/manifest/yaml/ManifestYmlSchema.java @@ -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> 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) { diff --git a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java index 4327525c6..066935510 100644 --- a/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java +++ b/headless-services/manifest-yaml-language-server/src/test/java/org/springframework/ide/vscode/manifest/yaml/ManifestYamlEditorTest.java @@ -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(