soar_core/package/
query.rs1use std::sync::OnceLock;
2
3use regex::Regex;
4
5use crate::{
6 database::packages::{FilterCondition, PackageQueryBuilder},
7 error::SoarError,
8};
9
10#[derive(Debug)]
11pub struct PackageQuery {
12 pub name: Option<String>,
13 pub repo_name: Option<String>,
14 pub pkg_id: Option<String>,
15 pub version: Option<String>,
16}
17
18impl PackageQuery {
19 pub fn apply_filters(&self, mut builder: PackageQueryBuilder) -> PackageQueryBuilder {
20 if let Some(ref repo_name) = self.repo_name {
21 builder = builder.where_and("repo_name", FilterCondition::Eq(repo_name.clone()));
22 }
23 if let Some(ref name) = self.name {
24 builder = builder.where_and("pkg_name", FilterCondition::Eq(name.clone()));
25 }
26 if let Some(ref pkg_id) = self.pkg_id {
27 if pkg_id != "all" {
28 builder = builder.where_and("pkg_id", FilterCondition::Eq(pkg_id.clone()));
29 }
30 }
31 if let Some(ref version) = self.version {
32 builder = builder.where_and("version", FilterCondition::Eq(version.clone()));
33 }
34 builder
35 }
36}
37
38impl TryFrom<&str> for PackageQuery {
39 type Error = SoarError;
40
41 fn try_from(value: &str) -> Result<Self, Self::Error> {
42 static PACKAGE_RE: OnceLock<Regex> = OnceLock::new();
43 let re = PACKAGE_RE.get_or_init(|| {
44 Regex::new(
45 r"(?x)
46 (?P<name>[^\/\#\@:]+)? # optional package name
47 (?:\#(?P<pkg_id>[^@:]+))? # optional pkg_id after #
48 (?:@(?P<version>[^:]+))? # optional version after @
49 (?::(?P<repo>[^:]+))?$ # optional repo after :
50 ",
51 )
52 .unwrap()
53 });
54
55 let query = value.trim().to_lowercase();
56 if query.is_empty() {
57 return Err(SoarError::InvalidPackageQuery(
58 "Package query can't be empty".into(),
59 ));
60 }
61
62 let caps = re.captures(&query).ok_or(SoarError::InvalidPackageQuery(
63 "Invalid package query format".into(),
64 ))?;
65
66 let name = caps.name("name").map(|m| m.as_str().to_string());
67 let pkg_id = caps.name("pkg_id").map(|m| m.as_str().to_string());
68 if pkg_id.is_none() && name.is_none() {
69 return Err(SoarError::InvalidPackageQuery(
70 "Either package name or pkg_id is required".into(),
71 ));
72 }
73
74 if let Some(ref pkg_id) = pkg_id {
75 if pkg_id == "all" && name.is_none() {
76 return Err(SoarError::InvalidPackageQuery(
77 "For all, package name is required.".into(),
78 ));
79 }
80 }
81
82 Ok(PackageQuery {
83 repo_name: caps.name("repo").map(|m| m.as_str().to_string()),
84 pkg_id,
85 name,
86 version: caps.name("version").map(|m| m.as_str().to_string()),
87 })
88 }
89}