Skip to main content

zixcel_github/
plan.rs

1use std::collections::BTreeMap;
2
3use serde::Serialize;
4use serde_json::{Value, json};
5use sha2::{Digest, Sha256};
6
7use crate::{CONNECTOR, ConnectorConfig, ConnectorError, PLAN_SCHEMA, PROVIDER};
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
10pub struct PlanStep {
11    pub sequence: u32,
12    pub action: &'static str,
13    pub target: String,
14    pub effect: &'static str,
15    pub network_required: bool,
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize)]
19pub struct ConnectorPlan {
20    pub schema: &'static str,
21    pub plan_id: String,
22    pub request_id: String,
23    pub provider: &'static str,
24    pub connector: &'static str,
25    pub mode: &'static str,
26    pub steps: Vec<PlanStep>,
27    pub extensions: BTreeMap<String, Value>,
28}
29
30#[derive(Serialize)]
31struct PlanSeed<'a> {
32    request_id: &'a str,
33    provider: &'static str,
34    connector: &'static str,
35    steps: &'a [PlanStep],
36    extensions: &'a BTreeMap<String, Value>,
37}
38
39pub fn build_plan(config: &ConnectorConfig) -> Result<ConnectorPlan, ConnectorError> {
40    config.validate()?;
41    let config = config.normalized();
42    let request_id = format!("request-{}", config.config_id);
43    let mut steps = vec![PlanStep {
44        sequence: 1,
45        action: "validate-config",
46        target: config.config_id.clone(),
47        effect: "none",
48        network_required: false,
49    }];
50    for (index, repository) in config.repositories.iter().enumerate() {
51        steps.push(PlanStep {
52            sequence: u32::try_from(index + 2)
53                .map_err(|_| ConnectorError::new("repositories", "too many repositories"))?,
54            action: "prepare-api-request",
55            target: format!("{}/{}", config.organization, repository),
56            effect: "normalize",
57            network_required: false,
58        });
59    }
60    let extensions = BTreeMap::from([
61        ("connection_ref".to_owned(), json!(config.connection_ref)),
62        ("allowed_actions".to_owned(), json!(config.allowed_actions)),
63        (
64            "safety".to_owned(),
65            json!({
66                "planning_only": true,
67                "network_calls_performed": false,
68                "credentials_resolved": false,
69                "codex_credential_reuse": false
70            }),
71        ),
72    ]);
73    let seed = PlanSeed {
74        request_id: &request_id,
75        provider: PROVIDER,
76        connector: CONNECTOR,
77        steps: &steps,
78        extensions: &extensions,
79    };
80    let bytes = serde_json::to_vec(&seed)
81        .map_err(|_| ConnectorError::new("plan", "could not serialize canonical plan"))?;
82    let digest = Sha256::digest(bytes);
83    let hex: String = digest.iter().map(|byte| format!("{byte:02x}")).collect();
84    Ok(ConnectorPlan {
85        schema: PLAN_SCHEMA,
86        plan_id: format!("plan-{PROVIDER}-{}", &hex[..16]),
87        request_id,
88        provider: PROVIDER,
89        connector: CONNECTOR,
90        mode: "propose",
91        steps,
92        extensions,
93    })
94}