OpenApi for TransportPCE with controller resources fix 81/111281/18
authorlubos-cicut <lubos.cicut@pantheon.tech>
Fri, 5 Apr 2024 11:54:13 +0000 (13:54 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Thu, 16 May 2024 13:56:20 +0000 (13:56 +0000)
Openapi did not fully work for TransportPCE when we selected
"Controller resource - Restconf RFC 8040". This was due to multiple
generation of the API for models with a different revision, now it
is only generated for the latest version.

Added tests for this case which show that the models are loaded
correctly and targeting to a model with an old revision returns the
correct output.

JIRA: NETCONF-1279
Change-Id: I3042c51715eddfaabe0a1179f1fd0d6cabc9c976
Signed-off-by: lubos-cicut <lubos.cicut@pantheon.tech>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/impl/BaseYangOpenApiGenerator.java
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/mountpoints/MountPointOpenApi.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/ToasterDocumentTest.java
restconf/restconf-openapi/src/test/resources/toaster-document/controller-toaster-old.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/toaster-document/device-toaster-old.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/toaster-document/toaster@2009-11-19.yang [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/toaster-document/toaster@2009-11-20.yang [moved from restconf/restconf-openapi/src/test/resources/toaster-document/toaster.yang with 100% similarity]

index 044bdb25a500573e5be00a990a1afaad84298e4c..953917a343aef43a88ef4ea2febb0c75f4ffedd2 100644 (file)
@@ -12,14 +12,20 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.base.Preconditions;
 import java.io.IOException;
 import java.time.format.DateTimeParseException;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import javax.ws.rs.core.UriInfo;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.Module;
 
 public abstract class BaseYangOpenApiGenerator {
     private static final String CONTROLLER_RESOURCE_NAME = "Controller";
@@ -38,7 +44,7 @@ public abstract class BaseYangOpenApiGenerator {
         final var title = "Controller modules of RESTCONF";
         final var url = schema + "://" + host + "/";
         final var basePath = getBasePath();
-        final var modules = context.getModules();
+        final var modules = getModulesWithoutDuplications(context);
         return new OpenApiInputStream(context, title, url, SECURITY, CONTROLLER_RESOURCE_NAME, "",false, false,
             modules, basePath);
     }
@@ -89,4 +95,16 @@ public abstract class BaseYangOpenApiGenerator {
     }
 
     public abstract String getBasePath();
+
+    public static Set<Module> getModulesWithoutDuplications(final @NonNull EffectiveModelContext schemaContext) {
+        return new LinkedHashSet<>(schemaContext.getModules()
+            .stream()
+            .collect(Collectors.toMap(
+                Module::getName,
+                Function.identity(),
+                (module1, module2) -> Revision.compare(
+                    module1.getRevision(), module2.getRevision()) > 0 ? module1 : module2,
+                LinkedHashMap::new))
+            .values());
+    }
 }
index 2fc707693120b0a5b44fed795445bcd28d37e0ee..4adccfc5a5a483b141cf76825b293790dea2bb6e 100644 (file)
@@ -30,7 +30,6 @@ import org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator;
 import org.opendaylight.restconf.openapi.impl.OpenApiInputStream;
 import org.opendaylight.yangtools.concepts.Registration;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
@@ -160,7 +159,7 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
         }
 
         boolean includeDataStore = true;
-        var modules = context.getModules();
+        var modules = BaseYangOpenApiGenerator.getModulesWithoutDuplications(context);
         if (strPageNum != null) {
             final var pageNum = Integer.parseInt(strPageNum);
             final var end = DEFAULT_PAGESIZE * pageNum - 1;
@@ -170,7 +169,7 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
             } else {
                 includeDataStore = false;
             }
-            modules = filterByRange(context, start);
+            modules = filterByRange(modules, start);
         }
 
         final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
@@ -193,7 +192,7 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
         final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
         final var url = schema + "://" + host + "/";
         final var basePath = openApiGenerator.getBasePath();
-        final var modules = modelContext.getModules();
+        final var modules = BaseYangOpenApiGenerator.getModulesWithoutDuplications(modelContext);
         return new OpenApiInputStream(modelContext, urlPrefix, url, SECURITY, deviceName, urlPrefix, true, false,
             modules, basePath);
     }
@@ -213,24 +212,12 @@ public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
         longIdToInstanceId.remove(id);
     }
 
-    private static Set<Module> filterByRange(final EffectiveModelContext schemaContext, final Integer start) {
-        final var sortedModules = new TreeSet<Module>((module1, module2) -> {
-            int result = module1.getName().compareTo(module2.getName());
-            if (result == 0) {
-                result = Revision.compare(module1.getRevision(), module2.getRevision());
-            }
-            if (result == 0) {
-                result = module1.getNamespace().compareTo(module2.getNamespace());
-            }
-            return result;
-        });
-        sortedModules.addAll(schemaContext.getModules());
-
+    private static Set<Module> filterByRange(final Set<Module> modulesWithoutDuplications, final Integer start) {
+        final var sortedModules = new TreeSet<>(modulesWithoutDuplications);
         final int end = start + DEFAULT_PAGESIZE - 1;
-
         var firstModule = sortedModules.first();
 
-        final var iterator = sortedModules.iterator();
+        final var iterator = modulesWithoutDuplications.iterator();
         int counter = 0;
         while (iterator.hasNext() && counter < end) {
             final var module = iterator.next();
index 88c8bc510e056fd5e826821ae5f8034e10971ee7..e7e9af0a74941819965dfdca0b16901cfa72e365 100644 (file)
@@ -7,13 +7,25 @@
  */
 package org.opendaylight.restconf.openapi.impl;
 
+import java.util.stream.Stream;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.skyscreamer.jsonassert.JSONAssert;
 
 public class ToasterDocumentTest extends AbstractDocumentTest {
+    /**
+     * Model toaster@2009-11-20 is used for test correct generating of complex openapi object.
+     */
     private static final String TOASTER = "toaster";
     private static final String TOASTER_REV = "2009-11-20";
+    /**
+     * Model toaster@2009-11-19 is used for test correct generating of openapi with models with same name and another
+     * revision date. We want to test that the same model is not duplicated and loaded just the newest version.
+     */
+    private static final String TOASTER_OLD_REV = "2009-11-19";
 
     @BeforeAll
     public static void beforeClass() {
@@ -31,16 +43,22 @@ public class ToasterDocumentTest extends AbstractDocumentTest {
     }
 
     /**
-     * Tests the swagger document that is result of the call to the '/toaster(2009-11-20)' endpoint.
-     *
-     * <p>
-     * Model toaster is used for test correct generating of complex openapi object.
+     * Tests the swagger document that is result of the call to the '/toaster@revision' endpoint.
      */
-    @Test
-    public void getDocByModuleTest() throws Exception {
-        final var jsonControllerDoc = getDocByModule(TOASTER, TOASTER_REV);
-        final var expectedJson = getExpectedDoc("toaster-document/controller-toaster.json");
-        JSONAssert.assertEquals(expectedJson, jsonControllerDoc, IGNORE_ORDER);
+    @ParameterizedTest
+    @MethodSource("getToasterRevisions")
+    public void getDocByModuleTest(final String revision, final String jsonPath) throws Exception {
+        final var expectedJson = getExpectedDoc("toaster-document/" + jsonPath);
+        final var moduleDoc = getDocByModule(TOASTER, revision);
+        JSONAssert.assertEquals(expectedJson, moduleDoc, IGNORE_ORDER);
+    }
+
+    static Stream<Arguments> getToasterRevisions() {
+        // moduleName, revision, jsonPath
+        return Stream.of(
+            Arguments.of(TOASTER_REV, "controller-toaster.json"),
+            Arguments.of(TOASTER_OLD_REV, "controller-toaster-old.json")
+        );
     }
 
     /**
@@ -54,15 +72,21 @@ public class ToasterDocumentTest extends AbstractDocumentTest {
     }
 
     /**
-     * Tests the swagger document that is result of the call to the '/mounts/1/toaster(2009-11-20)' endpoint.
-     *
-     * <p>
-     * Model toaster is used for test correct generating of complex openapi object.
+     * Tests the swagger document that is result of the call to the '/toaster@revision' endpoint.
      */
-    @Test
-    public void getMountDocByModuleTest() throws Exception {
-        final var jsonDeviceDoc = getMountDocByModule(TOASTER, TOASTER_REV);
-        final var expectedJson = getExpectedDoc("toaster-document/device-toaster.json");
-        JSONAssert.assertEquals(expectedJson, jsonDeviceDoc, IGNORE_ORDER);
+    @ParameterizedTest
+    @MethodSource("getMountToasterRevisions")
+    public void getMountDocByModuleTest(final String revision, final String jsonPath) throws Exception {
+        final var expectedJson = getExpectedDoc("toaster-document/" + jsonPath);
+        final var moduleDoc = getMountDocByModule(TOASTER, revision);
+        JSONAssert.assertEquals(expectedJson, moduleDoc, IGNORE_ORDER);
+    }
+
+    static Stream<Arguments> getMountToasterRevisions() {
+        // moduleName, revision, jsonPath
+        return Stream.of(
+            Arguments.of(TOASTER_REV, "device-toaster.json"),
+            Arguments.of(TOASTER_OLD_REV, "device-toaster-old.json")
+        );
     }
 }
diff --git a/restconf/restconf-openapi/src/test/resources/toaster-document/controller-toaster-old.json b/restconf/restconf-openapi/src/test/resources/toaster-document/controller-toaster-old.json
new file mode 100644 (file)
index 0000000..0ba3cfd
--- /dev/null
@@ -0,0 +1,304 @@
+{
+  "openapi": "3.0.3",
+  "info": {
+    "version": "1.0.0",
+    "title": "toaster",
+    "description": "We are providing full API for configurational data which can be edited (by POST, PUT, PATCH and DELETE).\nFor operational data we only provide GET API.\n\nFor majority of request you can see only config data in examples. That's because we can show only one example\nper request. The exception when you can see operational data in example is when data are representing\noperational (config false) container with no config data in it."
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8181/"
+    }
+  ],
+  "paths": {
+    "/rests/operations/toaster:cancel-toast": {
+      "post": {
+        "description": "Stop making toast, if any is being made.\nA 'resource-denied' error will be returned\nif the toaster service is disabled.",
+        "summary": "POST - Controller - toaster - cancel-toast",
+        "requestBody": {
+          "description": "cancel-toast_input",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "input": {
+                    "type": "object"
+                  }
+                },
+                "type": "object"
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "xml": {
+                  "name": "input",
+                  "namespace": "http://netconfcentral.org/ns/toaster"
+                },
+                "type": "object"
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "RPC cancel-toast success"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/operations/toaster:restock-toaster": {
+      "post": {
+        "description": "Restocks the toaster with the amount of bread specified.",
+        "summary": "POST - Controller - toaster - restock-toaster",
+        "requestBody": {
+          "description": "restock-toaster_input",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "input": {
+                    "$ref": "#/components/schemas/toaster_restock-toaster_input",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_restock-toaster_input"
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "RPC restock-toaster success"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/data": {
+      "post": {
+        "description": "YANG version of the TOASTER-MIB.\n\nNote:\nIn example payload, you can see only the first data node child of the resource to be created, following the\nguidelines of RFC 8040, which allows us to create only one resource in POST request.\n",
+        "summary": "POST - Controller - toaster - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "Created"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/data/toaster:toaster": {
+      "put": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "PUT - toaster - Controller - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "toaster:toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "Created"
+          },
+          "204": {
+            "description": "Updated"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      },
+      "patch": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "PATCH - toaster - Controller - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/yang-data+json": {
+              "schema": {
+                "properties": {
+                  "toaster:toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/yang-data+xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "204": {
+            "description": "Updated"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      },
+      "delete": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "DELETE - Controller - toaster - toaster",
+        "responses": {
+          "204": {
+            "description": "Deleted"
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": []
+      },
+      "get": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "GET - Controller - toaster - toaster",
+        "responses": {
+          "200": {
+            "description": "200",
+            "content": {
+              "application/xml": {
+                "schema": {
+                  "$ref": "#/components/schemas/toaster_toaster"
+                }
+              },
+              "application/json": {
+                "schema": {
+                  "properties": {
+                    "toaster": {
+                      "$ref": "#/components/schemas/toaster_toaster",
+                      "type": "object"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "tags": [
+          "Controller toaster"
+        ],
+        "parameters": [
+          {
+            "name": "content",
+            "in": "query",
+            "required": false,
+            "schema": {
+              "enum": [
+                "config",
+                "nonconfig",
+                "all"
+              ],
+              "type": "string"
+            }
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "toaster_restock-toaster_input": {
+        "title": "toaster_restock-toaster_input",
+        "type": "object",
+        "properties": {
+          "amountOfBreadToStock": {
+            "description": "Indicates the amount of bread to re-stock",
+            "type": "integer",
+            "format": "int64",
+            "example": 0
+          }
+        },
+        "xml": {
+          "name": "input",
+          "namespace": "http://netconfcentral.org/ns/toaster"
+        }
+      },
+      "toaster_toaster": {
+        "title": "toaster_toaster",
+        "type": "object",
+        "description": "Top-level container for all toaster database objects.",
+        "properties": {
+          "darknessFactor": {
+            "description": "The darkness factor. Basically, the number of ms to multiple the doneness value by.",
+            "type": "integer",
+            "format": "int64",
+            "default": 1000,
+            "example": 0
+          }
+        },
+        "xml": {
+          "name": "toaster",
+          "namespace": "http://netconfcentral.org/ns/toaster"
+        }
+      }
+    },
+    "securitySchemes": {
+      "basicAuth": {
+        "scheme": "basic",
+        "type": "http"
+      }
+    }
+  },
+  "security": [
+    {
+      "basicAuth": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/restconf/restconf-openapi/src/test/resources/toaster-document/device-toaster-old.json b/restconf/restconf-openapi/src/test/resources/toaster-document/device-toaster-old.json
new file mode 100644 (file)
index 0000000..c02c1c2
--- /dev/null
@@ -0,0 +1,304 @@
+{
+  "openapi": "3.0.3",
+  "info": {
+    "version": "1.0.0",
+    "title": "toaster",
+    "description": "We are providing full API for configurational data which can be edited (by POST, PUT, PATCH and DELETE).\nFor operational data we only provide GET API.\n\nFor majority of request you can see only config data in examples. That's because we can show only one example\nper request. The exception when you can see operational data in example is when data are representing\noperational (config false) container with no config data in it."
+  },
+  "servers": [
+    {
+      "url": "http://localhost:8181/"
+    }
+  ],
+  "paths": {
+    "/rests/operations/nodes/node=123/yang-ext:mount/toaster:cancel-toast": {
+      "post": {
+        "description": "Stop making toast, if any is being made.\nA 'resource-denied' error will be returned\nif the toaster service is disabled.",
+        "summary": "POST - 123 - toaster - cancel-toast",
+        "requestBody": {
+          "description": "cancel-toast_input",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "input": {
+                    "type": "object"
+                  }
+                },
+                "type": "object"
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "xml": {
+                  "name": "input",
+                  "namespace": "http://netconfcentral.org/ns/toaster"
+                },
+                "type": "object"
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "RPC cancel-toast success"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/operations/nodes/node=123/yang-ext:mount/toaster:restock-toaster": {
+      "post": {
+        "description": "Restocks the toaster with the amount of bread specified.",
+        "summary": "POST - 123 - toaster - restock-toaster",
+        "requestBody": {
+          "description": "restock-toaster_input",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "input": {
+                    "$ref": "#/components/schemas/toaster_restock-toaster_input",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_restock-toaster_input"
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "RPC restock-toaster success"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/data/nodes/node=123/yang-ext:mount": {
+      "post": {
+        "description": "YANG version of the TOASTER-MIB.\n\nNote:\nIn example payload, you can see only the first data node child of the resource to be created, following the\nguidelines of RFC 8040, which allows us to create only one resource in POST request.\n",
+        "summary": "POST - 123 - toaster - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "Created"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      }
+    },
+    "/rests/data/nodes/node=123/yang-ext:mount/toaster:toaster": {
+      "put": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "PUT - toaster - 123 - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/json": {
+              "schema": {
+                "properties": {
+                  "toaster:toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "201": {
+            "description": "Created"
+          },
+          "204": {
+            "description": "Updated"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      },
+      "patch": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "PATCH - toaster - 123 - toaster",
+        "requestBody": {
+          "description": "toaster",
+          "content": {
+            "application/yang-data+json": {
+              "schema": {
+                "properties": {
+                  "toaster:toaster": {
+                    "$ref": "#/components/schemas/toaster_toaster",
+                    "type": "object"
+                  }
+                }
+              }
+            },
+            "application/yang-data+xml": {
+              "schema": {
+                "$ref": "#/components/schemas/toaster_toaster"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK"
+          },
+          "204": {
+            "description": "Updated"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      },
+      "delete": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "DELETE - 123 - toaster - toaster",
+        "responses": {
+          "204": {
+            "description": "Deleted"
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": []
+      },
+      "get": {
+        "description": "Top-level container for all toaster database objects.",
+        "summary": "GET - 123 - toaster - toaster",
+        "responses": {
+          "200": {
+            "description": "200",
+            "content": {
+              "application/xml": {
+                "schema": {
+                  "$ref": "#/components/schemas/toaster_toaster"
+                }
+              },
+              "application/json": {
+                "schema": {
+                  "properties": {
+                    "toaster": {
+                      "$ref": "#/components/schemas/toaster_toaster",
+                      "type": "object"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "tags": [
+          "123 toaster"
+        ],
+        "parameters": [
+          {
+            "name": "content",
+            "in": "query",
+            "required": false,
+            "schema": {
+              "enum": [
+                "config",
+                "nonconfig",
+                "all"
+              ],
+              "type": "string"
+            }
+          }
+        ]
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "toaster_restock-toaster_input": {
+        "title": "toaster_restock-toaster_input",
+        "type": "object",
+        "properties": {
+          "amountOfBreadToStock": {
+            "description": "Indicates the amount of bread to re-stock",
+            "type": "integer",
+            "format": "int64",
+            "example": 0
+          }
+        },
+        "xml": {
+          "name": "input",
+          "namespace": "http://netconfcentral.org/ns/toaster"
+        }
+      },
+      "toaster_toaster": {
+        "title": "toaster_toaster",
+        "type": "object",
+        "description": "Top-level container for all toaster database objects.",
+        "properties": {
+          "darknessFactor": {
+            "description": "The darkness factor. Basically, the number of ms to multiple the doneness value by.",
+            "type": "integer",
+            "format": "int64",
+            "default": 1000,
+            "example": 0
+          }
+        },
+        "xml": {
+          "name": "toaster",
+          "namespace": "http://netconfcentral.org/ns/toaster"
+        }
+      }
+    },
+    "securitySchemes": {
+      "basicAuth": {
+        "scheme": "basic",
+        "type": "http"
+      }
+    }
+  },
+  "security": [
+    {
+      "basicAuth": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/restconf/restconf-openapi/src/test/resources/toaster-document/toaster@2009-11-19.yang b/restconf/restconf-openapi/src/test/resources/toaster-document/toaster@2009-11-19.yang
new file mode 100644 (file)
index 0000000..3c89277
--- /dev/null
@@ -0,0 +1,97 @@
+module toaster {
+
+  yang-version 1;
+
+  namespace
+    "http://netconfcentral.org/ns/toaster";
+
+  prefix toast;
+
+  organization "Netconf Central";
+
+  contact
+    "Andy Bierman <andy@netconfcentral.org>";
+
+  description
+    "YANG version of the TOASTER-MIB.";
+
+  revision "2009-11-19" {
+    description
+      "Changes for testing same module name with another revision. We need to have loaded the newest version.";
+  }
+
+  container toaster {
+    presence
+    "Indicates the toaster service is available";
+    description
+      "Top-level container for all toaster database objects.";
+    leaf toasterManufacturer {
+      type string;
+      config false;
+      mandatory true;
+      description
+        "The name of the toaster's manufacturer. For instance,
+        Microsoft Toaster.";
+    }
+
+    leaf toasterModelNumber {
+      type string;
+      config false;
+      mandatory true;
+      description
+        "The name of the toaster's model. For instance,
+        Radiant Automatic.";
+    }
+
+    leaf toasterStatus {
+      type enumeration {
+        enum "up" {
+          value 1;
+          description
+            "The toaster knob position is up.
+            No toast is being made now.";
+        }
+        enum "down" {
+          value 2;
+          description
+            "The toaster knob position is down.
+            Toast is being made now.";
+        }
+      }
+      config false;
+      mandatory true;
+      description
+        "This variable indicates the current state of
+        the toaster.";
+    }
+
+    leaf darknessFactor {
+      type uint32;
+      config true;
+      default 1000;
+      description
+        "The darkness factor. Basically, the number of ms to multiple the doneness value by.";
+    }
+  } // container toaster
+
+  rpc cancel-toast {
+    description
+      "Stop making toast, if any is being made.
+      A 'resource-denied' error will be returned
+      if the toaster service is disabled.";
+  } // rpc cancel-toast
+
+  rpc restock-toaster {
+    description
+      "Restocks the toaster with the amount of bread specified.";
+
+    input {
+      leaf amountOfBreadToStock {
+        type uint32;
+        description
+          "Indicates the amount of bread to re-stock";
+      }
+    }
+  } // rpc restock-toaster
+
+} // module toaster