1use crate::{Completeness, FileEdit, PlanError, PlanEvidence, PlanFingerprint, TextEdit};
2use blazingly_json::Value;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6pub const REFACTOR_PLAN_SCHEMA: &str = "weavatrix.refactor-plan.v1";
8
9#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct RefactorPlan {
13 pub schema_version: String,
14 pub operation: String,
15 pub operations: Vec<RefactorOperation>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub completeness: Option<Completeness>,
19 #[serde(flatten)]
20 pub evidence: PlanEvidence,
21}
22
23impl RefactorPlan {
24 #[must_use]
25 pub fn new(operation: impl Into<String>, operations: Vec<RefactorOperation>) -> Self {
26 Self {
27 schema_version: REFACTOR_PLAN_SCHEMA.to_owned(),
28 operation: operation.into(),
29 operations,
30 completeness: None,
31 evidence: PlanEvidence::default(),
32 }
33 }
34
35 pub fn validate(&self) -> Result<crate::ValidatedConsumerPlan<'_>, PlanError> {
36 crate::validate_consumer_plan(self, crate::RefactorPlanLimits::default())
37 }
38
39 pub fn validate_with(
40 &self,
41 limits: crate::RefactorPlanLimits,
42 ) -> Result<crate::ValidatedConsumerPlan<'_>, PlanError> {
43 crate::validate_consumer_plan(self, limits)
44 }
45
46 pub fn fingerprint(&self) -> Result<PlanFingerprint, PlanError> {
47 crate::fingerprint_plan(self)
48 }
49
50 pub fn from_text_edit_plan(plan: crate::EditPlan) -> Result<Self, PlanError> {
52 crate::conversion::from_text_edit_plan(plan)
53 }
54
55 pub fn try_into_text_edit_plan(self) -> Result<crate::EditPlan, PlanError> {
57 crate::conversion::into_text_edit_plan(self)
58 }
59}
60
61#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
63#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
64#[serde(deny_unknown_fields)]
65pub enum RefactorOperation {
66 Modify(FileEdit),
67 Create(CreateFile),
68 Delete(DeleteFile),
69 Rename(RenameFile),
70}
71
72#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct CreateFile {
76 pub path: String,
77 pub contents: String,
78 #[serde(default, skip_serializing_if = "CreatePermissions::is_default")]
79 pub permissions: CreatePermissions,
80 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
81 pub extensions: BTreeMap<String, Value>,
82}
83
84impl CreateFile {
85 #[must_use]
86 pub fn new(path: impl Into<String>, contents: impl Into<String>) -> Self {
87 Self {
88 path: path.into(),
89 contents: contents.into(),
90 permissions: CreatePermissions::default(),
91 extensions: BTreeMap::new(),
92 }
93 }
94
95 #[must_use]
96 pub const fn with_executable(mut self, executable: bool) -> Self {
97 self.permissions.executable = executable;
98 self
99 }
100}
101
102#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105#[serde(deny_unknown_fields)]
106pub struct CreatePermissions {
107 pub executable: bool,
108}
109
110impl CreatePermissions {
111 #[must_use]
112 pub const fn is_default(&self) -> bool {
113 !self.executable
114 }
115
116 #[must_use]
117 pub const fn readonly(self) -> bool {
118 false
119 }
120
121 #[must_use]
122 pub const fn unix_mode(self) -> u32 {
123 if self.executable { 0o755 } else { 0o644 }
124 }
125}
126
127#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct DeleteFile {
131 pub path: String,
132 pub expected_sha256: String,
133 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
134 pub extensions: BTreeMap<String, Value>,
135}
136
137impl DeleteFile {
138 #[must_use]
139 pub fn new(path: impl Into<String>, expected_sha256: impl Into<String>) -> Self {
140 Self {
141 path: path.into(),
142 expected_sha256: expected_sha256.into(),
143 extensions: BTreeMap::new(),
144 }
145 }
146}
147
148#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase")]
154pub struct RenameFile {
155 pub from: String,
156 pub to: String,
157 pub expected_source_sha256: String,
158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
159 pub edits: Vec<TextEdit>,
160 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
161 pub extensions: BTreeMap<String, Value>,
162}
163
164impl RenameFile {
165 #[must_use]
166 pub fn new(
167 from: impl Into<String>,
168 to: impl Into<String>,
169 expected_source_sha256: impl Into<String>,
170 ) -> Self {
171 Self {
172 from: from.into(),
173 to: to.into(),
174 expected_source_sha256: expected_source_sha256.into(),
175 edits: Vec::new(),
176 extensions: BTreeMap::new(),
177 }
178 }
179
180 #[must_use]
181 pub fn with_edits(mut self, edits: Vec<TextEdit>) -> Self {
182 self.edits = edits;
183 self
184 }
185}