From 2694fc4d37345d3e46ab11489d24169bce8bf4dd Mon Sep 17 00:00:00 2001 From: Dennis Huebner Date: Mon, 18 Dec 2023 16:00:07 +0100 Subject: [PATCH] yang-tool: Support add, replace and delete deviation --- .../yang/processor/DataTreeSerializer.xtend | 32 +-- .../processor/FeatureEvaluationContext.java | 2 +- .../yang/processor/FeatureExpressions.java | 2 +- .../yang/processor/JsonSerializer.java | 18 ++ ...dDataTree.java => ProcessedDataModel.java} | 57 +++-- .../yang/processor/ProcessorUtility.java | 24 ++- .../typefox/yang/processor/YangProcessor.java | 204 +++++++++++------- .../yang/tests/processor/DeviationTest.xtend | 162 ++++++++++++++ .../tests/processor/YangProcessorTest.java | 6 +- .../expectation/expectation-nodev.json | 15 ++ .../processor/expectation/expectation.json | 111 ++++++++++ 11 files changed, 513 insertions(+), 120 deletions(-) create mode 100644 yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/JsonSerializer.java rename yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/{ProcessedDataTree.java => ProcessedDataModel.java} (88%) create mode 100644 yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/DeviationTest.xtend diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/DataTreeSerializer.xtend b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/DataTreeSerializer.xtend index eb994f14..74c5b0d8 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/DataTreeSerializer.xtend +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/DataTreeSerializer.xtend @@ -1,25 +1,25 @@ package io.typefox.yang.processor -import io.typefox.yang.processor.ProcessedDataTree.AccessKind -import io.typefox.yang.processor.ProcessedDataTree.ElementData -import io.typefox.yang.processor.ProcessedDataTree.HasStatements -import io.typefox.yang.processor.ProcessedDataTree.ListData -import io.typefox.yang.processor.ProcessedDataTree.ModuleData import java.util.List -import io.typefox.yang.processor.ProcessedDataTree.ElementKind +import io.typefox.yang.processor.ProcessedDataModel.ElementKind +import io.typefox.yang.processor.ProcessedDataModel.AccessKind +import io.typefox.yang.processor.ProcessedDataModel.ModuleData +import io.typefox.yang.processor.ProcessedDataModel.ListData +import io.typefox.yang.processor.ProcessedDataModel.ElementData +import io.typefox.yang.processor.ProcessedDataModel.HasStatements class DataTreeSerializer { def CharSequence serialize(ModuleData moduleData) { ''' module: «moduleData.simpleName» - «FOR child : moduleData.children?:#[]» - «doSerialize(child, '', needsConnect(child, moduleData.children))» + «FOR child : moduleData.getChildren?:#[]» + «doSerialize(child, '', needsConnect(child, moduleData.getChildren))» «ENDFOR» rpcs: - «FOR rpc : moduleData.rpcs?:#[]» - «doSerialize(rpc, '', needsConnect(rpc, moduleData.rpcs))» + «FOR rpc : moduleData.getRpcs?:#[]» + «doSerialize(rpc, '', needsConnect(rpc, moduleData.getRpcs))» «ENDFOR» ''' } @@ -48,24 +48,24 @@ class DataTreeSerializer { } } val keys = if (ele instanceof ListData) { - ele.keys.empty ? null : ''' [«ele.keys.join(', ')»]''' + ele.getKeys.empty ? null : ''' [«ele.getKeys.join(', ')»]''' } val type = ele.getType // no idea why this indentation is needed in pyang val additionalIdent = if(ele.elementKind === ElementKind.Choice && prevSibling(ele)?.elementKind === ElementKind.Container) ' ' else '' ''' - «indent»«additionalIdent»+«prefix»«label»«ele.cardinality?.toString()»«keys»«IF type !== null» «type»«ENDIF»«IF ele.featureConditions !== null» {«ele.featureConditions.join(',')»}?«ENDIF» - «IF ele.children !== null» - «FOR child : ele.children» - «doSerialize(child, indent + (needsConnect?'| ':' '), needsConnect(child, ele.children))» + «indent»«additionalIdent»+«prefix»«label»«ele.cardinality?.toString()»«keys»«IF type !== null» «type»«ENDIF»«IF ele.getFeatureConditions !== null» {«ele.getFeatureConditions.join(',')»}?«ENDIF» + «IF ele.getChildren !== null» + «FOR child : ele.getChildren» + «doSerialize(child, indent + (needsConnect?'| ':' '), needsConnect(child, ele.getChildren))» «ENDFOR» «ENDIF» ''' } private def ElementData prevSibling(ElementData ele) { - val siblings = ele.parent?.children + val siblings = ele.getParent?.getChildren if(siblings === null) { return null } diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java index 8d99cdf2..ffb547fd 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureEvaluationContext.java @@ -7,7 +7,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import io.typefox.yang.processor.ProcessedDataTree.ElementIdentifier; +import io.typefox.yang.processor.ProcessedDataModel.ElementIdentifier; import io.typefox.yang.yang.Feature; public class FeatureEvaluationContext { diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureExpressions.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureExpressions.java index 88cdf488..fd982043 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureExpressions.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/FeatureExpressions.java @@ -2,7 +2,7 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils; -import io.typefox.yang.processor.ProcessedDataTree.ElementIdentifier; +import io.typefox.yang.processor.ProcessedDataModel.ElementIdentifier; import io.typefox.yang.yang.BinaryOperation; import io.typefox.yang.yang.Expression; import io.typefox.yang.yang.Feature; diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/JsonSerializer.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/JsonSerializer.java new file mode 100644 index 00000000..16c75b61 --- /dev/null +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/JsonSerializer.java @@ -0,0 +1,18 @@ +package io.typefox.yang.processor; + +import com.google.gson.GsonBuilder; + +import io.typefox.yang.processor.ProcessedDataModel.ModuleData; + +public class JsonSerializer { + + public CharSequence serialize(ModuleData moduleData) { + var writer = new StringBuilder(); + serialize(moduleData, writer); + return writer.toString(); + } + + public void serialize(ModuleData moduleData, Appendable writer) { + new GsonBuilder().setPrettyPrinting().create().toJson(moduleData, writer); + } +} diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataTree.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataModel.java similarity index 88% rename from yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataTree.java rename to yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataModel.java index f0768f79..64ed45d5 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataTree.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessedDataModel.java @@ -2,10 +2,10 @@ import static com.google.common.collect.Lists.newArrayList; +import java.util.ArrayList; import java.util.List; import java.util.Set; -import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import com.google.common.base.Objects; @@ -13,18 +13,21 @@ import io.typefox.yang.processor.FeatureExpressions.FeatureCondition; import io.typefox.yang.yang.Config; +import io.typefox.yang.yang.Default; import io.typefox.yang.yang.IfFeature; import io.typefox.yang.yang.Key; import io.typefox.yang.yang.Mandatory; +import io.typefox.yang.yang.MaxElements; +import io.typefox.yang.yang.MinElements; +import io.typefox.yang.yang.Must; import io.typefox.yang.yang.Path; import io.typefox.yang.yang.Presence; import io.typefox.yang.yang.SchemaNode; import io.typefox.yang.yang.Type; import io.typefox.yang.yang.TypeReference; import io.typefox.yang.yang.Typedef; -import io.typefox.yang.yang.XpathExpression; -public class ProcessedDataTree { +public class ProcessedDataModel { private List modules; @@ -169,8 +172,12 @@ public String toString() { } static public enum ElementKind { - Container, Leaf, LeafList, List, Rpc, Choice, Case, Action, Grouping, Refine, Uses, Input, Output, Notification, - AnyXml; + // SchemaNodes + Action, Case, Choice, + // DataSchemaNodes + AnyXml, Container, Leaf, LeafList, List, + // SchemaNodes + Grouping, Input, Notification, Output, Rpc; public static Set mayOmitCase = Sets.newHashSet(AnyXml, Container, Leaf, List, LeafList); } @@ -204,6 +211,16 @@ public static class ElementData extends HasStatements { transient private SchemaNode origin; + /** + * 0..1 Node properties that can be changed by a Deviate + */ + public String defaultValue, maxElements, minElements = null; + + /** + * 0..n Node properties that can be changed by a Deviate + */ + public List mustConstraint = null; + private ElementData(ElementIdentifier elementId, ElementKind elementKind) { super(elementId); this.elementKind = elementKind; @@ -268,6 +285,18 @@ protected void configureElement(SchemaNode ele) { this.cardinality = Cardinality.mandatory; } else if (sub instanceof Presence) { this.cardinality = Cardinality.presence; + } else if (sub instanceof Default) { + this.defaultValue = ((Default) sub).getDefaultStringValue(); + } else if (sub instanceof MaxElements) { + this.maxElements = ((MaxElements) sub).getMaxElements(); + } else if (sub instanceof MinElements) { + this.minElements = ((MinElements) sub).getMinElements(); + } else if (sub instanceof Must) { + if (this.mustConstraint == null) { + this.mustConstraint = new ArrayList<>(); + } + var exprAsString = ProcessorUtility.serializedXpath(((Must) sub).getConstraint()); + this.mustConstraint.add(exprAsString); } }); } @@ -279,7 +308,7 @@ private ValueType createValueType(Type typeStatement) { Path pathRef = (Path) typeStatement.getSubstatements().stream().filter(s -> s instanceof Path) .findFirst().get(); if (pathRef != null && pathRef.getReference() != null) { - return new ValueType(null, "-> " + serializedXpath(pathRef.getReference())); + return new ValueType(null, "-> " + ProcessorUtility.serializedXpath(pathRef.getReference())); } } return new ValueType(null, typeRef.getBuiltin()); @@ -308,22 +337,6 @@ private String referenceText(TypeReference typeRef) { return NodeModelUtils.getTokenText(node); } - private String serializedXpath(XpathExpression reference) { - // TODO use serializer or implement a an own simple one - ICompositeNode nodeFor = NodeModelUtils.findActualNodeFor(reference); - if (nodeFor != null) { - var nodeText = nodeFor.getText(); - nodeText = nodeText.replaceAll("\"|'|\\s|\n|\r", "").replaceAll("\\+", ""); - int firstColon = nodeText.indexOf(":"); - if (firstColon > 0) { - nodeText = nodeText.substring(0, firstColon) - + nodeText.substring(firstColon).replaceAll("\\/[a-zA-Z]+:", "/"); - } - return nodeText; - } - return "leafref"; - } - private void addFeatureCondition(String condition) { if (condition == null) { throw new IllegalArgumentException("Feature condition may not be null"); diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessorUtility.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessorUtility.java index 99fb2119..f2f256bc 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessorUtility.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/ProcessorUtility.java @@ -8,11 +8,12 @@ import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil.Copier; import org.eclipse.xtext.EcoreUtil2; +import org.eclipse.xtext.nodemodel.ICompositeNode; import org.eclipse.xtext.nodemodel.impl.CompositeNodeWithSemanticElement; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import io.typefox.yang.processor.FeatureExpressions.FeatureCondition; -import io.typefox.yang.processor.ProcessedDataTree.ElementIdentifier; +import io.typefox.yang.processor.ProcessedDataModel.ElementIdentifier; import io.typefox.yang.processor.YangProcessor.ForeignModuleAdapter; import io.typefox.yang.utils.YangExtensions; import io.typefox.yang.yang.AbstractModule; @@ -22,6 +23,7 @@ import io.typefox.yang.yang.SchemaNode; import io.typefox.yang.yang.Statement; import io.typefox.yang.yang.Submodule; +import io.typefox.yang.yang.XpathExpression; public class ProcessorUtility { @@ -108,4 +110,24 @@ protected EObject createCopy(EObject eObject) { copier.copyReferences(); return result; } + + public static String serializedXpath(XpathExpression reference) { + if(reference == null) { + return null; + } + // TODO use serializer or implement a an own simple one + ICompositeNode nodeFor = NodeModelUtils.findActualNodeFor(reference); + if (nodeFor != null) { + var nodeText = nodeFor.getText(); + nodeText = nodeText.replaceAll("\"|'|\\s|\n|\r", "").replaceAll("\\+", ""); + int firstColon = nodeText.indexOf(":"); + if (firstColon > 0) { + nodeText = nodeText.substring(0, firstColon) + + nodeText.substring(firstColon).replaceAll("\\/[a-zA-Z]+:", "/"); + } + return nodeText; + } + return "leafref"; + } + } diff --git a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/YangProcessor.java b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/YangProcessor.java index ffa066a5..b570795a 100644 --- a/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/YangProcessor.java +++ b/yang-lsp/io.typefox.yang/src/main/java/io/typefox/yang/processor/YangProcessor.java @@ -8,16 +8,19 @@ import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.EStructuralFeature; +import org.eclipse.emf.ecore.util.EcoreUtil; +import com.google.common.base.Objects; import com.google.common.collect.Lists; -import com.google.gson.GsonBuilder; -import io.typefox.yang.processor.ProcessedDataTree.ElementData; -import io.typefox.yang.processor.ProcessedDataTree.ElementIdentifier; -import io.typefox.yang.processor.ProcessedDataTree.ElementKind; -import io.typefox.yang.processor.ProcessedDataTree.HasStatements; -import io.typefox.yang.processor.ProcessedDataTree.ListData; -import io.typefox.yang.processor.ProcessedDataTree.ModuleData; +import io.typefox.yang.processor.ProcessedDataModel.ElementData; +import io.typefox.yang.processor.ProcessedDataModel.ElementIdentifier; +import io.typefox.yang.processor.ProcessedDataModel.ElementKind; +import io.typefox.yang.processor.ProcessedDataModel.HasStatements; +import io.typefox.yang.processor.ProcessedDataModel.ListData; +import io.typefox.yang.processor.ProcessedDataModel.ModuleData; import io.typefox.yang.yang.AbstractModule; import io.typefox.yang.yang.Action; import io.typefox.yang.yang.Augment; @@ -36,7 +39,6 @@ import io.typefox.yang.yang.Notification; import io.typefox.yang.yang.Output; import io.typefox.yang.yang.Prefix; -import io.typefox.yang.yang.Refine; import io.typefox.yang.yang.Rpc; import io.typefox.yang.yang.SchemaNode; import io.typefox.yang.yang.Statement; @@ -56,7 +58,7 @@ public static enum Format { * @return ProcessedDataTree or null if modules is * null or empty. */ - public ProcessedDataTree process(List modules, List includedFeatures, + public ProcessedDataModel process(List modules, List includedFeatures, List excludedFeatures) { if (modules == null || modules.isEmpty()) { return null; @@ -70,77 +72,30 @@ public ProcessedDataTree process(List modules, List incl * @param format tree or json. tree is default * @param output target */ - public void serialize(ProcessedDataTree processedData, Format format, StringBuilder output) { + public void serialize(ProcessedDataModel processedData, Format format, StringBuilder output) { + // TODO pick module by file name + ModuleData moduleData = processedData.getModules().get(0); switch (format) { case json: { - new GsonBuilder().setPrettyPrinting().create().toJson(processedData, output); + new JsonSerializer().serialize(moduleData, output); break; } case tree: { - // TODO pick module by file name - output.append(new DataTreeSerializer().serialize(processedData.getModules().get(0))); + output.append(new DataTreeSerializer().serialize(moduleData)); break; } } } - protected ProcessedDataTree processInternal(List modules, List includedFeatures, + protected ProcessedDataModel processInternal(List modules, List includedFeatures, List excludedFeatures) { var evalCtx = new FeatureEvaluationContext(includedFeatures, excludedFeatures); - ProcessedDataTree processedDataTree = new ProcessedDataTree(); + ProcessedDataModel processedDataTree = new ProcessedDataModel(); modules.forEach((module) -> module.eAllContents().forEachRemaining((ele) -> { if (ele instanceof Deviate) { - Deviate deviate = (Deviate) ele; - switch (deviate.getArgument()) { - // TODO implements other cases - case "add": - case "replace": - break; - case "delete": - case "not-supported": - var deviation = ((Deviation) ele.eContainer()); - SchemaNode schemaNode = deviation.getReference().getSchemaNode(); - Object eGet = schemaNode.eContainer().eGet(schemaNode.eContainingFeature(), true); - if (eGet instanceof EList) { - ((EList) eGet).remove(schemaNode); - } - break; - } + processDeviate((Deviate) ele); } else if (ele instanceof Augment) { - var augment = (Augment) ele; - List ifFeatures = ProcessorUtility.findIfFeatures(augment); - boolean featuresMatch = ProcessorUtility.checkIfFeatures(ifFeatures, evalCtx); - // disabled by feature - if (!featuresMatch) { - return; - } - var globalModuleId = ProcessorUtility.moduleIdentifier(module); - - augment.getSubstatements().stream().filter(sub -> !(sub instanceof IfFeature)) - .forEach((subStatement) -> { - // TODO check what can be added - if (subStatement instanceof SchemaNode) { - SchemaNode copy = ProcessorUtility.copyEObject((SchemaNode) subStatement); - // add augment's feature conditions to copied augment children - copy.getSubstatements().addAll(ProcessorUtility.copyAllEObjects(ifFeatures)); - - // memorize source module information as adapter - copy.eAdapters().add(new ForeignModuleAdapter(globalModuleId)); - - // Remove same named existing node - var existing = augment.getPath().getSchemaNode().getSubstatements().stream() - .filter((statement) -> { - if (statement instanceof SchemaNode) { - return copy.getName().equals(((SchemaNode) statement).getName()); - } - return false; - }).findFirst(); - if (existing.isPresent()) { - augment.getPath().getSchemaNode().getSubstatements().remove(existing.get()); - } - augment.getPath().getSchemaNode().getSubstatements().add(copy); - } - }); + processAugment((Augment) ele, module, evalCtx); } })); @@ -156,6 +111,114 @@ protected ProcessedDataTree processInternal(List modules, List matchingArguments(child, statement)).findFirst(); + if (existingProperty.isPresent()) { + targetNode.getSubstatements().remove(existingProperty.get()); + } + } + break; + case "replace": + for (Statement statement : deviate.getSubstatements()) { + var existingProperty = targetNode.getSubstatements().stream() + .filter(child -> child.eClass() == statement.eClass()).findFirst(); + if (existingProperty.isPresent()) { + targetNode.getSubstatements().remove(existingProperty.get()); + var copy = ProcessorUtility.copyEObject(statement); + targetNode.getSubstatements().add(copy); + } + } + break; + case "not-supported": + Object eGet = targetNode.eContainer().eGet(targetNode.eContainingFeature(), true); + if (eGet instanceof EList) { + ((EList) eGet).remove(targetNode); + } + break; + } + } + + /** + * The properties to delete are identified by substatements to the "delete" + * statement. The substatement's keyword MUST match a corresponding keyword in + * the target node, and the argument's string MUST be equal to the corresponding + * keyword's argument string in the target node. + * + * @param candidate + * @param objToMatch + * @return true if candidate matches the objToMatch + */ + protected boolean matchingArguments(Statement candidate, Statement objToMatch) { + if (candidate.eClass() != objToMatch.eClass()) { + return false; + } + for (EStructuralFeature feat : objToMatch.eClass().getEStructuralFeatures()) { + var toMatch = objToMatch.eGet(feat, true); + var candidateState = candidate.eGet(feat, true); + if (feat instanceof EReference && toMatch instanceof EObject && candidateState instanceof EObject) { + if (!EcoreUtil.equals((EObject) toMatch, (EObject) candidateState)) { + return false; + } + } else { + if (!Objects.equal(toMatch, candidateState)) { + return false; + } + } + } + return true; + } + + protected void processAugment(Augment augment, AbstractModule module, FeatureEvaluationContext evalCtx) { + List ifFeatures = ProcessorUtility.findIfFeatures(augment); + boolean featuresMatch = ProcessorUtility.checkIfFeatures(ifFeatures, evalCtx); + // disabled by feature + if (!featuresMatch) { + return; + } + var globalModuleId = ProcessorUtility.moduleIdentifier(module); + + augment.getSubstatements().stream().filter(sub -> !(sub instanceof IfFeature)).forEach((subStatement) -> { + // TODO check what can be added + if (subStatement instanceof SchemaNode) { + SchemaNode copy = ProcessorUtility.copyEObject((SchemaNode) subStatement); + // add augment's feature conditions to copied augment children + copy.getSubstatements().addAll(ProcessorUtility.copyAllEObjects(ifFeatures)); + + // memorize source module information as adapter + copy.eAdapters().add(new ForeignModuleAdapter(globalModuleId)); + + // Remove same named existing node + var existing = augment.getPath().getSchemaNode().getSubstatements().stream().filter((statement) -> { + if (statement instanceof SchemaNode) { + return copy.getName().equals(((SchemaNode) statement).getName()); + } + return false; + }).findFirst(); + if (existing.isPresent()) { + augment.getPath().getSchemaNode().getSubstatements().remove(existing.get()); + } + augment.getPath().getSchemaNode().getSubstatements().add(copy); + } + }); + } + protected void processChildren(Statement statement, HasStatements parent, FeatureEvaluationContext evalCtx) { statement.getSubstatements().stream().filter(ele -> ProcessorUtility.isEnabled(ele, evalCtx)).forEach((ele) -> { ElementData child = null; @@ -191,8 +254,6 @@ protected void processChildren(Statement statement, HasStatements parent, Featur } processChildren(grouping, parent, evalCtx); - } else if (ele instanceof Refine) { - child = new ElementData((SchemaNode) ele, ElementKind.Refine); } else if (ele instanceof Input) { child = new ElementData((SchemaNode) ele, ElementKind.Input, "input"); } else if (ele instanceof Output) { @@ -219,15 +280,6 @@ protected void processChildren(Statement statement, HasStatements parent, Featur }); } -// private ModuleData parentModule(HasStatements element) { -// if (element instanceof ModuleData) { -// return (ModuleData) element; -// } else if (element.getParent() != null) { -// return parentModule(element.getParent()); -// } -// return null; -// } - public static class ForeignModuleAdapter extends AdapterImpl { final ElementIdentifier moduleId; diff --git a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/DeviationTest.xtend b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/DeviationTest.xtend new file mode 100644 index 00000000..19ad044e --- /dev/null +++ b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/DeviationTest.xtend @@ -0,0 +1,162 @@ +package io.typefox.yang.tests.processor + +import io.typefox.yang.processor.JsonSerializer +import io.typefox.yang.processor.YangProcessor +import io.typefox.yang.tests.AbstractYangTest +import org.junit.Test + +import static org.junit.Assert.assertEquals + +class DeviationTest extends AbstractYangTest { + + @Test + def void testDeviationProcessing() { + + val mainModule = ''' + module base-test-module { + yang-version 1.1; + namespace urn:ietf:params:xml:ns:yang:base-test-module; + prefix base-test-module; + + container system { + must "daytime or time"; + must "user"; + + container daytime { + leaf date { + type string; + } + } + + leaf time { + type string; + } + + container user { + leaf type { + type string { + length "1..10"; + } + } + } + leaf-list name-server { + type string; + max-elements 10; + } + } + } + '''.load().root + + val deviateModule = ''' + module example-deviations { + yang-version 1.1; + namespace "urn:example:deviations"; + prefix md; + + import base-test-module { + prefix base; + } + + deviation /base:system/base:daytime { + // server does not support the "daytime" service + deviate not-supported; + } + + deviation /base:system/base:user/base:type { + deviate add { + // the users are admin by default + default "admin"; + } + } + + deviation /base:system/base:name-server { + deviate replace { + // the server limits the number of name servers to 3 + max-elements 3; + } + } + + deviation /base:system { + deviate delete { + // remove this "must" constraint + must "daytime or time"; + } + } + } + '''.load().root + mainModule.assertNoErrors; + deviateModule.assertNoErrors; + val processor = new YangProcessor() + val processedData = processor.process(#[mainModule, deviateModule], null, null) + + var asJson = new JsonSerializer().serialize(processedData.getModules.head) + assertEquals(''' + { + "children": [ + { + "elementKind": "Container", + "accessKind": "rw", + "cardinality": "not_set", + "mustConstraint": [ + "user" + ], + "children": [ + { + "elementKind": "Leaf", + "type": { + "name": "string" + }, + "accessKind": "rw", + "cardinality": "optional", + "id": { + "name": "time" + } + }, + { + "elementKind": "Container", + "accessKind": "rw", + "cardinality": "not_set", + "children": [ + { + "elementKind": "Leaf", + "type": { + "name": "string" + }, + "accessKind": "rw", + "cardinality": "optional", + "defaultValue": "admin", + "id": { + "name": "type" + } + } + ], + "id": { + "name": "user" + } + }, + { + "elementKind": "LeafList", + "type": { + "name": "string" + }, + "accessKind": "rw", + "cardinality": "many", + "maxElements": "3", + "id": { + "name": "name-server" + } + } + ], + "id": { + "name": "system" + } + } + ], + "id": { + "name": "base-test-module", + "prefix": "base-test-module" + } + }'''.toString(), asJson.toString()) + } + +} diff --git a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java index 8f353d3d..0bda2de8 100644 --- a/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java +++ b/yang-lsp/io.typefox.yang/src/test/java/io/typefox/yang/tests/processor/YangProcessorTest.java @@ -20,8 +20,8 @@ import com.google.gson.GsonBuilder; import io.typefox.yang.processor.DataTreeSerializer; -import io.typefox.yang.processor.ProcessedDataTree; -import io.typefox.yang.processor.ProcessedDataTree.ModuleData; +import io.typefox.yang.processor.ProcessedDataModel; +import io.typefox.yang.processor.ProcessedDataModel.ModuleData; import io.typefox.yang.processor.YangProcessor; import io.typefox.yang.tests.AbstractYangTest; import io.typefox.yang.yang.AbstractModule; @@ -175,7 +175,7 @@ private Optional processData(boolean withDeviation, List inc } }); - ProcessedDataTree dataTree = new YangProcessor().process(modules, includedFeatures, excludedFeatures); + ProcessedDataModel dataTree = new YangProcessor().process(modules, includedFeatures, excludedFeatures); var sysModule = dataTree.getModules().stream().filter(mod -> "ietf-system".equals(mod.getSimpleName())).findFirst(); return sysModule; diff --git a/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-nodev.json b/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-nodev.json index 9c88d01b..cc7661c7 100644 --- a/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-nodev.json +++ b/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation-nodev.json @@ -169,6 +169,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "true", "id": { "name": "enabled" } @@ -230,6 +231,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "123", "id": { "name": "port" } @@ -256,6 +258,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "server", "id": { "name": "association-type" } @@ -267,6 +270,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", "id": { "name": "iburst" } @@ -278,6 +282,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", "id": { "name": "prefer" } @@ -366,6 +371,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "53", "id": { "name": "port" } @@ -402,6 +408,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "timeout" } @@ -413,6 +420,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "2", "id": { "name": "attempts" } @@ -489,6 +497,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "1812", "id": { "name": "authentication-port" } @@ -526,6 +535,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "radius-pap", "id": { "name": "authentication-type" } @@ -547,6 +557,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "timeout" } @@ -558,6 +569,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "2", "id": { "name": "attempts" } @@ -587,6 +599,9 @@ }, "accessKind": "rw", "cardinality": "many", + "mustConstraint": [ + "(.!\u003dsys:radiusor../../radius/server)" + ], "id": { "name": "user-authentication-order" } diff --git a/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation.json b/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation.json index ae6a76b1..8a258e24 100644 --- a/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation.json +++ b/yang-lsp/io.typefox.yang/src/test/resources/processor/expectation/expectation.json @@ -161,6 +161,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "true", "id": { "name": "enabled" } @@ -222,6 +223,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "123", "id": { "name": "port" } @@ -248,6 +250,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "server", "id": { "name": "association-type" } @@ -259,6 +262,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", "id": { "name": "iburst" } @@ -270,6 +274,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", "id": { "name": "prefer" } @@ -284,6 +289,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "unlocked", "id": { "name": "administrative-state", "prefix": "sysxext" @@ -433,6 +439,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "53", "id": { "name": "port" } @@ -469,6 +476,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "timeout" } @@ -480,6 +488,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "2", "id": { "name": "attempts" } @@ -556,6 +565,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "1812", "id": { "name": "authentication-port" } @@ -593,6 +603,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "radius-pap", "id": { "name": "authentication-type" } @@ -614,6 +625,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "timeout" } @@ -625,6 +637,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "2", "id": { "name": "attempts" } @@ -654,6 +667,9 @@ }, "accessKind": "rw", "cardinality": "many", + "mustConstraint": [ + "(.!\u003dsys:radiusor../../radius/server)" + ], "id": { "name": "user-authentication-order" } @@ -778,6 +794,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "unlocked", "id": { "name": "administrative-state", "prefix": "sysxext" @@ -793,6 +810,9 @@ ], "accessKind": "rw", "cardinality": "many", + "mustConstraint": [ + "count(/nacm:nacm/groups/group/user-name)\u003d0" + ], "id": { "name": "groups", "prefix": "sysxext" @@ -855,6 +875,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "3", "id": { "name": "authentication-failure-delay", "prefix": "sysxext" @@ -886,6 +907,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "IF YOU ARE NOT AN AUTHORIZED USER,\n PLEASE EXIT IMMEDIATELY", "id": { "name": "legal-notice", "prefix": "sysxext" @@ -901,6 +923,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "This system processes sensitive personal data.\n The misuse of such data may generate considerable\n harm to the data subjects. Be reminded of the\n confidentiality obligations you have when accessing\n this kind of data and the disciplinary consequences\n of improper handling.\n Version: 1.0, Last Updated: May 21, 2019", "id": { "name": "privacy-notice", "prefix": "sysxext" @@ -916,6 +939,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "", "id": { "name": "post-login-notice", "prefix": "sysxext" @@ -1135,6 +1159,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "7", "id": { "name": "expiry-warning", "prefix": "sysxext" @@ -1147,6 +1172,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "1800", "id": { "name": "failure-count-interval", "prefix": "sysxext" @@ -1159,6 +1185,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "12", "id": { "name": "history-length", "prefix": "sysxext" @@ -1171,6 +1198,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "0", "id": { "name": "lockout-duration", "prefix": "sysxext" @@ -1183,6 +1211,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "90", "id": { "name": "max-age", "prefix": "sysxext" @@ -1195,6 +1224,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "max-failure", "prefix": "sysxext" @@ -1207,6 +1237,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "15", "id": { "name": "min-age", "prefix": "sysxext" @@ -1219,6 +1250,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "12", "id": { "name": "min-length", "prefix": "sysxext" @@ -1231,6 +1263,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "true", "id": { "name": "must-change", "prefix": "sysxext" @@ -1284,6 +1317,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "7", "id": { "name": "expiry-warning", "prefix": "sysxext" @@ -1296,6 +1330,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "1800", "id": { "name": "failure-count-interval", "prefix": "sysxext" @@ -1308,6 +1343,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "12", "id": { "name": "history-length", "prefix": "sysxext" @@ -1320,6 +1356,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "0", "id": { "name": "lockout-duration", "prefix": "sysxext" @@ -1332,6 +1369,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "90", "id": { "name": "max-age", "prefix": "sysxext" @@ -1344,6 +1382,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "max-failure", "prefix": "sysxext" @@ -1356,6 +1395,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "15", "id": { "name": "min-age", "prefix": "sysxext" @@ -1368,6 +1408,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "12", "id": { "name": "min-length", "prefix": "sysxext" @@ -1380,6 +1421,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "true", "id": { "name": "must-change", "prefix": "sysxext" @@ -1430,6 +1472,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "180", "id": { "name": "dormant-timer", "prefix": "sysxext" @@ -1442,6 +1485,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "0", "id": { "name": "dormant-action", "prefix": "sysxext" @@ -1454,6 +1498,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "90", "id": { "name": "dormant-action-timer", "prefix": "sysxext" @@ -1495,6 +1540,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "180", "id": { "name": "dormant-timer", "prefix": "sysxext" @@ -1507,6 +1553,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "0", "id": { "name": "dormant-action", "prefix": "sysxext" @@ -1519,6 +1566,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "90", "id": { "name": "dormant-action-timer", "prefix": "sysxext" @@ -1628,6 +1676,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "86400", "id": { "name": "failure-count-interval", "prefix": "sysxext" @@ -1643,6 +1692,7 @@ ], "accessKind": "rw", "cardinality": "optional", + "defaultValue": "3", "id": { "name": "max-failure", "prefix": "sysxext" @@ -1815,6 +1865,9 @@ }, "accessKind": "rw", "cardinality": "mandatory", + "mustConstraint": [ + "../public-key-format" + ], "id": { "name": "public-key", "prefix": "sysxext" @@ -1940,6 +1993,7 @@ "elementKind": "List", "accessKind": "rw", "cardinality": "many", + "minElements": "1", "children": [ { "elementKind": "Leaf", @@ -1985,6 +2039,7 @@ "elementKind": "Choice", "accessKind": "rw", "cardinality": "optional", + "defaultValue": "ldap", "children": [ { "elementKind": "Case", @@ -2004,6 +2059,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "389", "id": { "name": "port", "prefix": "sysxext" @@ -2030,6 +2086,9 @@ "elementKind": "Container", "accessKind": "rw", "cardinality": "presence", + "mustConstraint": [ + "(/sys:system/ldap/security/tls)" + ], "children": [ { "elementKind": "Leaf", @@ -2039,6 +2098,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "636", "id": { "name": "port", "prefix": "sysxext" @@ -2208,6 +2268,9 @@ }, "accessKind": "rw", "cardinality": "mandatory", + "mustConstraint": [ + "../public-key-format" + ], "id": { "name": "public-key", "prefix": "sysxext" @@ -2241,6 +2304,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "../private-key-format" + ], "id": { "name": "private-key", "prefix": "sysxext" @@ -2282,6 +2348,9 @@ "elementKind": "Container", "accessKind": "rw", "cardinality": "not_set", + "mustConstraint": [ + "../private-key-format" + ], "id": { "name": "encrypted-private-key", "prefix": "sysxext" @@ -2511,6 +2580,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "../certificate" + ], "id": { "name": "asymmetric-key", "prefix": "sysxext" @@ -2523,6 +2595,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "../asymmetric-key" + ], "id": { "name": "certificate", "prefix": "sysxext" @@ -2618,6 +2693,9 @@ }, "accessKind": "rw", "cardinality": "mandatory", + "mustConstraint": [ + "../public-key-format" + ], "id": { "name": "public-key", "prefix": "sysxext" @@ -2651,6 +2729,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "../private-key-format" + ], "id": { "name": "private-key", "prefix": "sysxext" @@ -2692,6 +2773,9 @@ "elementKind": "Container", "accessKind": "rw", "cardinality": "not_set", + "mustConstraint": [ + "../private-key-format" + ], "id": { "name": "encrypted-private-key", "prefix": "sysxext" @@ -2908,6 +2992,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "../key-format" + ], "id": { "name": "key", "prefix": "sysxext" @@ -2949,6 +3036,9 @@ "elementKind": "Container", "accessKind": "rw", "cardinality": "not_set", + "mustConstraint": [ + "../key-format" + ], "id": { "name": "encrypted-key", "prefix": "sysxext" @@ -3106,6 +3196,9 @@ "elementKind": "Container", "accessKind": "rw", "cardinality": "not_set", + "mustConstraint": [ + "ca-certsorserver-certs" + ], "children": [ { "elementKind": "Container", @@ -3415,6 +3508,9 @@ }, "accessKind": "rw", "cardinality": "mandatory", + "mustConstraint": [ + "../public-key-format" + ], "id": { "name": "public-key", "prefix": "sysxext" @@ -3571,6 +3667,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "30", "id": { "name": "max-wait", "prefix": "sysxext" @@ -3583,6 +3680,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "3", "id": { "name": "max-attempts", "prefix": "sysxext" @@ -3735,6 +3833,9 @@ }, "accessKind": "rw", "cardinality": "optional", + "mustConstraint": [ + "(/sys:system/ldap/security/tls)" + ], "id": { "name": "sasl-external", "prefix": "sysxext" @@ -3782,6 +3883,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "5", "id": { "name": "timeout", "prefix": "sysxext" @@ -3794,6 +3896,7 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", "id": { "name": "enable-referrals", "prefix": "sysxext" @@ -3808,6 +3911,7 @@ "elementKind": "Choice", "accessKind": "rw", "cardinality": "optional", + "defaultValue": "example-filter", "children": [ { "elementKind": "Case", @@ -3901,6 +4005,10 @@ }, "accessKind": "rw", "cardinality": "optional", + "defaultValue": "false", + "mustConstraint": [ + "(.\u003dfalseor/sys:system/authentication/target-types)" + ], "id": { "name": "enable-target-based-access-control", "prefix": "sysxext" @@ -3961,6 +4069,9 @@ ], "accessKind": "rw", "cardinality": "not_set", + "mustConstraint": [ + "not(/sys:system/authentication[sys:user-authentication-order\u003dsysxext:ldap])or/system/ldap/server" + ], "id": { "name": "ldap-checks", "prefix": "sysxext"