// Back - An extremely simple git issue tracking system. Inspired by tvix's // panettone // // Copyright (C) 2024 Benedikt Peetz // SPDX-License-Identifier: AGPL-3.0-or-later // // This file is part of Back. // // You should have received a copy of the License along with this program. // If not, see . use gix::{bstr::ByteSlice, Repository}; use serde::Deserialize; use serde_json::Value; use crate::issues::format::BackString; use super::{Author, Status}; macro_rules! get { ($value:expr, $name:expr, $type_fun:ident) => { $value .get($name) .expect(concat!( "Expected field ", stringify!($name), "to exists, but was missing." )) .$type_fun() .expect(concat!( "Failed to interpret field ", stringify!($name), " as ", stringify!($type), "!" )) }; } #[derive(Deserialize)] pub(super) struct RawIssue { pub(super) author: RawAuthor, #[serde(alias = "ops")] pub(super) operations: Vec, } #[derive(Deserialize, Clone, Default, Debug)] pub(super) struct RawAuthor { id: String, } impl RawAuthor { pub fn load_identity(&self, repo: &Repository) -> Author { let commit_obj = repo .find_reference(&format!("refs/identities/{}", self.id)) .expect("All authors should also have identities") .peel_to_commit() .expect("All identities should be commits"); let tree_obj = repo .find_tree( commit_obj .tree() .expect("The commit should have an tree associated with it") .id, ) .expect("This should be a tree"); let data = repo .find_blob( tree_obj .find_entry("version") .expect("This entry should exist") .object() .expect("This should point to a blob entry") .id, ) .expect("This blob should exist") .data .clone(); let json: Value = serde_json::from_str(data.to_str().expect("This is encoded json")) .expect("This is valid json"); Author { name: BackString::from(get! {json, "name", as_str}.to_owned()), email: BackString::from(get! {json, "email", as_str}.to_owned()), } } } #[derive(Deserialize)] #[serde(from = "Value")] pub(super) enum Operation { AddComment { timestamp: u64, message: String, // files: Option, TODO }, SetStatus { timestamp: u64, status: Status, }, Create { timestamp: u64, title: String, message: String, // files: Option, TODO }, } impl From for Status { fn from(value: u64) -> Self { match value { 1 => Status::Open, 2 => Status::Closed, other => todo!("The status ({other}) is not yet implemented."), } } } impl From for Operation { fn from(value: Value) -> Self { match value .get("type") .expect("Should exist") .as_u64() .expect("This should work") { 1 => Self::Create { title: get! {value, "title", as_str}.to_owned(), message: get! {value, "message", as_str}.to_owned(), timestamp: get! {value, "timestamp", as_u64}, }, 3 => Self::AddComment { message: get! {value, "message", as_str}.to_owned(), timestamp: get! {value, "timestamp", as_u64}, }, 4 => Self::SetStatus { status: Status::from(get! {value, "status", as_u64}), timestamp: get! {value, "timestamp", as_u64}, }, other => todo!("The type ({other}) is not yet added as a a valid operation. It's value is: '{value}''"), } } }