use std::time::Duration;
use serde::Deserialize;
use serde_json::Value;
use crate::{
CALL_RUST_BIN_TOOL, CHECK_RUST_BIN_TOOL, CHECK_RUST_LIB_TOOL, CHECK_WEB_LIB_TOOL,
CREATE_RUST_BIN_TOOL, CREATE_RUST_LIB_TOOL, CREATE_WEB_LIB_TOOL, DELETE_FILE_RUST_BIN_TOOL,
DELETE_FILE_RUST_LIB_TOOL, DELETE_FILE_WEB_LIB_TOOL, DOCS_RUST_BIN_TOOL, DOCS_RUST_LIB_TOOL,
DOCS_WEB_LIB_TOOL, ManagedSourceKind, OPEN_RUST_BIN_TOOL, OPEN_RUST_LIB_TOOL,
OPEN_WEB_LIB_TOOL, PREVIEW_WRITE_FILE_RUST_BIN_TOOL, PREVIEW_WRITE_FILE_RUST_LIB_TOOL,
PREVIEW_WRITE_FILE_WEB_LIB_TOOL, PUBLISH_RUST_BIN_TOOL, PUBLISH_RUST_LIB_TOOL,
PUBLISH_WEB_LIB_TOOL, Service, SourceSnapshot, ToolError, ToolExecution,
WRITE_FILE_FREEFORM_RUST_BIN_TOOL, WRITE_FILE_FREEFORM_RUST_LIB_TOOL,
WRITE_FILE_FREEFORM_WEB_LIB_TOOL, WRITE_RUST_BIN_TOOL, WRITE_RUST_LIB_TOOL, WRITE_WEB_LIB_TOOL,
};
pub(crate) enum OpenedSource {
RustLibrary(kcode_rust_libs_v2::Lib),
WebLibrary(kcode_web_libs::Lib),
RustBinary(kcode_rust_bins::Lib),
}
#[derive(Clone)]
struct ManagedFile {
path: String,
contents: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum Action {
Create,
Open,
Docs,
Write,
WriteFile,
PreviewWriteFile,
DeleteFile,
Check,
Publish,
Call,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct NameArguments {
name: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WriteArguments {
name: String,
files: Vec<WriteFile>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WriteFile {
path: String,
contents: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct SingleFileArguments {
name: String,
path: String,
contents: String,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct DeleteFileArguments {
name: String,
path: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
struct CallArguments {
name: String,
version: String,
#[serde(default)]
input: String,
#[serde(default)]
object_ids: Vec<String>,
timeout_seconds: Option<u64>,
}
impl Service {
pub(crate) fn execute_blocking(
&self,
session_id: &str,
tool_name: &str,
arguments: Value,
) -> Result<ToolExecution, ToolError> {
self.inner.registry.remove_expired()?;
let (kind, action) = classify(tool_name).ok_or_else(|| {
ToolError::invalid(format!("Tool {tool_name:?} is not a managed-source tool."))
})?;
match action {
Action::Create => {
let arguments: NameArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
self.create_source(session_id, kind, &arguments.name)
}
Action::Open => {
let arguments: NameArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
self.open_source(session_id, kind, &arguments.name)
}
Action::Docs => {
let arguments: NameArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let (version, documentation) = self.docs(kind, &arguments.name)?;
Ok(ToolExecution::plain(format!(
"{}: {}\nVersion: {version}\n\n{documentation}",
kind.label(),
arguments.name
)))
}
Action::Write => {
let arguments: WriteArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let files = arguments
.files
.into_iter()
.map(ManagedFile::from)
.collect::<Vec<_>>();
let name = arguments.name;
let key_name = name.clone();
self.with_open_source(session_id, kind, &key_name, move |source| {
let previous = source.files();
source.set_files(files);
if let Err(error) = source.write() {
source.set_files(previous);
return Err(self.map_backend_error(kind, error));
}
let paths = source
.files()
.iter()
.map(|file| format!("- {}", file.path))
.collect::<Vec<_>>()
.join("\n");
Ok(ToolExecution::with_snapshot(
format!(
"Wrote {} files to {} {name}.\n{paths}",
source.files().len(),
kind.label()
),
source.snapshot(&name),
))
})
}
Action::WriteFile => {
let arguments: SingleFileArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let name = arguments.name;
let path = arguments.path;
let contents = arguments.contents;
let key_name = name.clone();
self.with_open_source(session_id, kind, &key_name, move |source| {
let previous = source.files();
let mut files = previous.clone();
upsert_file(&mut files, path.clone(), contents);
source.set_files(files);
if let Err(error) = source.write() {
source.set_files(previous);
return Err(self.map_backend_error(kind, error));
}
Ok(ToolExecution::with_snapshot(
format!("Wrote file {path} in {} {name}.", kind.label()),
source.snapshot(&name),
))
})
}
Action::PreviewWriteFile => {
let arguments: SingleFileArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let name = arguments.name;
let key_name = name.clone();
self.with_open_source(session_id, kind, &key_name, move |source| {
let mut files = source.files();
upsert_file(&mut files, arguments.path, arguments.contents);
Ok(ToolExecution::snapshot(snapshot(kind, &name, &files)))
})
}
Action::DeleteFile => {
let arguments: DeleteFileArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let name = arguments.name;
let path = arguments.path;
let key_name = name.clone();
self.with_open_source(session_id, kind, &key_name, move |source| {
let previous = source.files();
let mut files = previous.clone();
let Some(index) = files.iter().position(|file| file.path == path) else {
return Err(ToolError::not_found(
file_not_found_code(kind),
format!("File {path:?} does not exist in {} {name:?}.", kind.label()),
));
};
files.remove(index);
source.set_files(files);
if let Err(error) = source.write() {
source.set_files(previous);
return Err(self.map_backend_error(kind, error));
}
Ok(ToolExecution::with_snapshot(
format!("Deleted file {path} from {} {name}.", kind.label()),
source.snapshot(&name),
))
})
}
Action::Check => {
let arguments: NameArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
self.with_open_source(session_id, kind, &arguments.name, |source| {
source
.check()
.map_err(|error| self.map_backend_error(kind, error))?;
Ok(ToolExecution::plain(format!(
"{} {} passed its checks.",
kind.label(),
arguments.name
)))
})
}
Action::Publish => {
let arguments: NameArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
self.with_open_source(session_id, kind, &arguments.name, |source| {
source
.publish()
.map_err(|error| self.map_backend_error(kind, error))?;
Ok(ToolExecution::plain(format!(
"Published {} {}.",
kind.label(),
arguments.name
)))
})
}
Action::Call => {
let arguments: CallArguments = parse_arguments(arguments)?;
validate_name(&arguments.name)?;
let timeout = arguments.timeout_seconds.map(Duration::from_secs);
let result = kcode_rust_bins::run(
&self.inner.rust_binary_publications_root,
self.inner.object_store.as_ref(),
&arguments.name,
&arguments.version,
&kcode_rust_bins::RunRequest {
text: arguments.input,
object_ids: arguments.object_ids,
},
timeout,
)
.map_err(|error| {
self.map_backend_error(ManagedSourceKind::RustBinary, error.to_string())
})?;
Ok(ToolExecution::plain(call_output(
result.text,
result.object_ids,
)))
}
}
}
pub(crate) fn release_blocking(&self, session_id: &str) -> Result<usize, ToolError> {
self.inner.registry.release(session_id)
}
fn create_source(
&self,
session_id: &str,
kind: ManagedSourceKind,
name: &str,
) -> Result<ToolExecution, ToolError> {
if self.inner.registry.contains(session_id, kind, name)? {
return Err(ToolError::conflict(
kind.code("already_open"),
format!("{} {name:?} is already open in this session.", kind.label()),
));
}
let source = self.create_backend(kind, name)?;
let snapshot = source.snapshot(name);
self.inner.registry.insert(session_id, kind, name, source)?;
Ok(ToolExecution::snapshot(snapshot))
}
fn open_source(
&self,
session_id: &str,
kind: ManagedSourceKind,
name: &str,
) -> Result<ToolExecution, ToolError> {
if self.inner.registry.contains(session_id, kind, name)? {
let reopened = self.open_backend(kind, name)?;
return self.with_open_source(session_id, kind, name, move |source| {
*source = reopened;
Ok(ToolExecution::snapshot(source.snapshot(name)))
});
}
let source = self.open_backend(kind, name)?;
let snapshot = source.snapshot(name);
self.inner.registry.insert(session_id, kind, name, source)?;
Ok(ToolExecution::snapshot(snapshot))
}
fn create_backend(
&self,
kind: ManagedSourceKind,
name: &str,
) -> Result<OpenedSource, ToolError> {
match kind {
ManagedSourceKind::RustLibrary => kcode_rust_libs_v2::create(
&self.inner.rust_libraries_root,
name,
self.inner.registry_token.as_str(),
)
.map(OpenedSource::RustLibrary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
ManagedSourceKind::WebLibrary => kcode_web_libs::create(
&self.inner.web_libraries_root,
&self.inner.web_publications_root,
name,
)
.map(OpenedSource::WebLibrary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
ManagedSourceKind::RustBinary => kcode_rust_bins::create(
&self.inner.rust_binaries_root,
&self.inner.rust_binary_publications_root,
name,
)
.map(OpenedSource::RustBinary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
}
}
fn open_backend(&self, kind: ManagedSourceKind, name: &str) -> Result<OpenedSource, ToolError> {
match kind {
ManagedSourceKind::RustLibrary => kcode_rust_libs_v2::open(
&self.inner.rust_libraries_root,
name,
self.inner.registry_token.as_str(),
)
.map(OpenedSource::RustLibrary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
ManagedSourceKind::WebLibrary => kcode_web_libs::open(
&self.inner.web_libraries_root,
&self.inner.web_publications_root,
name,
)
.map(OpenedSource::WebLibrary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
ManagedSourceKind::RustBinary => kcode_rust_bins::open(
&self.inner.rust_binaries_root,
&self.inner.rust_binary_publications_root,
name,
)
.map(OpenedSource::RustBinary)
.map_err(|error| self.map_backend_error(kind, error.to_string())),
}
}
fn docs(&self, kind: ManagedSourceKind, name: &str) -> Result<(String, String), ToolError> {
match kind {
ManagedSourceKind::RustLibrary => {
kcode_rust_libs_v2::docs(&self.inner.rust_libraries_root, name)
.map_err(|error| self.map_backend_error(kind, error.to_string()))
}
ManagedSourceKind::WebLibrary => {
kcode_web_libs::docs(&self.inner.web_libraries_root, name)
.map_err(|error| self.map_backend_error(kind, error.to_string()))
}
ManagedSourceKind::RustBinary => {
kcode_rust_bins::docs(&self.inner.rust_binaries_root, name)
.map_err(|error| self.map_backend_error(kind, error.to_string()))
}
}
}
fn with_open_source(
&self,
session_id: &str,
kind: ManagedSourceKind,
name: &str,
operation: impl FnOnce(&mut OpenedSource) -> Result<ToolExecution, ToolError>,
) -> Result<ToolExecution, ToolError> {
let (handle, active) = self.inner.registry.begin(session_id, kind, name)?;
let mut source = handle
.lock()
.map_err(|_| ToolError::internal("The opened source is unavailable."))?;
let result = operation(&mut source);
drop(source);
drop(active);
result
}
fn map_backend_error(&self, kind: ManagedSourceKind, rendered: String) -> ToolError {
let category = rendered
.split_once(':')
.map_or("unknown", |(value, _)| value);
let safe = self.scrub_paths(rendered.clone());
match category {
"invalid_name" | "unsafe_path" | "invalid_source" | "invalid_metadata"
| "invalid_selector" | "invalid_timeout" => ToolError::invalid(safe),
"already_exists" => ToolError::conflict(exists_code(kind), safe),
"already_published" => ToolError::conflict(already_published_code(kind), safe),
"not_found" => ToolError::not_found(not_found_code(kind), safe),
"stale_snapshot" => ToolError::conflict(stale_code(kind), safe),
"invalid_repository"
| "unsafe_source"
| "invalid_publication"
| "invalid_publication_storage"
| "source_commit_uncertain"
| "source_generation_commit_uncertain" => {
ToolError::unprocessable(invalid_repository_code(kind), safe)
}
"migration" => ToolError::unavailable(migration_code(kind), safe),
"invalid_token" => ToolError::unavailable(
"registry_token_unavailable",
"Rust library publication is unavailable because the operator-provisioned crates.io token is missing or invalid.",
),
value if value.starts_with("check.") => {
ToolError::unprocessable(check_failed_code(kind), safe)
}
"execution" | "protocol" => ToolError::unprocessable("rust_bin_call_failed", safe),
"object_store" => ToolError::unavailable("rust_bin_object_store_failed", safe),
"publish" => ToolError::unavailable(publish_failed_code(kind), safe),
value if value.starts_with("publish.") => {
ToolError::unavailable(publish_failed_code(kind), safe)
}
value if value.starts_with("sandbox.") || value == "io" => {
tracing::error!(error=%rendered, source_kind=?kind, "managed-source infrastructure operation failed");
ToolError::unavailable(
infrastructure_code(kind),
"The managed-source operation failed in local infrastructure.",
)
}
_ => {
tracing::error!(error=%rendered, source_kind=?kind, "managed-source operation failed");
ToolError::unavailable(
infrastructure_code(kind),
"The managed-source operation failed unexpectedly.",
)
}
}
}
fn scrub_paths(&self, mut message: String) -> String {
for (path, replacement) in [
(&self.inner.rust_libraries_root, "<managed-rust-libraries>"),
(&self.inner.web_libraries_root, "<managed-web-libraries>"),
(&self.inner.rust_binaries_root, "<managed-rust-binaries>"),
(
&self.inner.rust_binary_publications_root,
"<rust-binary-publications>",
),
] {
message = message.replace(path.to_string_lossy().as_ref(), replacement);
}
message
}
}
impl OpenedSource {
fn files(&self) -> Vec<ManagedFile> {
match self {
Self::RustLibrary(library) => library
.files
.iter()
.map(|file| ManagedFile {
path: file.path.clone(),
contents: file.contents.clone(),
})
.collect(),
Self::WebLibrary(library) => library
.files
.iter()
.map(|file| ManagedFile {
path: file.path.clone(),
contents: file.contents.clone(),
})
.collect(),
Self::RustBinary(binary) => binary
.files
.iter()
.map(|file| ManagedFile {
path: file.path.clone(),
contents: file.contents.clone(),
})
.collect(),
}
}
fn set_files(&mut self, files: Vec<ManagedFile>) {
match self {
Self::RustLibrary(library) => {
library.files = files
.into_iter()
.map(|file| kcode_rust_libs_v2::File {
path: file.path,
contents: file.contents,
})
.collect();
}
Self::WebLibrary(library) => {
library.files = files
.into_iter()
.map(|file| kcode_web_libs::File {
path: file.path,
contents: file.contents,
})
.collect();
}
Self::RustBinary(binary) => {
binary.files = files
.into_iter()
.map(|file| kcode_rust_bins::File {
path: file.path,
contents: file.contents,
})
.collect();
}
}
}
fn write(&mut self) -> Result<(), String> {
match self {
Self::RustLibrary(library) => library.write().map_err(|error| error.to_string()),
Self::WebLibrary(library) => library.write().map_err(|error| error.to_string()),
Self::RustBinary(binary) => binary.write().map_err(|error| error.to_string()),
}
}
fn check(&self) -> Result<(), String> {
match self {
Self::RustLibrary(library) => library.check().map_err(|error| error.to_string()),
Self::WebLibrary(library) => library.check().map_err(|error| error.to_string()),
Self::RustBinary(binary) => binary.check().map_err(|error| error.to_string()),
}
}
fn publish(&self) -> Result<(), String> {
match self {
Self::RustLibrary(library) => library.publish().map_err(|error| error.to_string()),
Self::WebLibrary(library) => library.publish().map_err(|error| error.to_string()),
Self::RustBinary(binary) => binary.publish().map_err(|error| error.to_string()),
}
}
fn snapshot(&self, name: &str) -> SourceSnapshot {
let kind = match self {
Self::RustLibrary(_) => ManagedSourceKind::RustLibrary,
Self::WebLibrary(_) => ManagedSourceKind::WebLibrary,
Self::RustBinary(_) => ManagedSourceKind::RustBinary,
};
snapshot(kind, name, &self.files())
}
}
impl From<WriteFile> for ManagedFile {
fn from(value: WriteFile) -> Self {
Self {
path: value.path,
contents: value.contents,
}
}
}
pub(crate) fn proposed_write_snapshot(
tool_name: &str,
arguments: &Value,
) -> Option<SourceSnapshot> {
let (kind, action) = classify(tool_name)?;
if action != Action::Write {
return None;
}
let arguments: WriteArguments = serde_json::from_value(arguments.clone()).ok()?;
validate_name(&arguments.name).ok()?;
let mut files = arguments
.files
.into_iter()
.map(ManagedFile::from)
.collect::<Vec<_>>();
files.sort_by(|left, right| left.path.cmp(&right.path));
Some(snapshot(kind, &arguments.name, &files))
}
fn classify(tool_name: &str) -> Option<(ManagedSourceKind, Action)> {
Some(match tool_name {
CREATE_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Create),
OPEN_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Open),
DOCS_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Docs),
WRITE_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Write),
WRITE_FILE_FREEFORM_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::WriteFile),
PREVIEW_WRITE_FILE_RUST_LIB_TOOL => {
(ManagedSourceKind::RustLibrary, Action::PreviewWriteFile)
}
DELETE_FILE_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::DeleteFile),
CHECK_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Check),
PUBLISH_RUST_LIB_TOOL => (ManagedSourceKind::RustLibrary, Action::Publish),
CREATE_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Create),
OPEN_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Open),
DOCS_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Docs),
WRITE_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Write),
WRITE_FILE_FREEFORM_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::WriteFile),
PREVIEW_WRITE_FILE_WEB_LIB_TOOL => {
(ManagedSourceKind::WebLibrary, Action::PreviewWriteFile)
}
DELETE_FILE_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::DeleteFile),
CHECK_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Check),
PUBLISH_WEB_LIB_TOOL => (ManagedSourceKind::WebLibrary, Action::Publish),
CREATE_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Create),
OPEN_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Open),
DOCS_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Docs),
WRITE_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Write),
WRITE_FILE_FREEFORM_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::WriteFile),
PREVIEW_WRITE_FILE_RUST_BIN_TOOL => {
(ManagedSourceKind::RustBinary, Action::PreviewWriteFile)
}
DELETE_FILE_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::DeleteFile),
CHECK_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Check),
PUBLISH_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Publish),
CALL_RUST_BIN_TOOL => (ManagedSourceKind::RustBinary, Action::Call),
_ => return None,
})
}
fn parse_arguments<T: for<'de> Deserialize<'de>>(arguments: Value) -> Result<T, ToolError> {
serde_json::from_value(arguments).map_err(|error| {
ToolError::invalid(format!("Invalid managed-source tool arguments: {error}"))
})
}
fn validate_name(name: &str) -> Result<(), ToolError> {
let mut characters = name.chars();
if name.len() > 255
|| !characters
.next()
.is_some_and(|character| character.is_ascii_alphanumeric())
|| !characters
.all(|character| character.is_ascii_alphanumeric() || matches!(character, '-' | '_'))
{
return Err(ToolError::invalid(
"name must begin with an ASCII letter or digit and contain only ASCII letters, digits, '-' or '_'.",
));
}
Ok(())
}
fn upsert_file(files: &mut Vec<ManagedFile>, path: String, contents: String) {
if let Some(file) = files.iter_mut().find(|file| file.path == path) {
file.contents = contents;
} else {
files.push(ManagedFile { path, contents });
}
}
fn snapshot(kind: ManagedSourceKind, name: &str, files: &[ManagedFile]) -> SourceSnapshot {
let mut text = format!("{}: {name}\nFiles: {}", kind.label(), files.len());
for file in files {
text.push_str("\n\nFile: ");
text.push_str(&file.path);
text.push('\n');
text.push_str(&file.contents);
}
SourceSnapshot {
kind,
name: name.to_owned(),
text,
}
}
fn call_output(mut text: String, object_ids: Vec<String>) -> String {
if object_ids.is_empty() {
return text;
}
if !text.is_empty() && !text.ends_with('\n') {
text.push('\n');
}
text.push_str(&object_ids.join("\n"));
text
}
fn file_not_found_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_file_not_found",
ManagedSourceKind::WebLibrary => "web_lib_file_not_found",
ManagedSourceKind::RustBinary => "rust_bin_file_not_found",
}
}
fn exists_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_exists",
ManagedSourceKind::WebLibrary => "web_lib_exists",
ManagedSourceKind::RustBinary => "rust_bin_exists",
}
}
fn already_published_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_already_published",
ManagedSourceKind::WebLibrary => "web_lib_already_published",
ManagedSourceKind::RustBinary => "rust_bin_already_published",
}
}
fn not_found_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_not_found",
ManagedSourceKind::WebLibrary => "web_lib_not_found",
ManagedSourceKind::RustBinary => "rust_bin_not_found",
}
}
fn stale_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_stale_snapshot",
ManagedSourceKind::WebLibrary => "web_lib_stale_snapshot",
ManagedSourceKind::RustBinary => "rust_bin_stale_snapshot",
}
}
fn invalid_repository_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_invalid_repository",
ManagedSourceKind::WebLibrary => "web_lib_invalid_repository",
ManagedSourceKind::RustBinary => "rust_bin_invalid_repository",
}
}
fn migration_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_migration_failure",
ManagedSourceKind::WebLibrary => "web_lib_migration_failure",
ManagedSourceKind::RustBinary => "rust_bin_migration_failure",
}
}
fn check_failed_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_check_failed",
ManagedSourceKind::WebLibrary => "web_lib_check_failed",
ManagedSourceKind::RustBinary => "rust_bin_check_failed",
}
}
fn publish_failed_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_publish_failed",
ManagedSourceKind::WebLibrary => "web_lib_publish_failed",
ManagedSourceKind::RustBinary => "rust_bin_publish_failed",
}
}
fn infrastructure_code(kind: ManagedSourceKind) -> &'static str {
match kind {
ManagedSourceKind::RustLibrary => "rust_lib_infrastructure_failure",
ManagedSourceKind::WebLibrary => "web_lib_infrastructure_failure",
ManagedSourceKind::RustBinary => "rust_bin_infrastructure_failure",
}
}
#[cfg(test)]
mod tests {
use super::{ManagedSourceKind, call_output, proposed_write_snapshot};
use crate::{WRITE_RUST_BIN_TOOL, WRITE_WEB_LIB_TOOL};
use serde_json::json;
#[test]
fn call_output_preserves_text_and_appends_only_object_ids() {
assert_eq!(call_output("plain text".into(), vec![]), "plain text");
assert_eq!(
call_output("plain text".into(), vec!["object-1".into()]),
"plain text\nobject-1"
);
assert_eq!(
call_output(String::new(), vec!["one".into(), "two".into()]),
"one\ntwo"
);
}
#[test]
fn proposed_snapshots_are_kind_aware() {
let arguments = json!({
"name":"demo",
"files":[
{"path":"z","contents":"last"},
{"path":"a","contents":"first"}
]
});
let web = proposed_write_snapshot(WRITE_WEB_LIB_TOOL, &arguments).unwrap();
assert_eq!(web.kind, ManagedSourceKind::WebLibrary);
assert!(web.text.find("File: a").unwrap() < web.text.find("File: z").unwrap());
let binary = proposed_write_snapshot(WRITE_RUST_BIN_TOOL, &arguments).unwrap();
assert_eq!(binary.kind, ManagedSourceKind::RustBinary);
}
}