Skip to content

Commit

Permalink
sources and sinks as trait objects
Browse files Browse the repository at this point in the history
  • Loading branch information
bmisiak committed May 2, 2023
1 parent a019129 commit 0d5d349
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 47 deletions.
32 changes: 21 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{bail, Context, Result};
use sources::Source;
use std::fs::File;
use std::io::BufReader;
use std::io::{BufRead, BufReader};

use crate::sinks::strongbox_csv::Strongbox;
use crate::sinks::Sink;
Expand All @@ -11,24 +12,33 @@ mod sources;
mod universal;

fn main() -> Result<()> {
let reader: Box<dyn std::io::BufRead> = match std::env::args().nth(1).as_deref() {
let reader: Box<dyn BufRead> = match std::env::args().nth(1).as_deref() {
Some("-") => Box::new(BufReader::new(std::io::stdin())),
Some(filename) => Box::new(BufReader::new(
File::open(filename).with_context(|| format!("Unable to read {filename}"))?,
)),
None => {
return Err(anyhow!("Export .json from your password manager and provide the path to the file as an argument, or \"-\" to read from stdin."));
bail!("Export .json from your password manager and provide the path to the file as an argument, or \"-\" to read from stdin.");
}
};

let source: EnpassJson =
serde_json::from_reader(reader).context("The input file has an incorrect format")?;
let source_name = "enpass";
let source: Box<dyn Source> = match source_name {
"enpass" => Box::new(EnpassJson::from_reader(reader)?),
_ => bail!("Unsupported source name"),
};

let sink_path = "./output.csv";
let sink_destination =
File::open(sink_path).with_context(|| format!("Unable to open {sink_path}"))?;
let sink_name = "strongbox";
let mut sink: Box<dyn Sink> = match sink_name {
"strongbox" => Box::new(Strongbox::with_output(sink_destination)),
_ => bail!("Unsupported target name"),
};

File::open("./output.csv")
.context("Unable to write to ./output.csv")
.map(Strongbox::new)?
.write(source)
.context("Failed writing to output")?;
sink.convert_from_source(source)
.with_context(|| format!("Conversion from {source_name} to {sink_name}"))?;

println!("Saved to output.csv");

Expand Down
3 changes: 1 addition & 2 deletions src/sinks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ use crate::sources::Source;
pub mod strongbox_csv;

pub trait Sink {
fn user_friendly_name() -> &'static str;
fn write<'i>(&mut self, items: impl Source<'i>) -> anyhow::Result<()>;
fn convert_from_source(&mut self, items: Box<dyn Source>) -> anyhow::Result<()>;
}
36 changes: 20 additions & 16 deletions src/sinks/strongbox_csv.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use anyhow::Result;
use anyhow::Context;
use chrono::{serde::ts_seconds, DateTime, Utc};
use serde::__private::from_utf8_lossy;
use std::{borrow::Cow, io::Write};
use std::borrow::Cow;
use std::fmt::Write;

use super::Sink;
use crate::{sources::Source, universal::UniversalItem};
Expand All @@ -23,11 +24,12 @@ pub struct StrongboxCsvItem<'a> {
pub created: DateTime<Utc>,
}

impl<'a, 'b> From<UniversalItem<'a>> for StrongboxCsvItem<'b>
impl<'a, 'b> TryFrom<UniversalItem<'a>> for StrongboxCsvItem<'b>
where
'a: 'b,
{
fn from(item: UniversalItem<'a>) -> Self {
type Error = anyhow::Error;
fn try_from(item: UniversalItem<'a>) -> Result<Self, Self::Error> {
let mut converted = StrongboxCsvItem {
title: item.title,
username: item.username,
Expand Down Expand Up @@ -61,25 +63,27 @@ where
}
}

converted
Ok(converted)
}
}

pub struct Strongbox<W: Write>(csv::Writer<W>);
impl<W: Write> Strongbox<W> {
pub fn new(out: W) -> Self {
pub struct Strongbox<W: std::io::Write>(csv::Writer<W>);
impl<W: std::io::Write> Strongbox<W> {
pub fn with_output(out: W) -> Self {
Self(csv::Writer::from_writer(out))
}
}

impl<W: Write> Sink for Strongbox<W> {
fn user_friendly_name() -> &'static str {
"strongbox"
}

fn write<'i>(&mut self, items: impl Source<'i>) -> Result<()> {
for item in items {
self.0.serialize(StrongboxCsvItem::from(item))?;
impl<W: std::io::Write> Sink for Strongbox<W> {
fn convert_from_source(&mut self, source: Box<dyn Source>) -> anyhow::Result<()> {
let mut error_context = String::new();
for item in source.into_item_iter() {
write!(error_context, "Converting item {item} to Strongbox")?;
let converted_item =
StrongboxCsvItem::try_from(item).with_context(|| error_context.clone())?;
self.0
.serialize(converted_item)
.context("Serializing to output")?;
}
self.0.flush()?;
Ok(())
Expand Down
22 changes: 10 additions & 12 deletions src/sources/enpass_json.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::Source;
use crate::universal::UniversalItem;
use chrono::{serde::ts_seconds, DateTime, Utc};
use std::borrow::Cow;
use std::{borrow::Cow, io::BufRead};

#[derive(serde::Deserialize)]
pub struct EnpassJson<'a> {
Expand Down Expand Up @@ -65,15 +64,14 @@ impl<'a> Iterator for EnpassIterator<'a> {
}
}

impl<'a> IntoIterator for EnpassJson<'a> {
type Item = UniversalItem<'a>;
type IntoIter = EnpassIterator<'a>;

fn into_iter(self) -> Self::IntoIter {
EnpassIterator(self.items.into_iter())
impl<'a> super::Source<'a> for EnpassJson<'a> {
fn from_reader<R: BufRead>(reader: R) -> anyhow::Result<Self>
where
Self: Sized,
{
Ok(serde_json::from_reader(reader)?)
}
fn into_item_iter(self: Box<Self>) -> Box<dyn Iterator<Item = UniversalItem<'a>> + 'a> {
Box::new(EnpassIterator(self.items.into_iter()))
}
}

impl<'a> Source<'a> for EnpassJson<'a> {
type Itr = EnpassIterator<'a>;
}
12 changes: 7 additions & 5 deletions src/sources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::io::BufRead;

use crate::universal::UniversalItem;

pub mod enpass_json;

pub trait Source<'a>
where
Self: IntoIterator<Item = UniversalItem<'a>, IntoIter = Self::Itr>,
{
type Itr: Iterator<Item = UniversalItem<'a>>;
pub trait Source<'a> {
fn from_reader<R: BufRead>(reader: R) -> anyhow::Result<Self>
where
Self: Sized;
fn into_item_iter(self: Box<Self>) -> Box<dyn Iterator<Item = UniversalItem<'a>> + 'a>;
}
14 changes: 13 additions & 1 deletion src/universal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chrono::{DateTime, Utc};
use std::borrow::Cow;
use std::{borrow::Cow, fmt::Display};

#[derive(Default)]
pub struct UniversalItem<'a> {
Expand All @@ -14,3 +14,15 @@ pub struct UniversalItem<'a> {
pub created: DateTime<Utc>,
pub unknown_fields: Vec<(Cow<'a, str>, Cow<'a, [u8]>)>,
}

impl<'a> Display for UniversalItem<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{\ntitle: {},\nusername: {}\n}}",
self.title,
self.username.as_ref().unwrap_or(&Cow::Borrowed("-"))
)?;
Ok(())
}
}

0 comments on commit 0d5d349

Please sign in to comment.