use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct DiagnosticCode(pub String);
impl DiagnosticCode {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for DiagnosticCode {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl From<String> for DiagnosticCode {
fn from(value: String) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Diagnostic {
pub severity: DiagnosticSeverity,
pub code: DiagnosticCode,
pub message: String,
pub source: Option<String>,
pub help: Option<String>,
}
impl Diagnostic {
pub fn new(
severity: DiagnosticSeverity,
code: impl Into<DiagnosticCode>,
message: impl Into<String>,
) -> Self {
Self {
severity,
code: code.into(),
message: message.into(),
source: None,
help: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum DiagnosticSeverity {
Info,
Warning,
Error,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeCapabilities {
pub native: bool,
pub server: bool,
pub wasm: bool,
pub mobile: MobileCapability,
pub requirements: Vec<RuntimeRequirement>,
pub max_recommended_input_bytes: Option<u64>,
}
impl RuntimeCapabilities {
pub fn pure_rust() -> Self {
Self {
native: true,
server: true,
wasm: true,
mobile: MobileCapability::Wasm,
requirements: Vec::new(),
max_recommended_input_bytes: None,
}
}
pub fn with_max_recommended_input_bytes(mut self, bytes: u64) -> Self {
self.max_recommended_input_bytes = Some(bytes);
self
}
pub fn with_requirement(
mut self,
name: impl Into<String>,
description: impl Into<String>,
required: bool,
) -> Self {
self.requirements.push(RuntimeRequirement {
name: name.into(),
description: Some(description.into()),
required,
});
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum MobileCapability {
Native,
Wasm,
ApiOnly,
Unsupported,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RuntimeRequirement {
pub name: String,
pub description: Option<String>,
pub required: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct OperationId(pub String);
impl OperationId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for OperationId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl From<String> for OperationId {
fn from(value: String) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OperationMetadata {
pub id: OperationId,
pub name: String,
pub description: Option<String>,
pub version: String,
pub capabilities: RuntimeCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PackageSurface {
pub library: String,
pub version: String,
pub operations: Vec<SurfaceOperation>,
pub capabilities: RuntimeCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SurfaceOperation {
pub id: OperationId,
pub name: String,
pub description: Option<String>,
pub input_schema: serde_json::Value,
pub output_schema: serde_json::Value,
pub example_request: serde_json::Value,
pub wasm_supported: bool,
pub server_supported: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SurfaceRequest {
pub operation: OperationId,
pub input: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SurfaceResponse {
pub operation: OperationId,
pub value: serde_json::Value,
pub diagnostics: Vec<Diagnostic>,
pub artifacts: Vec<serde_json::Value>,
}
pub fn surface_operation(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
example_request: serde_json::Value,
) -> SurfaceOperation {
SurfaceOperation {
id: OperationId::new(id),
name: name.into(),
description: Some(description.into()),
input_schema: serde_json::json!({"type": "object", "additionalProperties": true}),
output_schema: serde_json::json!({"type": "object"}),
example_request,
wasm_supported: true,
server_supported: true,
}
}
pub fn describe_surface_response(
surface: &PackageSurface,
request: SurfaceRequest,
) -> SurfaceResponse {
let result = serde_json::json!({
"library": &surface.library,
"version": &surface.version,
"operationCount": surface.operations.len(),
"operations": surface
.operations
.iter()
.map(|operation| operation.id.as_str())
.collect::<Vec<_>>(),
"input": request.input
});
structured_surface_response(
request.operation,
"Package surface metadata",
format!(
"{} exposes {} package-surface operations.",
surface.library,
surface.operations.len()
),
serde_json::json!({
"operationCount": surface.operations.len(),
"runtime": {
"wasm": surface.capabilities.wasm,
"server": surface.capabilities.server,
"native": surface.capabilities.native
}
}),
result,
)
}
pub fn surface_response(operation: OperationId, value: serde_json::Value) -> SurfaceResponse {
let title = operation.as_str().to_string();
let message = format!("Ran package-surface operation `{}`.", operation.as_str());
let value = ensure_structured_surface_value(&operation, title, message, value);
SurfaceResponse {
operation,
value,
diagnostics: Vec::new(),
artifacts: Vec::new(),
}
}
pub fn structured_surface_value(
operation: &OperationId,
title: impl Into<String>,
message: impl Into<String>,
summary: serde_json::Value,
result: serde_json::Value,
) -> serde_json::Value {
let mut object = match &result {
serde_json::Value::Object(map) => map.clone(),
_ => serde_json::Map::new(),
};
object.insert("title".to_string(), serde_json::Value::String(title.into()));
object.insert(
"operation".to_string(),
serde_json::Value::String(operation.as_str().to_string()),
);
object.insert(
"message".to_string(),
serde_json::Value::String(message.into()),
);
object.insert("summary".to_string(), summary);
object.insert("result".to_string(), result);
serde_json::Value::Object(object)
}
pub fn ensure_structured_surface_value(
operation: &OperationId,
title: impl Into<String>,
message: impl Into<String>,
value: serde_json::Value,
) -> serde_json::Value {
let result = value.clone();
let mut object = match value {
serde_json::Value::Object(map) => map,
_ => serde_json::Map::new(),
};
object
.entry("operation".to_string())
.or_insert_with(|| serde_json::Value::String(operation.as_str().to_string()));
object
.entry("title".to_string())
.or_insert_with(|| serde_json::Value::String(title.into()));
object
.entry("message".to_string())
.or_insert_with(|| serde_json::Value::String(message.into()));
object
.entry("summary".to_string())
.or_insert_with(|| operation_summary(&result));
object.entry("result".to_string()).or_insert(result);
serde_json::Value::Object(object)
}
pub fn structured_surface_response(
operation: OperationId,
title: impl Into<String>,
message: impl Into<String>,
summary: serde_json::Value,
result: serde_json::Value,
) -> SurfaceResponse {
let value = structured_surface_value(&operation, title, message, summary, result);
surface_response(operation, value)
}
pub fn structured_operation_response(
surface: &PackageSurface,
operation: OperationId,
result: serde_json::Value,
) -> SurfaceResponse {
let metadata = surface
.operations
.iter()
.find(|candidate| candidate.id.as_str() == operation.as_str());
let title = metadata
.map(|operation| operation.name.clone())
.unwrap_or_else(|| operation.as_str().to_string());
let message = metadata
.and_then(|operation| operation.description.clone())
.unwrap_or_else(|| format!("Ran package-surface operation `{}`.", operation.as_str()));
let summary = operation_summary(&result);
structured_surface_response(operation, title, message, summary, result)
}
fn operation_summary(result: &serde_json::Value) -> serde_json::Value {
match result {
serde_json::Value::Object(object) => {
let mut summary = serde_json::Map::new();
summary.insert("status".to_string(), serde_json::json!("ok"));
for key in [
"count",
"width",
"height",
"format",
"pixelFormat",
"dimensions",
"operationCount",
] {
if let Some(value) = object.get(key) {
summary.insert(key.to_string(), value.clone());
}
}
if let Some((key, value)) = object
.iter()
.find(|(_, value)| matches!(value, serde_json::Value::Array(_)))
{
summary.insert(
format!("{key}Count"),
serde_json::json!(value.as_array().map(Vec::len).unwrap_or(0)),
);
}
serde_json::Value::Object(summary)
}
serde_json::Value::Array(values) => {
serde_json::json!({"status": "ok", "count": values.len()})
}
_ => serde_json::json!({"status": "ok"}),
}
}
pub mod cli {
use std::fs;
use std::io::{self, Read};
use super::{
ensure_structured_surface_value, OperationId, PackageSurface, SurfaceRequest,
SurfaceResponse,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CliAdapterMetadata {
pub library_crate: &'static str,
pub surface_kind: &'static str,
pub library_import: &'static str,
pub server_package: &'static str,
pub app_package: &'static str,
pub wasm_package: &'static str,
}
pub fn package_metadata_json(metadata: CliAdapterMetadata, surface: PackageSurface) -> String {
serde_json::json!({
"package": format!("{}-cli", metadata.library_crate),
"surface": metadata.surface_kind,
"library": metadata.library_crate,
"libraryImport": metadata.library_import,
"serverPackage": metadata.server_package,
"appPackage": metadata.app_package,
"wasmPackage": metadata.wasm_package,
"operations": surface.operations
})
.to_string()
}
pub fn command_schema_json() -> String {
serde_json::json!({
"commands": [
{"name": "info", "description": "Print package and adapter metadata."},
{"name": "schema", "description": "Print the CLI command schema."},
{"name": "operations", "description": "Print library operations."},
{"name": "run", "description": "Run one library-owned operation."}
]
})
.to_string()
}
pub fn read_json_input(
json: Option<String>,
file: Option<String>,
) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
let input = if let Some(json) = json {
json
} else if let Some(file) = file {
fs::read_to_string(file)?
} else {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
if buffer.trim().is_empty() {
"{}".to_string()
} else {
buffer
}
};
Ok(serde_json::from_str(&input)?)
}
pub fn run_wrapped_operation(
operation: &str,
input: serde_json::Value,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> Result<SurfaceResponse, String> {
let mut response = runner(SurfaceRequest {
operation: OperationId::new(operation),
input,
})?;
let value = std::mem::take(&mut response.value);
response.value = ensure_structured_surface_value(
&response.operation,
operation.to_string(),
format!("Ran package-surface operation `{}`.", operation),
value,
);
Ok(response)
}
}
pub mod server {
use std::io::{self, BufRead, BufReader, Read, Write};
use std::net::{TcpListener, TcpStream};
use super::{
Diagnostic, DiagnosticSeverity, OperationId, PackageSurface, SurfaceRequest,
SurfaceResponse,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ServerAdapterMetadata {
pub library_crate: &'static str,
pub surface_kind: &'static str,
pub library_import: &'static str,
pub cli_package: &'static str,
pub app_package: &'static str,
pub wasm_package: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HttpResponse {
pub status_code: u16,
pub reason: &'static str,
pub content_type: &'static str,
pub body: String,
}
pub fn serve(
addr: &str,
metadata: ServerAdapterMetadata,
surface_provider: fn() -> PackageSurface,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> io::Result<()> {
let listener = TcpListener::bind(addr)?;
for stream in listener.incoming() {
handle_stream(stream?, metadata, surface_provider, runner)?;
}
Ok(())
}
pub fn response_for(
method: &str,
path: &str,
body: &str,
metadata: ServerAdapterMetadata,
surface_provider: fn() -> PackageSurface,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> HttpResponse {
match (method, path) {
("OPTIONS", _) => HttpResponse {
status_code: 204,
reason: "No Content",
content_type: "application/json",
body: String::new(),
},
("GET", "/health") => json_response(
200,
"OK",
serde_json::json!({
"ok": true,
"package": format!("{}-server", metadata.library_crate),
"library": metadata.library_crate
}),
),
("GET", "/api/package") => json_response(
200,
"OK",
package_metadata_value(metadata, surface_provider()),
),
("GET", "/api/schema") => {
json_response(200, "OK", schema_value(metadata, surface_provider()))
}
("GET", "/api/operations") => {
json_response(200, "OK", serde_json::json!(surface_provider().operations))
}
("POST", "/api/run") => run_response(body, metadata, runner),
("POST", path) if path.starts_with("/api/") => {
let operation = path.trim_start_matches("/api/");
run_request(
SurfaceRequest {
operation: OperationId::new(operation),
input: parse_json_or_empty(body),
},
metadata,
runner,
)
}
_ => json_response(
404,
"Not Found",
serde_json::json!({
"error": "not found",
"path": path
}),
),
}
}
pub fn package_metadata_json(
metadata: ServerAdapterMetadata,
surface: PackageSurface,
) -> String {
package_metadata_value(metadata, surface).to_string()
}
fn package_metadata_value(
metadata: ServerAdapterMetadata,
surface: PackageSurface,
) -> serde_json::Value {
serde_json::json!({
"package": format!("{}-server", metadata.library_crate),
"surface": metadata.surface_kind,
"library": metadata.library_crate,
"libraryImport": metadata.library_import,
"cliPackage": metadata.cli_package,
"appPackage": metadata.app_package,
"wasmPackage": metadata.wasm_package,
"endpoints": [
"GET /health",
"GET /api/package",
"GET /api/schema",
"GET /api/operations",
"POST /api/run",
"POST /api/<operation-id>"
],
"operations": surface.operations
})
}
fn schema_value(metadata: ServerAdapterMetadata, surface: PackageSurface) -> serde_json::Value {
let operations = surface
.operations
.into_iter()
.map(|operation| {
let path = format!("/api/{}", operation.id.as_str());
(
path,
serde_json::json!({
"post": {
"summary": operation.name,
"description": operation.description,
"requestBody": operation.input_schema,
"responses": {"200": operation.output_schema}
}
}),
)
})
.collect::<serde_json::Map<_, _>>();
serde_json::json!({
"openapi": "3.1.0",
"info": {
"title": format!("{} API", metadata.library_crate),
"version": env!("CARGO_PKG_VERSION")
},
"paths": operations
})
}
fn run_response(
body: &str,
metadata: ServerAdapterMetadata,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> HttpResponse {
let payload = match serde_json::from_str::<serde_json::Value>(body) {
Ok(value) => value,
Err(error) => {
return diagnostic_response(
400,
"Bad Request",
"invalid_request",
&format!("invalid JSON: {error}"),
metadata,
);
}
};
let operation = payload
.get("operation")
.and_then(serde_json::Value::as_str)
.unwrap_or("describe")
.to_string();
let input = payload
.get("input")
.cloned()
.unwrap_or_else(|| payload.clone());
run_request(
SurfaceRequest {
operation: OperationId::new(operation),
input,
},
metadata,
runner,
)
}
fn run_request(
request: SurfaceRequest,
metadata: ServerAdapterMetadata,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> HttpResponse {
match runner(request) {
Ok(response) => json_response(200, "OK", serde_json::json!(response)),
Err(error) => {
diagnostic_response(400, "Bad Request", "operation_failed", &error, metadata)
}
}
}
fn diagnostic_response(
status_code: u16,
reason: &'static str,
code: &str,
message: &str,
metadata: ServerAdapterMetadata,
) -> HttpResponse {
json_response(
status_code,
reason,
serde_json::json!({
"diagnostics": [Diagnostic {
severity: DiagnosticSeverity::Error,
code: code.into(),
message: message.to_string(),
source: Some(format!("{}-server", metadata.library_crate)),
help: None,
}]
}),
)
}
fn parse_json_or_empty(body: &str) -> serde_json::Value {
if body.trim().is_empty() {
serde_json::json!({})
} else {
serde_json::from_str(body).unwrap_or_else(|_| serde_json::json!({"raw": body}))
}
}
fn handle_stream(
mut stream: TcpStream,
metadata: ServerAdapterMetadata,
surface_provider: fn() -> PackageSurface,
runner: fn(SurfaceRequest) -> Result<SurfaceResponse, String>,
) -> io::Result<()> {
let mut reader = BufReader::new(stream.try_clone()?);
let mut request_line = String::new();
reader.read_line(&mut request_line)?;
let mut content_length = 0usize;
loop {
let mut header = String::new();
reader.read_line(&mut header)?;
let trimmed = header.trim_end();
if trimmed.is_empty() {
break;
}
if let Some((name, value)) = trimmed.split_once(':') {
if name.eq_ignore_ascii_case("content-length") {
content_length = value.trim().parse().unwrap_or(0);
}
}
}
let mut body = vec![0; content_length];
if content_length > 0 {
reader.read_exact(&mut body)?;
}
let body = String::from_utf8_lossy(&body);
let mut parts = request_line.split_whitespace();
let method = parts.next().unwrap_or("GET");
let path = parts.next().unwrap_or("/");
let response = response_for(method, path, &body, metadata, surface_provider, runner);
write_response(&mut stream, response)
}
fn json_response(
status_code: u16,
reason: &'static str,
value: serde_json::Value,
) -> HttpResponse {
HttpResponse {
status_code,
reason,
content_type: "application/json",
body: value.to_string(),
}
}
fn write_response(stream: &mut TcpStream, response: HttpResponse) -> io::Result<()> {
write!(
stream,
"HTTP/1.1 {} {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Headers: content-type\r\nAccess-Control-Allow-Methods: GET, POST, OPTIONS\r\nConnection: close\r\n\r\n{}",
response.status_code,
response.reason,
response.content_type,
response.body.len(),
response.body
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct JobId(pub String);
impl JobId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for JobId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl From<String> for JobId {
fn from(value: String) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(transparent)]
pub struct ArtifactId(pub String);
impl ArtifactId {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for ArtifactId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
impl From<String> for ArtifactId {
fn from(value: String) -> Self {
Self(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn diagnostic_uses_camel_case_json() {
let diagnostic = Diagnostic::new(DiagnosticSeverity::Warning, "demo.warning", "check");
let json = serde_json::to_string(&diagnostic).expect("serialize diagnostic");
assert!(json.contains("\"severity\":\"warning\""));
assert!(json.contains("\"code\":\"demo.warning\""));
}
#[test]
fn pure_rust_capabilities_allow_wasm_and_server() {
let capabilities = RuntimeCapabilities::pure_rust();
assert!(capabilities.native);
assert!(capabilities.server);
assert!(capabilities.wasm);
assert_eq!(capabilities.mobile, MobileCapability::Wasm);
}
#[test]
fn capability_builders_preserve_pure_rust_defaults() {
let capabilities = RuntimeCapabilities::pure_rust()
.with_max_recommended_input_bytes(1024)
.with_requirement("fixture", "test fixture input", false);
assert!(capabilities.native);
assert!(capabilities.server);
assert!(capabilities.wasm);
assert_eq!(capabilities.max_recommended_input_bytes, Some(1024));
assert_eq!(capabilities.requirements[0].name, "fixture");
assert!(!capabilities.requirements[0].required);
}
#[test]
fn package_surface_uses_camel_case_json() {
let surface = PackageSurface {
library: "demo-core".to_string(),
version: "0.1.0".to_string(),
capabilities: RuntimeCapabilities::pure_rust(),
operations: vec![SurfaceOperation {
id: OperationId::new("describe"),
name: "Describe".to_string(),
description: Some("Describe package surface".to_string()),
input_schema: serde_json::json!({"type": "object"}),
output_schema: serde_json::json!({"type": "object"}),
example_request: serde_json::json!({}),
wasm_supported: true,
server_supported: true,
}],
};
let json = serde_json::to_string(&surface).expect("serialize surface");
assert!(json.contains("\"inputSchema\""));
assert!(json.contains("\"exampleRequest\""));
assert!(json.contains("\"wasmSupported\":true"));
}
#[test]
fn surface_helpers_preserve_standard_response_shape() {
let surface = PackageSurface {
library: "demo".to_string(),
version: "0.1.0".to_string(),
capabilities: RuntimeCapabilities::pure_rust(),
operations: vec![surface_operation(
"describe",
"Describe",
"Describe demo package",
serde_json::json!({"includeOperations": true}),
)],
};
let response = describe_surface_response(
&surface,
SurfaceRequest {
operation: OperationId::new("describe"),
input: serde_json::json!({"includeOperations": true}),
},
);
assert_eq!(response.operation.as_str(), "describe");
assert_eq!(response.value["library"], "demo");
assert_eq!(response.value["operationCount"], 1);
assert_eq!(response.diagnostics, Vec::new());
assert_eq!(response.artifacts, Vec::<serde_json::Value>::new());
}
}