aion_package/codegen/
project.rs1use std::io;
11use std::path::Path;
12
13use serde::Deserialize;
14
15use super::error::CodegenError;
16use super::names::{is_reserved_word, is_snake_identifier};
17use crate::PackagingError;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum CodegenMode {
22 Write,
24 Check,
27}
28
29pub(crate) fn check_on_disk(module_path: &Path, contents: &str) -> Result<(), CodegenError> {
33 let on_disk = match std::fs::read(module_path) {
34 Ok(bytes) => bytes,
35 Err(source) if source.kind() == io::ErrorKind::NotFound => {
36 return Err(CodegenError::CheckMissing {
37 path: module_path.to_path_buf(),
38 });
39 }
40 Err(source) => {
41 return Err(CodegenError::CheckRead {
42 path: module_path.to_path_buf(),
43 source,
44 });
45 }
46 };
47 if on_disk != contents.as_bytes() {
48 return Err(CodegenError::CheckDrift {
49 path: module_path.to_path_buf(),
50 });
51 }
52 Ok(())
53}
54
55#[derive(Debug, Deserialize)]
56struct GleamTomlName {
57 name: String,
58}
59
60pub(crate) fn read_package_name(root: &Path) -> Result<String, CodegenError> {
64 let path = root.join("gleam.toml");
65 let text = match std::fs::read_to_string(&path) {
66 Ok(text) => text,
67 Err(source) if source.kind() == io::ErrorKind::NotFound => {
68 return Err(CodegenError::Config(PackagingError::GleamTomlMissing {
69 path,
70 }));
71 }
72 Err(source) => {
73 return Err(CodegenError::Config(PackagingError::GleamMetadataRead {
74 path,
75 source,
76 }));
77 }
78 };
79 let parsed: GleamTomlName = toml::from_str(&text).map_err(|source| {
80 CodegenError::Config(PackagingError::GleamMetadataParse { path, source })
81 })?;
82 if !is_snake_identifier(&parsed.name) || is_reserved_word(&parsed.name) {
83 return Err(CodegenError::ProjectName {
84 name: parsed.name,
85 reason: "must be a snake_case identifier and not a Gleam reserved word".to_owned(),
86 });
87 }
88 Ok(parsed.name)
89}
90
91#[cfg(test)]
92mod tests {
93 use std::fs;
94
95 use super::{check_on_disk, read_package_name};
96 use crate::PackagingError;
97 use crate::codegen::error::CodegenError;
98 use crate::project::fixture;
99
100 type TestResult = Result<(), Box<dyn std::error::Error>>;
101
102 #[test]
103 fn check_on_disk_passes_clean_and_types_missing_and_drift() -> TestResult {
104 let root = fixture::temp_project(
105 "codegen-check-on-disk",
106 &[("src/demo_codecs.gleam", b"generated contents\n" as &[u8])],
107 )?;
108 let path = root.join("src/demo_codecs.gleam");
109
110 check_on_disk(&path, "generated contents\n")?;
111
112 let drift = check_on_disk(&path, "other contents\n");
113 assert!(matches!(
114 drift,
115 Err(CodegenError::CheckDrift { path: ref drifted }) if *drifted == path
116 ));
117
118 let missing_path = root.join("src/absent.gleam");
119 let missing = check_on_disk(&missing_path, "anything");
120 assert!(matches!(
121 missing,
122 Err(CodegenError::CheckMissing { path: ref reported }) if *reported == missing_path
123 ));
124 fs::remove_dir_all(&root)?;
125 Ok(())
126 }
127
128 #[test]
129 fn package_name_reads_and_validates() -> TestResult {
130 let root = fixture::temp_project(
131 "codegen-package-name",
132 &[(
133 "gleam.toml",
134 b"name = \"demo\"\nversion = \"0.1.0\"\n" as &[u8],
135 )],
136 )?;
137 assert_eq!(read_package_name(&root)?, "demo");
138 fs::remove_dir_all(&root)?;
139
140 let missing = fixture::temp_project("codegen-package-name-missing", &[])?;
141 let result = read_package_name(&missing);
142 assert!(matches!(
143 result,
144 Err(CodegenError::Config(
145 PackagingError::GleamTomlMissing { .. }
146 ))
147 ));
148 fs::remove_dir_all(&missing)?;
149
150 let bad = fixture::temp_project(
151 "codegen-package-name-bad",
152 &[("gleam.toml", b"name = \"Demo-App\"\n" as &[u8])],
153 )?;
154 let result = read_package_name(&bad);
155 assert!(matches!(
156 result,
157 Err(CodegenError::ProjectName { ref name, .. }) if name == "Demo-App"
158 ));
159 fs::remove_dir_all(&bad)?;
160 Ok(())
161 }
162}