diff --git a/src/main/java/org/raml/parser/visitor/MediaTypeResolver.java b/src/main/java/org/raml/parser/visitor/MediaTypeResolver.java index 83ac59bf..2efeb38d 100644 --- a/src/main/java/org/raml/parser/visitor/MediaTypeResolver.java +++ b/src/main/java/org/raml/parser/visitor/MediaTypeResolver.java @@ -109,26 +109,78 @@ private boolean isValidMediaType(String value) public List resolve(MappingNode bodyNode) { List validationResults = new ArrayList(); - if (mediaType == null) - { - return validationResults; - } - for (NodeTuple tuple : bodyNode.getValue()) - { - if (!MEDIA_TYPE_KEYS.contains(((ScalarNode) tuple.getKeyNode()).getValue())) - { - return validationResults; - } + + if (mediaType!=null){ + + NodeTuple mediaTypeNodeTuple = findNodeByKey(bodyNode, mediaType); + + //if mediaTypeNode is null create it, but do not add it to the parent node yet + if (mediaTypeNodeTuple == null){ + Node keyNode = new ScalarNode(Tag.STR, mediaType, null, null, null); + Node valueNode = new MappingNode(Tag.MAP, new ArrayList(), false); + NodeTuple newTuple = new NodeTuple(keyNode, valueNode); + mediaTypeNodeTuple = newTuple; + } + + moveOrphanedNodes(mediaTypeNodeTuple, bodyNode); + + // in case the mediaTypeNodeTuple was not yet appended to the parent node, + // append it but only if it is not empty + if (!bodyNode.getValue().contains(mediaTypeNodeTuple) && + !((MappingNode)mediaTypeNodeTuple.getValueNode()).getValue().isEmpty()){ + bodyNode.getValue().add(mediaTypeNodeTuple); + } } - List copy = new ArrayList(bodyNode.getValue()); - Node keyNode = new ScalarNode(Tag.STR, mediaType, null, null, null); - Node valueNode = new MappingNode(Tag.MAP, copy, false); - bodyNode.getValue().clear(); - bodyNode.getValue().add(new NodeTuple(keyNode, valueNode)); + return validationResults; } /** + * It is moving all orphaned schema/example/formParameters nodes from the source node under the + * targetTouple node. + * + * @param targetTouple the target node which should become new parent of orphaned nodes + * @param sourceNode the current parent node of the possibly orphaned nodes + */ + private void moveOrphanedNodes(NodeTuple targetTouple, MappingNode sourceNode) { + + List tuplesToRemove = new ArrayList(); + + for (NodeTuple sourceTuple : sourceNode.getValue()) + { + String mediaTypeKey = ((ScalarNode) sourceTuple.getKeyNode()).getValue(); + + // if it is an orphaned schema/example/formParameters + if (MEDIA_TYPE_KEYS.contains(mediaTypeKey)) + { + // move it to the targetTouple + tuplesToRemove.add(sourceTuple); + MappingNode targetToupleNode = (MappingNode)targetTouple.getValueNode(); + NodeTuple existingTuple = findNodeByKey(targetToupleNode, mediaTypeKey); + if (existingTuple!=null){ + targetToupleNode.getValue().remove(existingTuple); + } + targetToupleNode.getValue().add(sourceTuple); + } + } + + // remove the orphaned schema/example/formParameters that were marked for removal earlier + for (NodeTuple toRemove: tuplesToRemove){ + sourceNode.getValue().remove(toRemove); + } + } + + private NodeTuple findNodeByKey(MappingNode parentNode, String key) { + for (NodeTuple tuple : parentNode.getValue()) + { + if (key.equals(((ScalarNode) tuple.getKeyNode()).getValue())){ + return tuple; + } + } + return null; + } + + /** * if no explicit media type is defined in either the request or * response body, the default one is applied * diff --git a/src/test/java/org/raml/parser/builder/DefaultMediaTypeTestCase.java b/src/test/java/org/raml/parser/builder/DefaultMediaTypeTestCase.java index 14c66938..a6b92d3c 100644 --- a/src/test/java/org/raml/parser/builder/DefaultMediaTypeTestCase.java +++ b/src/test/java/org/raml/parser/builder/DefaultMediaTypeTestCase.java @@ -17,6 +17,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.raml.model.ActionType.GET; @@ -28,6 +29,7 @@ import org.apache.commons.io.IOUtils; import org.junit.BeforeClass; import org.junit.Test; +import org.mockito.internal.matchers.NotNull; import org.raml.model.ActionType; import org.raml.model.MimeType; import org.raml.model.Raml; @@ -93,6 +95,42 @@ public void emptyBody() assertThat(resource.getAction(PUT).getBody().size(), is(1)); assertThat(resource.getAction(PUT).getResponses().get("200").getBody().size(), is(1)); } + + @Test + public void mergedWithType() + { + Resource resource = raml.getResource("/mergedWithType"); + Map getResponseBody = resource.getAction(ActionType.GET).getResponses().get("200").getBody(); + assertThat(getResponseBody.size(), is(2)); + + assertThat(getResponseBody.containsKey("application/json"), is(true)); + MimeType applicationJson = getResponseBody.get("application/json"); + assertThat(applicationJson.getSchema(), notNullValue()); + assertThat(applicationJson.getSchema().contains("merged"), is(true)); + assertThat(applicationJson.getExample(), notNullValue()); + assertThat(applicationJson.getExample().contains("foo"), is(true)); + + assertThat(getResponseBody.containsKey("text/html"), is(true)); + MimeType textHtml = getResponseBody.get("text/html"); + assertThat(textHtml.getSchema(), nullValue()); + assertThat(textHtml.getExample(), notNullValue()); + assertThat(textHtml.getExample().contains("dummy resource example"), is(true)); + } + + @Test + public void merged() + { + Resource resource = raml.getResource("/merged"); + Map getResponseBody = resource.getAction(ActionType.GET).getResponses().get("200").getBody(); + assertThat(getResponseBody.size(), is(1)); + + assertThat(getResponseBody.containsKey("application/json"), is(false)); + assertThat(getResponseBody.containsKey("text/html"), is(true)); + + MimeType textHtml = getResponseBody.get("text/html"); + assertThat(textHtml.getSchema().contains("merged"), is(true)); + assertThat(textHtml.getExample(), nullValue()); + } @Test public void noBody() @@ -123,4 +161,5 @@ public void emitter() assertThat(raml2.getResources().get("/simple").getActions().size(), is(raml1.getResources().get("/simple").getActions().size())); } + } diff --git a/src/test/resources/org/raml/media-type.yaml b/src/test/resources/org/raml/media-type.yaml index 47910493..9988bd2f 100644 --- a/src/test/resources/org/raml/media-type.yaml +++ b/src/test/resources/org/raml/media-type.yaml @@ -32,6 +32,26 @@ resourceTypes: type: typeParent put: is: [traitOne] + - dummy: + get: + responses: + 200: + body: + example: | + { + "id": "foo" + } + application/json: + schema: | + { "$schema": "http://json-schema.org/draft-03/schema", + "type": "object", + "description": "A dummy schema", + "properties": { + "id": { "type": "string" } + } + } + text/html: + example: dummy resource example /simple: type: typeChild @@ -57,3 +77,34 @@ resourceTypes: get: responses: 200: + +/merged: + get: + responses: + 200: + body: + text/html: + schema: | + { "$schema": "http://json-schema.org/draft-03/schema", + "type": "object", + "description": "A merged schema", + "properties": { + "id": { "type": "string" } + } + } + +/mergedWithType: + type: dummy + get: + responses: + 200: + body: + schema: | + { "$schema": "http://json-schema.org/draft-03/schema", + "type": "object", + "description": "A merged schema", + "properties": { + "id": { "type": "string" } + } + } +