use ts_rs::TS;
use crate::{
Action, CertCacheConfig, ChangeKind, CloudCreateSandboxResponse, CloudErrorBody,
CloudErrorDetails, CloudMessageResponse, CloudNetworkSpec, CloudPaginated, CloudRootfsSource,
CloudSandboxResources, CloudSandboxRuntimeOptions, CloudSandboxSpec, CloudSandboxStatus,
ConfigPlannedChange, Destination, DestinationGroup, Direction, DiskImageFormat, DnsConfig,
EnvVar, HandoffInit, HostPattern, HostPermissions, InterceptCaConfig, InterfaceOverrides,
ModificationConflict, ModificationDisposition, ModificationPolicy, ModificationWarning,
MountOptions, NamedVolumeCreate, NamedVolumeMode, NetworkPolicy, NetworkSpec, OciRootfsSource,
Patch, PlannedChange, PortProtocol, PortRange, Protocol, PublishedPortSpec, PullPolicy,
ResourceConvergenceState, ResourceKind, ResourceResizeStatus, Rlimit, RlimitResource,
RootfsSource, Rule, SandboxLogLevel, SandboxModificationPatch, SandboxModificationPlan,
SandboxPolicy, SandboxResources, SandboxRuntimeOptions, SandboxSpec, ScopedUpstreamCaCert,
ScopedVerifyUpstream, SecretChangeKind, SecretEntry, SecretInjection, SecretModificationPatch,
SecretPlannedChange, SecretSource, SecretsConfig, SecurityProfile, StatVirtualization,
TlsConfig, ViolationAction, 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![
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),
HostPattern::decl(&cfg),
SecretInjection::decl(&cfg),
ViolationAction::decl(&cfg),
SecretEntry::decl(&cfg),
SecretsConfig::decl(&cfg),
InterceptCaConfig::decl(&cfg),
CertCacheConfig::decl(&cfg),
ScopedUpstreamCaCert::decl(&cfg),
ScopedVerifyUpstream::decl(&cfg),
TlsConfig::decl(&cfg),
Action::decl(&cfg),
Direction::decl(&cfg),
Protocol::decl(&cfg),
DestinationGroup::decl(&cfg),
Destination::decl(&cfg),
PortRange::decl(&cfg),
Rule::decl(&cfg),
NetworkPolicy::decl(&cfg),
DnsConfig::decl(&cfg),
InterfaceOverrides::decl(&cfg),
NetworkSpec::decl(&cfg),
PublishedPortSpec::decl(&cfg),
PortProtocol::decl(&cfg),
HandoffInit::decl(&cfg),
SandboxPolicy::decl(&cfg),
SandboxSpec::decl(&cfg),
SandboxResources::decl(&cfg),
SandboxRuntimeOptions::decl(&cfg),
EnvVar::decl(&cfg),
SandboxLogLevel::decl(&cfg),
RlimitResource::decl(&cfg),
Rlimit::decl(&cfg),
SandboxModificationPatch::decl(&cfg),
ModificationPolicy::decl(&cfg),
SecretModificationPatch::decl(&cfg),
SecretSource::decl(&cfg),
SandboxModificationPlan::decl(&cfg),
PlannedChange::decl(&cfg),
ConfigPlannedChange::decl(&cfg),
SecretPlannedChange::decl(&cfg),
ChangeKind::decl(&cfg),
SecretChangeKind::decl(&cfg),
ModificationDisposition::decl(&cfg),
ModificationConflict::decl(&cfg),
ModificationWarning::decl(&cfg),
ResourceKind::decl(&cfg),
ResourceConvergenceState::decl(&cfg),
ResourceResizeStatus::decl(&cfg),
CloudNetworkSpec::decl(&cfg),
CloudRootfsSource::decl(&cfg),
CloudSandboxResources::decl(&cfg),
CloudSandboxRuntimeOptions::decl(&cfg),
CloudSandboxSpec::decl(&cfg),
CloudCreateSandboxResponse::decl(&cfg),
CloudSandboxStatus::decl(&cfg),
CloudPaginated::<CloudCreateSandboxResponse>::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");
if std::env::var_os("REGEN_TS_BINDINGS").is_some() {
fs::write(&bindings_path, &generated).expect("write bindings");
return;
}
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; run `just gen`",
bindings_path.display()
);
}
#[test]
fn ts_rs_renders_cloud_contract_declarations() {
let declarations = declarations();
assert_eq!(declarations.len(), 71);
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("CloudSandboxSpec"))
);
assert!(
declarations
.iter()
.any(|decl| decl.contains("CloudSandboxStatus"))
);
}
}