From 373b04f24e79006321a23ab42fecd06976f6b8ff Mon Sep 17 00:00:00 2001
From: Jean-Marie PEDURAND <jm81@hotmail.fr>
Date: Wed, 13 Nov 2019 21:42:10 +0100
Subject: [PATCH] [feat] gva: init gva module

---
 bin/dunitrust-server/Cargo.toml       |   7 +-
 bin/dunitrust-server/src/main.rs      |   3 +-
 lib/modules/gva/Cargo.toml            |  36 +++
 lib/modules/gva/resources/schema.gql  |  20 ++
 lib/modules/gva/src/context.rs        |  53 ++++
 lib/modules/gva/src/lib.rs            | 345 ++++++++++++++++++++++++++
 lib/modules/gva/src/schema.rs         |  96 +++++++
 lib/modules/gva/src/webserver.rs      |  77 ++++++
 lib/tools/dbs-tools/src/kv_db/file.rs |   1 +
 9 files changed, 634 insertions(+), 4 deletions(-)
 create mode 100644 lib/modules/gva/Cargo.toml
 create mode 100644 lib/modules/gva/resources/schema.gql
 create mode 100644 lib/modules/gva/src/context.rs
 create mode 100644 lib/modules/gva/src/lib.rs
 create mode 100644 lib/modules/gva/src/schema.rs
 create mode 100644 lib/modules/gva/src/webserver.rs

diff --git a/bin/dunitrust-server/Cargo.toml b/bin/dunitrust-server/Cargo.toml
index 0049af47..9d670032 100644
--- a/bin/dunitrust-server/Cargo.toml
+++ b/bin/dunitrust-server/Cargo.toml
@@ -14,13 +14,14 @@ edition = "2018"
 [dependencies]
 durs-network = { path = "../../lib/core/network" }
 durs-core = { path = "../../lib/core/core" }
+durs-gva = { path = "../../lib/modules/gva" }
 durs-module = { path = "../../lib/core/module" }
 #durs-skeleton = { path = "../../lib/modules/skeleton" }
 durs-ws2p = { path = "../../lib/modules/ws2p/ws2p" }
 durs-ws2p-v1-legacy = { path = "../../lib/modules/ws2p-v1-legacy" }
-log = "0.4.*"
-structopt = "0.2.*"
-human-panic = "1.0.*"
+human-panic = "1.0.1"
+log = "0.4.8"
+structopt= "0.2.18"
 
 [target.'cfg(unix)'.dependencies]
 durs-tui = { path = "../../lib/modules/tui" }
diff --git a/bin/dunitrust-server/src/main.rs b/bin/dunitrust-server/src/main.rs
index b0f123bf..00c91cef 100644
--- a/bin/dunitrust-server/src/main.rs
+++ b/bin/dunitrust-server/src/main.rs
@@ -36,6 +36,7 @@ use durs_core::durs_plug;
 use log::error;
 use structopt::StructOpt;
 
+pub use durs_gva::GvaModule;
 #[cfg(unix)]
 pub use durs_tui::TuiModule;
 //pub use durs_skeleton::SkeletonModule;
@@ -63,7 +64,7 @@ macro_rules! durs_cli_main {
 fn main() {
     durs_cli_main!(durs_plug!(
         [WS2Pv1Module, WS2PModule],
-        [TuiModule /*, SkeletonModule ,DasaModule*/]
+        [TuiModule, GvaModule /*, SkeletonModule ,DasaModule*/]
     ))
 }
 #[cfg(unix)]
diff --git a/lib/modules/gva/Cargo.toml b/lib/modules/gva/Cargo.toml
new file mode 100644
index 00000000..b6f91e4c
--- /dev/null
+++ b/lib/modules/gva/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "durs-gva"
+version = "0.1.0"
+authors = ["name <jm81@tuta.io>"]
+description = "Web client api"
+license = "AGPL-3.0"
+edition = "2018"
+
+[lib]
+path = "src/lib.rs"
+
+[dependencies]
+proc-macro2 = "1.0.6"
+actix-web = "1.0.8"
+dubp-block-doc = { path = "../../dubp/block-doc"} #, version = "0.1.0" }
+durs-bc-db-reader = { path = "../../modules-lib/bc-db-reader" }
+durs-conf = { path = "../../core/conf" }
+durs-message =  { path = "../../core/message" }
+durs-module = { path = "../../core/module" }
+durs-network = { path = "../../core/network" }
+dubp-common-doc = { path = "../../dubp/common-doc"} #, version = "0.1.0" }
+durs-common-tools = { path = "../../tools/common-tools" }
+dubp-currency-params = { path = "../../dubp/currency-params" }
+failure = "0.1.5"
+futures = "0.1"
+futures-cpupool = "0.1"
+juniper = "0.14.0"
+
+juniper-from-schema =  "0.5.0"
+log = "0.4.8"
+serde = "1.0.102"
+serde_derive = "1.0.102"
+serde_json = "1.0.41"
+structopt= "0.2.18"
+
+[features]
diff --git a/lib/modules/gva/resources/schema.gql b/lib/modules/gva/resources/schema.gql
new file mode 100644
index 00000000..8115701e
--- /dev/null
+++ b/lib/modules/gva/resources/schema.gql
@@ -0,0 +1,20 @@
+
+schema {
+  query: Query
+  mutation: Mutation
+}
+
+type Query {
+  current: Block @juniper(ownership: "owned")
+}
+
+type Mutation {
+  noop: Boolean!
+}
+
+type Block {
+  version: Int!
+  currency: String!
+  issuer: String!
+  number: Int!
+}
diff --git a/lib/modules/gva/src/context.rs b/lib/modules/gva/src/context.rs
new file mode 100644
index 00000000..29955f75
--- /dev/null
+++ b/lib/modules/gva/src/context.rs
@@ -0,0 +1,53 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use durs_bc_db_reader::BcDbRo;
+use durs_common_tools::fatal_error;
+
+/// GVA context (access to database)
+static mut CONTEXT: Option<Context> = None;
+
+#[derive(Debug)]
+pub struct Context {
+    db: BcDbRo,
+}
+
+impl juniper::Context for Context {}
+
+impl Context {
+    pub fn new(db: BcDbRo) -> Self {
+        Context { db }
+    }
+
+    pub fn get_db(&self) -> &BcDbRo {
+        &self.db
+    }
+}
+
+pub fn init(db: BcDbRo) {
+    unsafe {
+        CONTEXT.replace(Context::new(db));
+    }
+}
+
+pub fn get_context() -> &'static Context {
+    unsafe {
+        if let Some(ref context) = CONTEXT {
+            context
+        } else {
+            fatal_error!("GVA: no context");
+        }
+    }
+}
diff --git a/lib/modules/gva/src/lib.rs b/lib/modules/gva/src/lib.rs
new file mode 100644
index 00000000..f044741b
--- /dev/null
+++ b/lib/modules/gva/src/lib.rs
@@ -0,0 +1,345 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+//! Gva Module
+// This module provides a graphql API implementation of the 0003 RFC
+//
+// /src/schema.gql contains schema description
+// /src/schema.rs contains model and resolvers implementation
+// /src/webserver.rs contains web server implementaion based on actix-web
+//
+// Graphiql web client is accessible at
+// http://127.0.0.1:3000/graphiql
+
+#![deny(
+    missing_docs,
+    missing_debug_implementations,
+    missing_copy_implementations,
+    trivial_casts,
+    trivial_numeric_casts,
+    unstable_features,
+    unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate serde_derive;
+#[macro_use]
+extern crate structopt;
+
+extern crate juniper;
+
+mod context;
+mod schema;
+mod webserver;
+
+use dubp_currency_params::CurrencyName;
+use durs_common_tools::fatal_error;
+use durs_common_tools::traits::merge::Merge;
+use durs_conf::DuRsConf;
+use durs_message::events::*;
+use durs_message::*;
+use durs_module::*;
+use durs_network::events::NetworkEvent;
+
+use std::ops::Deref;
+use std::sync::mpsc;
+use std::thread;
+use std::time::{Duration, SystemTime};
+
+static MODULE_NAME: &str = "gva";
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Gva Module Configuration
+pub struct GvaConf {
+    test_fake_conf_field: String,
+}
+
+impl Default for GvaConf {
+    fn default() -> Self {
+        GvaConf {
+            test_fake_conf_field: String::from("default value"),
+        }
+    }
+}
+
+#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Gva user Configuration
+pub struct GvaUserConf {
+    test_fake_conf_field: Option<String>,
+}
+
+impl Merge for GvaUserConf {
+    fn merge(self, other: Self) -> Self {
+        GvaUserConf {
+            test_fake_conf_field: self.test_fake_conf_field.or(other.test_fake_conf_field),
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Message from others thread of gva module
+pub enum GvaThreadMsg {}
+
+#[derive(Debug, Clone)]
+/// Format of messages received by the gva module
+pub enum GvaMsg {
+    /// Message from another module
+    DursMsg(Box<DursMsg>),
+    /// Message from others thread of gva module
+    GvaThreadMsg(GvaThreadMsg),
+}
+
+#[derive(StructOpt, Debug, Clone)]
+#[structopt(
+    name = "gva",
+    raw(setting = "structopt::clap::AppSettings::ColoredHelp")
+)]
+/// Gva subcommand options
+pub struct GvaOpt {
+    /// Change test conf fake field
+    pub new_conf_field: String,
+}
+
+#[derive(Debug, Clone)]
+/// Data that the Gva module needs to cache
+pub struct GvaModuleDatas {
+    /// Sender of all child threads (except the proxy thread)
+    pub child_threads: Vec<mpsc::Sender<GvaMsg>>,
+    /// Any data
+    pub field: usize,
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Gva module
+pub struct GvaModule {}
+
+impl Default for GvaModule {
+    fn default() -> GvaModule {
+        GvaModule {}
+    }
+}
+
+impl DursModule<DuRsConf, DursMsg> for GvaModule {
+    type ModuleConf = GvaConf;
+    type ModuleUserConf = GvaUserConf;
+    type ModuleOpt = GvaOpt;
+
+    fn name() -> ModuleStaticName {
+        ModuleStaticName(MODULE_NAME)
+    }
+    fn priority() -> ModulePriority {
+        ModulePriority::Recommended()
+    }
+    fn ask_required_keys() -> RequiredKeys {
+        RequiredKeys::None()
+    }
+    fn have_subcommand() -> bool {
+        false
+    }
+    fn generate_module_conf(
+        _currency_name: Option<&CurrencyName>,
+        _global_conf: &<DuRsConf as DursConfTrait>::GlobalConf,
+        module_user_conf: Option<Self::ModuleUserConf>,
+    ) -> Result<(Self::ModuleConf, Option<Self::ModuleUserConf>), ModuleConfError> {
+        let mut conf = GvaConf::default();
+
+        if let Some(ref module_user_conf) = module_user_conf {
+            if let Some(ref test_fake_conf_field) = module_user_conf.test_fake_conf_field {
+                conf.test_fake_conf_field = test_fake_conf_field.to_owned();
+            }
+        }
+
+        Ok((conf, module_user_conf))
+    }
+    fn exec_subcommand(
+        _soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        _keys: RequiredKeysContent,
+        module_conf: Self::ModuleConf,
+        _module_user_conf: Option<Self::ModuleUserConf>,
+        subcommand_args: Self::ModuleOpt,
+    ) -> Option<Self::ModuleUserConf> {
+        let new_gva_conf = GvaUserConf {
+            test_fake_conf_field: Some(subcommand_args.new_conf_field.to_owned()),
+        };
+        info!(
+            "Succesfully exec skeleton subcommand whit terminal name : {} and conf={:?}!",
+            subcommand_args.new_conf_field, module_conf
+        );
+        Some(new_gva_conf)
+    }
+    fn start(
+        soft_meta_datas: &SoftwareMetaDatas<DuRsConf>,
+        _keys: RequiredKeysContent,
+        _conf: Self::ModuleConf,
+        router_sender: mpsc::Sender<RouterThreadMessage<DursMsg>>,
+    ) -> Result<(), failure::Error> {
+        let _start_time = SystemTime::now();
+
+        // Instanciate Gva module datas
+        let datas = GvaModuleDatas {
+            child_threads: Vec::new(),
+            field: 3,
+        };
+
+        // Create gva main thread channel
+        let (gva_sender, gva_receiver): (mpsc::Sender<GvaMsg>, mpsc::Receiver<GvaMsg>) =
+            mpsc::channel();
+
+        // Create proxy channel
+        let (proxy_sender, proxy_receiver): (mpsc::Sender<DursMsg>, mpsc::Receiver<DursMsg>) =
+            mpsc::channel();
+
+        // Launch a proxy thread that transform DursMsgContent() to GvaMsg::DursMsgContent(DursMsgContent())
+        let router_sender_clone = router_sender.clone();
+        let gva_sender_clone = gva_sender.clone();
+        thread::spawn(move || {
+            // Send gva module registration to router thread
+            router_sender_clone
+                .send(RouterThreadMessage::ModuleRegistration {
+                    static_name: ModuleStaticName(MODULE_NAME),
+                    sender: proxy_sender, // Messages sent by the router will be received by your proxy thread
+                    roles: vec![ModuleRole::UserInterface], // Roles assigned to your module
+                    events_subscription: vec![ModuleEvent::NewValidBlock], // Events to which your module subscribes
+                    reserved_apis_parts: vec![],
+                    endpoints: vec![],
+                })
+                .expect("Fatal error : gva module fail to register to router !"); // The registration of your module must be successful, in case of failure the program must be interrupted.
+
+            // If we are here it means that your module has successfully registered, we indicate it in the debug level log, it can be helpful.
+            debug!("Send gva module registration to router thread.");
+
+            /*
+             * Main loop of your proxy thread
+             */
+            loop {
+                match proxy_receiver.recv() {
+                    Ok(message) => {
+                        let stop = if let DursMsg::Stop = message {
+                            true
+                        } else {
+                            false
+                        };
+                        if gva_sender_clone
+                            .send(GvaMsg::DursMsg(Box::new(message)))
+                            .is_err()
+                        {
+                            // Log error
+                            warn!("Gva proxy : fail to relay DursMsg to gva main thread !")
+                        }
+                        if stop {
+                            break;
+                        }
+                    }
+                    Err(e) => {
+                        // Log error
+                        warn!("{}", e);
+                        break;
+                    }
+                }
+            }
+        });
+
+        let smd: SoftwareMetaDatas<DuRsConf> = soft_meta_datas.clone();
+        let router_sender_clone = router_sender.clone();
+        thread::spawn(move || {
+            match webserver::start_web_server(&smd) {
+                Ok(_) => {
+                    info!("GVA http web server stop.");
+                }
+                Err(e) => {
+                    error!("GVA http web server error  : {}  ", e);
+                }
+            }
+            let _result =
+                router_sender_clone.send(RouterThreadMessage::ModuleMessage(DursMsg::Stop));
+        });
+
+        /*
+         * Main loop of your module
+         */
+        loop {
+            // Get messages
+            match gva_receiver.recv_timeout(Duration::from_millis(250)) {
+                Ok(ref message) => match *message {
+                    GvaMsg::DursMsg(ref durs_message) => {
+                        match durs_message.deref() {
+                            DursMsg::Stop => {
+                                // Relay stop signal to all child threads
+                                let _result_stop_propagation: Result<(), mpsc::SendError<GvaMsg>> =
+                                    datas
+                                        .child_threads
+                                        .iter()
+                                        .map(|t| t.send(GvaMsg::DursMsg(Box::new(DursMsg::Stop))))
+                                        .collect();
+                                // Relay stop signal to router
+                                let _result = router_sender
+                                    .send(RouterThreadMessage::ModuleMessage(DursMsg::Stop));
+                                // Break main loop
+                                break;
+                            }
+                            DursMsg::Event {
+                                ref event_content, ..
+                            } => match *event_content {
+                                DursEvent::BlockchainEvent(ref blockchain_event) => {
+                                    match *blockchain_event.deref() {
+                                        BlockchainEvent::StackUpValidBlock(ref _block) => {
+                                            // Do something when the node has stacked a new block at its local blockchain
+                                        }
+                                        BlockchainEvent::RevertBlocks(ref _blocks) => {
+                                            // Do something when the node has destacked blocks from its local blockchain (roll back)
+                                        }
+                                        _ => {} // Do nothing for events that don't concern your module.
+                                    }
+                                }
+                                DursEvent::NetworkEvent(ref network_event_box) => {
+                                    match *network_event_box.deref() {
+                                        NetworkEvent::ReceivePeers(ref _peers) => {
+                                            // Do something when the node receive peers cards from network
+                                        }
+                                        NetworkEvent::ReceiveDocuments(ref _bc_documents) => {
+                                            // Do something when the node receive blockchain documents from network
+                                        }
+                                        _ => {} // Do nothing for events that don't concern your module.
+                                    }
+                                }
+                                _ => {} // Do nothing for DursEvent variants that don't concern your module.
+                            },
+                            _ => {} // Do nothing for DursMsgContent variants that don't concern your module.
+                        }
+                    }
+                    GvaMsg::GvaThreadMsg(ref _child_thread_msg) => {
+                        // Do something when receive a message from child thread.
+                    }
+                },
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        fatal_error!("Disconnected gva module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {
+                        // If you arrive here it's because your main thread did not receive anything at the end of the timeout.
+                        // This is quite normal and happens regularly when there is little activity, there is nothing particular to do.
+                    }
+                },
+            }
+            // If you want your module's main thread to do things even when it doesn't receive any messages, this is the place where it can do them.
+            // ...
+        }
+        // If we reach this point it means that the module has stopped correctly, so we return OK.
+        Ok(())
+    }
+}
diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs
new file mode 100644
index 00000000..678769e2
--- /dev/null
+++ b/lib/modules/gva/src/schema.rs
@@ -0,0 +1,96 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::context::Context;
+use dubp_block_doc::block::BlockDocumentTrait;
+use dubp_common_doc::traits::Document;
+use durs_bc_db_reader::BcDbRo;
+use juniper::Executor;
+use juniper::FieldResult;
+use juniper_from_schema::graphql_schema_from_file;
+
+graphql_schema_from_file!("resources/schema.gql");
+
+pub struct Query;
+
+pub struct Block {
+    version: i32,
+    currency: String,
+    issuer: String,
+    number: i32,
+}
+
+impl QueryFields for Query {
+    fn field_current(
+        &self,
+        _executor: &Executor<'_, Context>,
+        _trail: &QueryTrail<'_, Block, Walked>,
+    ) -> FieldResult<Option<Block>> {
+        let db: &BcDbRo = &_executor.context().get_db();
+        let current_blockstamp = durs_bc_db_reader::current_meta_datas::get_current_blockstamp(db);
+
+        match current_blockstamp {
+            Ok(option) => match option {
+                Some(v) => {
+                    let current_block = durs_bc_db_reader::blocks::get_block(db, v);
+                    match current_block {
+                        Ok(current_block_option) => match current_block_option {
+                            Some(block) => Ok(Some(Block {
+                                version: block.block.version() as i32,
+                                currency: block.block.currency().to_string(),
+                                issuer: block.block.issuers()[0].to_string(),
+                                number: block.block.number().0 as i32,
+                            })),
+                            None => Ok(None),
+                        },
+                        Err(_e) => Err(juniper::FieldError::from("No current block available")),
+                    }
+                }
+                None => Ok(None),
+            },
+            Err(_e) => Err(juniper::FieldError::from("No current block available")),
+        }
+    }
+}
+
+impl BlockFields for Block {
+    fn field_version(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
+        Ok(&self.version)
+    }
+
+    fn field_currency(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
+        Ok(&self.currency)
+    }
+
+    fn field_issuer(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
+        Ok(&self.issuer)
+    }
+
+    fn field_number(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
+        Ok(&self.number)
+    }
+}
+
+pub struct Mutation;
+
+impl MutationFields for Mutation {
+    fn field_noop(&self, _executor: &Executor<'_, Context>) -> FieldResult<&bool> {
+        Ok(&true)
+    }
+}
+
+pub fn create_schema() -> Schema {
+    Schema::new(Query {}, Mutation {})
+}
diff --git a/lib/modules/gva/src/webserver.rs b/lib/modules/gva/src/webserver.rs
new file mode 100644
index 00000000..3a7a7b89
--- /dev/null
+++ b/lib/modules/gva/src/webserver.rs
@@ -0,0 +1,77 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::context;
+use crate::schema::*;
+use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
+use durs_common_tools::fatal_error;
+use durs_conf::DuRsConf;
+use durs_module::*;
+use futures::future::Future;
+use juniper::http::graphiql::graphiql_source;
+use juniper::http::GraphQLRequest;
+use std::net::SocketAddr;
+use std::sync::Arc;
+
+fn graphiql() -> HttpResponse {
+    let html = graphiql_source("http://127.0.0.1:3000/graphql");
+    HttpResponse::Ok()
+        .content_type("text/html; charset=utf-8")
+        .body(html)
+}
+
+fn graphql(
+    schema: web::Data<Arc<Schema>>,
+    data: web::Json<GraphQLRequest>,
+) -> impl Future<Item = HttpResponse, Error = Error> {
+    let context = crate::context::get_context();
+    web::block(move || {
+        let result = data.execute(&schema, context);
+        Ok::<_, serde_json::error::Error>(serde_json::to_string(&result)?)
+    })
+    .map_err(Error::from)
+    .and_then(|user| {
+        Ok(HttpResponse::Ok()
+            .content_type("application/json")
+            .body(user))
+    })
+}
+
+pub fn start_web_server(soft_meta_datas: &SoftwareMetaDatas<DuRsConf>) -> std::io::Result<()> {
+    info!("GVA web server start.");
+    let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
+
+    // Create Juniper schema
+    let schema = std::sync::Arc::new(create_schema());
+
+    // Instanciate the context
+    let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone());
+    if let Ok(db) = durs_bc_db_reader::open_db_ro(&std::path::Path::new(&db_path)) {
+        context::init(db);
+    } else {
+        fatal_error!("GVA: fail to open DB.");
+    };
+
+    // Start http server
+    HttpServer::new(move || {
+        App::new()
+            .data(schema.clone())
+            .wrap(middleware::Logger::default())
+            .service(web::resource("/graphql").route(web::post().to_async(graphql)))
+            .service(web::resource("/graphiql").route(web::get().to(graphiql)))
+    })
+    .bind(addr)?
+    .run()
+}
diff --git a/lib/tools/dbs-tools/src/kv_db/file.rs b/lib/tools/dbs-tools/src/kv_db/file.rs
index 9b802071..b76ebc32 100644
--- a/lib/tools/dbs-tools/src/kv_db/file.rs
+++ b/lib/tools/dbs-tools/src/kv_db/file.rs
@@ -57,6 +57,7 @@ pub struct KvFileDbHandler {
 }
 
 /// Key-value file Database read-only handler
+#[derive(Debug)]
 pub struct KvFileDbRoHandler(KvFileDbHandler);
 
 impl KvFileDbRoHandler {
-- 
GitLab