use ts_rs::TS;
use crate::{
CloudCreateSandboxRequest, CloudErrorBody, CloudErrorDetails, CloudMessageResponse,
CloudPaginated, CloudSandbox, CloudSandboxStatus, DiskImageFormat, EnvVar, HandoffInit,
HostPermissions, LogSource, MountOptions, NamedVolumeCreate, NamedVolumeMode, NetworkSpec,
OciRootfsSource, Patch, PortProtocol, PublishedPortSpec, PullPolicy, Rlimit, RlimitResource,
RootfsSource, SandboxLogLevel, SandboxPolicy, SandboxResources, SandboxRuntimeOptions,
SandboxSpec, SecurityProfile, SnapshotDestination, SnapshotSpec, StatVirtualization,
VolumeKind, VolumeMount, VolumeSpec,
};
const HEADER: &str = "// @generated by microsandbox-types. Do not edit by hand.\n\n";
pub fn render_bindings() -> String {
let mut output = String::from(HEADER);
output.push_str(
&declarations()
.into_iter()
.map(export_declaration)
.map(trim_line_endings)
.collect::<Vec<_>>()
.join("\n\n"),
);
output.push('\n');
output
}
pub fn declarations() -> Vec<String> {
let cfg = ts_rs::Config::new().with_large_int("number");
vec![
serde_json::Value::decl(&cfg),
DiskImageFormat::decl(&cfg),
OciRootfsSource::decl(&cfg),
RootfsSource::decl(&cfg),
PullPolicy::decl(&cfg),
StatVirtualization::decl(&cfg),
HostPermissions::decl(&cfg),
SecurityProfile::decl(&cfg),
MountOptions::decl(&cfg),
VolumeKind::decl(&cfg),
VolumeSpec::decl(&cfg),
NamedVolumeMode::decl(&cfg),
NamedVolumeCreate::decl(&cfg),
VolumeMount::decl(&cfg),
Patch::decl(&cfg),
NetworkSpec::decl(&cfg),
PublishedPortSpec::decl(&cfg),
PortProtocol::decl(&cfg),
HandoffInit::decl(&cfg),
SandboxPolicy::decl(&cfg),
SnapshotDestination::decl(&cfg),
SnapshotSpec::decl(&cfg),
SandboxSpec::decl(&cfg),
SandboxResources::decl(&cfg),
SandboxRuntimeOptions::decl(&cfg),
EnvVar::decl(&cfg),
SandboxLogLevel::decl(&cfg),
RlimitResource::decl(&cfg),
Rlimit::decl(&cfg),
LogSource::decl(&cfg),
CloudCreateSandboxRequest::decl(&cfg),
CloudSandbox::decl(&cfg),
CloudSandboxStatus::decl(&cfg),
CloudPaginated::<CloudSandbox>::decl(&cfg),
CloudMessageResponse::decl(&cfg),
CloudErrorBody::decl(&cfg),
CloudErrorDetails::decl(&cfg),
]
}
fn export_declaration(declaration: String) -> String {
if declaration.starts_with("export ") {
return declaration;
}
for keyword in ["type ", "interface "] {
if let Some(rest) = declaration.strip_prefix(keyword) {
return format!("export {keyword}{rest}");
}
}
declaration
}
fn trim_line_endings(declaration: String) -> String {
declaration
.lines()
.map(str::trim_end)
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::PathBuf;
use super::*;
#[test]
fn checked_in_bindings_match_generated_output() {
let generated = render_bindings();
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let package_root = manifest_dir
.parent()
.expect("microsandbox-types rust crate should live under <package>/rust");
let bindings_path = package_root.join("typescript/src/index.ts");
let bindings = fs::read_to_string(&bindings_path).unwrap_or_else(|err| {
panic!("failed to read {}: {err}", bindings_path.display());
});
assert_eq!(bindings, generated, "{} is stale", bindings_path.display());
}
#[test]
fn ts_rs_renders_cloud_contract_declarations() {
let declarations = declarations();
assert_eq!(declarations.len(), 37);
assert!(
declarations
.iter()
.any(|decl| decl.contains("RootfsSource"))
);
assert!(declarations.iter().any(|decl| decl.contains("SandboxSpec")));
assert!(declarations.iter().any(|decl| decl.contains("Rlimit")));
assert!(
declarations
.iter()
.any(|decl| decl.contains("CloudCreateSandboxRequest"))
);
assert!(
declarations
.iter()
.any(|decl| decl.contains("CloudSandboxStatus"))
);
}
}