Skip to main content

agm_core/model/
code.rs

1//! Code block types (spec S23).
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7use super::fields::ParseEnumError;
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum CodeAction {
12    Create,
13    Append,
14    Prepend,
15    Replace,
16    InsertBefore,
17    InsertAfter,
18    Full,
19}
20
21impl fmt::Display for CodeAction {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            Self::Create => write!(f, "create"),
25            Self::Append => write!(f, "append"),
26            Self::Prepend => write!(f, "prepend"),
27            Self::Replace => write!(f, "replace"),
28            Self::InsertBefore => write!(f, "insert_before"),
29            Self::InsertAfter => write!(f, "insert_after"),
30            Self::Full => write!(f, "full"),
31        }
32    }
33}
34
35impl FromStr for CodeAction {
36    type Err = ParseEnumError;
37
38    fn from_str(s: &str) -> Result<Self, Self::Err> {
39        match s {
40            "create" => Ok(Self::Create),
41            "append" => Ok(Self::Append),
42            "prepend" => Ok(Self::Prepend),
43            "replace" => Ok(Self::Replace),
44            "insert_before" => Ok(Self::InsertBefore),
45            "insert_after" => Ok(Self::InsertAfter),
46            "full" => Ok(Self::Full),
47            _ => Err(ParseEnumError {
48                type_name: "CodeAction",
49                value: s.to_owned(),
50            }),
51        }
52    }
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56pub struct CodeBlock {
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub lang: Option<String>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub target: Option<String>,
61    pub action: CodeAction,
62    pub body: String,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub anchor: Option<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub old: Option<String>,
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_code_action_from_str_valid_returns_ok() {
75        assert_eq!("create".parse::<CodeAction>().unwrap(), CodeAction::Create);
76        assert_eq!("append".parse::<CodeAction>().unwrap(), CodeAction::Append);
77        assert_eq!(
78            "prepend".parse::<CodeAction>().unwrap(),
79            CodeAction::Prepend
80        );
81        assert_eq!(
82            "replace".parse::<CodeAction>().unwrap(),
83            CodeAction::Replace
84        );
85        assert_eq!(
86            "insert_before".parse::<CodeAction>().unwrap(),
87            CodeAction::InsertBefore
88        );
89        assert_eq!(
90            "insert_after".parse::<CodeAction>().unwrap(),
91            CodeAction::InsertAfter
92        );
93        assert_eq!("full".parse::<CodeAction>().unwrap(), CodeAction::Full);
94    }
95
96    #[test]
97    fn test_code_action_from_str_invalid_returns_error() {
98        let err = "overwrite".parse::<CodeAction>().unwrap_err();
99        assert_eq!(err.type_name, "CodeAction");
100        assert_eq!(err.value, "overwrite");
101    }
102
103    #[test]
104    fn test_code_action_display_roundtrip() {
105        for a in [
106            CodeAction::Create,
107            CodeAction::Append,
108            CodeAction::Prepend,
109            CodeAction::Replace,
110            CodeAction::InsertBefore,
111            CodeAction::InsertAfter,
112            CodeAction::Full,
113        ] {
114            let s = a.to_string();
115            assert_eq!(s.parse::<CodeAction>().unwrap(), a);
116        }
117    }
118
119    #[test]
120    fn test_code_block_serde_roundtrip() {
121        let cb = CodeBlock {
122            lang: Some("rust".to_owned()),
123            target: Some("src/main.rs".to_owned()),
124            action: CodeAction::Append,
125            body: "fn main() {}".to_owned(),
126            anchor: None,
127            old: None,
128        };
129        let json = serde_json::to_string(&cb).unwrap();
130        let back: CodeBlock = serde_json::from_str(&json).unwrap();
131        assert_eq!(cb, back);
132    }
133
134    #[test]
135    fn test_code_block_with_replace_fields() {
136        let cb = CodeBlock {
137            lang: Some("rust".to_owned()),
138            target: Some("src/lib.rs".to_owned()),
139            action: CodeAction::Replace,
140            body: "fn new_impl() {}".to_owned(),
141            anchor: None,
142            old: Some("fn old_impl() {}".to_owned()),
143        };
144        let json = serde_json::to_string(&cb).unwrap();
145        assert!(json.contains("old_impl"));
146        let back: CodeBlock = serde_json::from_str(&json).unwrap();
147        assert_eq!(cb, back);
148    }
149
150    #[test]
151    fn test_code_block_with_anchor() {
152        let cb = CodeBlock {
153            lang: Some("rust".to_owned()),
154            target: Some("src/lib.rs".to_owned()),
155            action: CodeAction::InsertAfter,
156            body: "    pub new_field: String,".to_owned(),
157            anchor: Some("pub struct Foo {".to_owned()),
158            old: None,
159        };
160        let json = serde_json::to_string(&cb).unwrap();
161        assert!(json.contains("anchor"));
162        let back: CodeBlock = serde_json::from_str(&json).unwrap();
163        assert_eq!(cb, back);
164    }
165}