Merge pull request #1444 from matthiasbeyer/libimagstore/remove-backend-mapper
libimagstore: remove backend mapper
This commit is contained in:
commit
2ac4fa42b3
13 changed files with 4 additions and 952 deletions
|
@ -1,39 +0,0 @@
|
||||||
//
|
|
||||||
// imag - the personal information management suite for the commandline
|
|
||||||
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
|
||||||
// License as published by the Free Software Foundation; version
|
|
||||||
// 2.1 of the License.
|
|
||||||
//
|
|
||||||
// This library 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
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use libimagrt::runtime::Runtime;
|
|
||||||
use libimagerror::trace::*;
|
|
||||||
|
|
||||||
pub fn dump(rt: &mut Runtime) {
|
|
||||||
rt.store()
|
|
||||||
.entries()
|
|
||||||
.map_err_trace_exit_unwrap(1)
|
|
||||||
.for_each(|elem| {
|
|
||||||
debug!("Working on {:?}", elem);
|
|
||||||
rt.store().get(elem).map_err_trace_exit_unwrap(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(_) = rt.store_backend_to_stdout().map_err_trace() {
|
|
||||||
error!("Loading Store IO backend failed");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -54,7 +54,6 @@ use libimagerror::trace::MapErrTrace;
|
||||||
|
|
||||||
mod create;
|
mod create;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod dump;
|
|
||||||
mod error;
|
mod error;
|
||||||
mod get;
|
mod get;
|
||||||
mod retrieve;
|
mod retrieve;
|
||||||
|
@ -67,7 +66,6 @@ use std::ops::Deref;
|
||||||
|
|
||||||
use create::create;
|
use create::create;
|
||||||
use delete::delete;
|
use delete::delete;
|
||||||
use dump::dump;
|
|
||||||
use get::get;
|
use get::get;
|
||||||
use retrieve::retrieve;
|
use retrieve::retrieve;
|
||||||
use ui::build_ui;
|
use ui::build_ui;
|
||||||
|
@ -76,10 +74,10 @@ use verify::verify;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let version = make_imag_version!();
|
let version = make_imag_version!();
|
||||||
let mut rt = generate_runtime_setup("imag-store",
|
let rt = generate_runtime_setup("imag-store",
|
||||||
&version,
|
&version,
|
||||||
"Direct interface to the store. Use with great care!",
|
"Direct interface to the store. Use with great care!",
|
||||||
build_ui);
|
build_ui);
|
||||||
|
|
||||||
let command = rt.cli().subcommand_name().map(String::from);
|
let command = rt.cli().subcommand_name().map(String::from);
|
||||||
|
|
||||||
|
@ -92,7 +90,6 @@ fn main() {
|
||||||
"retrieve" => retrieve(&rt),
|
"retrieve" => retrieve(&rt),
|
||||||
"update" => update(&rt),
|
"update" => update(&rt),
|
||||||
"verify" => verify(&rt),
|
"verify" => verify(&rt),
|
||||||
"dump" => dump(&mut rt),
|
|
||||||
other => {
|
other => {
|
||||||
debug!("Unknown command");
|
debug!("Unknown command");
|
||||||
let _ = rt.handle_unknown_subcommand("imag-store", other, rt.cli())
|
let _ = rt.handle_unknown_subcommand("imag-store", other, rt.cli())
|
||||||
|
|
|
@ -190,9 +190,4 @@ pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||||
.about("Verify the store")
|
.about("Verify the store")
|
||||||
.version("0.1")
|
.version("0.1")
|
||||||
)
|
)
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("dump")
|
|
||||||
.about("Dump the complete store to stdout. Currently does only support JSON")
|
|
||||||
.version("0.1")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,80 +178,3 @@ The `InMemoryFileAbstractionInstance` implementation is corrosponding to
|
||||||
the `InMemoryFileAbstraction` implementation - for the in-memory
|
the `InMemoryFileAbstraction` implementation - for the in-memory
|
||||||
"filesystem".
|
"filesystem".
|
||||||
|
|
||||||
## The StdIo backend {#sec:thestore:backends:stdio}
|
|
||||||
|
|
||||||
Sidenote: The name is "StdIo" because its main purpose is Stdin/Stdio, but it
|
|
||||||
is abstracted over Read/Write actually, so it is also possible to use this
|
|
||||||
backend in other ways, too.
|
|
||||||
|
|
||||||
### Why? {#sec:thestore:backends:stdio:why}
|
|
||||||
|
|
||||||
This is a backend for the imag store which is created
|
|
||||||
from stdin, by piping contents into the store (via JSON or TOML) and piping the
|
|
||||||
store contents (as JSON or TOML) to stdout when the backend is destructed.
|
|
||||||
|
|
||||||
This is one of some components which make command-chaining in imag possible.
|
|
||||||
With this, the application does not have to know whether the store actually
|
|
||||||
lives on the filesystem or just "in memory".
|
|
||||||
|
|
||||||
### Mappers {#sec:thestore:backends:stdio:mappers}
|
|
||||||
|
|
||||||
The backend contains a "Mapper" which defines how the contents get mapped into
|
|
||||||
the in-memory store representation: A JSON implementation or a TOML
|
|
||||||
implementation are possible.
|
|
||||||
|
|
||||||
The following section assumes a JSON mapper.
|
|
||||||
|
|
||||||
The mapper reads the JSON, parses it and translates it to a `Entry`.
|
|
||||||
Then, the entry is made available to the store codebase.
|
|
||||||
To summarize what we do right now, lets have a look at the awesome ascii-art
|
|
||||||
below:
|
|
||||||
|
|
||||||
```
|
|
||||||
libimag*
|
|
||||||
|
|
|
||||||
v
|
|
||||||
IO Mapper Store Mapper IO
|
|
||||||
+--+---------+----------------+--------+--+
|
|
||||||
| | | | | |
|
|
||||||
JSON -> Entry -> JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
This is what gets translated where for one imag call with a stdio store backend.
|
|
||||||
|
|
||||||
### The JSON Mapper {#sec:thestore:backends:stdio:json}
|
|
||||||
|
|
||||||
The JSON mapper maps JSON which is read from a source into a HashMap which
|
|
||||||
represents the in-memory filesystem.
|
|
||||||
|
|
||||||
The strucure is as follows:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "0.8.0",
|
|
||||||
"store": {
|
|
||||||
"example": {
|
|
||||||
"header": {
|
|
||||||
"imag": {
|
|
||||||
"version": "0.8.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"content": "hi there!",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### TODO {#sec:thestore:backends:todo}
|
|
||||||
|
|
||||||
If you look at the version history of this file you will see that this
|
|
||||||
implementation has grown from something complex and probably slow to what we
|
|
||||||
have today.
|
|
||||||
|
|
||||||
Still, there's one improvement we could make: abstract all the things away so
|
|
||||||
the `libimag*` crates handle the header without knowing whether it is JSON or
|
|
||||||
TOML.
|
|
||||||
With this, we would not even have to translate JSON to TOML anymore.
|
|
||||||
We should measure whether this would have actually any performance impact before
|
|
||||||
implementing it.
|
|
||||||
|
|
||||||
|
|
|
@ -368,50 +368,6 @@ impl<'a> Runtime<'a> {
|
||||||
&self.store
|
&self.store
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Change the store backend to stdout
|
|
||||||
///
|
|
||||||
/// For the documentation on purpose and cavecats, have a look at the documentation of the
|
|
||||||
/// `Store::reset_backend()` function.
|
|
||||||
///
|
|
||||||
pub fn store_backend_to_stdio(&mut self) -> Result<(), RuntimeError> {
|
|
||||||
use libimagstore::file_abstraction::stdio::*;
|
|
||||||
use libimagstore::file_abstraction::stdio::mapper::json::JsonMapper;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
let mut input = ::std::io::stdin();
|
|
||||||
let output = ::std::io::stdout();
|
|
||||||
let output = Rc::new(RefCell::new(output));
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
|
|
||||||
StdIoFileAbstraction::new(&mut input, output, mapper)
|
|
||||||
.chain_err(|| RuntimeErrorKind::Instantiate)
|
|
||||||
.and_then(|backend| {
|
|
||||||
self.store
|
|
||||||
.reset_backend(Box::new(backend))
|
|
||||||
.chain_err(|| RuntimeErrorKind::Instantiate)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn store_backend_to_stdout(&mut self) -> Result<(), RuntimeError> {
|
|
||||||
use libimagstore::file_abstraction::stdio::mapper::json::JsonMapper;
|
|
||||||
use libimagstore::file_abstraction::stdio::out::StdoutFileAbstraction;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
let output = ::std::io::stdout();
|
|
||||||
let output = Rc::new(RefCell::new(output));
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
|
|
||||||
StdoutFileAbstraction::new(output, mapper)
|
|
||||||
.chain_err(|| RuntimeErrorKind::Instantiate)
|
|
||||||
.and_then(|backend| {
|
|
||||||
self.store
|
|
||||||
.reset_backend(Box::new(backend))
|
|
||||||
.chain_err(|| RuntimeErrorKind::Instantiate)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a editor command object which can be called to open the $EDITOR
|
/// Get a editor command object which can be called to open the $EDITOR
|
||||||
pub fn editor(&self) -> Result<Option<Command>, RuntimeError> {
|
pub fn editor(&self) -> Result<Option<Command>, RuntimeError> {
|
||||||
self.cli()
|
self.cli()
|
||||||
|
|
|
@ -29,7 +29,6 @@ walkdir = "1"
|
||||||
is-match = "0.1"
|
is-match = "0.1"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_derive = "1"
|
|
||||||
error-chain = "0.11"
|
error-chain = "0.11"
|
||||||
toml-query = "0.6"
|
toml-query = "0.6"
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ use storeid::StoreId;
|
||||||
mod fs;
|
mod fs;
|
||||||
mod inmemory;
|
mod inmemory;
|
||||||
mod iter;
|
mod iter;
|
||||||
pub mod stdio;
|
|
||||||
|
|
||||||
pub use self::fs::FSFileAbstraction;
|
pub use self::fs::FSFileAbstraction;
|
||||||
pub use self::fs::FSFileAbstractionInstance;
|
pub use self::fs::FSFileAbstractionInstance;
|
||||||
|
|
|
@ -1,250 +0,0 @@
|
||||||
//
|
|
||||||
// imag - the personal information management suite for the commandline
|
|
||||||
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
|
||||||
// License as published by the Free Software Foundation; version
|
|
||||||
// 2.1 of the License.
|
|
||||||
//
|
|
||||||
// This library 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
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use serde_json;
|
|
||||||
use toml;
|
|
||||||
|
|
||||||
use error::StoreErrorKind as SEK;
|
|
||||||
use error::StoreError as SE;
|
|
||||||
use error::ResultExt;
|
|
||||||
use super::Mapper;
|
|
||||||
use store::Result;
|
|
||||||
use store::Entry;
|
|
||||||
use storeid::StoreId;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
struct BackendEntry {
|
|
||||||
header: serde_json::Value,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackendEntry {
|
|
||||||
|
|
||||||
fn to_string(self) -> Result<String> {
|
|
||||||
toml::to_string(&self.header)
|
|
||||||
.chain_err(|| SEK::IoError)
|
|
||||||
.map(|hdr| {
|
|
||||||
format!("---\n{header}---\n{content}",
|
|
||||||
header = hdr,
|
|
||||||
content = self.content)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
struct Document {
|
|
||||||
version: String,
|
|
||||||
store: HashMap<PathBuf, BackendEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct JsonMapper;
|
|
||||||
|
|
||||||
impl JsonMapper {
|
|
||||||
|
|
||||||
pub fn new() -> JsonMapper {
|
|
||||||
JsonMapper
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mapper for JsonMapper {
|
|
||||||
fn read_to_fs<R: Read>(&self, r: &mut R, hm: &mut HashMap<PathBuf, Entry>) -> Result<()> {
|
|
||||||
let mut document = {
|
|
||||||
debug!("Reading Document");
|
|
||||||
let mut s = String::new();
|
|
||||||
r.read_to_string(&mut s).chain_err(|| SEK::IoError)?;
|
|
||||||
debug!("Document = {:?}", s);
|
|
||||||
debug!("Parsing Document");
|
|
||||||
let doc : Document = serde_json::from_str(&s).chain_err(|| SEK::IoError)?;
|
|
||||||
debug!("Document = {:?}", doc);
|
|
||||||
doc
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = ::semver::Version::parse(&document.version)
|
|
||||||
.chain_err(|| SEK::VersionError)
|
|
||||||
.and_then(|doc_vers| {
|
|
||||||
// safe because cargo does not compile if crate version is not valid
|
|
||||||
let crate_version = ::semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
|
||||||
|
|
||||||
debug!("Document version vs. own version: {doc_vers} > {crate_vers}",
|
|
||||||
doc_vers = doc_vers,
|
|
||||||
crate_vers = crate_version);
|
|
||||||
|
|
||||||
if doc_vers > crate_version {
|
|
||||||
Err(SE::from_kind(SEK::VersionError))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
for (key, val) in document.store.drain() {
|
|
||||||
debug!("(key, value) ({:?}, {:?})", key, val);
|
|
||||||
let res = val
|
|
||||||
.to_string()
|
|
||||||
.and_then(|vals| {
|
|
||||||
debug!("value string = {:?}", vals);
|
|
||||||
StoreId::new_baseless(key.clone())
|
|
||||||
.and_then(|id| Entry::from_str(id, &vals))
|
|
||||||
.map(|entry| hm.insert(key, entry))
|
|
||||||
})
|
|
||||||
.map(|_| ());
|
|
||||||
|
|
||||||
let _ = res?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fs_to_write<W: Write>(&self, hm: &mut HashMap<PathBuf, Entry>, out: &mut W) -> Result<()> {
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct BackendEntry {
|
|
||||||
header: ::toml::Value,
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BackendEntry {
|
|
||||||
fn construct_from(e: Entry) -> BackendEntry {
|
|
||||||
BackendEntry {
|
|
||||||
header: e.get_header().clone(),
|
|
||||||
content: e.get_content().clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct OutDocument {
|
|
||||||
version: String,
|
|
||||||
store: HashMap<PathBuf, BackendEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut store = HashMap::new();
|
|
||||||
for (key, value) in hm.drain() {
|
|
||||||
store.insert(key, BackendEntry::construct_from(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
let doc = OutDocument {
|
|
||||||
version: String::from(env!("CARGO_PKG_VERSION")),
|
|
||||||
store: store,
|
|
||||||
};
|
|
||||||
|
|
||||||
serde_json::to_string(&doc)
|
|
||||||
.chain_err(|| SEK::IoError)
|
|
||||||
.and_then(|json| out.write(&json.into_bytes()).chain_err(|| SEK::IoError))
|
|
||||||
.and_then(|_| out.flush().chain_err(|| SEK::IoError))
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_json_to_fs() {
|
|
||||||
let json = format!(r#"{{"version":"{version}","store":{{}}}}"#,
|
|
||||||
version = env!("CARGO_PKG_VERSION"));
|
|
||||||
let mut json = Cursor::new(String::from(json).into_bytes());
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let mut hm = HashMap::new();
|
|
||||||
|
|
||||||
let io_res = mapper.read_to_fs(&mut json, &mut hm);
|
|
||||||
assert!(io_res.is_ok());
|
|
||||||
assert!(hm.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_json_to_fs() {
|
|
||||||
let json = format!(r#"
|
|
||||||
{{ "version": "{version}",
|
|
||||||
"store": {{
|
|
||||||
"example": {{
|
|
||||||
"header": {{
|
|
||||||
"imag": {{
|
|
||||||
"version": "{version}"
|
|
||||||
}}
|
|
||||||
}},
|
|
||||||
"content": "test"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
"#, version = env!("CARGO_PKG_VERSION"));
|
|
||||||
let mut json = Cursor::new(String::from(json).into_bytes());
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let mut hm = HashMap::new();
|
|
||||||
|
|
||||||
let io_res = mapper.read_to_fs(&mut json, &mut hm);
|
|
||||||
assert!(io_res.is_ok());
|
|
||||||
|
|
||||||
assert_eq!(1, hm.len()); // we should have exactly one entry
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fs_to_json() {
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let mut out : Cursor<Vec<u8>> = Cursor::new(vec![]);
|
|
||||||
|
|
||||||
let mut hm = {
|
|
||||||
let mut hm = HashMap::new();
|
|
||||||
let content = format!(r#"---
|
|
||||||
[imag]
|
|
||||||
version = "{}"
|
|
||||||
---
|
|
||||||
hi there!"#, env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
let id = PathBuf::from("example");
|
|
||||||
let entry = Entry::from_str(id.clone(), &content).unwrap();
|
|
||||||
hm.insert(id, entry);
|
|
||||||
hm
|
|
||||||
};
|
|
||||||
|
|
||||||
let io_res = mapper.fs_to_write(&mut hm, &mut out);
|
|
||||||
assert!(io_res.is_ok());
|
|
||||||
|
|
||||||
let example = format!(r#"
|
|
||||||
{{
|
|
||||||
"version": "{version}",
|
|
||||||
"store": {{
|
|
||||||
"example": {{
|
|
||||||
"header": {{
|
|
||||||
"imag": {{
|
|
||||||
"version": "{version}"
|
|
||||||
}}
|
|
||||||
}},
|
|
||||||
"content": "hi there!"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
"#, version = env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
let example_json : ::serde_json::Value = ::serde_json::from_str(&example).unwrap();
|
|
||||||
|
|
||||||
let output_json = String::from_utf8(out.into_inner()).unwrap();
|
|
||||||
let output_json : ::serde_json::Value = ::serde_json::from_str(&output_json).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(example_json, output_json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// imag - the personal information management suite for the commandline
|
|
||||||
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
|
||||||
// License as published by the Free Software Foundation; version
|
|
||||||
// 2.1 of the License.
|
|
||||||
//
|
|
||||||
// This library 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
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use store::Result;
|
|
||||||
use store::Entry;
|
|
||||||
|
|
||||||
pub trait Mapper {
|
|
||||||
fn read_to_fs<R: Read>(&self, &mut R, &mut HashMap<PathBuf, Entry>) -> Result<()>;
|
|
||||||
fn fs_to_write<W: Write>(&self, &mut HashMap<PathBuf, Entry>, &mut W) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod json;
|
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
//
|
|
||||||
// imag - the personal information management suite for the commandline
|
|
||||||
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
|
||||||
// License as published by the Free Software Foundation; version
|
|
||||||
// 2.1 of the License.
|
|
||||||
//
|
|
||||||
// This library 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
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
//
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fmt::Error as FmtError;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
|
|
||||||
use error::StoreErrorKind as SEK;
|
|
||||||
use error::StoreError as SE;
|
|
||||||
use super::FileAbstraction;
|
|
||||||
use super::FileAbstractionInstance;
|
|
||||||
use super::Drain;
|
|
||||||
use super::InMemoryFileAbstraction;
|
|
||||||
use store::Entry;
|
|
||||||
use file_abstraction::iter::PathIterator;
|
|
||||||
|
|
||||||
pub mod mapper;
|
|
||||||
pub mod out;
|
|
||||||
use self::mapper::Mapper;
|
|
||||||
use self::out::StdoutFileAbstraction;
|
|
||||||
|
|
||||||
// Because this is not exported in super::inmemory;
|
|
||||||
type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
|
|
||||||
|
|
||||||
pub struct StdIoFileAbstraction<W: Write, M: Mapper>(StdoutFileAbstraction<W, M>);
|
|
||||||
|
|
||||||
impl<W, M> StdIoFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
|
|
||||||
pub fn new<R: Read>(in_stream: &mut R, out_stream: Rc<RefCell<W>>, mapper: M) -> Result<StdIoFileAbstraction<W, M>, SE> {
|
|
||||||
StdoutFileAbstraction::new(out_stream, mapper)
|
|
||||||
.and_then(|out| {
|
|
||||||
let _ = out.backend()
|
|
||||||
.lock()
|
|
||||||
.map_err(|_| SE::from_kind(SEK::LockError))
|
|
||||||
.map(|mut mtx| out.mapper().read_to_fs(in_stream, mtx.get_mut()))?;
|
|
||||||
|
|
||||||
Ok(StdIoFileAbstraction(out))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backend(&self) -> &Backend {
|
|
||||||
self.0.backend()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W, M> Debug for StdIoFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
|
||||||
write!(f, "StdIoFileAbstraction({:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W, M> Deref for StdIoFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
type Target = StdoutFileAbstraction<W, M>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// basically #[derive(FileAbstraction)]
|
|
||||||
impl<W: Write, M: Mapper> FileAbstraction for StdIoFileAbstraction<W, M> {
|
|
||||||
|
|
||||||
fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.0.remove_file(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.0.copy(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.0.rename(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.0.create_dir_all(pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
|
|
||||||
self.0.new_instance(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists(&self, p: &PathBuf) -> Result<bool, SE> {
|
|
||||||
self.0.exists(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_file(&self, p: &PathBuf) -> Result<bool, SE> {
|
|
||||||
self.0.is_file(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drain(&self) -> Result<Drain, SE> {
|
|
||||||
self.0.drain()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill(&mut self, d: Drain) -> Result<(), SE> {
|
|
||||||
self.0.fill(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pathes_recursively(&self, basepath: PathBuf) -> Result<PathIterator, SE> {
|
|
||||||
self.0.pathes_recursively(basepath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
//
|
|
||||||
// imag - the personal information management suite for the commandline
|
|
||||||
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
|
|
||||||
//
|
|
||||||
// This library is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU Lesser General Public
|
|
||||||
// License as published by the Free Software Foundation; version
|
|
||||||
// 2.1 of the License.
|
|
||||||
//
|
|
||||||
// This library 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
|
|
||||||
// Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public
|
|
||||||
// License along with this library; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
//
|
|
||||||
|
|
||||||
//! A StdIoFileAbstraction which does not read from stdin.
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fmt::Error as FmtError;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use libimagerror::trace::*;
|
|
||||||
|
|
||||||
use error::StoreErrorKind as SEK;
|
|
||||||
use error::StoreError as SE;
|
|
||||||
use super::FileAbstraction;
|
|
||||||
use super::FileAbstractionInstance;
|
|
||||||
use super::Drain;
|
|
||||||
use super::InMemoryFileAbstraction;
|
|
||||||
use store::Entry;
|
|
||||||
use file_abstraction::iter::PathIterator;
|
|
||||||
|
|
||||||
use super::mapper::Mapper;
|
|
||||||
|
|
||||||
// Because this is not exported in super::inmemory;
|
|
||||||
type Backend = Arc<Mutex<RefCell<HashMap<PathBuf, Entry>>>>;
|
|
||||||
|
|
||||||
pub struct StdoutFileAbstraction<W: Write, M: Mapper> {
|
|
||||||
mapper: M,
|
|
||||||
mem: InMemoryFileAbstraction,
|
|
||||||
out: Rc<RefCell<W>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W, M> StdoutFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
|
|
||||||
pub fn new(out_stream: Rc<RefCell<W>>, mapper: M) -> Result<StdoutFileAbstraction<W, M>, SE> {
|
|
||||||
Ok(StdoutFileAbstraction {
|
|
||||||
mapper: mapper,
|
|
||||||
mem: InMemoryFileAbstraction::new(),
|
|
||||||
out: out_stream,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn backend(&self) -> &Backend {
|
|
||||||
self.mem.backend()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backend_cloned(&self) -> Result<HashMap<PathBuf, Entry>, SE> {
|
|
||||||
self.mem
|
|
||||||
.backend()
|
|
||||||
.lock()
|
|
||||||
.map_err(|_| SE::from_kind(SEK::LockError))
|
|
||||||
.map(|mtx| mtx.deref().borrow().clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mapper(&self) -> &M {
|
|
||||||
&self.mapper
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W, M> Debug for StdoutFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
|
||||||
write!(f, "StdoutFileAbstraction({:?}", self.mem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W, M> Drop for StdoutFileAbstraction<W, M>
|
|
||||||
where M: Mapper,
|
|
||||||
W: Write
|
|
||||||
{
|
|
||||||
fn drop(&mut self) {
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
|
|
||||||
let fill_res = match self.mem.backend().lock() {
|
|
||||||
Err(_) => Err(SE::from_kind(SEK::LockError)),
|
|
||||||
Ok(mut mtx) => {
|
|
||||||
self.mapper.fs_to_write(mtx.get_mut(), self.out.borrow_mut().deref_mut())
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// We can do nothing but end this here with a trace.
|
|
||||||
// As this drop gets called when imag almost exits, there is no point in exit()ing here
|
|
||||||
// again.
|
|
||||||
let _ = fill_res.map_err_trace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write, M: Mapper> FileAbstraction for StdoutFileAbstraction<W, M> {
|
|
||||||
|
|
||||||
fn remove_file(&self, path: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.mem.remove_file(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn copy(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.mem.copy(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rename(&self, from: &PathBuf, to: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.mem.rename(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_dir_all(&self, pb: &PathBuf) -> Result<(), SE> {
|
|
||||||
self.mem.create_dir_all(pb)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_instance(&self, p: PathBuf) -> Box<FileAbstractionInstance> {
|
|
||||||
self.mem.new_instance(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exists(&self, p: &PathBuf) -> Result<bool, SE> {
|
|
||||||
self.mem.exists(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_file(&self, p: &PathBuf) -> Result<bool, SE> {
|
|
||||||
self.mem.is_file(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drain(&self) -> Result<Drain, SE> {
|
|
||||||
self.backend_cloned().map(Drain::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill(&mut self, mut d: Drain) -> Result<(), SE> {
|
|
||||||
debug!("Draining into : {:?}", self);
|
|
||||||
let mut mtx = self.backend().lock().map_err(|_| SE::from_kind(SEK::IoError))?;
|
|
||||||
let backend = mtx.get_mut();
|
|
||||||
|
|
||||||
for (path, element) in d.iter() {
|
|
||||||
debug!("Drain into {:?}: {:?}", self, path);
|
|
||||||
backend.insert(path, element);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pathes_recursively(&self, basepath: PathBuf) -> Result<PathIterator, SE> {
|
|
||||||
self.mem.pathes_recursively(basepath)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ extern crate semver;
|
||||||
extern crate walkdir;
|
extern crate walkdir;
|
||||||
#[macro_use] extern crate is_match;
|
#[macro_use] extern crate is_match;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
#[macro_use] extern crate error_chain;
|
#[macro_use] extern crate error_chain;
|
||||||
extern crate toml_query;
|
extern crate toml_query;
|
||||||
|
|
||||||
|
|
|
@ -1208,96 +1208,6 @@ mod store_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_store_create_with_io_backend() {
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
//let sink = vec![];
|
|
||||||
//let output : Cursor<&mut [u8]> = Cursor::new(&mut sink);
|
|
||||||
//let output = Rc::new(RefCell::new(output));
|
|
||||||
let output = Rc::new(RefCell::new(vec![]));
|
|
||||||
|
|
||||||
{
|
|
||||||
let store = {
|
|
||||||
use file_abstraction::stdio::StdIoFileAbstraction;
|
|
||||||
use file_abstraction::stdio::mapper::json::JsonMapper;
|
|
||||||
|
|
||||||
// Lets have an empty store as input
|
|
||||||
let mut input = Cursor::new(format!(r#"
|
|
||||||
{{ "version": "{}",
|
|
||||||
"store": {{}}
|
|
||||||
}}
|
|
||||||
"#, env!("CARGO_PKG_VERSION")));
|
|
||||||
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let backend = StdIoFileAbstraction::new(&mut input, output.clone(), mapper).unwrap();
|
|
||||||
let backend = Box::new(backend);
|
|
||||||
|
|
||||||
Store::new_with_backend(PathBuf::from("/"), &None, backend).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
for n in 1..100 {
|
|
||||||
let s = format!("test-{}", n);
|
|
||||||
let entry = store.create(PathBuf::from(s.clone())).unwrap();
|
|
||||||
assert!(entry.verify().is_ok());
|
|
||||||
let loc = entry.get_location().clone().into_pathbuf().unwrap();
|
|
||||||
assert!(loc.starts_with("/"));
|
|
||||||
assert!(loc.ends_with(s));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let vec = Rc::try_unwrap(output).unwrap().into_inner();
|
|
||||||
|
|
||||||
let errstr = format!("Not UTF8: '{:?}'", vec);
|
|
||||||
let string = String::from_utf8(vec);
|
|
||||||
assert!(string.is_ok(), errstr);
|
|
||||||
let string = string.unwrap();
|
|
||||||
|
|
||||||
assert!(!string.is_empty(), format!("Expected not to be empty: '{}'", string));
|
|
||||||
|
|
||||||
let json : ::serde_json::Value = ::serde_json::from_str(&string).unwrap();
|
|
||||||
|
|
||||||
match json {
|
|
||||||
Value::Object(ref map) => {
|
|
||||||
assert!(map.get("version").is_some(), format!("No 'version' in JSON"));
|
|
||||||
match map.get("version").unwrap() {
|
|
||||||
&Value::String(ref s) => assert_eq!(env!("CARGO_PKG_VERSION"), s),
|
|
||||||
_ => panic!("Wrong type in JSON at 'version'"),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(map.get("store").is_some(), format!("No 'store' in JSON"));
|
|
||||||
match map.get("store").unwrap() {
|
|
||||||
&Value::Object(ref objs) => {
|
|
||||||
for n in 1..100 {
|
|
||||||
let s = format!("/test-{}", n);
|
|
||||||
assert!(objs.get(&s).is_some(), format!("No entry: '{}'", s));
|
|
||||||
match objs.get(&s).unwrap() {
|
|
||||||
&Value::Object(ref entry) => {
|
|
||||||
match entry.get("header").unwrap() {
|
|
||||||
&Value::Object(_) => assert!(true),
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}'.header'", s),
|
|
||||||
}
|
|
||||||
|
|
||||||
match entry.get("content").unwrap() {
|
|
||||||
&Value::String(_) => assert!(true),
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}'.content'", s),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}''", s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at 'store'"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at top level"),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_store_get_create_get_delete_get() {
|
fn test_store_get_create_get_delete_get() {
|
||||||
let store = get_store();
|
let store = get_store();
|
||||||
|
@ -1497,104 +1407,5 @@ mod store_tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_swap_backend_during_runtime_with_io() {
|
|
||||||
use std::io::Cursor;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use serde_json::Value;
|
|
||||||
use file_abstraction::stdio::out::StdoutFileAbstraction;
|
|
||||||
use file_abstraction::stdio::mapper::json::JsonMapper;
|
|
||||||
|
|
||||||
// The output we later read from and check whether there is an entry
|
|
||||||
let output = Rc::new(RefCell::new(vec![]));
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut store = {
|
|
||||||
use file_abstraction::stdio::StdIoFileAbstraction;
|
|
||||||
use file_abstraction::stdio::mapper::json::JsonMapper;
|
|
||||||
|
|
||||||
// Lets have an empty store as input
|
|
||||||
let mut input = Cursor::new(format!(r#"
|
|
||||||
{{ "version": "{version}",
|
|
||||||
"store": {{
|
|
||||||
"example": {{
|
|
||||||
"header": {{
|
|
||||||
"imag": {{
|
|
||||||
"version": "{version}"
|
|
||||||
}}
|
|
||||||
}},
|
|
||||||
"content": "foobar"
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
"#, version = env!("CARGO_PKG_VERSION")));
|
|
||||||
|
|
||||||
let output = Rc::new(RefCell::new(::std::io::sink()));
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let backend = StdIoFileAbstraction::new(&mut input, output, mapper).unwrap();
|
|
||||||
let backend = Box::new(backend);
|
|
||||||
|
|
||||||
Store::new_with_backend(PathBuf::from("/"), &None, backend).unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Replacing the backend
|
|
||||||
|
|
||||||
{
|
|
||||||
let mapper = JsonMapper::new();
|
|
||||||
let backend = StdoutFileAbstraction::new(output.clone(), mapper);
|
|
||||||
let _ = assert!(backend.is_ok(), format!("Should be ok: {:?}", backend));
|
|
||||||
let backend = backend.unwrap();
|
|
||||||
let backend = Box::new(backend);
|
|
||||||
|
|
||||||
assert!(store.reset_backend(backend).is_ok());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let vec = Rc::try_unwrap(output).unwrap().into_inner();
|
|
||||||
let errstr = format!("Not UTF8: '{:?}'", vec);
|
|
||||||
let string = String::from_utf8(vec);
|
|
||||||
assert!(string.is_ok(), errstr);
|
|
||||||
let string = string.unwrap();
|
|
||||||
|
|
||||||
assert!(!string.is_empty(), format!("Expected not to be empty: '{}'", string));
|
|
||||||
|
|
||||||
let json : ::serde_json::Value = ::serde_json::from_str(&string).unwrap();
|
|
||||||
|
|
||||||
match json {
|
|
||||||
Value::Object(ref map) => {
|
|
||||||
assert!(map.get("version").is_some(), format!("No 'version' in JSON"));
|
|
||||||
match map.get("version").unwrap() {
|
|
||||||
&Value::String(ref s) => assert_eq!(env!("CARGO_PKG_VERSION"), s),
|
|
||||||
_ => panic!("Wrong type in JSON at 'version'"),
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(map.get("store").is_some(), format!("No 'store' in JSON"));
|
|
||||||
match map.get("store").unwrap() {
|
|
||||||
&Value::Object(ref objs) => {
|
|
||||||
let s = String::from("example");
|
|
||||||
assert!(objs.get(&s).is_some(), format!("No entry: '{}' in \n{:?}", s, objs));
|
|
||||||
match objs.get(&s).unwrap() {
|
|
||||||
&Value::Object(ref entry) => {
|
|
||||||
match entry.get("header").unwrap() {
|
|
||||||
&Value::Object(_) => assert!(true),
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}'.header'", s),
|
|
||||||
}
|
|
||||||
|
|
||||||
match entry.get("content").unwrap() {
|
|
||||||
&Value::String(_) => assert!(true),
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}'.content'", s),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at 'store.'{}''", s),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at 'store'"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("Wrong type in JSON at top level"),
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue