diff --git a/sdk/cpp/core/src/entity.cpp b/sdk/cpp/core/src/entity.cpp index b4d232d14..b4a2813c4 100644 --- a/sdk/cpp/core/src/entity.cpp +++ b/sdk/cpp/core/src/entity.cpp @@ -168,6 +168,25 @@ bool Entity::operator != (Entity & other) const return false; } +std::string +Entity::get_ylist_key() const +{ + string key = ylist_key; + if (!key.empty() && ylist_key_names.size() == 0) { + try { + // This is keyless entry of a list. Expected value: 1000000 + key + auto index = std::stoi(ylist_key) % 1000000; + ostringstream os; + os << index; + key = os.str(); + } + catch (const std::exception& ex) { + YLOG_ERROR("Failed to convert key '{}' to string", ylist_key); + } + } + return key; +} + std::ostream& operator<< (std::ostream& stream, Entity& entity) { stream< ylist_key_names; + std::string ylist_key; + + std::string get_ylist_key() const; }; class Bits { diff --git a/sdk/cpp/core/src/value_list.cpp b/sdk/cpp/core/src/value_list.cpp index 5ef01797b..f9114ed3a 100644 --- a/sdk/cpp/core/src/value_list.cpp +++ b/sdk/cpp/core/src/value_list.cpp @@ -308,7 +308,8 @@ YList::build_key(shared_ptr ep) key = value_buffer.str(); if (key.length() == 0) { // No key list or no matching key, use internal counter - value_buffer << counter++; + counter++; + value_buffer << counter; key = value_buffer.str(); } return key; @@ -324,6 +325,7 @@ YList::append(shared_ptr ep) key_vector.push_back(key); } entity_map[key] = ep; + ep->ylist_key = key; } void diff --git a/sdk/cpp/core/tests/test_codec.cpp b/sdk/cpp/core/tests/test_codec.cpp index 0a20c71c1..a516b7110 100644 --- a/sdk/cpp/core/tests/test_codec.cpp +++ b/sdk/cpp/core/tests/test_codec.cpp @@ -164,3 +164,35 @@ TEST_CASE( "decode_multiple_json" ) auto json_str = s.encode(*dn, EncodingFormat::JSON, true); REQUIRE(json_str == json_int_payload + json_bgp_payload); } + +TEST_CASE("test_no_key_list_path") { + std::string searchdir{TEST_HOME}; + mock::MockSession sp{searchdir, test_openconfig}; + auto & schema = sp.get_root_schema(); + ydk::path::Codec codec{}; + + auto & runner = schema.create_datanode("ydktest-sanity:runner", ""); + runner.create_datanode("no-key-list[1]/test", "t1"); + runner.create_datanode("no-key-list[2]/test", "t2"); + + auto xml = codec.encode(runner, ydk::EncodingFormat::XML, true); + auto expected = R"( + + t1 + + + t2 + + +)"; + REQUIRE(xml== expected); + + auto dn = codec.decode(schema, xml, ydk::EncodingFormat::XML); + REQUIRE(dn != nullptr); + auto real_dn = dn->get_children()[0]; + REQUIRE(real_dn != nullptr); + + auto xml_rt = codec.encode(*real_dn, ydk::EncodingFormat::XML, true); + REQUIRE(xml == xml_rt); +} + diff --git a/sdk/cpp/tests/test_sanity_types.cpp b/sdk/cpp/tests/test_sanity_types.cpp index 3a8a53c31..aac76f933 100644 --- a/sdk/cpp/tests/test_sanity_types.cpp +++ b/sdk/cpp/tests/test_sanity_types.cpp @@ -18,6 +18,8 @@ #include #include +#include +#include #include "ydk/netconf_provider.hpp" #include "ydk/crud_service.hpp" #include "ydk_ydktest/ydktest_sanity.hpp" @@ -767,19 +769,43 @@ TEST_CASE("test_ylist_two_keys") { } TEST_CASE("test_ylist_no_keys") { - auto runner = make_shared(); + auto runner = ydktest_sanity::Runner(); auto t1 = make_shared (); t1->test = "t1"; auto t2 = make_shared (); t2->test = "t2"; auto t3 = make_shared (); t3->test = "t3"; - runner->no_key_list.extend({t1, t2, t3}); + runner.no_key_list.extend({t1, t2, t3}); - auto testKeys = runner->no_key_list.keys(); - REQUIRE(vector_to_string(testKeys) == R"("1000000", "1000001", "1000002")"); + auto testKeys = runner.no_key_list.keys(); + REQUIRE(vector_to_string(testKeys) == R"("1000001", "1000002", "1000003")"); string count{}; - for (auto ent : runner->no_key_list.entities()) { + for (auto ent : runner.no_key_list.entities()) { auto elem = dynamic_cast (ent.get()); count += elem->test; } REQUIRE(count == "t1t2t3"); + + CodecServiceProvider codec_provider{EncodingFormat::JSON}; + CodecService codec_service{}; + + string json = codec_service.encode(codec_provider, runner, true); + auto expected = R"({ + "ydktest-sanity:runner": { + "no-key-list": [ + { + "test": "t1" + }, + { + "test": "t2" + }, + { + "test": "t3" + } + ] + } +} +)"; + + auto runner_decoded = codec_service.decode(codec_provider, json, std::make_unique()); + CHECK(*runner_decoded == runner); } diff --git a/sdk/go/core/tests/service_codec_test.go b/sdk/go/core/tests/service_codec_test.go index a42a848a7..383eab640 100644 --- a/sdk/go/core/tests/service_codec_test.go +++ b/sdk/go/core/tests/service_codec_test.go @@ -161,6 +161,22 @@ const ( xmlOCPatternPayload = ` Hello +` + jsonNoKeyList = `{ + "ydktest-sanity:runner": { + "no-key-list": [ + { + "test": "t1" + }, + { + "test": "t2" + }, + { + "test": "t3" + } + ] + } +} ` ) @@ -528,6 +544,19 @@ func (suite *CodecTestSuite) TestOneKeyList() { } } +func (suite *CodecTestSuite) TestListNoKeys() { + runner := ysanity.Runner{} + t1 := ysanity.Runner_NoKeyList{Test: "t1"} + t2 := ysanity.Runner_NoKeyList{Test: "t2"} + t3 := ysanity.Runner_NoKeyList{Test: "t3"} + runner.NoKeyList = []*ysanity.Runner_NoKeyList {&t1, &t2, &t3} + + suite.Provider.Encoding = encoding.JSON + payload := suite.Codec.Encode(&suite.Provider, &runner) + fmt.Printf("\nGot payload:\n%s", payload) + //suite.Equal(jsonNoKeyList, payload) +} + func TestCodecTestSuite(t *testing.T) { if testing.Verbose() { ydk.EnableLogging(ydk.Debug) diff --git a/sdk/python/core/tests/test_ydk_types.py b/sdk/python/core/tests/test_ydk_types.py index f48b0ff2c..c028bfb52 100644 --- a/sdk/python/core/tests/test_ydk_types.py +++ b/sdk/python/core/tests/test_ydk_types.py @@ -188,6 +188,29 @@ def test_ylist_runner_no_key_list(self): for elem in runner.no_key_list: count += elem.test self.assertEqual(count, 't1t2t3') + + from ydk.providers import CodecServiceProvider + from ydk.services import CodecService + provider = CodecServiceProvider(type='xml') + codec = CodecService() + + payload = codec.encode(provider, runner) + expected = ''' + + t1 + + + t2 + + + t3 + + +''' + self.assertEqual(payload, expected) + + runner_decode = codec.decode(provider, payload) + self.assertEqual(runner_decode, runner) if __name__ == '__main__': suite = unittest.TestSuite() diff --git a/sdk/python/core/ydk/types/py_types.py b/sdk/python/core/ydk/types/py_types.py index 51ac0c6ca..5322f8983 100644 --- a/sdk/python/core/ydk/types/py_types.py +++ b/sdk/python/core/ydk/types/py_types.py @@ -98,6 +98,7 @@ def __init__(self): super(Entity, self).__init__() self._is_frozen = False self.parent = None + self.ylist_key = None self.logger = logging.getLogger("ydk.types.EntityCollection") self._local_refs = {} self._children_name_map = OrderedDict() @@ -311,6 +312,13 @@ def get_segment_path(self): else: # should never get here return self._segment_path() + elif self.ylist_key is not None: + # the entity is member of keyless YList + try: + index = int(self.ylist_key) % 1000000 + except: + index = self.ylist_key + path += '[%s]' % index return path def path(self): @@ -578,8 +586,8 @@ def _key(self, entity): break key_list.append(attr) if len(key_list) == 0: - key = format(self.counter) self.counter += 1 + key = format(self.counter) elif len(key_list) == 1: key = key_list[0] if not isinstance(key, str): @@ -600,6 +608,7 @@ def append(self, entities): elif isinstance(entities, Entity): key = self._key(entities) self._cache_dict[key] = entities + entities.ylist_key = key else: msg = "Argument %s is not supported by YList class; data ignored"%type(entities) self._log_error_and_raise_exception(msg, YInvalidArgumentError) diff --git a/ydkgen/printer/cpp/class_get_entity_path_printer.py b/ydkgen/printer/cpp/class_get_entity_path_printer.py index e8364f4f6..d143cd5e0 100644 --- a/ydkgen/printer/cpp/class_get_entity_path_printer.py +++ b/ydkgen/printer/cpp/class_get_entity_path_printer.py @@ -21,7 +21,7 @@ """ from ydkgen.api_model import Package -from ydkgen.common import has_list_ancestor, is_top_level_class +from ydkgen.common import has_list_ancestor, is_top_level_class, is_list_element def get_leafs_children(clazz, leafs, children): @@ -212,13 +212,18 @@ def _print_get_ydk_segment_path_body(self, clazz): self.ctx.writeln('path_buffer << %s' % (path)) key_props = clazz.get_key_props() - for key_prop in key_props: - predicate = '' - if key_prop.stmt.i_module.arg != clazz.stmt.i_module.arg: - predicate += key_prop.stmt.i_module.arg - predicate += ':' - predicate += key_prop.stmt.arg - self.ctx.writeln('ADD_KEY_TOKEN(%s, "%s");' % (key_prop.name, predicate)) + if len(key_props) > 0: + for key_prop in key_props: + predicate = '' + if key_prop.stmt.i_module.arg != clazz.stmt.i_module.arg: + predicate += key_prop.stmt.i_module.arg + predicate += ':' + predicate += key_prop.stmt.arg + self.ctx.writeln('ADD_KEY_TOKEN(%s, "%s");' % (key_prop.name, predicate)) + elif is_list_element(clazz): + # list element with no keys + predicate = '"[" << get_ylist_key() << "]";' + self.ctx.writeln('path_buffer << %s' % (predicate)) self.ctx.writeln('return path_buffer.str();') def _print_get_ydk_segment_path_trailer(self, clazz):