From e07bd5c2a0415fa8b87cdf7e794636fae014f709 Mon Sep 17 00:00:00 2001 From: Graeme Campbell Date: Thu, 19 Sep 2024 09:56:17 +1200 Subject: [PATCH] Fix the response to the restconf depth parameter RFC 8040 section B.3.2 gives examples of how to respond to the depth parameter on a restconf query. The current output is not consistent with the example output. --- apteryx-xml.h | 5 +- schema.c | 252 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 236 insertions(+), 21 deletions(-) diff --git a/apteryx-xml.h b/apteryx-xml.h index 5ec07c6..b252ba1 100644 --- a/apteryx-xml.h +++ b/apteryx-xml.h @@ -124,14 +124,17 @@ typedef enum SCH_F_IDREF_VALUES = (1 << 15), /* Expand identityref based values to include type information */ SCH_F_MODIFY_DATA = (1 << 16), /* The created tree will be used to modify the associated model */ SCH_F_CONDITIONS = (1 << 17), /* Check the schema node for any condition attributes */ + SCH_F_DEPTH = (1 << 18), /* Query to a specific depth */ } sch_flags; GNode *sch_path_to_gnode (sch_instance * instance, sch_node * schema, const char * path, int flags, sch_node ** rschema); -bool sch_query_to_gnode (sch_instance * instance, sch_node * schema, GNode *parent, const char * query, int flags, int *rflags); +bool sch_query_to_gnode (sch_instance * instance, sch_node * schema, GNode *parent, const char * query, int flags, + int *rflags, int *param_depth); bool sch_traverse_tree (sch_instance * instance, sch_node * schema, GNode * node, int flags, int rdepth); GNode *sch_path_to_query (sch_instance * instance, sch_node * schema, const char * path, int flags); //DEPRECATED void sch_gnode_sort_children (sch_node * schema, GNode * parent); void sch_check_condition (sch_node *node, GNode *root, int flags, char **path, char **condition); bool sch_apply_conditions (sch_instance * instance, sch_node * schema, GNode *node, int flags); +bool sch_trim_tree_by_depth (sch_instance *instance, sch_node *schema, GNode *node, int flags, int rdepth); #ifdef APTERYX_XML_JSON #include diff --git a/schema.c b/schema.c index a3bfa5b..892bb74 100644 --- a/schema.c +++ b/schema.c @@ -2443,7 +2443,7 @@ parse_query_fields (sch_node * schema, char *fields, GNode *parent, int flags, i } static bool -_sch_query_to_gnode (GNode *root, sch_node *schema, char *query, int *rflags, int depth) +_sch_query_to_gnode (GNode *root, sch_node *schema, char *query, int *rflags, int depth, int *param_depth) { int flags = rflags? * rflags : 0; GNode *node; @@ -2509,7 +2509,10 @@ _sch_query_to_gnode (GNode *root, sch_node *schema, char *query, int *rflags, in } if (qdepth == 1) flags |= SCH_F_DEPTH_ONE; + + *param_depth = qdepth; } + flags |= SCH_F_DEPTH; depth_seen = true; } else if (strncmp (parameter, "with-defaults=", strlen ("with-defaults=")) == 0) @@ -2562,7 +2565,7 @@ _sch_query_to_gnode (GNode *root, sch_node *schema, char *query, int *rflags, in else { if (qdepth != INT_MAX) - qdepth += depth; + qdepth = depth; for (sch_node *s = sch_node_child_first (schema); s; s = sch_node_next_sibling (s)) { /* Recurse tree only adding config elements */ @@ -2584,10 +2587,12 @@ _sch_query_to_gnode (GNode *root, sch_node *schema, char *query, int *rflags, in return rc; } -bool sch_query_to_gnode (sch_instance * instance, sch_node * schema, GNode *parent, const char * query, int flags, int *rflags) +bool sch_query_to_gnode (sch_instance * instance, sch_node * schema, GNode *parent, const char * query, int flags, + int *rflags, int *param_depth) { int _flags = flags; - bool rc = _sch_query_to_gnode (parent, schema ?: xmlDocGetRootElement (instance->doc), (char *) query, &_flags, 0); + bool rc = _sch_query_to_gnode (parent, schema ?: xmlDocGetRootElement (instance->doc), (char *) query, + &_flags, 0, param_depth); if (rflags) *rflags = _flags; return rc; @@ -2912,6 +2917,7 @@ sch_path_to_query (sch_instance * instance, sch_node * schema, const char *path, char *query; GNode *root; int depth; + int param_depth = 0; /* Split off any query first */ query = strchr (path, '?'); @@ -2937,7 +2943,7 @@ sch_path_to_query (sch_instance * instance, sch_node * schema, const char *path, /* Process the query */ depth = g_node_max_height (root); - if (query && !_sch_query_to_gnode (root, schema, query, &flags, depth)) + if (query && !_sch_query_to_gnode (root, schema, query, &flags, depth, ¶m_depth)) { apteryx_free_tree (root); root = NULL; @@ -3592,21 +3598,24 @@ _sch_gnode_to_json (sch_instance * instance, sch_node * schema, xmlNs *ns, GNode DEBUG (flags, "%*s%s[", depth * 2, " ", APTERYX_NAME (node)); for (GNode * child = node->children; child; child = child->next) { - bool added = false; - if (flags & SCH_F_JSON_TYPES) - { - sch_node *cschema = sch_node_child_first (schema); - char *value = g_strdup (APTERYX_VALUE (child) ?: ""); - value = sch_translate_to (cschema, value); - json_array_append_new (data, encode_json_type (cschema, value)); - DEBUG (flags, "%s%s", value, child->next ? ", " : ""); - free (value); - added = true; - } - if (!added) + if (child->children) { - DEBUG (flags, "%s%s", APTERYX_VALUE (child), child->next ? ", " : ""); - json_array_append_new (data, json_string ((const char* ) APTERYX_VALUE (child))); + bool added = false; + if (flags & SCH_F_JSON_TYPES) + { + sch_node *cschema = sch_node_child_first (schema); + char *value = g_strdup (APTERYX_VALUE (child) ?: ""); + value = sch_translate_to (cschema, value); + json_array_append_new (data, encode_json_type (cschema, value)); + DEBUG (flags, "%s%s", value, child->next ? ", " : ""); + free (value); + added = true; + } + if (!added) + { + DEBUG (flags, "%s%s", APTERYX_VALUE (child), child->next ? ", " : ""); + json_array_append_new (data, json_string ((const char* ) APTERYX_VALUE (child))); + } } } DEBUG (flags, "]\n"); @@ -3658,6 +3667,9 @@ _sch_gnode_to_json (sch_instance * instance, sch_node * schema, xmlNs *ns, GNode sch_gnode_sort_children (schema, node); for (GNode * child = node->children; child; child = child->next) { + if (!child->data && (flags & SCH_F_DEPTH)) + continue; + json_t *node = _sch_gnode_to_json (instance, schema, ns, child, flags, depth + 1); bool added = false; if (flags & SCH_F_NS_PREFIX) @@ -3680,7 +3692,8 @@ _sch_gnode_to_json (sch_instance * instance, sch_node * schema, xmlNs *ns, GNode json_object_set_new (data, APTERYX_NAME (child), node); } /* Throw away this node if no chldren (unless it's a presence container) */ - if (json_object_iter (data) == NULL && ((xmlNode *)schema)->children) + if ((flags & SCH_F_DEPTH) == 0 && json_object_iter (data) == NULL && + ((xmlNode *)schema)->children) { json_decref (data); data = NULL; @@ -4121,3 +4134,202 @@ sch_apply_conditions (sch_instance * instance, sch_node * schema, GNode *node, i return rc; } + +static bool +_sch_trim_tree_by_depth (sch_instance *instance, sch_node *schema, GNode *parent, int flags, int depth, int rdepth) +{ + char *name = sch_name (schema); + GNode *child = apteryx_find_child (parent, name); + bool rc = true; + + if (sch_is_proxy (schema) && g_strcmp0 (name, "*") == 0) + { + xmlNs *nns = NULL; + char *colon; + + /* move to the list index specifier */ + child = parent->children; + if (!child) + { + rc = false; + goto exit; + } + /* skip over the list index specifier */ + child = child->children; + if (!child) + { + rc = false; + goto exit; + } + g_free (name); + name = g_strdup (APTERYX_NAME (child)); + schema = xmlDocGetRootElement (instance->doc); + colon = strchr (name, ':'); + if (schema && colon) + { + colon[0] = '\0'; + nns = sch_lookup_ns (instance, schema, name, flags, false); + if (!nns) + { + /* No namespace found assume the node is supposed to have a colon in it */ + colon[0] = ':'; + } + else + { + /* We found a namespace. Remove the prefix */ + char *_name = name; + name = g_strdup (colon + 1); + free (_name); + } + } + schema = _sch_node_child (nns, schema, name); + if (schema) + { + g_free (name); + name = sch_name (schema); + } + else + { + /* This can happen if a query is for a field of the proxy schema itself */ + rc = false; + goto exit; + } + depth++; + } + + if (sch_is_leaf_list (schema)) + { + if (depth >= rdepth - 1) + { + GList *deletes = NULL; + if (g_strcmp0 (name, APTERYX_NAME (parent->children)) == 0) + { + for (GNode *pcc = parent->children->children; pcc; pcc = pcc->next) + deletes = g_list_prepend (deletes, pcc); + + for (GList *iter = g_list_first (deletes); iter; iter = g_list_next (iter)) + { + GNode *pc = iter->data; + g_node_unlink (pc); + apteryx_free_tree (pc); + } + g_list_free (deletes); + } + } + } + else if (sch_is_leaf (schema)) + { + if ((depth >= rdepth) && child) + { + free ((void *)child->children->data); + free ((void *)child->data); + g_node_unlink (child); + g_node_destroy (child); + child = NULL; + } + } + else if (g_strcmp0 (name, "*") == 0) + { + if (depth < rdepth) + { + for (GNode *child = parent->children; child; child = child->next) + { + for (sch_node *s = sch_node_child_first (schema); s; s = sch_node_next_sibling (s)) + { + rc = _sch_trim_tree_by_depth (instance, s, child, flags, depth+1, rdepth); + if (!rc) + goto exit; + } + } + } + } + else if (child && depth < rdepth) + { + for (sch_node *s = sch_node_child_first (schema); s; s = sch_node_next_sibling (s)) + { + if (depth + 2 >= rdepth) + { + GList *deletes = NULL; + for (GNode *cc = child->children; cc; cc = cc->next) + deletes = g_list_prepend (deletes, cc); + + for (GList *iter = g_list_first (deletes); iter; iter = g_list_next (iter)) + { + GNode *cc = iter->data; + g_node_unlink (cc); + apteryx_free_tree (cc); + } + g_list_free (deletes); + break; + } + + rc = _sch_trim_tree_by_depth (instance, s, child, flags, depth+1, rdepth); + if (!rc) + goto exit; + } + } + +exit: + free (name); + return rc; +} + +bool +sch_trim_tree_by_depth (sch_instance *instance, sch_node *schema, GNode *node, int flags, int rdepth) +{ + bool rc = false; + + schema = schema ?: xmlDocGetRootElement (instance->doc); + /* if this has been called from restconf, then the schema may be at a proxy node */ + if (sch_is_proxy (schema)) + { + xmlNs *nns = NULL; + char *colon; + char *name; + + /* move to the list index specifier */ + node = node->children; + if (!node) + return rc; + name = g_strdup (APTERYX_NAME (node)); + schema = xmlDocGetRootElement (instance->doc); + colon = strchr (name, ':'); + if (schema && colon) + { + colon[0] = '\0'; + nns = sch_lookup_ns (instance, schema, name, flags, false); + if (!nns) + { + /* No namespace found assume the node is supposed to have a colon in it */ + colon[0] = ':'; + } + else + { + /* We found a namespace. Remove the prefix */ + char *_name = name; + name = g_strdup (colon + 1); + free (_name); + } + } + schema = _sch_node_child (nns, schema, name); + g_free (name); + if (!schema) + return rc; + } + + if (sch_is_leaf (schema)) + { + rc = _sch_trim_tree_by_depth (instance, schema, node->parent, flags, 0, rdepth); + } + else + { + for (sch_node *s = sch_node_child_first (schema); s; s = sch_node_next_sibling (s)) + { + rc = _sch_trim_tree_by_depth (instance, s, node, flags, 0, rdepth); + if (!rc) + break; + } + } + + return rc; +}