cyfs_core/app/
dec_app.rs

1use crate::codec::protos;
2use crate::coreobj::CoreObjectType;
3use cyfs_base::*;
4use serde::Serialize;
5
6use std::collections::hash_map::RandomState;
7use std::collections::{BTreeMap, HashMap};
8
9#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransform, Serialize)]
10#[cyfs_protobuf_type(crate::codec::protos::DecAppDescContent)]
11pub struct DecAppDescContent {
12    id: String,
13}
14
15impl DescContent for DecAppDescContent {
16    fn obj_type() -> u16 {
17        CoreObjectType::DecApp as u16
18    }
19
20    fn format(&self) -> u8 {
21        OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF
22    }
23
24    type OwnerType = Option<ObjectId>;
25    type AreaType = SubDescNone;
26    type AuthorType = SubDescNone;
27    type PublicKeyType = SubDescNone;
28}
29
30#[derive(Clone, ProtobufEncode, ProtobufDecode, ProtobufTransformType, Serialize)]
31#[cyfs_protobuf_type(crate::codec::protos::DecAppContent)]
32pub struct DecAppContent {
33    source: HashMap<String, ObjectId>,
34    icon: Option<String>,
35    desc: Option<String>,
36    source_desc: HashMap<String, String>,
37    tags: HashMap<String, String>,
38}
39
40impl BodyContent for DecAppContent {
41    fn format(&self) -> u8 {
42        OBJECT_CONTENT_CODEC_FORMAT_PROTOBUF
43    }
44}
45
46impl ProtobufTransform<protos::DecAppContent> for DecAppContent {
47    fn transform(value: protos::DecAppContent) -> BuckyResult<Self> {
48        let mut source = HashMap::new();
49        for item in value.source {
50            source.insert(item.key, ObjectId::clone_from_slice(item.value.as_slice())?);
51        }
52
53        let mut source_desc = HashMap::new();
54        for item in value.source_desc {
55            source_desc.insert(item.key, item.value);
56        }
57
58        let mut tags = HashMap::new();
59        for item in value.tags {
60            tags.insert(item.key, item.value);
61        }
62
63        let mut ret = DecAppContent {
64            source,
65            source_desc,
66            icon: None,
67            desc: None,
68            tags,
69        };
70
71        if value.icon.is_some() {
72            ret.icon = Some(value.icon.unwrap());
73        }
74        if value.desc.is_some() {
75            ret.desc = Some(value.desc.unwrap());
76        }
77
78        Ok(ret)
79    }
80}
81impl ProtobufTransform<&DecAppContent> for protos::DecAppContent {
82    fn transform(value: &DecAppContent) -> BuckyResult<Self> {
83        let source_map: BTreeMap<String, ObjectId> = value.source.clone().into_iter().collect();
84        let mut source = vec![];
85        for (k, v) in source_map {
86            source.push(protos::StringBytesMapItem {
87                key: k,
88                value: v.to_vec()?,
89            });
90        }
91
92        let source_desc_map: BTreeMap<String, String> =
93            value.source_desc.clone().into_iter().collect();
94        let mut source_desc = vec![];
95        for (k, v) in source_desc_map {
96            source_desc.push(protos::StringStringMapItem { key: k, value: v });
97        }
98
99        let tags_map: BTreeMap<String, String> = value.tags.clone().into_iter().collect();
100        let mut tags = vec![];
101        for (k, v) in tags_map {
102            tags.push(protos::StringStringMapItem { key: k, value: v });
103        }
104
105        let mut ret = Self {
106            source,
107            source_desc,
108            icon: None,
109            desc: None,
110            tags,
111        };
112
113        if let Some(icon) = &value.icon {
114            ret.icon = Some(icon.to_owned());
115        }
116        if let Some(desc) = &value.desc {
117            ret.desc = Some(desc.to_owned());
118        }
119
120        Ok(ret)
121    }
122}
123
124type DecAppType = NamedObjType<DecAppDescContent, DecAppContent>;
125type DecAppBuilder = NamedObjectBuilder<DecAppDescContent, DecAppContent>;
126type DecAppDesc = NamedObjectDesc<DecAppDescContent>;
127
128pub type DecAppId = NamedObjectId<DecAppType>;
129pub type DecApp = NamedObjectBase<DecAppType>;
130
131pub trait DecAppObj {
132    fn create(owner: ObjectId, id: &str) -> Self;
133    fn name(&self) -> &str;
134    fn app_desc(&self) -> Option<&str>;
135    fn icon(&self) -> Option<&str>;
136
137    // return (origin version, semversion);
138    fn find_version(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<(&str, String)>;
139
140    fn find_source_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<ObjectId>;
141    fn find_source(&self, version: &str) -> BuckyResult<ObjectId>;
142
143    fn find_source_desc_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<Option<&str>>;
144    fn find_source_desc(&self, version: &str) -> Option<&str>;
145
146    fn remove_source(&mut self, version: &str);
147    fn clear_source(&mut self);
148    fn set_source(&mut self, version: String, id: ObjectId, desc: Option<String>);
149    fn source(&self) -> &HashMap<String, ObjectId>;
150
151    fn find_tag(&self, tag: &str) -> BuckyResult<&str>;
152    fn set_tag(&mut self, tag: String, version: String);
153    fn remove_tag(&mut self, tag: &str);
154    fn tags(&self) -> &HashMap<String, String>;
155
156    fn generate_id(owner: ObjectId, id: &str) -> ObjectId;
157}
158
159pub struct SemVerHelper {}
160
161impl SemVerHelper {
162    // x.y.?.z => x.y.z
163    // x.y.?.z-? => x.y.z-?
164    pub fn fix_semver(ver: &str) -> String {
165        let mut top: Vec<&str> = ver.split('-').collect();
166        let mut ret: Vec<&str> = top[0].split('.').collect();
167        if ret.len() == 4 {
168            ret.remove(2);
169        }
170    
171        let ret = ret.join(".");
172        let ret = if top.len() > 1 {
173            top[0] = &ret;
174            top.join("-")
175        } else {
176            ret
177        };
178
179        // println!("{} -> {}", ver, ret);
180        ret
181    }
182}
183
184// 同owner, 同id的AppId应该始终相同,允许不同的话会造成混乱
185impl DecAppObj for DecApp {
186    fn create(owner: ObjectId, id: &str) -> Self {
187        let body = DecAppContent {
188            source: HashMap::new(),
189            icon: None,
190            desc: None,
191            source_desc: HashMap::new(),
192            tags: HashMap::new(),
193        };
194        let desc = DecAppDescContent { id: id.to_owned() };
195        DecAppBuilder::new(desc, body)
196            .owner(owner)
197            .no_create_time()
198            .build()
199    }
200
201    fn name(&self) -> &str {
202        &self.desc().content().id
203    }
204
205    fn app_desc(&self) -> Option<&str> {
206        self.body_expect("").content().desc.as_deref()
207    }
208
209    fn icon(&self) -> Option<&str> {
210        self.body_expect("").content().icon.as_deref()
211    }
212
213    // https://nodesource.com/blog/semver-tilde-and-caret/
214    // When pre is specified, all matching prerelease versions will be included; 
215    // otherwise, only all versions that do not contain any prerelease will be matched
216    fn find_version(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<(&str, String)> {
217        let name = self.name();
218        let id = self.desc().calculate_id();
219
220        let req_version = semver::VersionReq::parse(req_semver).map_err(|e| {
221            let msg = format!(
222                "invalid semver request string! id={}, name={}, value={}, pre={:?}, {}",
223                id, name, req_semver, pre, e
224            );
225            error!("{}", msg);
226            BuckyError::new(BuckyErrorCode::InvalidFormat, msg)
227        })?;
228
229        let list: Vec<_> = self
230            .body_expect("")
231            .content()
232            .source
233            .keys()
234            .map(|key| {
235                let new_version = SemVerHelper::fix_semver(&key);
236                (key, new_version)
237            })
238            .collect();
239
240        let mut semver_list = vec![];
241        for (version, new_version) in list {
242            let mut semver = semver::Version::parse(&new_version).map_err(|e| {
243                let msg = format!(
244                    "invalid semver string! id={}, name={}, value={}, pre={:?}, {}",
245                    id, name, version, pre, e,
246                );
247                error!("{}", msg);
248                BuckyError::new(BuckyErrorCode::InvalidFormat, msg)
249            })?;
250
251            if !semver.pre.is_empty() {
252                if let Some(pre) = pre {
253                    if semver.pre.as_str() != pre {
254                        continue;
255                    }
256                    semver.pre = semver::Prerelease::EMPTY;
257                } else {
258                    continue;
259                }
260            }
261
262            semver_list.push((version, semver));
263        }
264
265        semver_list.sort_by(|left, right| right.1.partial_cmp(&left.1).unwrap());
266
267        let ret = semver_list.iter().find(|(version, semver)| {
268            if req_version.matches(semver) {
269                info!(
270                    "app version matched: id={}, name={}, req={}, got={}, prev={:?}",
271                    id, name, req_semver, version, pre,
272                );
273                true
274            } else {
275                false
276            }
277        });
278
279        if ret.is_none() {
280            let msg = format!(
281                "no matching semver found for app: id={}, name={}, req={}",
282                id, name, req_semver
283            );
284            warn!("{}", msg);
285            return Err(BuckyError::new(BuckyErrorCode::NotFound, msg));
286        }
287
288        let (version, semver) = ret.unwrap();
289        Ok((version, semver.to_string()))
290    }
291
292    fn find_source_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<ObjectId> {
293        let ret = self.find_version(req_semver, pre)?;
294        self.find_source(&ret.0)
295    }
296
297    fn find_source(&self, version: &str) -> BuckyResult<ObjectId> {
298        self.body_expect("")
299            .content()
300            .source
301            .get(version)
302            .cloned()
303            .ok_or(BuckyError::from(BuckyErrorCode::NotFound))
304    }
305
306    fn find_source_desc_by_semver(&self, req_semver: &str, pre: Option<&str>) -> BuckyResult<Option<&str>> {
307        let ret = self.find_version(req_semver, pre)?;
308        Ok(self.find_source_desc(&ret.0))
309    }
310
311    fn find_source_desc(&self, version: &str) -> Option<&str> {
312        self.body_expect("")
313            .content()
314            .source_desc
315            .get(version)
316            .map(String::as_str)
317    }
318
319    fn remove_source(&mut self, version: &str) {
320        self.body_mut_expect("")
321            .content_mut()
322            .source
323            .remove(version);
324        self.body_mut_expect("")
325            .content_mut()
326            .source_desc
327            .remove(version);
328        self.body_mut_expect("")
329            .increase_update_time(bucky_time_now());
330    }
331
332    fn clear_source(&mut self) {
333        self.body_mut_expect("").content_mut().source.clear();
334        self.body_mut_expect("").content_mut().source_desc.clear();
335        self.body_mut_expect("")
336            .increase_update_time(bucky_time_now());
337    }
338
339    fn set_source(&mut self, version: String, id: ObjectId, desc: Option<String>) {
340        self.body_mut_expect("")
341            .content_mut()
342            .source
343            .insert(version.clone(), id);
344        if let Some(desc) = desc {
345            self.body_mut_expect("")
346                .content_mut()
347                .source_desc
348                .insert(version, desc);
349        }
350        self.body_mut_expect("")
351            .increase_update_time(bucky_time_now());
352    }
353
354    fn source(&self) -> &HashMap<String, ObjectId, RandomState> {
355        &self.body_expect("").content().source
356    }
357
358    fn find_tag(&self, tag: &str) -> BuckyResult<&str> {
359        self.body_expect("")
360            .content()
361            .tags
362            .get(tag)
363            .map(String::as_str)
364            .ok_or(BuckyError::from(BuckyErrorCode::NotFound))
365    }
366
367    fn set_tag(&mut self, tag: String, version: String) {
368        self.body_mut_expect("")
369            .content_mut()
370            .tags
371            .insert(tag, version);
372        self.body_mut_expect("")
373            .increase_update_time(bucky_time_now());
374    }
375
376    fn remove_tag(&mut self, tag: &str) {
377        self.body_mut_expect("").content_mut().tags.remove(tag);
378    }
379
380    fn tags(&self) -> &HashMap<String, String> {
381        &self.body_expect("").content().tags
382    }
383
384    fn generate_id(owner: ObjectId, id: &str) -> ObjectId {
385        Self::create(owner, id).desc().calculate_id()
386    }
387}
388
389#[cfg(test)]
390mod test {
391    use super::*;
392
393    #[test]
394    fn test() {
395        let owner = ObjectId::default();
396        let mut dec_app = DecApp::create(owner.clone(), "test-dec-app");
397        dec_app.set_source("1.0.0".to_owned(), owner.clone(), None);
398        dec_app.set_source("1.0.1".to_owned(), owner.clone(), None);
399        dec_app.set_source("1.0.2".to_owned(), owner.clone(), None);
400        dec_app.set_source("1.1.2".to_owned(), owner.clone(), None);
401        dec_app.set_source("1.1.5".to_owned(), owner.clone(), None);
402        
403        dec_app.set_source("1.3.7".to_owned(), owner.clone(), None);
404        dec_app.set_source("1.3.10".to_owned(), owner.clone(), None);
405        dec_app.set_source("1.4.0.20".to_owned(), owner.clone(), None);
406        dec_app.set_source("1.4.1.21-preview".to_owned(), owner.clone(), None);
407        dec_app.set_source("1.5.1.22-preview".to_owned(), owner.clone(), None);
408
409        dec_app.set_source("2.5.28".to_owned(), owner.clone(), None);
410        dec_app.set_source("2.5.30".to_owned(), owner.clone(), None);
411
412        let ret = dec_app.find_version("*", None).unwrap();
413        assert_eq!(ret, "2.5.30");
414
415        let ret = dec_app.find_version("2.5.28", None).unwrap();
416        assert_eq!(ret, "2.5.30");
417
418        let ret = dec_app.find_version("=1.0", None).unwrap();
419        assert_eq!(ret, "1.0.2");
420
421        // ^ first none zero version seg, and is default if not present
422
423        dec_app.find_version("=1.4.21-preview", None).unwrap_err();
424
425        let ret = dec_app.find_version("=1.4.21", Some("preview")).unwrap();
426        assert_eq!(ret, "1.4.1.21-preview");
427        let ret = dec_app.find_version("1.4", Some("preview")).unwrap();
428        assert_eq!(ret, "1.5.1.22-preview");
429        let ret = dec_app.find_version("1.0", Some("preview")).unwrap();
430        assert_eq!(ret, "1.5.1.22-preview");
431
432        let ret = dec_app.find_version("~1.4", None).unwrap();
433        assert_eq!(ret, "1.4.0.20");
434
435        // ~ second none zero version seg
436        let ret = dec_app.find_version("~1.1", None).unwrap();
437        assert_eq!(ret, "1.1.5");
438
439        let ret = dec_app.find_version("<1.3", None).unwrap();
440        assert_eq!(ret, "1.1.5");
441
442        let ret = dec_app.find_version("=1.3", None).unwrap();
443        assert_eq!(ret, "1.3.10");
444
445        let ret = dec_app.find_version("<=1.3.8", None).unwrap();
446        assert_eq!(ret, "1.3.7");
447    }
448}