Fix string example generation error 08/113508/2
authorSangwook Ha <sangwook.ha@verizon.com>
Fri, 13 Sep 2024 04:03:03 +0000 (21:03 -0700)
committerSangwook Ha <sangwook.ha@verizon.com>
Fri, 13 Sep 2024 05:44:29 +0000 (22:44 -0700)
Following a single regex transition path does not guarantee an example
conforming to the regex & length constraints.

Search all transition paths adding a character at a time until the
minimum length requirement & regex pattern are satisfied or the maximum
length limit is reached.

This guarantees the shortest possible example that satisfies the regex
& length constraints.

JIRA: NETCONF-1386
Change-Id: Ib7b7714330b5176541fe928dc5660c20579f8b2d
Signed-off-by: Sangwook Ha <sangwook.ha@verizon.com>
restconf/restconf-openapi/src/main/java/org/opendaylight/restconf/openapi/model/PropertyEntity.java
restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1386Test.java [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386-document/regex1.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386-document/regex2.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386-document/regex3.json [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386/regex1@2024-09-12.yang [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386/regex2@2024-09-12.yang [new file with mode: 0644]
restconf/restconf-openapi/src/test/resources/nc1386/regex3@2024-09-12.yang [new file with mode: 0644]

index 56506a6ead06b6246215f38173617abcfc3360a2..0149542fa948e4de8a37607a2b784e9a65c5a6c6 100644 (file)
@@ -608,7 +608,8 @@ public class PropertyEntity {
             try {
                 final var automaton = new RegExp(regex).toAutomaton();
                 if (minLength > 0) {
-                    defaultValue = prepareExample(defaultValue, automaton.getInitialState(), minLength, maxLength);
+                    defaultValue = prepareExample(List.of(new ExampleCandidate(
+                        defaultValue, automaton.getInitialState())), minLength, maxLength);
                 } else {
                     defaultValue = automaton.getShortestExample(true);
                 }
@@ -705,28 +706,32 @@ public class PropertyEntity {
         return STRING_TYPE;
     }
 
-    private static String prepareExample(final String strMatch, final State state, final int minLength,
+    private static String prepareExample(final List<ExampleCandidate> candidates, final int minLength,
             final int maxLength) {
-        final var transitions = state.getSortedTransitions(false);
+        final var nextCandidates = new ArrayList<ExampleCandidate>();
+        for (var candidate : candidates) {
+            final var string = candidate.string();
+            final var state = candidate.state();
 
-        if (state.isAccept() && strMatch.length() >= minLength) {
-            return strMatch;
-        }
+            if (string.length() >= minLength && state.isAccept()) {
+                return candidate.string();
+            }
 
-        if (transitions.isEmpty()) {
-            return strMatch;
+            if (string.length() < maxLength) {
+                final var transitions = state.getSortedTransitions(false);
+                transitions.forEach(t -> nextCandidates.add(
+                    new ExampleCandidate(string + t.getMin(), t.getDest())));
+            }
         }
 
-        // Always choose the first transition and the minimum character
-        final var firstTransition = transitions.get(0);
-        final var firstChar = firstTransition.getMin();
-        final var result = prepareExample(strMatch + firstChar, firstTransition.getDest(), minLength, maxLength);
-
-        if (minLength <= result.length() && result.length() <= maxLength) {
-            return result;
+        if (nextCandidates.isEmpty()) {
+            // If no string satisfies the length & regex constraints, return the first
+            return candidates.getFirst().string();
         }
 
-        // If the resulting string does not satisfy the length constraints, return the current string
-        return strMatch;
+        return prepareExample(nextCandidates, minLength, maxLength);
+    }
+
+    private record ExampleCandidate(String string, State state) {
     }
 }
diff --git a/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1386Test.java b/restconf/restconf-openapi/src/test/java/org/opendaylight/restconf/openapi/impl/NC1386Test.java
new file mode 100644 (file)
index 0000000..21b4bfe
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 Verizon and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.openapi.impl;
+
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeAll;
+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;
+
+class NC1386Test extends AbstractDocumentTest {
+    @BeforeAll
+    static void beforeAll() {
+        initializeClass("/nc1386/");
+    }
+
+    /**
+     * Tests the swagger document that is result of the call to the '/moduleName' endpoint.
+     */
+    @ParameterizedTest
+    @MethodSource
+    void getDocByModuleTest(final String moduleName, final String revision, final String jsonPath)
+            throws Exception {
+        final var expectedJson = getExpectedDoc("nc1386-document/" + jsonPath);
+        final var moduleDoc = getDocByModule(moduleName, revision);
+        JSONAssert.assertEquals(expectedJson, moduleDoc, IGNORE_ORDER);
+    }
+
+    private static Stream<Arguments> getDocByModuleTest() {
+        // moduleName, revision, jsonPath
+        return Stream.of(
+            Arguments.of("regex1", "2024-09-12", "regex1.json"),
+            Arguments.of("regex2", "2024-09-12", "regex2.json"),
+            Arguments.of("regex3", "2024-09-12", "regex3.json")
+        );
+    }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386-document/regex1.json b/restconf/restconf-openapi/src/test/resources/nc1386-document/regex1.json
new file mode 100644 (file)
index 0000000..0126f63
--- /dev/null
@@ -0,0 +1,213 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "version": "1.0.0",
+        "title": "regex1",
+        "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/data": {
+            "post": {
+                "description": "\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 - regex1 - regex1",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "cntr": {
+                                        "$ref": "#/components/schemas/regex1_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex1_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    }
+                },
+                "tags": [
+                    "Controller regex1"
+                ],
+                "parameters": []
+            }
+        },
+        "/rests/data/regex1:cntr": {
+            "put": {
+                "description": "",
+                "summary": "PUT - regex1 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "regex1:cntr": {
+                                        "$ref": "#/components/schemas/regex1_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex1_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex1"
+                ],
+                "parameters": []
+            },
+            "patch": {
+                "description": "",
+                "summary": "PATCH - regex1 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/yang-data+json": {
+                            "schema": {
+                                "properties": {
+                                    "regex1:cntr": {
+                                        "$ref": "#/components/schemas/regex1_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/yang-data+xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex1_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "OK"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex1"
+                ],
+                "parameters": []
+            },
+            "delete": {
+                "description": "",
+                "summary": "DELETE - Controller - regex1 - cntr",
+                "responses": {
+                    "204": {
+                        "description": "Deleted"
+                    }
+                },
+                "tags": [
+                    "Controller regex1"
+                ],
+                "parameters": []
+            },
+            "get": {
+                "description": "",
+                "summary": "GET - Controller - regex1 - cntr",
+                "responses": {
+                    "200": {
+                        "description": "200",
+                        "content": {
+                            "application/xml": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/regex1_cntr"
+                                }
+                            },
+                            "application/json": {
+                                "schema": {
+                                    "properties": {
+                                        "cntr": {
+                                            "$ref": "#/components/schemas/regex1_cntr",
+                                            "type": "object"
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "tags": [
+                    "Controller regex1"
+                ],
+                "parameters": [
+                    {
+                        "name": "content",
+                        "in": "query",
+                        "required": false,
+                        "schema": {
+                            "enum": [
+                                "config",
+                                "nonconfig",
+                                "all"
+                            ],
+                            "type": "string"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "regex1_cntr": {
+                "title": "regex1_cntr",
+                "type": "object",
+                "properties": {
+                    "lf": {
+                        "description": "",
+                        "type": "string",
+                        "example": "0",
+                        "minLength": 1,
+                        "maxLength": 100
+                    }
+                },
+                "xml": {
+                    "name": "cntr",
+                    "namespace": "urn:regex1"
+                }
+            }
+        },
+        "securitySchemes": {
+            "basicAuth": {
+                "scheme": "basic",
+                "type": "http"
+            }
+        }
+    },
+    "security": [
+        {
+            "basicAuth": []
+        }
+    ]
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386-document/regex2.json b/restconf/restconf-openapi/src/test/resources/nc1386-document/regex2.json
new file mode 100644 (file)
index 0000000..e7f9323
--- /dev/null
@@ -0,0 +1,213 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "version": "1.0.0",
+        "title": "regex2",
+        "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/data": {
+            "post": {
+                "description": "\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 - regex2 - regex2",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "cntr": {
+                                        "$ref": "#/components/schemas/regex2_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex2_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    }
+                },
+                "tags": [
+                    "Controller regex2"
+                ],
+                "parameters": []
+            }
+        },
+        "/rests/data/regex2:cntr": {
+            "put": {
+                "description": "",
+                "summary": "PUT - regex2 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "regex2:cntr": {
+                                        "$ref": "#/components/schemas/regex2_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex2_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex2"
+                ],
+                "parameters": []
+            },
+            "patch": {
+                "description": "",
+                "summary": "PATCH - regex2 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/yang-data+json": {
+                            "schema": {
+                                "properties": {
+                                    "regex2:cntr": {
+                                        "$ref": "#/components/schemas/regex2_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/yang-data+xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex2_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "OK"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex2"
+                ],
+                "parameters": []
+            },
+            "delete": {
+                "description": "",
+                "summary": "DELETE - Controller - regex2 - cntr",
+                "responses": {
+                    "204": {
+                        "description": "Deleted"
+                    }
+                },
+                "tags": [
+                    "Controller regex2"
+                ],
+                "parameters": []
+            },
+            "get": {
+                "description": "",
+                "summary": "GET - Controller - regex2 - cntr",
+                "responses": {
+                    "200": {
+                        "description": "200",
+                        "content": {
+                            "application/xml": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/regex2_cntr"
+                                }
+                            },
+                            "application/json": {
+                                "schema": {
+                                    "properties": {
+                                        "cntr": {
+                                            "$ref": "#/components/schemas/regex2_cntr",
+                                            "type": "object"
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "tags": [
+                    "Controller regex2"
+                ],
+                "parameters": [
+                    {
+                        "name": "content",
+                        "in": "query",
+                        "required": false,
+                        "schema": {
+                            "enum": [
+                                "config",
+                                "nonconfig",
+                                "all"
+                            ],
+                            "type": "string"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "regex2_cntr": {
+                "title": "regex2_cntr",
+                "type": "object",
+                "properties": {
+                    "lf": {
+                        "description": "",
+                        "type": "string",
+                        "example": "A",
+                        "minLength": 1,
+                        "maxLength": 100
+                    }
+                },
+                "xml": {
+                    "name": "cntr",
+                    "namespace": "urn:regex2"
+                }
+            }
+        },
+        "securitySchemes": {
+            "basicAuth": {
+                "scheme": "basic",
+                "type": "http"
+            }
+        }
+    },
+    "security": [
+        {
+            "basicAuth": []
+        }
+    ]
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386-document/regex3.json b/restconf/restconf-openapi/src/test/resources/nc1386-document/regex3.json
new file mode 100644 (file)
index 0000000..60c8998
--- /dev/null
@@ -0,0 +1,213 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "version": "1.0.0",
+        "title": "regex3",
+        "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/data": {
+            "post": {
+                "description": "\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 - regex3 - regex3",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "cntr": {
+                                        "$ref": "#/components/schemas/regex3_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex3_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    }
+                },
+                "tags": [
+                    "Controller regex3"
+                ],
+                "parameters": []
+            }
+        },
+        "/rests/data/regex3:cntr": {
+            "put": {
+                "description": "",
+                "summary": "PUT - regex3 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/json": {
+                            "schema": {
+                                "properties": {
+                                    "regex3:cntr": {
+                                        "$ref": "#/components/schemas/regex3_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex3_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "201": {
+                        "description": "Created"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex3"
+                ],
+                "parameters": []
+            },
+            "patch": {
+                "description": "",
+                "summary": "PATCH - regex3 - Controller - cntr",
+                "requestBody": {
+                    "description": "cntr",
+                    "content": {
+                        "application/yang-data+json": {
+                            "schema": {
+                                "properties": {
+                                    "regex3:cntr": {
+                                        "$ref": "#/components/schemas/regex3_cntr",
+                                        "type": "object"
+                                    }
+                                }
+                            }
+                        },
+                        "application/yang-data+xml": {
+                            "schema": {
+                                "$ref": "#/components/schemas/regex3_cntr"
+                            }
+                        }
+                    }
+                },
+                "responses": {
+                    "200": {
+                        "description": "OK"
+                    },
+                    "204": {
+                        "description": "Updated"
+                    }
+                },
+                "tags": [
+                    "Controller regex3"
+                ],
+                "parameters": []
+            },
+            "delete": {
+                "description": "",
+                "summary": "DELETE - Controller - regex3 - cntr",
+                "responses": {
+                    "204": {
+                        "description": "Deleted"
+                    }
+                },
+                "tags": [
+                    "Controller regex3"
+                ],
+                "parameters": []
+            },
+            "get": {
+                "description": "",
+                "summary": "GET - Controller - regex3 - cntr",
+                "responses": {
+                    "200": {
+                        "description": "200",
+                        "content": {
+                            "application/xml": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/regex3_cntr"
+                                }
+                            },
+                            "application/json": {
+                                "schema": {
+                                    "properties": {
+                                        "cntr": {
+                                            "$ref": "#/components/schemas/regex3_cntr",
+                                            "type": "object"
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "tags": [
+                    "Controller regex3"
+                ],
+                "parameters": [
+                    {
+                        "name": "content",
+                        "in": "query",
+                        "required": false,
+                        "schema": {
+                            "enum": [
+                                "config",
+                                "nonconfig",
+                                "all"
+                            ],
+                            "type": "string"
+                        }
+                    }
+                ]
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "regex3_cntr": {
+                "title": "regex3_cntr",
+                "type": "object",
+                "properties": {
+                    "lf": {
+                        "description": "",
+                        "type": "string",
+                        "example": "A-0",
+                        "minLength": 3,
+                        "maxLength": 250
+                    }
+                },
+                "xml": {
+                    "name": "cntr",
+                    "namespace": "urn:regex3"
+                }
+            }
+        },
+        "securitySchemes": {
+            "basicAuth": {
+                "scheme": "basic",
+                "type": "http"
+            }
+        }
+    },
+    "security": [
+        {
+            "basicAuth": []
+        }
+    ]
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386/regex1@2024-09-12.yang b/restconf/restconf-openapi/src/test/resources/nc1386/regex1@2024-09-12.yang
new file mode 100644 (file)
index 0000000..4920976
--- /dev/null
@@ -0,0 +1,16 @@
+module regex1 {
+    namespace "urn:regex1";
+    prefix "re1";
+    revision 2024-09-12 {
+        description "Initial revision.";
+    }
+
+    container cntr {
+        leaf lf {
+            type string {
+                length "1..100";
+                pattern "[A-Z]*[0-9]";
+            }
+        }
+    }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386/regex2@2024-09-12.yang b/restconf/restconf-openapi/src/test/resources/nc1386/regex2@2024-09-12.yang
new file mode 100644 (file)
index 0000000..214e3c5
--- /dev/null
@@ -0,0 +1,16 @@
+module regex2 {
+    namespace "urn:regex2";
+    prefix "re2";
+    revision 2024-09-12 {
+        description "Initial revision.";
+    }
+
+    container cntr {
+        leaf lf {
+            type string {
+                length "1..100";
+                pattern "[0-9]*[A-Z]";
+            }
+        }
+    }
+}
diff --git a/restconf/restconf-openapi/src/test/resources/nc1386/regex3@2024-09-12.yang b/restconf/restconf-openapi/src/test/resources/nc1386/regex3@2024-09-12.yang
new file mode 100644 (file)
index 0000000..7c890d6
--- /dev/null
@@ -0,0 +1,16 @@
+module regex3 {
+    namespace "urn:regex3";
+    prefix "re3";
+    revision 2024-09-12 {
+        description "Initial revision.";
+    }
+
+    container cntr {
+        leaf lf {
+            type string {
+                length "3..250";
+                pattern "([a-zA-Z]([a-zA-Z0-9_.-]*)([a-zA-Z0-9]))";
+            }
+        }
+    }
+}