Skip to main content

kcode_rust_libs_v2/
lib.rs

1//! Six-operation facade for managed Rust libraries.
2
3use std::error::Error as StdError;
4use std::fmt;
5use std::path::Path;
6
7use kcode_rust_library_repository::Repository;
8use kcode_rust_source::Source;
9
10pub use kcode_rust_source::File;
11
12/// A managed-library operation failure.
13pub struct Error(String);
14
15/// Result type returned by this crate.
16pub type Result<T> = std::result::Result<T, Error>;
17
18/// One complete editable managed-library source snapshot.
19///
20/// Edit [`Lib::files`] directly, then call [`Lib::write`] to commit the
21/// complete replacement. Private state binds the snapshot to its repository
22/// generation and publication credential.
23pub struct Lib {
24    /// Every managed UTF-8 source file, canonically sorted after open or write.
25    pub files: Vec<File>,
26    repository: Repository,
27    name: String,
28    token: Secret,
29}
30
31/// Creates a minimal Rust 2024 library and returns its complete source.
32pub fn create(
33    rust_libs_root: impl AsRef<Path>,
34    name: &str,
35    crates_io_registry_token: impl AsRef<str>,
36) -> Result<Lib> {
37    let token = Secret::new(crates_io_registry_token.as_ref())?;
38    let source = initial_source(name)?;
39    let repository = kcode_rust_library_repository::create(rust_libs_root, name, &source)
40        .map_err(Error::leaf)?;
41    Ok(Lib::new(repository, name, token))
42}
43
44/// Opens an existing current-generation library repository.
45pub fn open(
46    rust_libs_root: impl AsRef<Path>,
47    name: &str,
48    crates_io_registry_token: impl AsRef<str>,
49) -> Result<Lib> {
50    let token = Secret::new(crates_io_registry_token.as_ref())?;
51    let repository =
52        kcode_rust_library_repository::open(rust_libs_root, name).map_err(Error::leaf)?;
53    Ok(Lib::new(repository, name, token))
54}
55
56/// Returns the current package version and root `Documentation.md`.
57pub fn docs(rust_libs_root: impl AsRef<Path>, name: &str) -> Result<(String, String)> {
58    kcode_rust_library_repository::docs(rust_libs_root, name).map_err(Error::leaf)
59}
60
61impl Lib {
62    fn new(repository: Repository, name: &str, token: Secret) -> Self {
63        let files = repository.source().files().to_vec();
64        Self {
65            files,
66            repository,
67            name: name.to_owned(),
68            token,
69        }
70    }
71
72    /// Atomically commits `files` if this repository snapshot is still current.
73    pub fn write(&mut self) -> Result<()> {
74        let source = Source::validate(&self.files, &self.name).map_err(Error::leaf)?;
75        self.repository.replace(&source).map_err(Error::leaf)?;
76        self.files = source.files().to_vec();
77        Ok(())
78    }
79
80    /// Formats and validates exactly the current in-memory complete source.
81    pub fn check(&self) -> Result<()> {
82        let source = Source::validate(&self.files, &self.name).map_err(Error::leaf)?;
83        kcode_rust_library_toolchain::check(&source).map_err(Error::leaf)
84    }
85
86    /// Rechecks and publishes exactly the current in-memory complete source.
87    pub fn publish(&self) -> Result<()> {
88        let source = Source::validate(&self.files, &self.name).map_err(Error::leaf)?;
89        kcode_rust_library_toolchain::publish(&source, self.token.expose()).map_err(Error::leaf)
90    }
91}
92
93impl Error {
94    fn new(category: &str, message: impl fmt::Display) -> Self {
95        Self(format!("{category}: {message}"))
96    }
97
98    fn leaf(error: impl fmt::Display) -> Self {
99        Self(error.to_string())
100    }
101}
102
103impl fmt::Display for Error {
104    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
105        formatter.write_str(&self.0)
106    }
107}
108
109impl fmt::Debug for Error {
110    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
111        formatter.debug_tuple("Error").field(&self.0).finish()
112    }
113}
114
115impl StdError for Error {}
116
117impl fmt::Debug for Lib {
118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
119        formatter
120            .debug_struct("Lib")
121            .field("files", &self.files)
122            .field("repository", &"[PRIVATE]")
123            .field("name", &self.name)
124            .field("token", &"[REDACTED]")
125            .finish()
126    }
127}
128
129struct Secret(String);
130
131impl Secret {
132    fn new(value: &str) -> Result<Self> {
133        let value = value.trim();
134        if value.is_empty() {
135            return Err(Error::new(
136                "invalid_token",
137                "the crates.io registry token is empty",
138            ));
139        }
140        Ok(Self(value.to_owned()))
141    }
142
143    fn expose(&self) -> &str {
144        &self.0
145    }
146}
147
148fn initial_source(name: &str) -> Result<Source> {
149    Source::validate(
150        &[
151            File {
152                path: "Cargo.toml".to_owned(),
153                contents: format!(
154                    "[package]\nname = \"{name}\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\n"
155                ),
156            },
157            File {
158                path: "Documentation.md".to_owned(),
159                contents: String::new(),
160            },
161            File {
162                path: "src/lib.rs".to_owned(),
163                contents: String::new(),
164            },
165        ],
166        name,
167    )
168    .map_err(Error::leaf)
169}