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}