git-bug 0.2.4

A rust library for interfacing with git-bug repositories
Documentation
// git-bug-rs - A rust library for interfacing with git-bug repositories
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of git-bug-rs/git-gub.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/agpl.txt>.

//! Operations that affect an [`Identity`][`super::Identity`]

use serde::{Deserialize, Serialize};
use simd_json::{derived::ValueTryIntoObject, owned};
use url::Url;

use self::identity_operation_type::IdentityOperationType;
use crate::replica::entity::operation::operation_data::OperationData;

pub mod identity_operation_type;
mod op;

/// The operations available on an [`Identity`][`super::Identity`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IdentityOperationData {
    /// Creating an user.
    Create {
        /// The name of this user.
        name: String,
    },

    /// Setting the user's name.
    SetName {
        /// The new name.
        name: String,
    },

    /// Setting the user's email address.
    SetEmail {
        /// The new email address.
        email: String,
    },

    /// Setting the user's login name.
    SetLoginName {
        /// The name used to login to a bridge.
        // TODO(@bpeetz): Why are we storing this in that way? Shouldn't this name be associated
        // whit a platform or a source URL. And why is that not metadata, like the rest of the
        // bridge data? <2025-04-20>
        login_name: String,
    },

    /// Setting the URL to the user's avatar.
    SetAvatarUrl {
        /// The new avatar URL.
        url: Url,
    },

    /// Setting metadata.
    SetMetadata {
        /// Which metadata to add.
        // Use a vec here to preserve the ordering.
        metadata: Vec<(String, String)>,
    },
    // TODO: Support pgp keys <2025-04-20>
    // AddKey {
    //     key: Key,
    // },
    // RemoveKey {
    //     key: Key,
    // },
}

impl OperationData for IdentityOperationData {
    type DecodeError = decode::Error;

    fn is_root(&self) -> bool {
        matches!(self, Self::Create { .. })
    }

    fn from_value(raw: owned::Value, predicted_type: u64) -> Result<Self, Self::DecodeError>
    where
        Self: Sized,
    {
        let r#type = IdentityOperationType::try_from(predicted_type)?;

        let object = raw
            .try_into_object()
            .map_err(|err| decode::Error::ObjectExpected { err })?;

        match r#type {
            IdentityOperationType::Create => op::create(object),
            IdentityOperationType::SetName => op::set_name(object),
            IdentityOperationType::SetEmail => op::set_email(object),
            IdentityOperationType::SetLoginName => op::set_login_name(object),
            IdentityOperationType::SetAvatarUrl => op::set_avatar_url(object),
            IdentityOperationType::SetMetadata => op::set_metadata(object),
        }
    }

    fn as_value(&self) -> simd_json::borrowed::Object<'_> {
        match self {
            IdentityOperationData::Create { name } => op::create_value(name),
            IdentityOperationData::SetName { name } => op::set_name_value(name),
            IdentityOperationData::SetEmail { email } => op::set_email_value(email),
            IdentityOperationData::SetLoginName { login_name } => {
                op::set_login_name_value(login_name)
            }
            IdentityOperationData::SetAvatarUrl { url } => op::set_avatar_url_value(url),
            IdentityOperationData::SetMetadata { metadata } => op::set_metadata_value(metadata),
        }
    }

    fn to_json_type(&self) -> u64 {
        IdentityOperationType::from(self).into()
    }
}

#[allow(missing_docs)]
pub mod decode {

    use super::identity_operation_type;

    #[derive(Debug, thiserror::Error)]
    pub enum Error {
        #[error(transparent)]
        InvalidType(#[from] identity_operation_type::decode::Error),

        #[error("The json value did not contain the expected field '{field}'.")]
        MissingJsonField { field: &'static str },

        #[error("Expected to parse the '{field}' field in the json data: {err}")]
        WrongJsonType {
            err: simd_json::TryTypeError,
            field: &'static str,
        },

        #[error("Failed to parse an url value: {0}")]
        UrlParse(#[from] url::ParseError),

        #[error("Expected this operation json data to be a object, but was not: {err}")]
        ObjectExpected { err: simd_json::TryTypeError },
    }
}