openconfiguration 1.7.0

OpenConfiguration (OC) is a modular, efficient and flexible approach for the uni-directional exchange of visual e-commerce configurations.
Documentation
use std::collections::HashMap;

use super::{visit_urls, SceneVisitorExtension, Visitor};

pub fn merge_scenes(scenes: Vec<crate::Scene>) -> crate::Scene {
    let mut new_scene = crate::Scene::new();

    for mut scene in scenes {
        register_resources(&mut new_scene, &mut scene);

        if let Some(redirections) = scene.redirections {
            let new_redirections = new_scene.redirections.get_or_insert(HashMap::new());
            for (key, value) in redirections {
                new_redirections.insert(key, value);
            }
        }

        for product in scene.products {
            new_scene.products.push(product);
        }

        if let Some(representations) = scene.representations {
            for representation in representations {
                let available_representations = new_scene.representations.get_or_insert(Vec::new());

                if !available_representations.contains(&representation) {
                    available_representations.push(representation);
                }
            }
        }
    }

    new_scene
}

fn register_resources(final_scene: &mut crate::Scene, scene: &mut crate::Scene) {
    let base_path = scene.base_path.clone();
    let mut updated_hashes = scene.hashes.clone();

    visit_urls(scene, &mut |url| {
        let absolute_url = get_absolute_url(base_path.as_deref(), url);
        let hash = updated_hashes.remove(url);

        if let Some(hash) = hash {
            final_scene.hashes.insert(absolute_url.clone(), hash);
        }

        *url = absolute_url;
    });

    for (url, hash) in updated_hashes {
        final_scene.hashes.insert(url, hash);
    }

    scene.visit(&mut MergeVisitor { final_scene });
}

struct MergeVisitor<'a> {
    final_scene: &'a mut crate::Scene,
}

impl<'a> Visitor for MergeVisitor<'a> {
    fn visit_geometry_index(&mut self, geometry_name: &str, url: &mut String) {
        let available_geometries = self
            .final_scene
            .geometry_indexes
            .get_or_insert(HashMap::new());

        if !available_geometries.contains_key(geometry_name) {
            available_geometries.insert(geometry_name.to_string(), url.clone());
        }
    }

    fn visit_material_index(&mut self, material_name: &str, url: &mut String) {
        let available_materials = self
            .final_scene
            .material_indexes
            .get_or_insert(HashMap::new());

        if !available_materials.contains_key(material_name) {
            available_materials.insert(material_name.to_string(), url.clone());
        }
    }

    fn visit_geometry(&mut self, geometry_name: &str, geometry: &mut crate::ig::Geometry) {
        let available_geometries = self.final_scene.geometries.get_or_insert(HashMap::new());

        if !available_geometries.contains_key(geometry_name) {
            available_geometries.insert(
                geometry_name.to_string(),
                crate::Geometry {
                    ig: Some(geometry.clone()),
                    cns: None,
                },
            );
        }
    }

    fn visit_material(&mut self, material_name: &str, material: &mut crate::ig::Material) {
        let available_materials = self.final_scene.materials.get_or_insert(HashMap::new());

        if !available_materials.contains_key(material_name) {
            available_materials.insert(
                material_name.to_string(),
                crate::Material {
                    ig: Some(material.clone()),
                },
            );
        }
    }

    fn visit_script(&mut self, script: &mut crate::Script) {
        let scripts = self.final_scene.scripts.get_or_insert(Vec::new());

        for added_script in scripts.iter_mut() {
            if added_script.name == script.name {
                if is_script_version_higher(&added_script.version, &script.version) {
                    *added_script = script.clone();
                }
                return;
            }
        }

        scripts.push(script.clone());
    }
}

fn get_absolute_url(base_url: Option<&str>, url: &str) -> String {
    if url.starts_with("http://") || url.starts_with("https://") {
        url.to_string()
    } else if let Some(base_url) = base_url {
        let url = url.trim_start_matches('/');
        let base_url = base_url.trim_end_matches('/');

        format!("{}/{}", base_url, url)
    } else {
        url.to_string()
    }
}

fn is_script_version_higher(current_version: &str, compare_version: &str) -> bool {
    let current_version_parts = current_version
        .split(".")
        .map(|value| i32::from_str_radix(value, 10).unwrap())
        .collect::<Vec<_>>();
    let compare_version_parts = compare_version
        .split(".")
        .map(|value| i32::from_str_radix(value, 10).unwrap())
        .collect::<Vec<_>>();

    for (current_part, compare_part) in current_version_parts
        .iter()
        .zip(compare_version_parts.iter())
    {
        match current_part.cmp(compare_part) {
            std::cmp::Ordering::Less => return true,
            std::cmp::Ordering::Greater => return false,
            _ => {}
        }
    }

    false
}

#[cfg(test)]
mod tests {
    use crate::support::merge::get_absolute_url;

    #[test]
    fn get_absolute_url_handles_absolute_url() {
        let base_url = Some("http://example.com");
        let url = "http://example2.com/image.jpg";
        let result = get_absolute_url(base_url, url);
        assert_eq!(result, "http://example2.com/image.jpg");
    }

    #[test]
    fn get_absolute_url_handles_trailing_slash() {
        let base_url = Some("http://example.com/");
        let url = "image.jpg";
        let result = get_absolute_url(base_url, url);
        assert_eq!(result, "http://example.com/image.jpg");
    }

    #[test]
    fn get_absolute_url_handles_leading_slash() {
        let base_url = Some("http://example.com");
        let url = "/image.jpg";
        let result = get_absolute_url(base_url, url);
        assert_eq!(result, "http://example.com/image.jpg");
    }

    #[test]
    fn get_absolute_url_leading_and_trailing_slash() {
        let base_url = Some("http://example.com/");
        let url = "/image.jpg";
        let result = get_absolute_url(base_url, url);
        assert_eq!(result, "http://example.com/image.jpg");
    }

    #[test]
    fn get_absolute_url_handles_no_base_url() {
        let base_url = None;
        let url = "image.jpg";
        let result = get_absolute_url(base_url, url);
        assert_eq!(result, "image.jpg");
    }

    #[test]
    fn version_3_2_1_100_is_newer_than_3_2_1_99() {
        let a = "3.2.1.99";
        let b = "3.2.1.100";

        assert!(super::is_script_version_higher(a, b));
    }

    #[test]
    fn version_3_2_1_99_is_newer_than_3_1_1_100() {
        let a = "3.1.1.100";
        let b = "3.2.1.99";

        assert!(super::is_script_version_higher(a, b));
    }

    #[test]
    fn version_3_2_1_99_is_not_newer_than_3_2_1_99() {
        let a = "3.2.1.99";
        let b = "3.2.1.99";

        assert!(!super::is_script_version_higher(a, b));
    }

    #[test]
    fn version_3_0_0_100_is_newer_than_2_6_0_100() {
        let a = "2.6.0.100";
        let b = "3.0.0.100";

        assert!(super::is_script_version_higher(a, b));
    }
}