melodium-loader 0.10.1

Loading engine for Mélodium, with dependency resolution and retrieval
Documentation
use crate::compo::Compo;
use crate::content::Content;
use crate::package::package::PackageTrait;
use crate::{Loader, PackageInfo, LIB_ROOT_FILENAME};
use melodium_common::descriptor::{
    Collection, Identifier, IdentifierRequirement, LoadingError, LoadingResult, PackageRequirement,
    Version,
};
use std::collections::HashMap;
use std::sync::Arc;

#[derive(Debug)]
pub struct MappedPackage {
    name: String,
    version: Version,
    requirements: Vec<PackageRequirement>,
    entrypoints: HashMap<String, Identifier>,
    contents: HashMap<String, Arc<Content>>,
}

impl MappedPackage {
    pub fn new(map: HashMap<String, Vec<u8>>) -> LoadingResult<Self> {
        let mut result = LoadingResult::new_success(());

        let composition;
        if let Some(compo) = map
            .get("Compo.toml")
            .map(|content| String::from_utf8(content.clone()).ok())
            .flatten()
        {
            if let Some(compo) = result.merge_degrade_failure(Compo::parse(&compo)) {
                composition = compo;
            } else {
                return result.and_degrade_failure(LoadingResult::new_failure(
                    LoadingError::mapping_format_error(
                        243,
                        "Invalid Compo.toml content".to_string(),
                    ),
                ));
            }
        } else {
            return result.and_degrade_failure(LoadingResult::new_failure(
                LoadingError::mapping_format_error(
                    244,
                    "Mapping does not contain 'Compo.toml' entry".to_string(),
                ),
            ));
        };
        let mut contents = HashMap::new();

        for (path, data) in map.iter() {
            if path.ends_with(".mel") {
                if let Some(content) = result.merge_degrade_failure(
                    Content::new(
                        &if path == LIB_ROOT_FILENAME {
                            composition.name.clone()
                        } else {
                            format!("{}/{}", composition.name, path,)
                        },
                        data,
                        &composition.version,
                        &composition
                            .requirements
                            .iter()
                            .map(|pkg_req| {
                                (pkg_req.package.clone(), pkg_req.version_requirement.clone())
                            })
                            .collect(),
                    )
                    .convert_failure_errors(|err| LoadingError::content_error(231, Arc::new(err))),
                ) {
                    contents.insert(path.clone(), Arc::new(content));
                }
            }
        }

        result.and(LoadingResult::new_success(Self {
            name: composition.name,
            entrypoints: composition.entrypoints,
            version: composition.version,
            requirements: composition.requirements,
            contents,
        }))
    }

    fn designation(identifier: &Identifier) -> String {
        if identifier.path().len() == 1 {
            LIB_ROOT_FILENAME.to_string()
        } else {
            format!(
                "{}.mel",
                identifier
                    .path()
                    .clone()
                    .into_iter()
                    .skip(1)
                    .collect::<Vec<_>>()
                    .join("/")
            )
        }
    }

    fn insure_loading(
        loader: &Loader,
        identifiers: Vec<IdentifierRequirement>,
    ) -> LoadingResult<()> {
        let mut result = LoadingResult::new_success(());
        for identifier in &identifiers {
            result.merge_degrade_failure(loader.get_with_load(identifier));
        }

        result
    }
}

impl PackageInfo for MappedPackage {
    fn name(&self) -> &str {
        &self.name
    }

    fn version(&self) -> &semver::Version {
        &self.version
    }

    fn requirements(&self) -> &Vec<PackageRequirement> {
        &self.requirements
    }

    fn entrypoints(&self) -> &HashMap<String, Identifier> {
        &self.entrypoints
    }
}

impl PackageTrait for MappedPackage {
    fn embedded_collection(&self, _loader: &Loader) -> LoadingResult<Collection> {
        LoadingResult::new_success(Collection::new())
    }

    fn full_collection(&self, loader: &Loader) -> LoadingResult<Collection> {
        let mut collection = Collection::new();
        let mut result = LoadingResult::new_success(());
        if let Some(identifiers) = result.merge_degrade_failure(self.all_identifiers(loader)) {
            for identifier in &identifiers {
                let identifier_requirement = identifier.into();
                if collection.get(&identifier_requirement).is_none() {
                    if let Some(specific_collection) =
                        result.merge_degrade_failure(self.element(loader, &identifier_requirement))
                    {
                        for identifier in &specific_collection.identifiers() {
                            collection.insert(
                                specific_collection.get(&identifier.into()).unwrap().clone(),
                            );
                        }
                    }
                }
            }
        }

        result.and(LoadingResult::new_success(collection))
    }

    fn all_identifiers(&self, _loader: &Loader) -> LoadingResult<Vec<Identifier>> {
        let mut identifiers = Vec::new();
        self.contents.iter().for_each(|(_, content)| {
            identifiers.extend(
                content
                    .provide()
                    .into_iter()
                    .map(|id| id.with_version(&self.version))
                    .collect::<Vec<_>>(),
            )
        });

        LoadingResult::new_success(identifiers)
    }

    fn element(
        &self,
        loader: &Loader,
        identifier_requirement: &IdentifierRequirement,
    ) -> LoadingResult<Collection> {
        let mut result = LoadingResult::new_success(Collection::new());
        let designation = Self::designation(&identifier_requirement.to_identifier());

        if let Some(content) = self.contents.get(&designation) {
            if let Ok(_guard) = content.try_lock() {
                let needs = content.require();
                result.merge_degrade_failure(Self::insure_loading(loader, needs));

                let mut collection = loader.collection().clone();
                result.merge_degrade_failure(
                    content
                        .insert_descriptors(&mut collection)
                        .convert_failure_errors(|err| {
                            LoadingError::content_error(233, Arc::new(err))
                        }),
                );

                result = result.and_degrade_failure(LoadingResult::new_success(collection));
            } else {
                result = result.and_degrade_failure(LoadingResult::new_failure(
                    LoadingError::circular_reference(234, identifier_requirement.clone()),
                ));
            }
        } else {
            result = result.and_degrade_failure(LoadingResult::new_failure(
                LoadingError::not_found(235, identifier_requirement.to_string()),
            ));
        }

        result
    }

    fn make_building(&self, collection: &Arc<Collection>) -> LoadingResult<()> {
        let mut result = LoadingResult::new_success(());
        for (_, content) in self.contents.iter() {
            result.merge_degrade_failure(
                content
                    .make_design(collection)
                    .convert_failure_errors(|err| LoadingError::content_error(236, Arc::new(err))),
            );
        }

        result
    }
}