tag2upload_service_manager/
forge.rs

1//! forge API
2//!
3//! To add a kind of forge
4//!
5//!  * Make a unit type to hang everything off.
6//!  * Implement `ForgeKind`, `ForgeReceiver` and `ForgeDataVersion` for it.
7//!  * Add the route to `api_routes.rs` (and to `mount_api` there).
8//!  * Add the forge to `FORGES`, below.
9//!
10//! `gitlab.rs` has an example; its route is in `routes.rs`.
11
12use crate::prelude::*;
13
14/// A forge kind that may be able to receive requests and/or parse data
15///
16/// Typically there will be one type implementing this and [`ForgeReceiver`],
17/// and one or more types implementing this and [`ForgeDataVersion`].
18pub trait ForgeKind {
19    /// Matches [`config::Forge::kind`]
20    ///
21    /// Also `SomeWebhookPayload::KIND_NAME`.
22    fn kind_name(&self) -> &'static str;
23}
24
25/// A forge kind implementation that can receive new webhook requests
26pub trait ForgeReceiver: ForgeKind + ForgeDataVersion + Default {
27    type Payload: for <'de> Deserialize<'de>;
28
29    /// Checks to see if this payload should be ignored (and unlogged)
30    fn prefilter_payload(&self, _payload: &serde_json::Value)
31        -> Result<(), WebError>
32    { Ok(()) }
33
34    /// Deserialises the `Payload` into a forge-specific payload.
35    fn analyse_payload(&self, payload: Self::Payload)
36        -> Result<webhook::AnalysedPayload<Self>, WebError>;
37}
38
39#[derive(Debug, Clone, Eq, PartialEq, Deftly, derive_more::Display)]
40#[derive_deftly(SqlViaString)]
41#[display("{kind_name}-{data_version}")]
42pub struct Namever {
43    pub kind_name: Cow<'static, str>,
44    pub data_version: u32,
45}
46ui_display_via_to_string!(Namever);
47
48/// A forge kind data version, for data still possibly in the db, and
49///
50/// Forge kinds are unit types.
51#[async_trait]
52pub trait ForgeDataVersion: ForgeKind + Debug + Send + Sync + 'static {
53    type DbData: Display + FromStr<Err: Display> where Self: Sized;
54
55    /// Recorded data version
56    ///
57    /// This allows us to change the format of the data in the db,
58    /// especially in the `forge_data` field.
59    ///
60    /// ### `namever_str` history and data semantics:
61    ///
62    ///  * **`gitlab-1`**: db data is just the `ProjectId` in decimal
63    ///   (`project.id` from the webhook event).
64    ///
65    ///  * **`gitlab-2`**: db data is JSON,
66    ///    `{ "project_id": NNNN, "user_id": NNNN }`
67    ///    (`project.id` and `user_id` from the webhook event).
68    fn data_version(&self) -> u32;
69
70    /// Name and recorded data version
71    fn namever(&self) -> Namever {
72        Namever {
73            data_version: self.data_version(),
74            kind_name: self.kind_name().into(),
75        }
76    }
77
78    /// Try to fetches some tag data
79    ///
80    /// Should find a job with [`JobInWorkflow::start`]
81    /// and process it.
82    /// Should call `job.update` to report intermediate progress.
83    /// and, eventually `record_fetch_outcome`.
84    ///
85    /// `tmpdir` is for this function's exclusive use,
86    /// until it returns.
87    async fn make_progress(
88        &self,
89        host: &Hostname,
90        task_tmpdir: &str,
91    ) -> Result<(), QuitTask>;
92}
93
94#[derive(Error, Clone, Eq, PartialEq, Debug)]
95pub enum BadNamever {
96    #[error("missing `-` in {0:?}")]
97    NoHyphen(String),
98    #[error("bad version integer in {0:?}")]
99    BadNumber(String),
100}
101
102impl FromStr for Namever {
103    type Err = BadNamever;
104
105    fn from_str(s: &str) -> Result<Namever, Self::Err> {
106        let (kind_name, data_version) = s.rsplit_once('-')
107            .ok_or_else(|| BadNamever::NoHyphen(s.to_owned()))?;
108        let kind_name = kind_name.to_owned().into();
109        let data_version = data_version.parse()
110            .map_err(|_| BadNamever::BadNumber(s.to_owned()))?;
111        Ok(Namever { kind_name, data_version })
112    }
113}
114
115impl From<Namever> for String {
116    fn from(s: Namever) -> String {
117        s.to_string()
118    }
119}
120
121impl TryFrom<String> for Namever {
122    type Error = BadNamever;
123    fn try_from(s: String) -> Result<Namever, Self::Error> {
124        s.parse()
125    }
126}
127
128pub const FORGES: &[&dyn ForgeDataVersion] = &[
129    &gitlab::Forge1,
130    &gitlab::Forge2,
131];