anicca 0.1.0

Libary and CLI to diff two OpenAPI description documents
Documentation
use super::common::{OptionalStringDiff, StringListDiff};
use super::parameters::ParametersDiff;
use super::request_body::RequestBodyDiff;
use super::responses::ResponsesDiff;
use crate::openapi::Operation;
use serde::Serialize;

#[derive(Debug, Serialize)]
pub struct OperationDiff {
    pub tags: StringListDiff,
    pub summary: Option<OptionalStringDiff>,
    pub description: Option<OptionalStringDiff>,
    pub operation_id: Option<OptionalStringDiff>,
    pub parameters: ParametersDiff,
    pub request_body: Option<RequestBodyDiff>,
    pub responses: ResponsesDiff,
}

impl OperationDiff {
    pub fn has_changes(&self) -> bool {
        self.tags.has_changes()
            || self.summary.is_some()
            || self.description.is_some()
            || self.operation_id.is_some()
    }

    pub fn from_operations(base: &Operation, head: &Operation) -> Self {
        let tags_diff = StringListDiff::from_lists(&base.tags, &head.tags);

        let summary_diff = OptionalStringDiff::from_strings(&base.summary, &head.summary);

        let description_diff =
            OptionalStringDiff::from_strings(&base.description, &head.description);

        let operation_id_diff =
            OptionalStringDiff::from_strings(&base.operation_id, &head.operation_id);

        let parameters = ParametersDiff::from_params(&base.parameters, &head.parameters);

        let request_body_diff =
            RequestBodyDiff::from_request_bodies(&base.request_body, &head.request_body);

        let request_body = if request_body_diff.has_changes() {
            Some(request_body_diff)
        } else {
            None
        };

        let responses = ResponsesDiff::from_responses(&base.responses, &head.responses);

        Self {
            tags: tags_diff,
            summary: summary_diff,
            description: description_diff,
            operation_id: operation_id_diff,
            parameters,
            request_body,
            responses,
        }
    }
}

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

    #[test]
    fn operation_id_changed_from_none_to_some() {
        let base_operation = Operation::default();
        let mut head_operation = Operation::default();
        head_operation.operation_id = Some("cats/create".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let op_id_diff = diff.operation_id.unwrap();

        assert_eq!(op_id_diff.from, None);
        assert_eq!(op_id_diff.to, Some("cats/create".into()));
    }

    #[test]
    fn operation_id_changed_from_some_to_some() {
        let mut base_operation = Operation::default();
        base_operation.operation_id = Some("cats-create".into());
        let mut head_operation = Operation::default();
        head_operation.operation_id = Some("cats/create".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let op_id_diff = diff.operation_id.unwrap();

        assert_eq!(op_id_diff.from, Some("cats-create".into()));
        assert_eq!(op_id_diff.to, Some("cats/create".into()));
    }

    #[test]
    fn summary_changed_from_none_to_some() {
        let base_operation = Operation::default();
        let mut head_operation = Operation::default();
        head_operation.summary = Some("Creates a feline.".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let summary_diff = diff.summary.unwrap();

        assert_eq!(summary_diff.from, None);
        assert_eq!(summary_diff.to, Some("Creates a feline.".into()));
    }

    #[test]
    fn summary_changed_from_some_to_some() {
        let mut base_operation = Operation::default();
        base_operation.summary = Some("Creates a cat.".into());
        let mut head_operation = Operation::default();
        head_operation.summary = Some("Creates a feline.".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let summary_diff = diff.summary.unwrap();

        assert_eq!(summary_diff.from, Some("Creates a cat.".into()));
        assert_eq!(summary_diff.to, Some("Creates a feline.".into()));
    }

    #[test]
    fn description_changed_from_none_to_some() {
        let base_operation = Operation::default();
        let mut head_operation = Operation::default();
        head_operation.description = Some("Creates a feline.".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let description_diff = diff.description.unwrap();

        assert_eq!(description_diff.from, None);
        assert_eq!(description_diff.to, Some("Creates a feline.".into()));
    }

    #[test]
    fn description_changed_from_some_to_some() {
        let mut base_operation = Operation::default();
        base_operation.description = Some("Creates a cat.".into());
        let mut head_operation = Operation::default();
        head_operation.description = Some("Creates a feline.".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);
        let description_diff = diff.description.unwrap();

        assert_eq!(description_diff.from, Some("Creates a cat.".into()));
        assert_eq!(description_diff.to, Some("Creates a feline.".into()));
    }

    #[test]
    fn operation_tags_added() {
        let mut base_operation = Operation::default();
        base_operation.tags.push("Cats".into());
        base_operation.tags.push("Dogs".into());

        let mut head_operation = Operation::default();
        head_operation.tags.push("Cats".into());
        head_operation.tags.push("Fish".into());

        let diff = OperationDiff::from_operations(&base_operation, &head_operation);

        assert_eq!(vec!["Fish"], diff.tags.added);
        assert_eq!(vec!["Dogs"], diff.tags.removed);
    }
}