FEATURE rpc & model metadata

Also fixed USES and GROUPING handling.
diff --git a/src/mod_netconf.c b/src/mod_netconf.c
index 12f1023..250164e 100644
--- a/src/mod_netconf.c
+++ b/src/mod_netconf.c
@@ -479,9 +479,9 @@
 }
 
 static void
-node_metadata_children(struct lys_node *node, json_object *parent)
+node_metadata_children_recursive(struct lys_node *node, json_object **child_array, json_object **choice_array)
 {
-    json_object *child_array = NULL, *choice_array = NULL, *obj;
+    json_object *obj;
     struct lys_node *child;
 
     if (!node->child) {
@@ -489,49 +489,42 @@
     }
 
     LY_TREE_FOR(node->child, child) {
-        if (child->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYXML)) {
+        if (child->nodetype == LYS_USES) {
+            node_metadata_children_recursive(child, child_array, choice_array);
+        } else if (child->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYXML)) {
             obj = json_object_new_string(child->name);
-            if (!child_array) {
-                child_array = json_object_new_array();
+            if (!*child_array) {
+                *child_array = json_object_new_array();
             }
-            json_object_array_add(child_array, obj);
+            json_object_array_add(*child_array, obj);
         } else if (child->nodetype == LYS_CHOICE) {
             obj = json_object_new_string(child->name);
-            if (!choice_array) {
-                choice_array = json_object_new_array();
+            if (!*choice_array) {
+                *choice_array = json_object_new_array();
             }
-            json_object_array_add(choice_array, obj);
+            json_object_array_add(*choice_array, obj);
         }
     }
-
-    if (child_array) {
-        json_object_object_add(parent, "children", child_array);
-    }
-    if (choice_array) {
-        json_object_object_add(parent, "choice", choice_array);
-    }
 }
 
 static void
-node_metadata_cases(struct lys_node_choice *choice, json_object *parent)
+node_metadata_cases_recursive(struct lys_node_choice *choice, json_object *array)
 {
-    json_object *array, *obj;
+    json_object *obj;
     struct lys_node *child;
 
     if (!choice->child) {
         return;
     }
 
-    array = json_object_new_array();
-
     LY_TREE_FOR(choice->child, child) {
-        if (child->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYXML | LYS_CASE)) {
+        if (child->nodetype == LYS_USES) {
+            node_metadata_cases_recursive((struct lys_node_choice *)child, array);
+        } else if (child->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_ANYXML | LYS_CASE)) {
             obj = json_object_new_string(child->name);
             json_object_array_add(array, obj);
         }
     }
-
-    json_object_object_add(parent, "cases", array);
 }
 
 static void
@@ -746,7 +739,7 @@
 static void
 node_metadata_container(struct lys_node_container *cont, json_object *parent)
 {
-    json_object *obj;
+    json_object *obj, *child_array = NULL, *choice_array = NULL;
 
     /* element type */
     obj = json_object_new_string("container");
@@ -765,13 +758,19 @@
     node_metadata_when(cont->when, parent);
 
     /* children & choice */
-    node_metadata_children((struct lys_node *)cont, parent);
+    node_metadata_children_recursive((struct lys_node *)cont, &child_array, &choice_array);
+    if (child_array) {
+        json_object_object_add(parent, "children", child_array);
+    }
+    if (choice_array) {
+        json_object_object_add(parent, "choice", choice_array);
+    }
 }
 
 static void
 node_metadata_choice(struct lys_node_choice *choice, json_object *parent)
 {
-    json_object *obj;
+    json_object *obj, *array;
 
     /* element type */
     obj = json_object_new_string("choice");
@@ -787,7 +786,11 @@
     node_metadata_when(choice->when, parent);
 
     /* cases */
-    node_metadata_cases(choice, parent);
+    if (choice->child) {
+        array = json_object_new_array();
+        node_metadata_cases_recursive(choice, array);
+        json_object_object_add(parent, "cases", array);
+    }
 }
 
 static void
@@ -865,7 +868,7 @@
 static void
 node_metadata_list(struct lys_node_list *list, json_object *parent)
 {
-    json_object *obj, *array;
+    json_object *obj, *array, *child_array = NULL, *choice_array = NULL;;
     int i;
     unsigned int j;
 
@@ -906,6 +909,15 @@
         }
         json_object_object_add(parent, "unique", array);
     }
+
+    /* children & choice */
+    node_metadata_children_recursive((struct lys_node *)list, &child_array, &choice_array);
+    if (child_array) {
+        json_object_object_add(parent, "children", child_array);
+    }
+    if (choice_array) {
+        json_object_object_add(parent, "choice", choice_array);
+    }
 }
 
 static void
@@ -944,6 +956,100 @@
     node_metadata_when(cas->when, parent);
 }
 
+static void
+node_metadata_rpc(struct lys_node_rpc *rpc, json_object *parent)
+{
+    json_object *obj;
+
+    /* element type */
+    obj = json_object_new_string("rpc");
+    json_object_object_add(parent, "eltype", obj);
+
+    /* description */
+    node_metadata_text(rpc->dsc, "description", parent);
+
+    /* reference */
+    node_metadata_text(rpc->ref, "reference", parent);
+
+    /* status */
+    if (rpc->flags & LYS_STATUS_DEPRC) {
+        obj = json_object_new_string("deprecated");
+    } else if (rpc->flags & LYS_STATUS_OBSLT) {
+        obj = json_object_new_string("obsolete");
+    } else {
+        obj = json_object_new_string("current");
+    }
+    json_object_object_add(parent, "status", obj);
+}
+
+static void
+node_metadata_model(struct lys_module *module, json_object *parent)
+{
+    json_object *obj, *array, *item;
+    int i;
+
+    /* yang-version */
+    if (module->version == 2) {
+        obj = json_object_new_string("1.1");
+    } else {
+        obj = json_object_new_string("1.0");
+    }
+    json_object_object_add(parent, "yang-version", obj);
+
+    /* namespace */
+    node_metadata_text(module->ns, "namespace", parent);
+
+    /* prefix */
+    node_metadata_text(module->prefix, "prefix", parent);
+
+    /* contact */
+    node_metadata_text(module->contact, "contact", parent);
+
+    /* organization */
+    node_metadata_text(module->org, "organization", parent);
+
+    /* revision */
+    if (module->rev_size) {
+        node_metadata_text(module->rev[0].date, "revision", parent);
+    }
+
+    /* description */
+    node_metadata_text(module->dsc, "description", parent);
+
+    /* import */
+    if (module->imp_size) {
+        array = json_object_new_array();
+        for (i = 0; i < module->imp_size; ++i) {
+            item = json_object_new_object();
+
+            node_metadata_text(module->imp[i].module->name, "name", item);
+            node_metadata_text(module->imp[i].prefix, "prefix", item);
+            if (module->imp[i].rev && module->imp[i].rev[0]) {
+                node_metadata_text(module->imp[i].rev, "revision", item);
+            }
+
+            json_object_array_add(array, item);
+        }
+        json_object_object_add(parent, "imports", array);
+    }
+
+    /* include */
+    if (module->inc_size) {
+        array = json_object_new_array();
+        for (i = 0; i < module->inc_size; ++i) {
+            item = json_object_new_object();
+
+            node_metadata_text(module->inc[i].submodule->name, "name", item);
+            if (module->inc[i].rev && module->inc[i].rev[0]) {
+                node_metadata_text(module->inc[i].rev, "revision", item);
+            }
+
+            json_object_array_add(array, item);
+        }
+        json_object_object_add(parent, "includes", array);
+    }
+}
+
 /**
  * \defgroup netconf_operations NETCONF operations
  * The list of NETCONF operations that mod_netconf supports.
@@ -1930,6 +2036,11 @@
     json_object *meta_obj;
     char *obj_name;
 
+    if (node->nodetype == LYS_INPUT) {
+        /* silently skipped */
+        return 0;
+    }
+
     cur_module = node->module;
     if (cur_module->type) {
         cur_module = ((struct lys_submodule *)cur_module)->belongsto;
@@ -1941,7 +2052,7 @@
     }
 
     /* in (leaf-)lists the metadata could have already been added */
-    if (json_object_object_get_ex(parent, obj_name, NULL) == TRUE) {
+    if ((node->nodetype & (LYS_LEAFLIST | LYS_LIST)) && (json_object_object_get_ex(parent, obj_name, NULL) == TRUE)) {
         free(obj_name);
         return 1;
     }
@@ -1970,11 +2081,10 @@
         case LYS_CASE:
             node_metadata_case((struct lys_node_case *)node, meta_obj);
             break;
-        /* TODO
-        LYS_INPUT
-        LYS_OUTPUT
-        LYS_RPC*/
-        default:
+        case LYS_RPC:
+            node_metadata_rpc((struct lys_node_rpc *)node, meta_obj);
+            break;
+        default: /* LYS_OUTPUT */
             ERROR("Internal: unuxpected nodetype (%s:%d)", __FILE__, __LINE__);
             break;
     }
@@ -2001,6 +2111,10 @@
     char *child_name;
     int list_idx;
 
+    if (data_tree->schema->nodetype & (LYS_OUTPUT | LYS_GROUPING)) {
+        return;
+    }
+
     /* add data_tree metadata */
     if (node_add_metadata(data_tree->schema, module, data_json_parent)) {
         return;
@@ -2067,6 +2181,19 @@
 }
 
 static void
+node_add_model_metadata(struct lys_module *module, json_object *parent)
+{
+    json_object *obj;
+    char *str;
+
+    obj = json_object_new_object();
+    node_metadata_model(module, obj);
+    asprintf(&str, "$@@%s", module->name);
+    json_object_object_add(parent, str, obj);
+    free(str);
+}
+
+static void
 node_add_children_with_metadata_recursive(struct lys_node *node, struct lys_module *module, json_object *parent)
 {
     struct lys_module *cur_module;
@@ -2074,12 +2201,27 @@
     json_object *node_json;
     char *json_name;
 
+    if (node->nodetype & (LYS_OUTPUT | LYS_GROUPING)) {
+        return;
+    }
+
+    if (node->nodetype & LYS_USES) {
+        cur_module = module;
+        node_json = parent;
+        goto children;
+    }
+
     /* add node metadata */
     if (node_add_metadata(node, module, parent)) {
         ERROR("Internal: metadata duplicate for \"%s\".", node->name);
         return;
     }
 
+    /* no other metadata */
+    if (!node->child) {
+        return;
+    }
+
     /* get node module */
     cur_module = node->module;
     if (cur_module->type) {
@@ -2096,6 +2238,7 @@
         free(json_name);
     }
 
+children:
     LY_TREE_FOR(node->child, child) {
         node_add_children_with_metadata_recursive(child, cur_module, node_json);
     }
@@ -2105,6 +2248,7 @@
 libyang_query(unsigned int session_key, const char *filter, int load_children)
 {
     struct lys_node *node;
+    struct lys_module *module = NULL;
     struct session_with_mutex *locked_session;
     json_object *ret = NULL, *data;
 
@@ -2116,29 +2260,44 @@
 
     locked_session->last_activity = time(NULL);
 
-    /* collect schema metadata and create reply */
-    node = ly_ctx_get_node(locked_session->ctx, filter);
+    if (filter[0] == '/') {
+        node = ly_ctx_get_node(locked_session->ctx, filter);
+        if (!node) {
+            ret = create_error_reply("Failed to resolve XPath filter node.");
+            goto finish;
+        }
+    } else {
+        module = ly_ctx_get_module(locked_session->ctx, filter, NULL);
+        if (!module) {
+            ret = create_error_reply("Failed to find model.");
+            goto finish;
+        }
+    }
 
-    if (node) {
-        pthread_mutex_lock(&json_lock);
-        data = json_object_new_object();
+    pthread_mutex_lock(&json_lock);
+    data = json_object_new_object();
 
+    if (module) {
+        node_add_model_metadata(module, data);
+        if (load_children) {
+            LY_TREE_FOR(module->data, node) {
+                node_add_children_with_metadata_recursive(node, NULL, data);
+            }
+        }
+    } else {
         if (load_children) {
             node_add_children_with_metadata_recursive(node, NULL, data);
         } else {
             node_add_metadata(node, NULL, data);
         }
-
-        pthread_mutex_unlock(&json_lock);
-        ret = create_data_reply(json_object_to_json_string(data));
-        json_object_put(data);
-    } else {
-        ret = create_error_reply("Failed to resolve XPath filter node.");
     }
 
-    session_unlock(locked_session);
+    pthread_mutex_unlock(&json_lock);
+    ret = create_data_reply(json_object_to_json_string(data));
+    json_object_put(data);
 
 finish:
+    session_unlock(locked_session);
     return ret;
 }