ginger_shared_rs/
lib.rs

1use clap::ValueEnum;
2use std::{
3    cmp::Ordering,
4    collections::HashMap,
5    error::Error,
6    fmt,
7    fs::{self, File},
8    io::{Read, Write},
9    path::Path,
10    process::exit,
11    str::FromStr,
12};
13
14use serde::{Deserialize, Serialize};
15
16pub mod rocket_models;
17pub mod rocket_utils;
18pub mod utils;
19
20#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
21pub enum ORM {
22    TypeORM,
23    SQLAlchemy,
24    DjangoORM,
25    Diesel,
26}
27
28impl fmt::Display for ORM {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            ORM::TypeORM => write!(f, "TypeORM"),
32            ORM::SQLAlchemy => write!(f, "SQLAlchemy"),
33            ORM::DjangoORM => write!(f, "DjangoORM"),
34            ORM::Diesel => write!(f, "Diesel"),
35        }
36    }
37}
38
39#[derive(Deserialize, Debug, Serialize, Clone)]
40pub struct ConsumerDBSchema {
41    pub url: String,
42    pub lang: LANG,
43    pub orm: ORM,
44    pub root: String,
45    pub schema_id: Option<String>,
46    pub cache_schema_id: Option<String>,
47    pub message_queue_schema_id: Option<String>,
48    pub branch: Option<String>,
49}
50
51#[derive(Deserialize, Debug, Serialize, Clone)]
52pub struct ConsumerDBTables {
53    pub names: Vec<String>,
54}
55
56#[derive(Deserialize, Debug, Serialize, Clone)]
57pub struct ConsumerDBConfig {
58    pub schema: ConsumerDBSchema,
59    pub tables: ConsumerDBTables,
60}
61
62pub fn write_consumer_db_config<P: AsRef<Path>>(path: P, config: &ConsumerDBConfig) -> () {
63    let toml_string = toml::to_string(config).unwrap();
64    let mut file = File::create(path).unwrap();
65    file.write_all(toml_string.as_bytes()).unwrap();
66}
67
68pub fn read_consumer_db_config<P: AsRef<Path>>(
69    path: P,
70) -> Result<ConsumerDBConfig, Box<dyn Error>> {
71    // Try to open the file
72    let mut file = File::open(&path).map_err(|e| {
73        format!(
74            "Failed to open the file '{}': {}",
75            path.as_ref().display(),
76            e
77        )
78    })?;
79
80    let mut contents = String::new();
81
82    // Read the file contents
83    file.read_to_string(&mut contents).map_err(|e| {
84        format!(
85            "Failed to read the file '{}': {}",
86            path.as_ref().display(),
87            e
88        )
89    })?;
90
91    // Deserialize the TOML contents into the ConsumerDBConfig struct
92    toml::from_str(&contents).map_err(|e| {
93        format!(
94            "Failed to parse TOML from file '{}': {}",
95            path.as_ref().display(),
96            e
97        )
98        .into()
99    })
100}
101
102#[derive(Debug, Clone)]
103pub struct Service {
104    pub schema_url: String,
105    pub name: String,
106}
107
108#[derive(Debug, Serialize, Deserialize, Clone, Copy, ValueEnum)]
109pub enum LANG {
110    Rust,
111    TS,
112    Python,
113    Shell,
114}
115
116impl fmt::Display for LANG {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            LANG::Rust => write!(f, "Rust"),
120            LANG::TS => write!(f, "TS"),
121            LANG::Python => write!(f, "Python"),
122            LANG::Shell => write!(f, "Shell"),
123        }
124    }
125}
126
127impl LANG {
128    pub fn all() -> Vec<LANG> {
129        vec![LANG::Rust, LANG::TS, LANG::Python, LANG::Shell]
130    }
131}
132
133#[derive(Deserialize, Debug, Serialize, Clone)]
134pub struct ServiceConfig {
135    pub services: Option<HashMap<String, HashMap<String, String>>>,
136    pub portals_refs: Option<HashMap<String, HashMap<String, String>>>,
137    pub ws_refs: Option<HashMap<String, HashMap<String, String>>>,
138    pub lang: LANG,
139    pub organization_id: String,
140    pub dir: Option<String>, // in case the project does not need any service integration
141    pub refs_file: Option<String>,
142    pub spec_url: Option<String>,
143    pub urls: Option<HashMap<String, String>>,
144    pub urls_ws: Option<HashMap<String, String>>,
145    pub override_name: Option<String>,
146    pub service_type: Option<String>,
147    pub portal_config: Option<PortalConfig>,
148}
149
150#[derive(Deserialize, Debug, Serialize, Clone)]
151pub struct PortalConfig {
152    pub id: String,
153    pub logo_url: String,
154    pub disabled: bool,
155    pub access_group_id: Option<i64>,
156    pub tnc_url: Option<String>,
157    pub allow_registration: bool,
158    pub auth_redirection_path: Option<String>,
159    pub has_web_interface: bool,
160    pub friendly_name: String,
161}
162
163#[derive(Debug, Deserialize, Serialize, Clone)]
164pub struct Link {
165    pub internal: bool,
166    pub label: String,
167    pub icon: String,
168    pub link: String,
169}
170
171impl fmt::Display for Link {
172    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173        write!(
174            f,
175            "[internal: {}, label: {}, icon: {}]",
176            self.internal, self.label, self.icon
177        )
178    }
179}
180
181impl PartialEq for Link {
182    fn eq(&self, other: &Self) -> bool {
183        self.internal == other.internal
184            && self.label == other.label
185            && self.icon == other.icon
186            && self.link == other.link
187    }
188}
189
190#[derive(Deserialize, Debug, Serialize, Clone)]
191pub struct PackageMetadata {
192    pub lang: LANG,
193    pub package_type: String,
194    #[serde(default = "default_links")]
195    pub links: Vec<Link>,
196}
197
198fn default_links() -> Vec<Link> {
199    vec![]
200}
201
202#[derive(Debug, Clone)]
203pub enum FileType {
204    Py,
205    Toml,
206    Json,
207    Unknown,
208}
209
210impl FileType {
211    pub fn from_extension(ext: Option<&str>) -> FileType {
212        match ext {
213            Some("py") => FileType::Py,
214            Some("toml") => FileType::Toml,
215            Some("json") => FileType::Json,
216            _ => FileType::Unknown,
217        }
218    }
219}
220
221impl fmt::Display for FileType {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        match self {
224            FileType::Py => write!(f, "Py"),
225            FileType::Toml => write!(f, "Toml"),
226            FileType::Json => write!(f, "Json"),
227            FileType::Unknown => write!(f, "Unknown"),
228        }
229    }
230}
231
232#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
233pub enum Channel {
234    Final,
235    Nightly, // Also known as Dev branch
236    Alpha,
237    Beta,
238}
239impl fmt::Display for Channel {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        match self {
242            Channel::Nightly => write!(f, "nightly"),
243            Channel::Final => write!(f, "final"),
244            Channel::Alpha => write!(f, "alpha"),
245            Channel::Beta => write!(f, "beta"),
246        }
247    }
248}
249
250impl From<&str> for Channel {
251    fn from(channel: &str) -> Self {
252        match channel {
253            "nightly" => Channel::Nightly,
254            "alpha" => Channel::Alpha,
255            "beta" => Channel::Beta,
256            "final" => Channel::Final,
257            c => {
258                println!("Unable to recognize the channel {:?}", c);
259                exit(1)
260            }
261        }
262    }
263}
264
265#[derive(Debug, Deserialize, Serialize, Clone, Copy, Eq)]
266pub struct Version {
267    pub channel: Channel,
268    pub major: u32,
269    pub minor: u32,
270    pub patch: u32,
271    pub revision: u32,
272}
273
274impl Version {
275    pub fn formatted(&self) -> String {
276        match &self.channel {
277            Channel::Final => {
278                format!("{}.{}.{}", self.major, self.minor, self.patch)
279            }
280            _ => {
281                format!(
282                    "{}.{}.{}-{}.{}",
283                    self.major, self.minor, self.patch, self.channel, self.revision
284                )
285            }
286        }
287    }
288    pub fn tuple(&self) -> String {
289        format!(
290            "({}, {}, {}, \"{}\", {})",
291            self.major, self.minor, self.patch, self.channel, self.revision
292        )
293    }
294
295    pub fn from_str(version: &str) -> Self {
296        let parts: Vec<&str> = version.split(|c| c == '.' || c == '-').collect();
297        let major = parts[0].parse().unwrap_or(0);
298        let minor = parts[1].parse().unwrap_or(0);
299        let patch = parts[2].parse().unwrap_or(0);
300        let (channel, revision) = if parts.len() > 3 {
301            (Channel::from(parts[3]), parts[4].parse().unwrap_or(0))
302        } else {
303            (Channel::Final, 0)
304        };
305
306        Version {
307            major,
308            minor,
309            patch,
310            channel,
311            revision,
312        }
313    }
314}
315
316impl Ord for Version {
317    fn cmp(&self, other: &Self) -> Ordering {
318        self.major
319            .cmp(&other.major)
320            .then(self.minor.cmp(&other.minor))
321            .then(self.patch.cmp(&other.patch))
322            .then(self.channel.cmp(&other.channel))
323            .then(self.revision.cmp(&other.revision))
324    }
325}
326
327impl PartialOrd for Version {
328    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
329        Some(self.cmp(other))
330    }
331}
332
333impl PartialEq for Version {
334    fn eq(&self, other: &Self) -> bool {
335        self.major == other.major
336            && self.minor == other.minor
337            && self.patch == other.patch
338            && self.channel == other.channel
339            && self.revision == other.revision
340    }
341}
342#[derive(Debug, Serialize, Deserialize, Clone)]
343pub enum OutputType {
344    String,
345    Tuple,
346}
347
348impl fmt::Display for OutputType {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        match self {
351            OutputType::String => write!(f, "String"),
352            OutputType::Tuple => write!(f, "Tuple"),
353        }
354    }
355}
356
357#[derive(Debug, Deserialize, Serialize, Clone)]
358pub struct Reference {
359    pub file_name: String,
360    #[serde(default = "default_output_type")] // Use a default value function
361    pub output_type: OutputType, // `type` is a reserved keyword in Rust
362    pub variable: String,
363    #[serde(skip, default = "default_file_type")] // This field is not in the TOML file
364    pub file_type: FileType,
365}
366
367fn default_file_type() -> FileType {
368    FileType::Unknown
369}
370
371fn default_output_type() -> OutputType {
372    OutputType::String // Default value is "string"
373}
374
375#[derive(Debug, Deserialize, Serialize, Clone)]
376pub struct ReleaserSettings {
377    pub git_url_prefix: Option<String>,
378    #[serde(default = "default_take_snapshots")]
379    pub take_snapshots: bool,
380}
381
382fn default_take_snapshots() -> bool {
383    false
384}
385
386#[derive(Debug, Deserialize, Serialize, Clone)]
387pub struct ReleaserConfig {
388    pub settings: ReleaserSettings,
389    pub version: Version,
390    #[serde(default = "default_references")]
391    pub references: Vec<Reference>,
392}
393
394fn default_references() -> Vec<Reference> {
395    vec![]
396}
397
398pub fn read_releaser_config_file<P: AsRef<Path>>(
399    file_path: P,
400) -> Result<ReleaserConfig, Box<dyn std::error::Error>> {
401    // Read the file content into a string
402    let contents = fs::read_to_string(file_path)?;
403
404    // Parse the TOML string into the Settings struct
405    let settings: ReleaserConfig = toml::de::from_str(&contents)?;
406
407    Ok(settings)
408}
409
410pub fn write_releaser_config_file(
411    file_path: &str,
412    config: &ReleaserConfig,
413) -> Result<(), Box<dyn Error>> {
414    let toml_str = toml::to_string(config)?;
415    fs::write(file_path, toml_str)?;
416    Ok(())
417}
418
419pub fn read_service_config_file<P: AsRef<Path>>(path: P) -> Result<ServiceConfig, Box<dyn Error>> {
420    let content = fs::read_to_string(path)?;
421    let config: ServiceConfig = toml::from_str(&content)?;
422    Ok(config)
423}
424
425pub fn read_package_metadata_file<P: AsRef<Path>>(
426    path: P,
427) -> Result<PackageMetadata, Box<dyn Error>> {
428    let content = fs::read_to_string(path)?;
429    let config: PackageMetadata = toml::from_str(&content)?;
430    Ok(config)
431}
432
433pub fn write_service_config_file<P: AsRef<Path>>(
434    path: P,
435    config: &ServiceConfig,
436) -> Result<(), Box<dyn Error>> {
437    let content = toml::to_string(config)?;
438    fs::write(path, content)?;
439    Ok(())
440}
441
442#[derive(Debug, Deserialize, Serialize, PartialEq)]
443pub struct GingerDBConfig {
444    pub branch: String,
445    pub organization_id: String,
446    pub database: Vec<DatabaseConfig>, // Unified all db types in one vector
447}
448
449#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
450pub struct DatabaseConfig {
451    pub db_type: DbType, // Use DbType enum
452    pub description: String,
453    pub enable: bool,
454    pub id: Option<String>,
455    pub name: String,
456    pub port: String,
457    pub studio_port: Option<String>,
458    #[serde(default = "default_links")]
459    pub links: Vec<Link>,
460}
461
462impl fmt::Display for DatabaseConfig {
463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464        write!(f, "{}", self.name)
465    }
466}
467
468#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
469#[serde(rename_all = "lowercase")] // This will map the enum to/from lowercase strings
470pub enum DbType {
471    Rdbms,
472    DocumentDb,
473    Cache,
474    MessageQueue,
475}
476
477impl fmt::Display for DbType {
478    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479        let db_type_str = match self {
480            DbType::Rdbms => "rdbms",
481            DbType::DocumentDb => "documentdb",
482            DbType::Cache => "cache",
483            DbType::MessageQueue => "messagequeue",
484        };
485        write!(f, "{}", db_type_str)
486    }
487}
488
489impl FromStr for DbType {
490    type Err = String;
491
492    fn from_str(s: &str) -> Result<Self, Self::Err> {
493        match s.to_lowercase().as_str() {
494            "rdbms" => Ok(DbType::Rdbms),
495            "documentdb" => Ok(DbType::DocumentDb),
496            "cache" => Ok(DbType::Cache),
497            "messagequeue" => Ok(DbType::MessageQueue),
498            _ => Err(format!("'{}' is not a valid DbType", s)),
499        }
500    }
501}
502
503pub fn read_db_config(file_path: &str) -> Result<GingerDBConfig, Box<dyn std::error::Error>> {
504    let contents = fs::read_to_string(file_path)?;
505    let config: GingerDBConfig = toml::from_str(&contents)?;
506    Ok(config)
507}
508
509pub fn write_db_config(
510    file_path: &str,
511    config: &GingerDBConfig,
512) -> Result<(), Box<dyn std::error::Error>> {
513    let toml_string = toml::to_string(config)?;
514    let mut file = fs::File::create(file_path)?;
515    file.write_all(toml_string.as_bytes())?;
516    Ok(())
517}
518
519#[derive(ValueEnum, Clone, PartialEq)]
520pub enum Environment {
521    Dev,
522    Stage,
523    Prod,
524    ProdK8,
525    StageK8,
526}
527
528impl fmt::Display for Environment {
529    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
530        match self {
531            Environment::Dev => write!(f, "dev"),
532            Environment::Stage => write!(f, "stage"),
533            Environment::Prod => write!(f, "prod"),
534            Environment::ProdK8 => write!(f, "prod_k8"),
535            Environment::StageK8 => write!(f, "stage_k8"),
536        }
537    }
538}