use std::collections::HashMap;
use crate::BoxFuture;
use crate::error::Error;
use crate::goals::CodedError;
use crate::js::{self, Object, Value};
use crate::store::DbError;
pub type Credentials = HashMap<String, String>;
pub(crate) fn credential<'a>(credentials: &'a Credentials, name: &str) -> Option<&'a str> {
credentials.get(name).map(|v| js::trim(v))
}
pub(crate) fn filled<'a>(credentials: &'a Credentials, name: &str) -> Option<&'a str> {
credential(credentials, name).filter(|v| !v.is_empty())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ImportError {
Coded {
error: CodedError,
status: Option<u16>,
},
Other(Error),
}
impl ImportError {
pub fn new(message: impl Into<String>, code: &str, params: &[(&str, &str)]) -> ImportError {
ImportError::Coded { error: CodedError::new(message, code, params), status: None }
}
pub fn http(message: impl Into<String>, status: u16, code: &str, params: &[(&str, &str)]) -> ImportError {
ImportError::Coded { error: CodedError::new(message, code, params), status: Some(status) }
}
pub fn other(message: impl Into<String>) -> ImportError {
ImportError::Other(Error::Other(message.into()))
}
pub fn coded(&self) -> Option<&CodedError> {
match self {
ImportError::Coded { error, .. } => Some(error),
ImportError::Other(_) => None,
}
}
pub fn status(&self) -> Option<u16> {
match self {
ImportError::Coded { status, .. } => *status,
ImportError::Other(_) => None,
}
}
pub fn is_http(&self) -> bool {
self.status().is_some()
}
pub fn message(&self) -> &str {
match self {
ImportError::Coded { error, .. } => &error.message,
ImportError::Other(e) => e.message(),
}
}
}
impl std::fmt::Display for ImportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message())
}
}
impl std::error::Error for ImportError {}
impl From<Error> for ImportError {
fn from(e: Error) -> ImportError {
ImportError::Other(e)
}
}
impl From<DbError> for ImportError {
fn from(e: DbError) -> ImportError {
ImportError::Other(Error::Db(e))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ImportStep {
pub cursor: Option<String>,
pub done: f64,
pub total: Option<f64>,
pub links: f64,
pub clicks: f64,
pub skipped: f64,
pub failed: Vec<Value>,
}
impl ImportStep {
pub fn to_value(&self) -> Value {
let mut o = Object::new();
o.set("cursor", self.cursor.clone());
o.set("done", self.done);
o.set("total", self.total);
o.set("links", self.links);
o.set("clicks", self.clicks);
o.set("skipped", self.skipped);
o.set("failed", Value::Array(self.failed.clone()));
Value::Object(o)
}
}
pub type Known =
dyn Fn(String, Option<String>, Option<String>) -> BoxFuture<'static, Result<bool, ImportError>> + Send + Sync;
pub struct StepInput<'a> {
pub credentials: &'a Credentials,
pub cursor: Option<&'a str>,
pub known: &'a Known,
pub now: i64,
}
pub trait Importer: Send + Sync {
fn step<'a>(
&'a self,
http: &'a super::http::Http,
input: StepInput<'a>,
) -> BoxFuture<'a, Result<Value, ImportError>>;
}
pub(crate) fn step_answer(cursor: Option<String>, total: Option<Value>, links: Vec<Value>) -> Value {
let mut o = Object::new();
o.set("cursor", cursor);
set_opt(&mut o, "total", total);
o.set("links", Value::Array(links));
Value::Object(o)
}
pub(crate) fn foreign_link(
source_id: Option<Value>,
slug: Option<Value>,
domain: Option<Value>,
name: Option<Value>,
url: Option<Value>,
created_at: f64,
) -> Object {
let mut o = Object::new();
set_opt(&mut o, "sourceId", source_id);
set_opt(&mut o, "slug", slug);
set_opt(&mut o, "domain", domain);
set_opt(&mut o, "name", name);
set_opt(&mut o, "url", url);
o.set("createdAt", created_at);
Object::new().with("link", o)
}
pub(crate) fn known_link(
source_id: Option<Value>,
slug: Option<Value>,
name: Option<Value>,
url: Option<Value>,
) -> Value {
Value::Object(foreign_link(source_id, slug, Some(Value::from("")), name, url, 0.0).with("known", true))
}
pub(crate) fn arg(value: Option<&Value>) -> Option<String> {
match value {
None | Some(Value::Null) => None,
Some(v) => Some(js::js_string(v)),
}
}
pub(crate) fn text(value: &str) -> Option<Value> {
Some(Value::from(value))
}
pub(crate) fn set_opt(o: &mut Object, key: &str, value: Option<Value>) {
if let Some(v) = value {
o.set(key, v);
}
}
pub(crate) fn field<'a>(value: Option<&'a Value>, key: &str) -> Option<&'a Value> {
match value {
Some(Value::Object(o)) => o.get(key),
Some(Value::Array(a)) => key.parse::<usize>().ok().and_then(|i| a.get(i)),
_ => None,
}
}
pub(crate) fn or<'a>(a: Option<&'a Value>, b: Option<&'a Value>) -> Option<&'a Value> {
if js::opt_truthy(a) { a } else { b }
}
pub(crate) fn nullish<'a>(a: Option<&'a Value>, b: Option<&'a Value>) -> Option<&'a Value> {
match a {
None | Some(Value::Null) => b,
_ => a,
}
}
pub(crate) fn items<'a>(value: Option<&'a Value>, what: &str) -> Result<&'a [Value], ImportError> {
match value {
Some(Value::Array(a)) => Ok(a),
_ => Err(ImportError::other(format!("{what} is not iterable"))),
}
}