schemadoc-diff 0.1.20

OpenApi diff library and breaking changes detector
Documentation
use crate::diff_result_type::DiffResultType;

#[derive(Debug, Clone)]
pub enum PointerAncestor {
    Scope(PathPointerScope),
    Relative(usize),
}

impl PointerAncestor {
    pub fn parent() -> Self {
        Self::Relative(1)
    }

    pub fn path() -> Self {
        Self::Scope(PathPointerScope::Path)
    }

    pub fn operation() -> Self {
        Self::Scope(PathPointerScope::Operation)
    }

    pub fn schema() -> Self {
        Self::Scope(PathPointerScope::Schema)
    }

    pub fn schema_property() -> Self {
        Self::Scope(PathPointerScope::SchemaProperty)
    }

    pub fn schema_properties() -> Self {
        Self::Scope(PathPointerScope::SchemaProperties)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum PathPointerScope {
    Paths,
    Path,
    Operation,

    RequestBody,
    Responses,
    ResponseCode,
    Parameters,

    MediaType,

    Schema,
    SchemaProperties,
    SchemaProperty,

    SchemaItems,
    SchemaNot,

    SchemaAllOf,
    SchemaAnyOf,
    SchemaOneOf,
    SchemaAdditionalProperties,
}

#[derive(Debug, Clone, PartialEq)]
pub struct PathPointerComponent {
    pub kind: DiffResultType,
    pub path: Option<String>,
    pub scope: Option<PathPointerScope>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct PathPointer {
    pub components: Vec<PathPointerComponent>,
}

impl PathPointer {
    pub fn new<S: Into<String>, C: Into<DiffResultType>>(
        context: C,
        path: Option<S>,
        scope: Option<PathPointerScope>,
    ) -> Self {
        Self {
            components: vec![PathPointerComponent {
                scope,
                kind: context.into(),
                path: path.map(|path| path.into()),
            }],
        }
    }

    pub fn add<S: Into<String>, C: Into<DiffResultType>>(
        &self,
        context: C,
        path: S,
        scope: Option<PathPointerScope>,
    ) -> Self {
        self.add_component(context, Some(path), scope)
    }

    pub fn add_context<C: Into<DiffResultType>>(&self, context: C) -> Self {
        self.add_component::<String, C>(context, None, None)
    }

    pub fn add_component<S: Into<String>, C: Into<DiffResultType>>(
        &self,
        context: C,
        path: Option<S>,
        scope: Option<PathPointerScope>,
    ) -> Self {
        let mut new = self.clone();
        new.components.push(PathPointerComponent {
            scope,
            kind: context.into(),
            path: path.map(|path| path.into()),
        });
        new
    }

    pub fn get(
        &self,
        scope: PathPointerScope,
    ) -> Option<&PathPointerComponent> {
        self.components
            .iter()
            .find(|c| c.scope.as_ref().map_or(false, |v| v == &scope))
    }

    pub fn is_in(&self, scope: PathPointerScope) -> bool {
        self.get(scope).is_some()
    }

    pub fn this(&self) -> DiffResultType {
        self.get_primary(None)
    }

    pub fn parent(&self) -> DiffResultType {
        self.get_primary(Some(PointerAncestor::parent()))
    }

    pub fn ancestor(&self, ancestor: PointerAncestor) -> DiffResultType {
        self.get_primary(Some(ancestor))
    }

    fn get_primary(
        &self,
        ancestor: Option<PointerAncestor>,
    ) -> DiffResultType {
        let mut found = true;
        let mut result = DiffResultType::None;

        if let Some(ancestor) = ancestor {
            match ancestor {
                PointerAncestor::Scope(ref target_scope) => {
                    let latest = self
                        .components
                        .iter()
                        .rfind(|c| c.scope.as_ref() == Some(target_scope));
                    found = match latest {
                        Some(latest) => {
                            let mut is_latest_found = false;
                            let iterator =
                                self.components.iter().take_while(|c| {
                                    if is_latest_found {
                                        return false;
                                    }
                                    is_latest_found |= *c == latest;
                                    true
                                });

                            for PathPointerComponent { kind, .. } in iterator {
                                if kind.is_added() | kind.is_removed() {
                                    return *kind;
                                } else if kind.is_same() || kind.is_updated() {
                                    result = *kind;
                                }
                            }
                            true
                        }
                        None => false,
                    };
                }
                PointerAncestor::Relative(value) => {
                    let slice =
                        &self.components[..(self.components.len() - value)];
                    for PathPointerComponent { kind, .. } in slice {
                        if kind.is_added() | kind.is_removed() {
                            return *kind;
                        } else if kind.is_same() || kind.is_updated() {
                            result = *kind;
                        }
                    }
                }
            }
        } else {
            for PathPointerComponent { kind, .. } in self.components.iter() {
                if kind.is_added() | kind.is_removed() {
                    return *kind;
                } else if kind.is_same() || kind.is_updated() {
                    result = *kind;
                }
            }
        };

        if found {
            result
        } else {
            DiffResultType::None
        }
    }

    pub fn is_same(&self) -> bool {
        self.this().is_same()
    }

    pub fn is_added(&self) -> bool {
        self.this().is_added()
    }

    pub fn is_updated(&self) -> bool {
        self.this().is_updated()
    }

    pub fn is_upserted(&self) -> bool {
        self.this().is_upserted()
    }

    pub fn is_removed(&self) -> bool {
        self.this().is_removed()
    }

    pub fn get_path(&self) -> String {
        self.components
            .iter()
            .filter_map(|c| c.path.clone())
            .collect::<Vec<String>>()
            .join("/")
    }

    pub fn startswith(&self, value: &PathPointer) -> bool {
        self.get_path().starts_with(&value.get_path())
    }

    pub fn matches(&self, value: &str) -> bool {
        if value == "*" {
            true
        } else {
            value.trim_matches('/') == self.get_path().trim_matches('/')
        }
    }
}