use std::path::Path;
#[derive(Debug, Default)]
pub struct PreservedAppYamlIdentity {
pub name: Option<String>,
pub region: Option<String>,
pub repo: Option<String>,
pub branch: Option<String>,
}
pub fn parse_existing(path: &Path) -> Option<PreservedAppYamlIdentity> {
let src = std::fs::read_to_string(path).ok()?;
let mut out = PreservedAppYamlIdentity::default();
for line in src.lines() {
if out.name.is_none() {
if let Some(v) = line.strip_prefix("name: ") {
out.name = Some(v.trim().to_string());
continue;
}
}
if out.region.is_none() {
if let Some(v) = line.strip_prefix("region: ") {
out.region = Some(v.trim().to_string());
continue;
}
}
if out.repo.is_none() {
if let Some(v) = line.trim_start().strip_prefix("repo: ") {
out.repo = Some(v.trim().to_string());
continue;
}
}
if out.branch.is_none() {
if let Some(v) = line.trim_start().strip_prefix("branch: ") {
out.branch = Some(v.trim().to_string());
continue;
}
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn write_yaml(td: &TempDir, body: &str) -> std::path::PathBuf {
let p = td.path().join("app.yaml");
fs::write(&p, body).unwrap();
p
}
#[test]
fn returns_none_for_missing_file() {
assert!(parse_existing(Path::new("/tmp/nonexistent-ferro-test-xyz/app.yaml")).is_none());
}
#[test]
fn parses_all_four_fields() {
let td = TempDir::new().unwrap();
let yaml = "# Generated by ferro do:init — edit to your needs\n\
name: my-custom-app\n\
region: nyc3\n\
\n\
services:\n\
- name: web\n\
github:\n\
repo: myorg/my-repo\n\
branch: production\n\
deploy_on_push: true\n";
let p = write_yaml(&td, yaml);
let id = parse_existing(&p).unwrap();
assert_eq!(id.name.as_deref(), Some("my-custom-app"));
assert_eq!(id.region.as_deref(), Some("nyc3"));
assert_eq!(id.repo.as_deref(), Some("myorg/my-repo"));
assert_eq!(id.branch.as_deref(), Some("production"));
}
#[test]
fn top_level_name_not_confused_with_service_name() {
let td = TempDir::new().unwrap();
let yaml = "name: top-level-app\nregion: fra1\n\nservices:\n - name: web\n github:\n repo: o/r\n branch: main\n";
let p = write_yaml(&td, yaml);
let id = parse_existing(&p).unwrap();
assert_eq!(id.name.as_deref(), Some("top-level-app"));
}
#[test]
fn top_level_region_not_confused_with_indented_region() {
let td = TempDir::new().unwrap();
let yaml = "name: app\nregion: ams3\n\nservices:\n - name: web\n region: inner-ignored\n github:\n repo: o/r\n branch: main\n";
let p = write_yaml(&td, yaml);
let id = parse_existing(&p).unwrap();
assert_eq!(id.region.as_deref(), Some("ams3"));
}
#[test]
fn missing_fields_return_none() {
let td = TempDir::new().unwrap();
let yaml = "services:\n - name: web\n http_port: 8080\n";
let p = write_yaml(&td, yaml);
let id = parse_existing(&p).unwrap();
assert!(id.name.is_none());
assert!(id.region.is_none());
assert!(id.repo.is_none());
assert!(id.branch.is_none());
}
#[test]
fn first_match_wins_for_repeated_top_level_name() {
let td = TempDir::new().unwrap();
let yaml = "name: first-name\nname: second-name\nregion: fra1\nservices: []\n";
let p = write_yaml(&td, yaml);
let id = parse_existing(&p).unwrap();
assert_eq!(id.name.as_deref(), Some("first-name"));
}
}