use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeployTarget {
Native,
Lambda,
Docker,
Wasm,
}
impl DeployTarget {
#[must_use]
pub fn detect() -> Self {
#[cfg(target_arch = "wasm32")]
{
return Self::Wasm;
}
#[cfg(not(target_arch = "wasm32"))]
{
if std::env::var("AWS_LAMBDA_FUNCTION_NAME").is_ok() {
return Self::Lambda;
}
if std::path::Path::new("/.dockerenv").exists() {
return Self::Docker;
}
Self::Native
}
}
#[must_use]
pub const fn capabilities(&self) -> TargetCapabilities {
match self {
Self::Native | Self::Docker => TargetCapabilities {
supports_simd: true,
supports_gpu: true,
supports_threads: true,
supports_filesystem: true,
supports_async_io: true,
max_memory_mb: 0, },
Self::Lambda => TargetCapabilities {
supports_simd: true,
supports_gpu: false,
supports_threads: true,
supports_filesystem: false, supports_async_io: true,
max_memory_mb: 10240, },
Self::Wasm => TargetCapabilities {
supports_simd: false, supports_gpu: false,
supports_threads: false, supports_filesystem: false, supports_async_io: false, max_memory_mb: 128, },
}
}
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::Native => "native",
Self::Lambda => "lambda",
Self::Docker => "docker",
Self::Wasm => "wasm",
}
}
#[must_use]
pub const fn supports(&self, feature: TargetFeature) -> bool {
let caps = self.capabilities();
match feature {
TargetFeature::Simd => caps.supports_simd,
TargetFeature::Gpu => caps.supports_gpu,
TargetFeature::Threads => caps.supports_threads,
TargetFeature::Filesystem => caps.supports_filesystem,
TargetFeature::AsyncIo => caps.supports_async_io,
}
}
}
impl std::fmt::Display for DeployTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::struct_excessive_bools)] pub struct TargetCapabilities {
pub supports_simd: bool,
pub supports_gpu: bool,
pub supports_threads: bool,
pub supports_filesystem: bool,
pub supports_async_io: bool,
pub max_memory_mb: u32,
}
impl TargetCapabilities {
#[must_use]
pub const fn has_all(&self, required: &[TargetFeature]) -> bool {
let mut i = 0;
while i < required.len() {
let has = match required[i] {
TargetFeature::Simd => self.supports_simd,
TargetFeature::Gpu => self.supports_gpu,
TargetFeature::Threads => self.supports_threads,
TargetFeature::Filesystem => self.supports_filesystem,
TargetFeature::AsyncIo => self.supports_async_io,
};
if !has {
return false;
}
i += 1;
}
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetFeature {
Simd,
Gpu,
Threads,
Filesystem,
AsyncIo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DockerConfig {
pub builder_image: String,
pub runtime_image: String,
pub target_triple: String,
pub strip_binary: bool,
pub expose_port: u16,
}
impl Default for DockerConfig {
fn default() -> Self {
Self {
builder_image: "rust:1.83".to_string(),
runtime_image: "gcr.io/distroless/static-debian12:latest".to_string(),
target_triple: "x86_64-unknown-linux-musl".to_string(),
strip_binary: true,
expose_port: 8080,
}
}
}
impl DockerConfig {
#[must_use]
pub fn arm64() -> Self {
Self {
target_triple: "aarch64-unknown-linux-musl".to_string(),
..Self::default()
}
}
#[must_use]
pub fn scratch() -> Self {
Self {
runtime_image: "scratch".to_string(),
..Self::default()
}
}
#[must_use]
pub fn generate_dockerfile(&self) -> String {
format!(
r#"# Auto-generated by realizar target module
# Per docs/specifications/serve-deploy-apr.md Section 6.2
# Stage 1: Build
FROM {builder} AS builder
WORKDIR /build
# Cache dependencies
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo "fn main() {{}}" > src/main.rs
RUN cargo build --release --target {target}
RUN rm -rf src
# Build actual binary
COPY src ./src
RUN cargo build --release --target {target}
{strip}
# Stage 2: Runtime
FROM {runtime}
COPY --from=builder /build/target/{target}/release/realizar /serve
EXPOSE {port}
ENTRYPOINT ["/serve"]
"#,
builder = self.builder_image,
runtime = self.runtime_image,
target = self.target_triple,
strip = if self.strip_binary {
format!(
"RUN strip target/{}/release/realizar || true",
self.target_triple
)
} else {
String::new()
},
port = self.expose_port
)
}
#[must_use]
pub fn estimated_size_mb(&self) -> u32 {
let base_size = if self.runtime_image.contains("scratch") {
0
} else if self.runtime_image.contains("distroless/static") {
2
} else if self.runtime_image.contains("distroless/cc") {
20
} else {
50 };
let binary_size = if self.strip_binary { 5 } else { 10 };
base_size + binary_size
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WasmConfig {
pub target: WasmTarget,
pub out_dir: String,
pub enable_simd: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WasmTarget {
Web,
Bundler,
NodeJs,
}
impl WasmTarget {
#[must_use]
pub const fn flag(&self) -> &'static str {
match self {
Self::Web => "web",
Self::Bundler => "bundler",
Self::NodeJs => "nodejs",
}
}
}
impl Default for WasmConfig {
fn default() -> Self {
Self {
target: WasmTarget::Web,
out_dir: "pkg".to_string(),
enable_simd: false,
}
}
}
impl WasmConfig {
#[must_use]
pub fn build_command(&self) -> String {
let mut cmd = format!(
"wasm-pack build --target {} --release --out-dir {}",
self.target.flag(),
self.out_dir
);
if self.enable_simd {
cmd.push_str(" -- -C target-feature=+simd128");
}
cmd
}
#[must_use]
pub fn cloudflare_worker_template(&self) -> String {
format!(
r"// Auto-generated Cloudflare Worker
// Per docs/specifications/serve-deploy-apr.md Section 6.3
import init, {{ predict }} from './{}/realizar.js';
let initialized = false;
export default {{
async fetch(request, env) {{
if (!initialized) {{
await init();
initialized = true;
}}
if (request.method !== 'POST') {{
return new Response('Method not allowed', {{ status: 405 }});
}}
try {{
const body = await request.json();
const result = predict(body.features);
return new Response(JSON.stringify(result), {{
headers: {{ 'Content-Type': 'application/json' }}
}});
}} catch (e) {{
return new Response(JSON.stringify({{ error: e.message }}), {{
status: 500,
headers: {{ 'Content-Type': 'application/json' }}
}});
}}
}}
}};
",
self.out_dir
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildManifest {
pub version: String,
pub git_hash: Option<String>,
pub build_time: String,
pub targets: Vec<BuildTarget>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildTarget {
pub name: String,
pub triple: String,
pub deploy_target: DeployTarget,
pub features: Vec<String>,
}
impl BuildManifest {
#[must_use]
pub fn default_all_targets() -> Self {
Self {
version: env!("CARGO_PKG_VERSION").to_string(),
git_hash: None,
build_time: String::new(),
targets: vec![
BuildTarget {
name: "linux-x86_64".to_string(),
triple: "x86_64-unknown-linux-musl".to_string(),
deploy_target: DeployTarget::Docker,
features: vec!["server".to_string()],
},
BuildTarget {
name: "linux-arm64".to_string(),
triple: "aarch64-unknown-linux-musl".to_string(),
deploy_target: DeployTarget::Lambda,
features: vec!["lambda".to_string()],
},
BuildTarget {
name: "wasm".to_string(),
triple: "wasm32-unknown-unknown".to_string(),
deploy_target: DeployTarget::Wasm,
features: vec![],
},
],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deploy_target_detect_native() {
let target = DeployTarget::detect();
assert!(
matches!(
target,
DeployTarget::Native | DeployTarget::Lambda | DeployTarget::Docker
),
"Should detect a valid non-WASM target"
);
}
#[test]
fn test_deploy_target_names() {
assert_eq!(DeployTarget::Native.name(), "native");
assert_eq!(DeployTarget::Lambda.name(), "lambda");
assert_eq!(DeployTarget::Docker.name(), "docker");
assert_eq!(DeployTarget::Wasm.name(), "wasm");
}
#[test]
fn test_deploy_target_display() {
assert_eq!(format!("{}", DeployTarget::Native), "native");
assert_eq!(format!("{}", DeployTarget::Wasm), "wasm");
}
#[test]
fn test_native_capabilities() {
let caps = DeployTarget::Native.capabilities();
assert!(caps.supports_simd);
assert!(caps.supports_gpu);
assert!(caps.supports_threads);
assert!(caps.supports_filesystem);
assert!(caps.supports_async_io);
assert_eq!(caps.max_memory_mb, 0); }
#[test]
fn test_lambda_capabilities() {
let caps = DeployTarget::Lambda.capabilities();
assert!(caps.supports_simd);
assert!(!caps.supports_gpu); assert!(caps.supports_threads);
assert!(!caps.supports_filesystem); assert!(caps.supports_async_io);
assert_eq!(caps.max_memory_mb, 10240);
}
#[test]
fn test_wasm_capabilities() {
let caps = DeployTarget::Wasm.capabilities();
assert!(!caps.supports_simd); assert!(!caps.supports_gpu);
assert!(!caps.supports_threads); assert!(!caps.supports_filesystem); assert!(!caps.supports_async_io);
assert_eq!(caps.max_memory_mb, 128);
}
#[test]
fn test_deploy_target_supports_feature() {
assert!(DeployTarget::Native.supports(TargetFeature::Simd));
assert!(DeployTarget::Native.supports(TargetFeature::Gpu));
assert!(DeployTarget::Lambda.supports(TargetFeature::Simd));
assert!(!DeployTarget::Lambda.supports(TargetFeature::Gpu));
assert!(!DeployTarget::Wasm.supports(TargetFeature::Threads));
assert!(!DeployTarget::Wasm.supports(TargetFeature::Filesystem));
}
#[test]
fn test_capabilities_has_all() {
let native_caps = DeployTarget::Native.capabilities();
assert!(native_caps.has_all(&[TargetFeature::Simd, TargetFeature::Gpu]));
assert!(native_caps.has_all(&[TargetFeature::Threads, TargetFeature::Filesystem]));
let wasm_caps = DeployTarget::Wasm.capabilities();
assert!(!wasm_caps.has_all(&[TargetFeature::Simd]));
assert!(!wasm_caps.has_all(&[TargetFeature::Threads]));
assert!(wasm_caps.has_all(&[])); }
#[test]
fn test_docker_config_default() {
let config = DockerConfig::default();
assert_eq!(config.builder_image, "rust:1.83");
assert!(config.runtime_image.contains("distroless"));
assert_eq!(config.target_triple, "x86_64-unknown-linux-musl");
assert!(config.strip_binary);
assert_eq!(config.expose_port, 8080);
}
#[test]
fn test_docker_config_arm64() {
let config = DockerConfig::arm64();
assert!(config.target_triple.contains("aarch64"));
}
#[test]
fn test_docker_config_scratch() {
let config = DockerConfig::scratch();
assert_eq!(config.runtime_image, "scratch");
}
#[test]
fn test_docker_generate_dockerfile() {
let config = DockerConfig::default();
let dockerfile = config.generate_dockerfile();
assert!(dockerfile.contains("FROM rust:1.83 AS builder"));
assert!(dockerfile.contains("distroless"));
assert!(dockerfile.contains("x86_64-unknown-linux-musl"));
assert!(dockerfile.contains("EXPOSE 8080"));
assert!(dockerfile.contains("strip"));
}
#[test]
fn test_docker_estimated_size() {
let distroless = DockerConfig::default();
assert!(distroless.estimated_size_mb() < 20);
let scratch = DockerConfig::scratch();
assert!(scratch.estimated_size_mb() < 10); }
#[test]
fn test_wasm_config_default() {
let config = WasmConfig::default();
assert_eq!(config.target, WasmTarget::Web);
assert_eq!(config.out_dir, "pkg");
assert!(!config.enable_simd);
}
#[test]
fn test_wasm_target_flags() {
assert_eq!(WasmTarget::Web.flag(), "web");
assert_eq!(WasmTarget::Bundler.flag(), "bundler");
assert_eq!(WasmTarget::NodeJs.flag(), "nodejs");
}
#[test]
fn test_wasm_build_command() {
let config = WasmConfig::default();
let cmd = config.build_command();
assert!(cmd.contains("wasm-pack build"));
assert!(cmd.contains("--target web"));
assert!(cmd.contains("--release"));
assert!(cmd.contains("--out-dir pkg"));
}
#[test]
fn test_wasm_build_command_with_simd() {
let config = WasmConfig {
enable_simd: true,
..WasmConfig::default()
};
let cmd = config.build_command();
assert!(cmd.contains("simd128"));
}
#[test]
fn test_wasm_cloudflare_worker_template() {
let config = WasmConfig::default();
let template = config.cloudflare_worker_template();
assert!(template.contains("import init"));
assert!(template.contains("predict"));
assert!(template.contains("async fetch"));
assert!(template.contains("application/json"));
}
#[test]
fn test_build_manifest_default() {
let manifest = BuildManifest::default_all_targets();
assert!(!manifest.version.is_empty());
assert_eq!(manifest.targets.len(), 3);
let names: Vec<_> = manifest.targets.iter().map(|t| t.name.as_str()).collect();
assert!(names.contains(&"linux-x86_64"));
assert!(names.contains(&"linux-arm64"));
assert!(names.contains(&"wasm"));
}
#[test]
fn test_build_manifest_targets() {
let manifest = BuildManifest::default_all_targets();
let x86_target = manifest
.targets
.iter()
.find(|t| t.name == "linux-x86_64")
.unwrap();
assert_eq!(x86_target.deploy_target, DeployTarget::Docker);
let arm_target = manifest
.targets
.iter()
.find(|t| t.name == "linux-arm64")
.unwrap();
assert_eq!(arm_target.deploy_target, DeployTarget::Lambda);
let wasm_target = manifest.targets.iter().find(|t| t.name == "wasm").unwrap();
assert_eq!(wasm_target.deploy_target, DeployTarget::Wasm);
}
#[test]
fn test_deploy_target_serialization() {
let target = DeployTarget::Lambda;
let json = serde_json::to_string(&target).unwrap();
assert!(json.contains("Lambda"));
let deserialized: DeployTarget = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, DeployTarget::Lambda);
}
#[test]
fn test_docker_config_serialization() {
let config = DockerConfig::default();
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("rust:1.83"));
assert!(json.contains("distroless"));
let deserialized: DockerConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.expose_port, 8080);
}
#[test]
fn test_wasm_config_serialization() {
let config = WasmConfig::default();
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("Web"));
assert!(json.contains("pkg"));
let deserialized: WasmConfig = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.target, WasmTarget::Web);
}
}