Skip to content
This repository has been archived by the owner on Jul 15, 2024. It is now read-only.

Commit

Permalink
Some refactoring for readability
Browse files Browse the repository at this point in the history
  • Loading branch information
MalteJanz committed Jul 3, 2024
1 parent 8b49ee9 commit 9346359
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 176 deletions.
2 changes: 2 additions & 0 deletions src/api/filter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Data structures to build criteria objects for the shopware API

use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

Expand Down
4 changes: 3 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Everything needed for communicating with the Shopware API

pub mod filter;

use crate::api::filter::{Criteria, CriteriaFilter};
use crate::config::Credentials;
use crate::config_file::Credentials;
use anyhow::anyhow;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{header, Client, Response, StatusCode};
Expand Down
64 changes: 64 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//! Definitions for the CLI commands, arguments and help texts
//!
//! Makes heavy use of https://docs.rs/clap/latest/clap/

use clap::{Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
/// Authenticate with a given shopware shop via integration admin API.
/// Credentials are stored in .credentials.toml in the current working directory.
Auth {
/// base URL of the shop
#[arg(short, long)]
domain: String,

/// access_key_id
#[arg(short, long)]
id: String,

/// access_key_secret
#[arg(short, long)]
secret: String,
},

/// Import data into shopware or export data to a file
Sync {
/// Mode (import or export)
#[arg(value_enum, short, long)]
mode: SyncMode,

/// Path to profile schema.yaml
#[arg(short, long)]
schema: PathBuf,

/// Path to data file
#[arg(short, long)]
file: PathBuf,

/// Maximum amount of entities, can be used for debugging
#[arg(short, long)]
limit: Option<u64>,

// Verbose output, used for debugging
// #[arg(short, long, action = ArgAction::SetTrue)]
// verbose: bool,
/// How many requests can be "in-flight" at the same time
#[arg(short, long, default_value = "8")]
in_flight_limit: usize,
},
}

#[derive(Debug, Clone, Copy, clap::ValueEnum)]
pub enum SyncMode {
Import,
Export,
}
14 changes: 14 additions & 0 deletions src/config.rs → src/config_file.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
//! Definitions for the `schema.yaml` and `.credentials.toml` files
//!
//! Allows deserialization into a proper typed structure from these files
//! or also write these typed structures to a file (in case of `.credentials.toml`)
//!
//! Utilizes https://serde.rs/

use crate::api::filter::{CriteriaFilter, CriteriaSorting};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
Expand All @@ -12,15 +19,22 @@ pub struct Credentials {
#[derive(Debug, Deserialize)]
pub struct Schema {
pub entity: String,

#[serde(default = "Vec::new")]
pub filter: Vec<CriteriaFilter>,

#[serde(default = "Vec::new")]
pub sort: Vec<CriteriaSorting>,

/// Are unique thanks to `HashSet`
#[serde(default = "HashSet::new")]
pub associations: HashSet<String>,

pub mappings: Vec<Mapping>,

#[serde(default = "String::new")]
pub serialize_script: String,

#[serde(default = "String::new")]
pub deserialize_script: String,
}
Expand Down
2 changes: 2 additions & 0 deletions src/data/export.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Everything related to exporting data out of shopware

use crate::api::filter::Criteria;
use crate::data::transform::serialize_entity;
use crate::SyncContext;
Expand Down
2 changes: 2 additions & 0 deletions src/data/import.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Everything related to import data into shopware

use crate::api::{SwApiError, SyncAction};
use crate::data::transform::deserialize_row;
use crate::SyncContext;
Expand Down
5 changes: 3 additions & 2 deletions src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ mod export;
mod import;
mod transform;

// reexport the important functions / structs as part of this module
pub use export::export;
pub use import::import;
pub use transform::prepare_scripting_environment;
pub use transform::ScriptingEnvironment;
pub use transform::script::prepare_scripting_environment;
pub use transform::script::ScriptingEnvironment;
119 changes: 7 additions & 112 deletions src/data/transform.rs → src/data/transform/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
//! Everything related to data transformations

pub mod script;

use crate::api::Entity;
use crate::config::Mapping;
use crate::config_file::Mapping;
use crate::SyncContext;
use anyhow::Context;
use csv::StringRecord;
use rhai::packages::{BasicArrayPackage, CorePackage, MoreStringPackage, Package};
use rhai::{Engine, Position, Scope, AST};
use rhai::Scope;
use std::str::FromStr;

/// Deserialize a single row of the input file into a json object
/// Deserialize a single row of the input (CSV) file into a json object
pub fn deserialize_row(
headers: &StringRecord,
row: StringRecord,
Expand Down Expand Up @@ -155,114 +158,6 @@ pub fn serialize_entity(entity: Entity, context: &SyncContext) -> anyhow::Result
Ok(row)
}

#[derive(Debug)]
pub struct ScriptingEnvironment {
engine: Engine,
serialize: Option<AST>,
deserialize: Option<AST>,
}

pub fn prepare_scripting_environment(
raw_serialize_script: &str,
raw_deserialize_script: &str,
) -> anyhow::Result<ScriptingEnvironment> {
let engine = get_base_engine();
let serialize_ast = if !raw_serialize_script.is_empty() {
let ast = engine
.compile(raw_serialize_script)
.context("serialize_script compilation failed")?;
Some(ast)
} else {
None
};
let deserialize_ast = if !raw_deserialize_script.is_empty() {
let ast = engine
.compile(raw_deserialize_script)
.context("serialize_script compilation failed")?;
Some(ast)
} else {
None
};

Ok(ScriptingEnvironment {
engine,
serialize: serialize_ast,
deserialize: deserialize_ast,
})
}

fn get_base_engine() -> Engine {
let mut engine = Engine::new_raw();
// Default print/debug implementations
engine.on_print(|text| println!("{text}"));
engine.on_debug(|text, source, pos| match (source, pos) {
(Some(source), Position::NONE) => println!("{source} | {text}"),
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
(None, Position::NONE) => println!("{text}"),
(None, pos) => println!("{pos:?} | {text}"),
});

let core_package = CorePackage::new();
core_package.register_into_engine(&mut engine);
let string_package = MoreStringPackage::new();
string_package.register_into_engine(&mut engine);
let array_package = BasicArrayPackage::new();
array_package.register_into_engine(&mut engine);

// ToDo: add custom utility functions to engine
engine.register_fn("get_default", script::get_default);

// Some reference implementations below
/*
engine.register_type::<Uuid>();
engine.register_fn("uuid", scripts::uuid);
engine.register_fn("uuidFromStr", scripts::uuid_from_str);
engine.register_type::<scripts::Mapper>();
engine.register_fn("map", scripts::Mapper::map);
engine.register_fn("get", scripts::Mapper::get);
engine.register_type::<scripts::DB>();
engine.register_fn("fetchFirst", scripts::DB::fetch_first);
*/

engine
}

/// utilities for inside scripts
mod script {
/// Imitate
/// https://github.com/shopware/shopware/blob/03cfe8cca937e6e45c9c3e15821d1449dfd01d82/src/Core/Defaults.php
pub fn get_default(name: &str) -> String {
match name {
"LANGUAGE_SYSTEM" => "2fbb5fe2e29a4d70aa5854ce7ce3e20b".to_string(),
"LIVE_VERSION" => "0fa91ce3e96a4bc2be4bd9ce752c3425".to_string(),
"CURRENCY" => "b7d2554b0ce847cd82f3ac9bd1c0dfca".to_string(),
"SALES_CHANNEL_TYPE_API" => "f183ee5650cf4bdb8a774337575067a6".to_string(),
"SALES_CHANNEL_TYPE_STOREFRONT" => "8a243080f92e4c719546314b577cf82b".to_string(),
"SALES_CHANNEL_TYPE_PRODUCT_COMPARISON" => "ed535e5722134ac1aa6524f73e26881b".to_string(),
"STORAGE_DATE_TIME_FORMAT" => "Y-m-d H:i:s.v".to_string(),
"STORAGE_DATE_FORMAT" => "Y-m-d".to_string(),
"CMS_PRODUCT_DETAIL_PAGE" => "7a6d253a67204037966f42b0119704d5".to_string(),
n => panic!(
"get_default called with '{}' but there is no such definition. Have a look into Shopware/src/Core/Defaults.php. Available constants: {:?}",
n,
vec![
"LANGUAGE_SYSTEM",
"LIVE_VERSION",
"CURRENCY",
"SALES_CHANNEL_TYPE_API",
"SALES_CHANNEL_TYPE_STOREFRONT",
"SALES_CHANNEL_TYPE_PRODUCT_COMPARISON",
"STORAGE_DATE_TIME_FORMAT",
"STORAGE_DATE_FORMAT",
"CMS_PRODUCT_DETAIL_PAGE",
]
)
}
}
}

trait EntityPath {
/// Search for a value inside a json object tree by a given path.
/// Example path `object.child.attribute`
Expand Down
118 changes: 118 additions & 0 deletions src/data/transform/script.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Everything scripting related

use anyhow::Context;
use rhai::packages::{BasicArrayPackage, CorePackage, MoreStringPackage, Package};
use rhai::{Engine, Position, AST};

#[derive(Debug)]
pub struct ScriptingEnvironment {
pub engine: Engine,
pub serialize: Option<AST>,
pub deserialize: Option<AST>,
}

pub fn prepare_scripting_environment(
raw_serialize_script: &str,
raw_deserialize_script: &str,
) -> anyhow::Result<ScriptingEnvironment> {
let engine = get_base_engine();
let serialize_ast = if !raw_serialize_script.is_empty() {
let ast = engine
.compile(raw_serialize_script)
.context("serialize_script compilation failed")?;
Some(ast)
} else {
None
};
let deserialize_ast = if !raw_deserialize_script.is_empty() {
let ast = engine
.compile(raw_deserialize_script)
.context("serialize_script compilation failed")?;
Some(ast)
} else {
None
};

Ok(ScriptingEnvironment {
engine,
serialize: serialize_ast,
deserialize: deserialize_ast,
})
}

fn get_base_engine() -> Engine {
let mut engine = Engine::new_raw();
// Default print/debug implementations
engine.on_print(|text| println!("{text}"));
engine.on_debug(|text, source, pos| match (source, pos) {
(Some(source), Position::NONE) => println!("{source} | {text}"),
(Some(source), pos) => println!("{source} @ {pos:?} | {text}"),
(None, Position::NONE) => println!("{text}"),
(None, pos) => println!("{pos:?} | {text}"),
});

let core_package = CorePackage::new();
core_package.register_into_engine(&mut engine);
let string_package = MoreStringPackage::new();
string_package.register_into_engine(&mut engine);
let array_package = BasicArrayPackage::new();
array_package.register_into_engine(&mut engine);

// ToDo: add custom utility functions to engine
engine.register_fn("get_default", inside_script::get_default);

// Some reference implementations below
/*
engine.register_type::<Uuid>();
engine.register_fn("uuid", scripts::uuid);
engine.register_fn("uuidFromStr", scripts::uuid_from_str);
engine.register_type::<scripts::Mapper>();
engine.register_fn("map", scripts::Mapper::map);
engine.register_fn("get", scripts::Mapper::get);
engine.register_type::<scripts::DB>();
engine.register_fn("fetchFirst", scripts::DB::fetch_first);
*/

engine
}

/// Utilities for inside scripts
///
/// Important, don't use the type `String` as function parameters, see
/// https://rhai.rs/book/rust/strings.html
mod inside_script {
use rhai::ImmutableString;

/// Imitate
/// https://github.com/shopware/shopware/blob/03cfe8cca937e6e45c9c3e15821d1449dfd01d82/src/Core/Defaults.php
pub fn get_default(name: &str) -> ImmutableString {
match name {
"LANGUAGE_SYSTEM" => "2fbb5fe2e29a4d70aa5854ce7ce3e20b".into(),
"LIVE_VERSION" => "0fa91ce3e96a4bc2be4bd9ce752c3425".into(),
"CURRENCY" => "b7d2554b0ce847cd82f3ac9bd1c0dfca".into(),
"SALES_CHANNEL_TYPE_API" => "f183ee5650cf4bdb8a774337575067a6".into(),
"SALES_CHANNEL_TYPE_STOREFRONT" => "8a243080f92e4c719546314b577cf82b".into(),
"SALES_CHANNEL_TYPE_PRODUCT_COMPARISON" => "ed535e5722134ac1aa6524f73e26881b".into(),
"STORAGE_DATE_TIME_FORMAT" => "Y-m-d H:i:s.v".into(),
"STORAGE_DATE_FORMAT" => "Y-m-d".into(),
"CMS_PRODUCT_DETAIL_PAGE" => "7a6d253a67204037966f42b0119704d5".into(),
n => panic!(
"get_default called with '{}' but there is no such definition. Have a look into Shopware/src/Core/Defaults.php. Available constants: {:?}",
n,
vec![
"LANGUAGE_SYSTEM",
"LIVE_VERSION",
"CURRENCY",
"SALES_CHANNEL_TYPE_API",
"SALES_CHANNEL_TYPE_STOREFRONT",
"SALES_CHANNEL_TYPE_PRODUCT_COMPARISON",
"STORAGE_DATE_TIME_FORMAT",
"STORAGE_DATE_FORMAT",
"CMS_PRODUCT_DETAIL_PAGE",
]
)
}
}
}
Loading

0 comments on commit 9346359

Please sign in to comment.