1use crate::util::Tagged;
2use serde::{Deserialize, Serialize};
3use serde_json::Error as SerdeError;
4use std::str::FromStr;
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct Site {
26 kind: String,
27 position: (f64, f64, f64),
28 tags: Option<Vec<String>>,
29}
30
31impl FromStr for Site {
32 type Err = SerdeError;
33 fn from_str(source: &str) -> Result<Site, Self::Err> {
34 serde_json::from_str(source)
35 }
36}
37
38impl Tagged for Site {
39 fn tags(&self) -> Option<Vec<&str>> {
40 self.tags
41 .as_ref()
42 .map(|tags| tags.iter().map(|tag| tag.as_ref()).collect())
43 }
44}
45
46impl Site {
47 pub fn new(kind: &str) -> Self {
49 Site {
50 kind: kind.to_string(),
51 position: (0.0, 0.0, 0.0),
52 tags: None,
53 }
54 }
55
56 pub fn position(&self) -> (f64, f64, f64) {
58 self.position
59 }
60
61 pub fn kind(&self) -> &str {
63 &self.kind
64 }
65
66 pub fn move_x(mut self, distance: f64) -> Self {
68 self.position.0 += distance;
69 self
70 }
71
72 pub fn move_y(mut self, distance: f64) -> Self {
74 self.position.1 += distance;
75 self
76 }
77
78 pub fn move_z(mut self, distance: f64) -> Self {
80 self.position.2 += distance;
81 self
82 }
83
84 pub fn with_kind(mut self, kind: &str) -> Self {
86 self.kind = kind.to_string();
87 self
88 }
89
90 pub fn with_position(mut self, position: (f64, f64, f64)) -> Self {
92 self.position = position;
93 self
94 }
95
96 pub fn with_tags(mut self, tags: Vec<&str>) -> Self {
98 self.tags = Some(tags.iter().map(|s| s.to_string()).collect());
99 self
100 }
101}
102
103#[cfg(test)]
104mod test {
105 use super::Site;
106 use std::str::FromStr;
107
108 #[test]
109 fn site_can_be_created() {
110 let site = Site::new("Fe");
111 assert_eq!(site.kind, "Fe");
112 assert_eq!(site.position, (0.0, 0.0, 0.0));
113 }
114
115 #[test]
116 fn site_can_be_moved() {
117 let site = Site::new("Fe").move_x(1.0);
118 assert_eq!(site.position, (1.0, 0.0, 0.0));
119 }
120
121 #[test]
122 fn site_can_be_changed() {
123 let site = Site::new("Fe").with_kind("Cu");
124 assert_eq!(site.kind, "Cu");
125 }
126
127 #[test]
128 fn site_can_be_positioned() {
129 let site = Site::new("Fe").with_position((1.0, 1.0, 1.0));
130 assert_eq!(site.position, (1.0, 1.0, 1.0));
131 }
132
133 #[test]
134 fn site_can_be_tagged() {
135 let site = Site::new("Fe").with_tags(vec!["core", "inner"]);
136 assert_eq!(
137 site.tags,
138 Some(vec!["core".to_string(), "inner".to_string()])
139 );
140 }
141
142 #[test]
143 fn site_can_be_read_from_string() {
144 let data = r#"
145 {"kind": "Fe", "position": [0, 0, 0]}
146 "#;
147 let site_result = Site::from_str(data);
148 assert!(site_result.is_ok());
149 }
150
151 #[test]
152 fn site_will_take_optional_tags() {
153 let data = r#"
154 {"kind": "Fe", "position": [0, 0, 0], "tags": ["core", "inner"]}
155 "#;
156 let site_result: Result<Site, _> = data.parse();
157 assert!(site_result.is_ok());
158 assert_eq!(
159 site_result.unwrap().tags,
160 Some(vec!["core".to_string(), "inner".to_string()])
161 );
162 }
163}