BUG 932 - Swagger HTTP POST contains incorrect object 68/9068/10
authorJozef Gloncak <jgloncak@cisco.com>
Wed, 16 Jul 2014 12:03:20 +0000 (14:03 +0200)
committerJozef Gloncak <jgloncak@cisco.com>
Wed, 20 Aug 2014 06:48:39 +0000 (08:48 +0200)
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 <jgloncak@cisco.com>
opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/BaseYangSwaggerGenerator.java
opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/impl/ModelGenerator.java
opendaylight/md-sal/sal-rest-docgen/src/main/java/org/opendaylight/controller/sal/rest/doc/model/builder/OperationBuilder.java
opendaylight/md-sal/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java
opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster.yang
opendaylight/md-sal/sal-rest-docgen/src/test/resources/yang/toaster_short.yang

index 1b2718251446dde021d3cc62b794b990f8852dc4..5d0f3612e4f3c931c39199a0f180461b8430e0f8 100644 (file)
@@ -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<Parameter> pathParams = new ArrayList<Parameter>();
                 String resourcePath = getDataStorePath("/config/", context);
+                addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, apis);
                 addApis(node, apis, resourcePath, pathParams, schemaContext, true);
 
                 pathParams = new ArrayList<Parameter>();
@@ -199,6 +202,16 @@ public class BaseYangSwaggerGenerator {
         return null;
     }
 
+    private void addRootPostLink(final Module m, final DataNodeContainer node, final List<Parameter> pathParams,
+            final String resourcePath, final List<Api> 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<DataSchemaNode> childSchemaNodes = Collections.<DataSchemaNode> 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<DataSchemaNode> nodes) {
+        for (DataSchemaNode child : nodes) {
+            if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @param node
      * @param pathParams
      * @return
      */
-    private List<Operation> operations(DataSchemaNode node, List<Parameter> pathParams, boolean isConfig) {
+    private List<Operation> operation(DataSchemaNode node, List<Parameter> pathParams, boolean isConfig, Iterable<DataSchemaNode> childSchemaNodes) {
         List<Operation> 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<Operation> operationPost(final String name, final String description, final DataNodeContainer dataNodeContainer, List<Parameter> pathParams, boolean isConfig) {
+        List<Operation> operations = new ArrayList<>();
+        if (isConfig) {
+            OperationBuilder.Post postBuilder = new OperationBuilder.Post(name, description, dataNodeContainer);
+            operations.add(postBuilder.pathParams(pathParams).build());
         }
         return operations;
     }
index 819892f6477b2994e53c927cf1ab3a8dd2c2b545..f4274870c9305d84f1ee53798bb934f8721748a4 100644 (file)
@@ -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<DataSchemaNode> 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<DataSchemaNode> 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
index 9a33ee31b371cb99febb084da4198f83e74fa97c..7e27b505413bb795f4ec51dff41927237c7679d5 100644 (file)
@@ -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<Parameter> params) {
             List<Parameter> 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<Parameter> params) {
+            List<Parameter> 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;
+        }
     }
 
     /**
index 19f82b53867b603af20576759485999ca1fe28c4..9165281f9d27cc29cc6b236f73e518c886452ed5 100644 (file)
@@ -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<File, Module> 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<Operation> operations, String operationName, String type,
+            String... searchedParameters) {
+        Set<Operation> filteredOperations = findOperations(operations, operationName);
+        for (Operation operation : filteredOperations) {
+            if (operation.getType().equals(type)) {
+                List<Parameter> parameters = operation.getParameters();
+                return containAllParameters(parameters, searchedParameters);
             }
         }
+        return false;
+    }
+
+    private Set<Operation> findOperations(final List<Operation> operations, final String operationName) {
+        final Set<Operation> filteredOperations = new HashSet<>();
+        for (Operation operation : operations) {
+            if (operation.getMethod().equals(operationName)) {
+                filteredOperations.add(operation);
+            }
+        }
+        return filteredOperations;
+    }
+
+    private boolean containAllParameters(final List<Parameter> 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<File, Module> 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<String> 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<String> 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<String> actualUrls = new TreeSet<>();
@@ -120,8 +245,7 @@ public class ApiDocGeneratorTest {
             fail("Missing expected urls: " + expectedUrls);
         }
 
-        Set<String> expectedConfigMethods = new TreeSet<>(Arrays.asList(new String[] { "GET",
-                "PUT", "DELETE" }));
+        Set<String> expectedConfigMethods = new TreeSet<>(Arrays.asList(new String[] { "GET", "PUT", "DELETE" }));
         Set<String> 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);
         }
     }
 }
index d33bc4622530dd44a3a26bc03b41ed0a90b8dd18..20bbd78622eb839d9679e3909769ed188ebd3c22 100644 (file)
@@ -164,7 +164,7 @@ module toaster {
           "This variable indicates the current state of 
                the toaster.";
       }
-    } 
+    }
 
     rpc make-toast {
       description
index 6884076d5daafa1a8ced3753d1460754b950e1a1..1a4d94d2d98f5310de23ed26373f0e5c2770489e 100644 (file)
           "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