From: Devin Avery Date: Wed, 20 Aug 2014 13:14:36 +0000 (+0000) Subject: Merge "BUG 932 - Swagger HTTP POST contains incorrect object" X-Git-Tag: release/helium~261 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=63b36aa3537d77bd9be323e1113716ef2cd54098;hp=79e737d256d5d3ccb7dd52893caa8dce26a4e17b Merge "BUG 932 - Swagger HTTP POST contains incorrect object" --- 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