diff --git a/src/main/java/org/raml/parser/builder/DefaultTupleBuilder.java b/src/main/java/org/raml/parser/builder/DefaultTupleBuilder.java index 7cb523ee..22b903ee 100644 --- a/src/main/java/org/raml/parser/builder/DefaultTupleBuilder.java +++ b/src/main/java/org/raml/parser/builder/DefaultTupleBuilder.java @@ -65,7 +65,7 @@ public NodeBuilder getBuilderForTuple(NodeTuple tuple) return tupleBuilder; } } - throw new RuntimeException("Builder not found for " + tuple); + return null; } protected Map> getBuilders() diff --git a/src/main/java/org/raml/parser/builder/TupleBuilderFactory.java b/src/main/java/org/raml/parser/builder/TupleBuilderFactory.java index 749315d7..7dc767be 100644 --- a/src/main/java/org/raml/parser/builder/TupleBuilderFactory.java +++ b/src/main/java/org/raml/parser/builder/TupleBuilderFactory.java @@ -18,7 +18,7 @@ import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -38,7 +38,7 @@ public class TupleBuilderFactory extends AbstractFactory public void addBuildersTo(Class pojoClass, TupleBuilder parent) { final List declaredFields = ReflectionUtils.getInheritedFields(pojoClass); - final Map> innerBuilders = new HashMap>(); + final Map> innerBuilders = new LinkedHashMap>(); for (Field declaredField : declaredFields) { Scalar scalar = declaredField.getAnnotation(Scalar.class); diff --git a/src/main/java/org/raml/parser/visitor/RamlDocumentBuilder.java b/src/main/java/org/raml/parser/visitor/RamlDocumentBuilder.java index 9b73acac..b5a7f9ae 100644 --- a/src/main/java/org/raml/parser/visitor/RamlDocumentBuilder.java +++ b/src/main/java/org/raml/parser/visitor/RamlDocumentBuilder.java @@ -49,9 +49,19 @@ public RamlDocumentBuilder() this(new DefaultResourceLoader()); } + public RamlDocumentBuilder(Class documentClass) + { + this(documentClass, new DefaultResourceLoader()); + } + public RamlDocumentBuilder(ResourceLoader resourceLoader, TagResolver... tagResolvers) { - super(Raml.class, resourceLoader, defaultResolver(tagResolvers)); + this(Raml.class, resourceLoader, tagResolvers); + } + + public RamlDocumentBuilder(Class documentClass, ResourceLoader resourceLoader, TagResolver... tagResolvers) + { + super(documentClass, resourceLoader, defaultResolver(tagResolvers)); } private static TagResolver[] defaultResolver(TagResolver[] tagResolvers) diff --git a/src/main/java/org/raml/parser/visitor/YamlDocumentBuilder.java b/src/main/java/org/raml/parser/visitor/YamlDocumentBuilder.java index ecb9d601..4b84813f 100644 --- a/src/main/java/org/raml/parser/visitor/YamlDocumentBuilder.java +++ b/src/main/java/org/raml/parser/visitor/YamlDocumentBuilder.java @@ -56,7 +56,7 @@ public class YamlDocumentBuilder implements NodeHandler, ContextPathAware { - private Class documentClass; + private Class documentClass; private T documentObject; private Stack> builderContext = new Stack>(); private Stack documentContext = new Stack(); @@ -65,7 +65,7 @@ public class YamlDocumentBuilder implements NodeHandler, ContextPathAware private TagResolver[] tagResolvers; private ContextPath contextPath; - public YamlDocumentBuilder(Class documentClass, ResourceLoader resourceLoader, TagResolver... tagResolvers) + public YamlDocumentBuilder(Class documentClass, ResourceLoader resourceLoader, TagResolver... tagResolvers) { this.documentClass = documentClass; this.resourceLoader = resourceLoader; @@ -270,6 +270,10 @@ public boolean onTupleStart(NodeTuple nodeTuple) if (currentBuilder != null) { NodeBuilder builder = currentBuilder.getBuilderForTuple(nodeTuple); + if (builder == null) + { + return false; + } builderContext.push(builder); } else diff --git a/src/main/java/org/raml/parser/visitor/YamlDocumentSuggester.java b/src/main/java/org/raml/parser/visitor/YamlDocumentSuggester.java index 5290dc31..1a393efd 100644 --- a/src/main/java/org/raml/parser/visitor/YamlDocumentSuggester.java +++ b/src/main/java/org/raml/parser/visitor/YamlDocumentSuggester.java @@ -307,7 +307,11 @@ public boolean onTupleStart(NodeTuple nodeTuple) { try { - builder.onTupleStart(nodeTuple); + boolean found = builder.onTupleStart(nodeTuple); + if (!found) + { + return false; + } MappingNode mapping = nodeTuple.getValueNode().getNodeId() == NodeId.mapping ? (MappingNode) nodeTuple.getValueNode() : null; pushNode(nodeTuple.getKeyNode(), mapping); } diff --git a/src/test/java/org/raml/parser/visitor/RamlDocumentBuilderTestCase.java b/src/test/java/org/raml/parser/visitor/RamlDocumentBuilderTestCase.java new file mode 100644 index 00000000..c323c7ce --- /dev/null +++ b/src/test/java/org/raml/parser/visitor/RamlDocumentBuilderTestCase.java @@ -0,0 +1,101 @@ +/* + * Copyright 2015 (c) SAP SE + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package org.raml.parser.visitor; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.raml.model.Raml; +import org.raml.model.SecurityScheme; +import org.raml.parser.annotation.Mapping; +import org.raml.parser.annotation.Scalar; +import org.raml.parser.annotation.Sequence; +import org.raml.parser.builder.AbstractRamlTestCase; + +public class RamlDocumentBuilderTestCase extends AbstractRamlTestCase +{ + + @Test + public void parseExtendedModel() + { + RamlExt raml = (RamlExt) new RamlDocumentBuilder(RamlExt.class).build("org/raml/parser/visitor/extended.yaml"); + assertThat(raml.getTitle(), is("extended model")); // standard property + assertThat(raml.getExtension(), is("additional data")); // non-standard property + } + + @Test + public void parseModelWithExtensionInExistingKey() + { + RamlExt2 raml = (RamlExt2) new RamlDocumentBuilder(RamlExt2.class).build("org/raml/parser/visitor/extended.yaml"); + SecuritySchemeExt scheme = raml.getSecuritySchemesExt().get(0).get("extended"); + assertThat(scheme.getDescription(), is(notNullValue())); + assertThat(scheme.getExtension().get("key1"), is("foo")); + } + + public static class RamlExt extends Raml + { + private static final long serialVersionUID = 533345138584973337L; + + @Scalar + private String extension; + + public String getExtension() { + return extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + } + + public static class RamlExt2 extends Raml + { + private static final long serialVersionUID = 1451208177799874616L; + + @Sequence(alias = "securitySchemes") + private List> securitySchemesExt = new ArrayList>(); + + public List> getSecuritySchemesExt() { + return securitySchemesExt; + } + + public void setSecuritySchemesExt(List> securitySchemesExt) { + this.securitySchemesExt = securitySchemesExt; + } + } + + public static class SecuritySchemeExt extends SecurityScheme + { + private static final long serialVersionUID = -7059558387326732177L; + + @Mapping + private Map extension; + + public Map getExtension() { + return extension; + } + + public void setExtension(Map extension) { + this.extension = extension; + } + } +} diff --git a/src/test/java/org/raml/validation/ValidationTestCase.java b/src/test/java/org/raml/validation/ValidationTestCase.java index 2800465c..3572ffa3 100644 --- a/src/test/java/org/raml/validation/ValidationTestCase.java +++ b/src/test/java/org/raml/validation/ValidationTestCase.java @@ -16,6 +16,7 @@ package org.raml.validation; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.matchers.JUnitMatchers.containsString; @@ -28,11 +29,13 @@ import org.junit.Ignore; import org.junit.Test; +import org.raml.model.ActionType; import org.raml.model.Raml; import org.raml.parser.builder.AbstractRamlTestCase; import org.raml.parser.rule.ValidationResult; import org.raml.parser.tagresolver.ContextPath; import org.raml.parser.visitor.IncludeInfo; +import org.raml.parser.visitor.RamlDocumentBuilder; public class ValidationTestCase extends AbstractRamlTestCase { @@ -222,6 +225,21 @@ public void circularInclude() assertThat(includeInfo.getLine() + 1, is(3)); } + @Test + public void unknownKey() + { + String resource = "org/raml/validation/unknown-key.yaml"; + + // validation reports the unknown key... + List validationResults = validateRaml(resource); + assertThat(validationResults.size(), is(1)); + assertThat(validationResults.get(0).getMessage(), is("Unknown key: unknown")); + + // ... but the parser doesn't choke on it + Raml validContent = new RamlDocumentBuilder().build(resource); + assertThat(validContent.getResource("/partiallyInvalid").getAction(ActionType.POST), is(notNullValue())); + } + @Test public void badMediaTypeName() { diff --git a/src/test/resources/org/raml/parser/visitor/extended.yaml b/src/test/resources/org/raml/parser/visitor/extended.yaml new file mode 100644 index 00000000..a95f546b --- /dev/null +++ b/src/test/resources/org/raml/parser/visitor/extended.yaml @@ -0,0 +1,10 @@ +#%RAML 0.8 +title: extended model +extension: additional data + +securitySchemes: + - extended: + description: Security schema documentation + extension: + key1: foo + key2: bar diff --git a/src/test/resources/org/raml/validation/unknown-key.yaml b/src/test/resources/org/raml/validation/unknown-key.yaml new file mode 100644 index 00000000..a4499700 --- /dev/null +++ b/src/test/resources/org/raml/validation/unknown-key.yaml @@ -0,0 +1,7 @@ +#%RAML 0.8 +title: unknown key +/partiallyInvalid: + post: + unknown: + body: + application/json: