anicca 0.1.0

Libary and CLI to diff two OpenAPI description documents
Documentation
use super::parameter::ParameterDiff;
use crate::openapi::{Parameter, ReferenceOr};
use serde::Serialize;
use std::collections::HashMap;

#[derive(Debug, Serialize)]
pub struct ParametersDiff {
    pub added: Vec<ReferenceOr<Parameter>>,
    pub removed: Vec<ReferenceOr<Parameter>>,
    pub changed: HashMap<String, ParameterDiff>,
}

impl ParametersDiff {
    pub fn param_name(param: &Parameter) -> String {
        match param {
            Parameter::Query {
                parameter_data,
                allow_reserved: _,
                style: _,
                allow_empty_value: _,
            } => parameter_data.name.clone(),
            Parameter::Header {
                parameter_data,
                style: _,
            } => parameter_data.name.clone(),
            Parameter::Path {
                parameter_data,
                style: _,
            } => parameter_data.name.clone(),
            Parameter::Cookie {
                parameter_data,
                style: _,
            } => parameter_data.name.clone(),
        }
    }

    pub fn from_params(
        base: &Vec<ReferenceOr<Parameter>>,
        head: &Vec<ReferenceOr<Parameter>>,
    ) -> Self {
        let mut added = vec![];
        let mut removed = vec![];
        let mut changed: HashMap<String, ParameterDiff> = HashMap::default();

        for ref_or_param in base {
            match ref_or_param {
                ReferenceOr::Reference { reference } => {
                    let ref_match = head.iter().find(|p| match p {
                        ReferenceOr::Reference { reference: r } => r == reference,
                        ReferenceOr::Item(_) => false,
                    });

                    match ref_match {
                        Some(_param) => {
                            panic!("Comparing changed parameter refs is not implemented yet");
                        }
                        None => removed.push(ref_or_param.clone()),
                    }
                }
                ReferenceOr::Item(param) => {
                    let param_match = head.iter().find(|p| match p {
                        ReferenceOr::Reference { reference: _ } => false,
                        ReferenceOr::Item(p) => Self::param_name(p) == Self::param_name(param),
                    });

                    match param_match {
                        Some(head_param) => {
                            if let ReferenceOr::Item(head_param) = head_param {
                                let diff = ParameterDiff::from_params(param, head_param);

                                if diff.has_changes() {
                                    changed.insert(Self::param_name(param), diff);
                                }
                            }
                        }
                        None => removed.push(ref_or_param.clone()),
                    }
                }
            }
        }

        for ref_or_param in head {
            match ref_or_param {
                ReferenceOr::Reference { reference } => {
                    let ref_match = base.iter().find(|p| match p {
                        ReferenceOr::Reference { reference: r } => r == reference,
                        ReferenceOr::Item(_) => false,
                    });

                    match ref_match {
                        Some(_param) => {}
                        None => added.push(ref_or_param.clone()),
                    }
                }
                ReferenceOr::Item(param) => {
                    let param_match = base.iter().find(|p| match p {
                        ReferenceOr::Reference { reference: _ } => false,
                        ReferenceOr::Item(p) => Self::param_name(p) == Self::param_name(param),
                    });

                    match param_match {
                        Some(_param) => {}
                        None => added.push(ref_or_param.clone()),
                    }
                }
            }
        }

        Self {
            added,
            removed,
            changed,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::openapi::{Operation, ParameterData, ParameterSchemaOrContent, QueryStyle, Schema};
    use std::collections::BTreeMap;

    #[test]
    fn added_parameter() {
        let base_operation = Operation::default();
        let mut head_operation = Operation::default();
        let my_param = Parameter::Query {
            parameter_data: ParameterData {
                name: String::from("myParam"),
                description: Some(String::from("myParam")),
                deprecated: None,
                example: None,
                examples: BTreeMap::default(),
                extensions: BTreeMap::default(),
                format: ParameterSchemaOrContent::Schema(ReferenceOr::Item(Schema::default())),
                required: false,
                explode: None,
            },
            allow_empty_value: None,
            allow_reserved: Some(false),
            style: QueryStyle::Form,
        };

        head_operation.parameters.push(ReferenceOr::Item(my_param));

        let diff =
            ParametersDiff::from_params(&base_operation.parameters, &head_operation.parameters);

        assert_eq!(diff.added.len(), 1);
        assert_eq!(diff.removed.len(), 0);

        match diff.added.first().unwrap() {
            ReferenceOr::Item(p) => match p {
                Parameter::Query {
                    parameter_data,
                    allow_reserved: _,
                    style: _,
                    allow_empty_value: _,
                } => {
                    assert_eq!(parameter_data.name, "myParam")
                }
                _ => {
                    panic!("Unexpected parameter type")
                }
            },
            _ => {
                panic!("Unexpected parameter type")
            }
        }
    }

    #[test]
    fn removed_parameter() {
        let mut base_operation = Operation::default();
        let head_operation = Operation::default();

        let my_param = Parameter::Query {
            parameter_data: ParameterData {
                name: String::from("myParam"),
                description: Some(String::from("myParam")),
                deprecated: None,
                example: None,
                examples: BTreeMap::default(),
                extensions: BTreeMap::default(),
                format: ParameterSchemaOrContent::Schema(ReferenceOr::Item(Schema::default())),
                required: true,
                explode: None,
            },
            allow_empty_value: None,
            allow_reserved: Some(false),
            style: QueryStyle::Form,
        };

        base_operation.parameters.push(ReferenceOr::Item(my_param));

        let diff =
            ParametersDiff::from_params(&base_operation.parameters, &head_operation.parameters);

        assert_eq!(diff.added.len(), 0);
        assert_eq!(diff.removed.len(), 1);

        match diff.removed.first().unwrap() {
            ReferenceOr::Item(p) => match p {
                Parameter::Query {
                    parameter_data,
                    allow_reserved: _,
                    style: _,
                    allow_empty_value: _,
                } => {
                    assert_eq!(parameter_data.name, "myParam")
                }
                _ => {
                    panic!("Unexpected parameter type")
                }
            },
            _ => {
                panic!("Unexpected parameter type")
            }
        }
    }
}