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> {
70 crate::conversion::from_text_edit_plan(plan)
71 }
72
73 pub fn try_into_text_edit_plan(self) -> Result<crate::EditPlan, PlanError> {
75 crate::conversion::into_text_edit_plan(self)
76 }
77}
78
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
81#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
82#[serde(deny_unknown_fields)]
83pub enum RefactorOperation {
84 Modify(FileEdit),
85 Create(CreateFile),
86 Delete(DeleteFile),
87 Rename(RenameFile),
88}
89
90#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct CreateFile {
94 pub path: String,
95 pub contents: String,
96 #[serde(default, skip_serializing_if = "CreatePermissions::is_default")]
97 pub permissions: CreatePermissions,
98 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
99 pub extensions: BTreeMap<String, Value>,
100}
101
102impl CreateFile {
103 #[must_use]
104 pub fn new(path: impl Into<String>, contents: impl Into<String>) -> Self {
105 Self {
106 path: path.into(),
107 contents: contents.into(),
108 permissions: CreatePermissions::default(),
109 extensions: BTreeMap::new(),
110 }
111 }
112
113 #[must_use]
114 pub const fn with_executable(mut self, executable: bool) -> Self {
115 self.permissions.executable = executable;
116 self
117 }
118}
119
120#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123#[serde(deny_unknown_fields)]
124pub struct CreatePermissions {
125 pub executable: bool,
126}
127
128impl CreatePermissions {
129 #[must_use]
130 pub const fn is_default(&self) -> bool {
131 !self.executable
132 }
133
134 #[must_use]
135 pub const fn readonly(self) -> bool {
136 false
137 }
138
139 #[must_use]
140 pub const fn unix_mode(self) -> u32 {
141 if self.executable { 0o755 } else { 0o644 }
142 }
143}
144
145#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase")]
148pub struct DeleteFile {
149 pub path: String,
150 pub expected_sha256: String,
151 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
152 pub extensions: BTreeMap<String, Value>,
153}
154
155impl DeleteFile {
156 #[must_use]
157 pub fn new(path: impl Into<String>, expected_sha256: impl Into<String>) -> Self {
158 Self {
159 path: path.into(),
160 expected_sha256: expected_sha256.into(),
161 extensions: BTreeMap::new(),
162 }
163 }
164}
165
166#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct RenameFile {
173 pub from: String,
174 pub to: String,
175 pub expected_source_sha256: String,
176 #[serde(default, skip_serializing_if = "Vec::is_empty")]
177 pub edits: Vec<TextEdit>,
178 #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
179 pub extensions: BTreeMap<String, Value>,
180}
181
182impl RenameFile {
183 #[must_use]
184 pub fn new(
185 from: impl Into<String>,
186 to: impl Into<String>,
187 expected_source_sha256: impl Into<String>,
188 ) -> Self {
189 Self {
190 from: from.into(),
191 to: to.into(),
192 expected_source_sha256: expected_source_sha256.into(),
193 edits: Vec::new(),
194 extensions: BTreeMap::new(),
195 }
196 }
197
198 #[must_use]
199 pub fn with_edits(mut self, edits: Vec<TextEdit>) -> Self {
200 self.edits = edits;
201 self
202 }
203}