From 6ab0aae9f5fcc5a464670c85e8249c575c4d9b9e Mon Sep 17 00:00:00 2001 From: Jozef Gloncak Date: Wed, 16 Jul 2014 14:03:20 +0200 Subject: [PATCH] BUG 932 - Swagger HTTP POST contains incorrect object POST link now contains specific JSON structure which ends with POST suffix and contains all containers and lists which are direct subchildren of current node (URI link). It is list of possible objects which can be used in POST request. Concretelly are these objects specified below this *POST JSON object and are also all specified in Parameters section of GUI POST URI link with ** prefix. Two stars (**) mean that only one of parameters with ** prefix should be specified. Change-Id: I3acdfd1f181fbef7cca0060d534b706d934bb1e3 Signed-off-by: Jozef Gloncak --- .../doc/impl/BaseYangSwaggerGenerator.java | 74 ++++++-- .../sal/rest/doc/impl/ModelGenerator.java | 171 ++++++++---------- .../doc/model/builder/OperationBuilder.java | 51 ++++-- .../rest/doc/impl/ApiDocGeneratorTest.java | 169 ++++++++++++++--- .../src/test/resources/yang/toaster.yang | 2 +- .../test/resources/yang/toaster_short.yang | 33 ++++ 6 files changed, 354 insertions(+), 146 deletions(-) diff --git a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/BaseYangSwaggerGenerator.java b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/BaseYangSwaggerGenerator.java index 1b27182514..5d0f3612e4 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/BaseYangSwaggerGenerator.java +++ b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/BaseYangSwaggerGenerator.java @@ -57,6 +57,8 @@ public class BaseYangSwaggerGenerator { protected static final String API_VERSION = "1.0.0"; protected static final String SWAGGER_VERSION = "1.2"; protected static final String RESTCONF_CONTEXT_ROOT = "restconf"; + + static final String MODULE_NAME_SUFFIX = "_module"; protected final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); private final ModelGenerator jsonConverter = new ModelGenerator(); @@ -164,6 +166,7 @@ public class BaseYangSwaggerGenerator { List pathParams = new ArrayList(); String resourcePath = getDataStorePath("/config/", context); + addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, apis); addApis(node, apis, resourcePath, pathParams, schemaContext, true); pathParams = new ArrayList(); @@ -199,6 +202,16 @@ public class BaseYangSwaggerGenerator { return null; } + private void addRootPostLink(final Module m, final DataNodeContainer node, final List pathParams, + final String resourcePath, final List apis) { + if (containsListOrContainer(m.getChildNodes())) { + final Api apiForRootPostUri = new Api(); + apiForRootPostUri.setPath(resourcePath); + apiForRootPostUri.setOperations(operationPost(m.getName()+MODULE_NAME_SUFFIX, m.getDescription(), m, pathParams, true)); + apis.add(apiForRootPostUri); + } + } + protected ApiDeclaration createApiDeclaration(String basePath) { ApiDeclaration doc = new ApiDeclaration(); doc.setApiVersion(API_VERSION); @@ -229,45 +242,72 @@ public class BaseYangSwaggerGenerator { String resourcePath = parentPath + createPath(node, pathParams, schemaContext) + "/"; _logger.debug("Adding path: [{}]", resourcePath); api.setPath(resourcePath); - api.setOperations(operations(node, pathParams, addConfigApi)); - apis.add(api); + + Iterable childSchemaNodes = Collections. emptySet(); if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) { - DataNodeContainer schemaNode = (DataNodeContainer) node; - - for (DataSchemaNode childNode : schemaNode.getChildNodes()) { - // We don't support going to leaf nodes today. Only lists and - // containers. - if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) { - // keep config and operation attributes separate. - if (childNode.isConfiguration() == addConfigApi) { - addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi); - } + DataNodeContainer dataNodeContainer = (DataNodeContainer) node; + childSchemaNodes = dataNodeContainer.getChildNodes(); + } + api.setOperations(operation(node, pathParams, addConfigApi, childSchemaNodes)); + apis.add(api); + + for (DataSchemaNode childNode : childSchemaNodes) { + if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) { + // keep config and operation attributes separate. + if (childNode.isConfiguration() == addConfigApi) { + addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi); } } } } + private boolean containsListOrContainer(final Iterable nodes) { + for (DataSchemaNode child : nodes) { + if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) { + return true; + } + } + return false; + } + /** * @param node * @param pathParams * @return */ - private List operations(DataSchemaNode node, List pathParams, boolean isConfig) { + private List operation(DataSchemaNode node, List pathParams, boolean isConfig, Iterable childSchemaNodes) { List operations = new ArrayList<>(); OperationBuilder.Get getBuilder = new OperationBuilder.Get(node, isConfig); operations.add(getBuilder.pathParams(pathParams).build()); if (isConfig) { - OperationBuilder.Post postBuilder = new OperationBuilder.Post(node); - operations.add(postBuilder.pathParams(pathParams).build()); - - OperationBuilder.Put putBuilder = new OperationBuilder.Put(node); + OperationBuilder.Put putBuilder = new OperationBuilder.Put(node.getQName().getLocalName(), + node.getDescription()); operations.add(putBuilder.pathParams(pathParams).build()); OperationBuilder.Delete deleteBuilder = new OperationBuilder.Delete(node); operations.add(deleteBuilder.pathParams(pathParams).build()); + + if (containsListOrContainer(childSchemaNodes)) { + operations.addAll(operationPost(node.getQName().getLocalName(), node.getDescription(), (DataNodeContainer) node, + pathParams, isConfig)); + } + } + return operations; + } + + /** + * @param node + * @param pathParams + * @return + */ + private List operationPost(final String name, final String description, final DataNodeContainer dataNodeContainer, List pathParams, boolean isConfig) { + List operations = new ArrayList<>(); + if (isConfig) { + OperationBuilder.Post postBuilder = new OperationBuilder.Post(name, description, dataNodeContainer); + operations.add(postBuilder.pathParams(pathParams).build()); } return operations; } diff --git a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java index 819892f647..f4274870c9 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java +++ b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java @@ -7,8 +7,11 @@ */ package org.opendaylight.controller.sal.rest.doc.impl; +import static org.opendaylight.controller.sal.rest.doc.impl.BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX; +import static org.opendaylight.controller.sal.rest.doc.model.builder.OperationBuilder.Post.METHOD_NAME; import static org.opendaylight.controller.sal.rest.doc.util.RestDocgenUtil.resolveNodesName; +import com.google.common.base.Preconditions; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -27,6 +30,7 @@ import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.ChoiceNode; import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; @@ -35,6 +39,7 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition; @@ -123,42 +128,26 @@ public class ModelGenerator { public JSONObject convertToJsonSchema(Module module, SchemaContext schemaContext) throws IOException, JSONException { JSONObject models = new JSONObject(); topLevelModule = module; - processContainers(module, models, schemaContext); + processModules(module, models); + processContainersAndLists(module, models, schemaContext); processRPCs(module, models, schemaContext); processIdentities(module, models); return models; } - private void processContainers(Module module, JSONObject models, SchemaContext schemaContext) throws IOException, - JSONException { + private void processModules(Module module, JSONObject models) throws JSONException { + createConcreteModelForPost(models, module.getName()+MODULE_NAME_SUFFIX, createPropertiesForPost(module)); + } + + private void processContainersAndLists(Module module, JSONObject models, SchemaContext schemaContext) + throws IOException, JSONException { String moduleName = module.getName(); for (DataSchemaNode childNode : module.getChildNodes()) { - JSONObject configModuleJSON = null; - JSONObject operationalModuleJSON = null; - - String childNodeName = childNode.getQName().getLocalName(); - /* - * For every container in the module - */ - if (childNode instanceof ContainerSchemaNode) { - configModuleJSON = processContainer((ContainerSchemaNode) childNode, moduleName, true, models, true, - schemaContext); - operationalModuleJSON = processContainer((ContainerSchemaNode) childNode, moduleName, true, models, - false, schemaContext); - } - - if (configModuleJSON != null) { - _logger.debug("Adding model for [{}]", OperationBuilder.CONFIG + childNodeName); - configModuleJSON.put("id", OperationBuilder.CONFIG + childNodeName); - models.put(OperationBuilder.CONFIG + childNodeName, configModuleJSON); - } - if (operationalModuleJSON != null) { - _logger.debug("Adding model for [{}]", OperationBuilder.OPERATIONAL + childNodeName); - operationalModuleJSON.put("id", OperationBuilder.OPERATIONAL + childNodeName); - models.put(OperationBuilder.OPERATIONAL + childNodeName, operationalModuleJSON); - } + // For every container and list in the module + processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, true, schemaContext); + processDataNodeContainer((DataNodeContainer) childNode, moduleName, models, false, schemaContext); } } @@ -180,7 +169,7 @@ public class ModelGenerator { ContainerSchemaNode input = rpc.getInput(); if (input != null) { - JSONObject inputJSON = processContainer(input, moduleName, true, models, schemaContext); + JSONObject inputJSON = processDataNodeContainer(input, moduleName, models, schemaContext); String filename = "(" + rpc.getQName().getLocalName() + ")input"; inputJSON.put("id", filename); // writeToFile(filename, inputJSON.toString(2), moduleName); @@ -189,7 +178,7 @@ public class ModelGenerator { ContainerSchemaNode output = rpc.getOutput(); if (output != null) { - JSONObject outputJSON = processContainer(output, moduleName, true, models, schemaContext); + JSONObject outputJSON = processDataNodeContainer(output, moduleName, models, schemaContext); String filename = "(" + rpc.getQName().getLocalName() + ")output"; outputJSON.put("id", filename); models.put(filename, outputJSON); @@ -251,7 +240,7 @@ public class ModelGenerator { } /** - * Processes the container node and populates the moduleJSON + * Processes the container and list nodes and populates the moduleJSON * * @param container * @param moduleName @@ -259,28 +248,67 @@ public class ModelGenerator { * @throws JSONException * @throws IOException */ - private JSONObject processContainer(ContainerSchemaNode container, String moduleName, boolean addSchemaStmt, - JSONObject models, SchemaContext schemaContext) throws JSONException, IOException { - return processContainer(container, moduleName, addSchemaStmt, models, (Boolean) null, schemaContext); + private JSONObject processDataNodeContainer(DataNodeContainer dataNode, String moduleName, JSONObject models, + SchemaContext schemaContext) throws JSONException, IOException { + return processDataNodeContainer(dataNode, moduleName, models, (Boolean) null, schemaContext); } - private JSONObject processContainer(ContainerSchemaNode container, String moduleName, boolean addSchemaStmt, - JSONObject models, Boolean isConfig, SchemaContext schemaContext) throws JSONException, IOException { - JSONObject moduleJSON = getSchemaTemplate(); - if (addSchemaStmt) { - moduleJSON = getSchemaTemplate(); - } else { - moduleJSON = new JSONObject(); + private JSONObject processDataNodeContainer(DataNodeContainer dataNode, String moduleName, JSONObject models, + Boolean isConfig, SchemaContext schemaContext) throws JSONException, IOException { + if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) { + Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node"); + Iterable containerChildren = dataNode.getChildNodes(); + JSONObject properties = processChildren(containerChildren, ((SchemaNode) dataNode).getQName(), moduleName, + models, isConfig, schemaContext); + + String nodeName = (BooleanUtils.isNotFalse(isConfig) ? OperationBuilder.CONFIG + : OperationBuilder.OPERATIONAL) + ((SchemaNode) dataNode).getQName().getLocalName(); + + JSONObject childSchema = getSchemaTemplate(); + childSchema.put(TYPE_KEY, OBJECT_TYPE); + childSchema.put(PROPERTIES_KEY, properties); + childSchema.put("id", nodeName); + models.put(nodeName, childSchema); + + if (BooleanUtils.isNotFalse(isConfig)) { + createConcreteModelForPost(models, ((SchemaNode) dataNode).getQName().getLocalName(), + createPropertiesForPost(dataNode)); + } + + JSONObject items = new JSONObject(); + items.put(REF_KEY, nodeName); + JSONObject dataNodeProperties = new JSONObject(); + dataNodeProperties.put(TYPE_KEY, dataNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE); + dataNodeProperties.put(ITEMS_KEY, items); + + return dataNodeProperties; } - moduleJSON.put(TYPE_KEY, OBJECT_TYPE); + return null; + } - String containerDescription = container.getDescription(); - moduleJSON.put(DESCRIPTION_KEY, containerDescription); + private void createConcreteModelForPost(final JSONObject models, final String localName, final JSONObject properties) + throws JSONException { + String nodePostName = OperationBuilder.CONFIG + localName + METHOD_NAME; + JSONObject postSchema = getSchemaTemplate(); + postSchema.put(TYPE_KEY, OBJECT_TYPE); + postSchema.put("id", nodePostName); + postSchema.put(PROPERTIES_KEY, properties); + models.put(nodePostName, postSchema); + } - JSONObject properties = processChildren(container.getChildNodes(), container.getQName(), moduleName, models, - isConfig, schemaContext); - moduleJSON.put(PROPERTIES_KEY, properties); - return moduleJSON; + private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer) throws JSONException { + JSONObject properties = new JSONObject(); + for (DataSchemaNode childNode : dataNodeContainer.getChildNodes()) { + if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) { + JSONObject items = new JSONObject(); + items.put(REF_KEY, "(config)" + childNode.getQName().getLocalName()); + JSONObject property = new JSONObject(); + property.put(TYPE_KEY, childNode instanceof ListSchemaNode ? ARRAY_TYPE : OBJECT_TYPE); + property.put(ITEMS_KEY, items); + properties.put(childNode.getQName().getLocalName(), property); + } + } + return properties; } private JSONObject processChildren(Iterable nodes, QName parentQName, String moduleName, @@ -312,7 +340,8 @@ public class ModelGenerator { if (node instanceof LeafSchemaNode) { property = processLeafNode((LeafSchemaNode) node); } else if (node instanceof ListSchemaNode) { - property = processListSchemaNode((ListSchemaNode) node, moduleName, models, isConfig, schemaContext); + property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig, + schemaContext); } else if (node instanceof LeafListSchemaNode) { property = processLeafListNode((LeafListSchemaNode) node); @@ -324,7 +353,7 @@ public class ModelGenerator { property = processAnyXMLNode((AnyXmlSchemaNode) node); } else if (node instanceof ContainerSchemaNode) { - property = processContainer((ContainerSchemaNode) node, moduleName, false, models, isConfig, + property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig, schemaContext); } else { @@ -407,50 +436,6 @@ public class ModelGenerator { } } - /** - * Parses a ListSchema node. - * - * Due to a limitation of the RAML--->JAX-RS tool, sub-properties must be in a separate JSON schema file. Hence, we - * have to write some properties to a new file, while continuing to process the rest. - * - * @param listNode - * @param moduleName - * @param isConfig - * @return - * @throws JSONException - * @throws IOException - */ - private JSONObject processListSchemaNode(ListSchemaNode listNode, String moduleName, JSONObject models, - Boolean isConfig, SchemaContext schemaContext) throws JSONException, IOException { - - String fileName = (BooleanUtils.isNotFalse(isConfig) ? OperationBuilder.CONFIG : OperationBuilder.OPERATIONAL) - + listNode.getQName().getLocalName(); - - JSONObject childSchemaProperties = processChildren(listNode.getChildNodes(), listNode.getQName(), moduleName, - models, schemaContext); - JSONObject childSchema = getSchemaTemplate(); - childSchema.put(TYPE_KEY, OBJECT_TYPE); - childSchema.put(PROPERTIES_KEY, childSchemaProperties); - - /* - * Due to a limitation of the RAML--->JAX-RS tool, sub-properties must be in a separate JSON schema file. Hence, - * we have to write some properties to a new file, while continuing to process the rest. - */ - // writeToFile(fileName, childSchema.toString(2), moduleName); - childSchema.put("id", fileName); - models.put(fileName, childSchema); - - JSONObject listNodeProperties = new JSONObject(); - listNodeProperties.put(TYPE_KEY, ARRAY_TYPE); - - JSONObject items = new JSONObject(); - items.put(REF_KEY, fileName); - listNodeProperties.put(ITEMS_KEY, items); - - return listNodeProperties; - - } - /** * * @param leafNode diff --git a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/model/builder/OperationBuilder.java b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/model/builder/OperationBuilder.java index 9a33ee31b3..7e27b50541 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/model/builder/OperationBuilder.java +++ b/opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/model/builder/OperationBuilder.java @@ -9,10 +9,12 @@ package org.opendaylight.controller.sal.rest.doc.model.builder; import java.util.ArrayList; import java.util.List; - import org.opendaylight.controller.sal.rest.doc.swagger.Operation; import org.opendaylight.controller.sal.rest.doc.swagger.Parameter; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; /** * @@ -56,21 +58,21 @@ public final class OperationBuilder { */ public static class Put { protected Operation spec; - protected DataSchemaNode schemaNode; + protected String nodeName; private final String METHOD_NAME = "PUT"; - public Put(DataSchemaNode node) { - this.schemaNode = node; + public Put(String nodeName, final String description) { + this.nodeName = nodeName; spec = new Operation(); - spec.setType(CONFIG + node.getQName().getLocalName()); - spec.setNotes(node.getDescription()); + spec.setType(CONFIG + nodeName); + spec.setNotes(description); } public Put pathParams(List params) { List parameters = new ArrayList<>(params); Parameter payload = new Parameter(); payload.setParamType("body"); - payload.setType(CONFIG + schemaNode.getQName().getLocalName()); + payload.setType(CONFIG + nodeName); parameters.add(payload); spec.setParameters(parameters); return this; @@ -78,7 +80,7 @@ public final class OperationBuilder { public Operation build() { spec.setMethod(METHOD_NAME); - spec.setNickname(METHOD_NAME + "-" + schemaNode.getQName().getLocalName()); + spec.setNickname(METHOD_NAME + "-" + nodeName); return spec; } } @@ -88,18 +90,43 @@ public final class OperationBuilder { */ public static final class Post extends Put { - private final String METHOD_NAME = "POST"; + public static final String METHOD_NAME = "POST"; + private final DataNodeContainer dataNodeContainer; - public Post(DataSchemaNode node) { - super(node); + public Post(final String nodeName, final String description, final DataNodeContainer dataNodeContainer) { + super(nodeName, description); + this.dataNodeContainer = dataNodeContainer; + spec.setType(CONFIG + nodeName + METHOD_NAME); } @Override public Operation build() { spec.setMethod(METHOD_NAME); - spec.setNickname(METHOD_NAME + "-" + schemaNode.getQName().getLocalName()); + spec.setNickname(METHOD_NAME + "-" + nodeName); return spec; } + + @Override + public Put pathParams(List params) { + List parameters = new ArrayList<>(params); + for (DataSchemaNode node : dataNodeContainer.getChildNodes()) { + if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) { + Parameter payload = new Parameter(); + payload.setParamType("body"); + payload.setType(CONFIG + node.getQName().getLocalName()); + payload.setName("**"+CONFIG + node.getQName().getLocalName()); + parameters.add(payload); + } + } + spec.setParameters(parameters); + return this; + + } + + public Post summary(final String summary) { + spec.setSummary(summary); + return this; + } } /** diff --git a/opendaylight/md-sal/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java b/opendaylight/md-sal/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java index 19f82b5386..9165281f9d 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java +++ b/opendaylight/md-sal/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java @@ -9,6 +9,7 @@ import com.google.common.base.Preconditions; import java.io.File; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; @@ -23,6 +24,7 @@ import org.opendaylight.controller.sal.core.api.model.SchemaService; import org.opendaylight.controller.sal.rest.doc.swagger.Api; import org.opendaylight.controller.sal.rest.doc.swagger.ApiDeclaration; import org.opendaylight.controller.sal.rest.doc.swagger.Operation; +import org.opendaylight.controller.sal.rest.doc.swagger.Parameter; import org.opendaylight.controller.sal.rest.doc.swagger.Resource; import org.opendaylight.controller.sal.rest.doc.swagger.ResourceList; import org.opendaylight.yangtools.yang.model.api.Module; @@ -52,8 +54,7 @@ public class ApiDocGeneratorTest { } /** - * Method: getApiDeclaration(String module, String revision, UriInfo - * uriInfo) + * Method: getApiDeclaration(String module, String revision, UriInfo uriInfo) */ @Test public void testGetModuleDoc() throws Exception { @@ -61,13 +62,139 @@ public class ApiDocGeneratorTest { for (Entry m : helper.getModules().entrySet()) { if (m.getKey().getAbsolutePath().endsWith("toaster_short.yang")) { - ApiDeclaration doc = generator.getSwaggerDocSpec(m.getValue(), - "http://localhost:8080/restconf", "",schemaContext); + ApiDeclaration doc = generator.getSwaggerDocSpec(m.getValue(), "http://localhost:8080/restconf", "", + schemaContext); validateToaster(doc); validateTosterDocContainsModulePrefixes(doc); - Assert.assertNotNull(doc); + validateSwaggerModules(doc); + validateSwaggerApisForPost(doc); + } + } + } + + /** + * Validate whether ApiDelcaration contains Apis with concrete path and whether this Apis contain specified POST + * operations. + */ + private void validateSwaggerApisForPost(final ApiDeclaration doc) { + // two POST URI with concrete schema name in summary + Api lstApi = findApi("/config/toaster2:lst/", doc); + assertNotNull("Api /config/toaster2:lst/ wasn't found", lstApi); + assertTrue("POST for cont1 in lst is missing", + findOperation(lstApi.getOperations(), "POST", "(config)lstPOST", "(config)lst1", "(config)cont1")); + + Api cont1Api = findApi("/config/toaster2:lst/cont1/", doc); + assertNotNull("Api /config/toaster2:lst/cont1/ wasn't found", cont1Api); + assertTrue("POST for cont11 in cont1 is missing", + findOperation(cont1Api.getOperations(), "POST", "(config)cont1POST", "(config)cont11", "(config)lst11")); + + // no POST URI + Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11/", doc); + assertNotNull("Api /config/toaster2:lst/cont1/cont11/ wasn't found", cont11Api); + assertTrue("POST operation shouldn't be present.", findOperations(cont11Api.getOperations(), "POST").isEmpty()); + + } + + /** + * Tries to find operation with name {@code operationName} and with summary {@code summary} + */ + private boolean findOperation(List operations, String operationName, String type, + String... searchedParameters) { + Set filteredOperations = findOperations(operations, operationName); + for (Operation operation : filteredOperations) { + if (operation.getType().equals(type)) { + List parameters = operation.getParameters(); + return containAllParameters(parameters, searchedParameters); } } + return false; + } + + private Set findOperations(final List operations, final String operationName) { + final Set filteredOperations = new HashSet<>(); + for (Operation operation : operations) { + if (operation.getMethod().equals(operationName)) { + filteredOperations.add(operation); + } + } + return filteredOperations; + } + + private boolean containAllParameters(final List searchedIns, String[] searchedWhats) { + for (String searchedWhat : searchedWhats) { + boolean parameterFound = false; + for (Parameter searchedIn : searchedIns) { + if (searchedIn.getType().equals(searchedWhat)) { + parameterFound = true; + } + } + if (!parameterFound) { + return false; + } + } + return true; + } + + /** + * Tries to find {@code Api} with path {@code path} + */ + private Api findApi(final String path, final ApiDeclaration doc) { + for (Api api : doc.getApis()) { + if (api.getPath().equals(path)) { + return api; + } + } + return null; + } + + /** + * Validates whether doc {@code doc} contains concrete specified models. + */ + private void validateSwaggerModules(ApiDeclaration doc) { + JSONObject models = doc.getModels(); + assertNotNull(models); + try { + JSONObject configLst = models.getJSONObject("(config)lst"); + assertNotNull(configLst); + + containsReferences(configLst, "lst1"); + containsReferences(configLst, "cont1"); + + JSONObject configLst1 = models.getJSONObject("(config)lst1"); + assertNotNull(configLst1); + + JSONObject configCont1 = models.getJSONObject("(config)cont1"); + assertNotNull(configCont1); + + containsReferences(configCont1, "cont11"); + containsReferences(configCont1, "lst11"); + + JSONObject configCont11 = models.getJSONObject("(config)cont11"); + assertNotNull(configCont11); + + JSONObject configLst11 = models.getJSONObject("(config)lst11"); + assertNotNull(configLst11); + } catch (JSONException e) { + fail("JSONException wasn't expected"); + } + + } + + /** + * Checks whether object {@code mainObject} contains in properties/items key $ref with concrete value. + */ + private void containsReferences(final JSONObject mainObject, final String childObject) throws JSONException { + JSONObject properties = mainObject.getJSONObject("properties"); + assertNotNull(properties); + + JSONObject nodeInProperties = properties.getJSONObject(childObject); + assertNotNull(nodeInProperties); + + JSONObject itemsInNodeInProperties = nodeInProperties.getJSONObject("items"); + assertNotNull(itemsInNodeInProperties); + + String itemRef = itemsInNodeInProperties.getString("$ref"); + assertEquals("(config)" + childObject, itemRef); } @Test @@ -76,14 +203,13 @@ public class ApiDocGeneratorTest { for (Entry m : helper.getModules().entrySet()) { if (m.getKey().getAbsolutePath().endsWith("toaster.yang")) { - ApiDeclaration doc = generator.getSwaggerDocSpec(m.getValue(), - "http://localhost:8080/restconf", "",schemaContext); + ApiDeclaration doc = generator.getSwaggerDocSpec(m.getValue(), "http://localhost:8080/restconf", "", + schemaContext); Assert.assertNotNull(doc); - //testing bugs.opendaylight.org bug 1290. UnionType model type. + // testing bugs.opendaylight.org bug 1290. UnionType model type. String jsonString = doc.getModels().toString(); - assertTrue( - jsonString.contains( "testUnion\":{\"type\":\"integer or string\",\"required\":false}" ) ); + assertTrue(jsonString.contains("testUnion\":{\"type\":\"integer or string\",\"required\":false}")); } } } @@ -98,10 +224,9 @@ public class ApiDocGeneratorTest { * @throws Exception */ private void validateToaster(ApiDeclaration doc) throws Exception { - Set expectedUrls = new TreeSet<>(Arrays.asList(new String[] { - "/config/toaster2:toaster/", "/operational/toaster2:toaster/", - "/operations/toaster2:cancel-toast", "/operations/toaster2:make-toast", - "/operations/toaster2:restock-toaster", + Set expectedUrls = new TreeSet<>(Arrays.asList(new String[] { "/config/toaster2:toaster/", + "/operational/toaster2:toaster/", "/operations/toaster2:cancel-toast", + "/operations/toaster2:make-toast", "/operations/toaster2:restock-toaster", "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo/" })); Set actualUrls = new TreeSet<>(); @@ -120,8 +245,7 @@ public class ApiDocGeneratorTest { fail("Missing expected urls: " + expectedUrls); } - Set expectedConfigMethods = new TreeSet<>(Arrays.asList(new String[] { "GET", - "PUT", "DELETE" })); + Set expectedConfigMethods = new TreeSet<>(Arrays.asList(new String[] { "GET", "PUT", "DELETE" })); Set actualConfigMethods = new TreeSet<>(); for (Operation oper : configApi.getOperations()) { actualConfigMethods.add(oper.getMethod()); @@ -136,8 +260,7 @@ public class ApiDocGeneratorTest { // TODO: we should really do some more validation of the // documentation... /** - * Missing validation: Explicit validation of URLs, and their methods - * Input / output models. + * Missing validation: Explicit validation of URLs, and their methods Input / output models. */ } @@ -173,25 +296,25 @@ public class ApiDocGeneratorTest { try { JSONObject configToaster = topLevelJson.getJSONObject("(config)toaster"); assertNotNull("(config)toaster JSON object missing", configToaster); - //without module prefix + // without module prefix containsProperties(configToaster, "toasterSlot"); JSONObject toasterSlot = topLevelJson.getJSONObject("(config)toasterSlot"); assertNotNull("(config)toasterSlot JSON object missing", toasterSlot); - //with module prefix + // with module prefix containsProperties(toasterSlot, "toaster-augmented:slotInfo"); } catch (JSONException e) { - fail("Json exception while reading JSON object. Original message "+e.getMessage()); + fail("Json exception while reading JSON object. Original message " + e.getMessage()); } } - private void containsProperties(final JSONObject jsonObject,final String...properties) throws JSONException { + private void containsProperties(final JSONObject jsonObject, final String... properties) throws JSONException { for (String property : properties) { JSONObject propertiesObject = jsonObject.getJSONObject("properties"); assertNotNull("Properties object missing in ", propertiesObject); JSONObject concretePropertyObject = propertiesObject.getJSONObject(property); - assertNotNull(property + " is missing",concretePropertyObject); + assertNotNull(property + " is missing", concretePropertyObject); } } } diff --git a/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster.yang b/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster.yang index d33bc46225..20bbd78622 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster.yang +++ b/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster.yang @@ -164,7 +164,7 @@ module toaster { "This variable indicates the current state of the toaster."; } - } + } rpc make-toast { description diff --git a/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster_short.yang b/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster_short.yang index 6884076d5d..1a4d94d2d9 100644 --- a/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster_short.yang +++ b/opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster_short.yang @@ -129,6 +129,39 @@ "The darkness factor. Basically, the number of ms to multiple the doneness value by."; } } // container toaster + + list lst { + container cont1 { + container cont11 { + leaf lf111 { + type uint32; + } + leaf lf112 { + type string; + } + } + list lst11 { + leaf lf111 { + type string; + } + } + } + list lst1 { + key "key1 key2"; + leaf key1 { + type int32; + } + leaf key2 { + type int8; + } + leaf lf11 { + type int16; + } + } + leaf lf1 { + type string; + } + } rpc make-toast { description -- 2.36.6