1use serde::Deserialize;
4use video_analysis_core::runtime::{
5 OperationId, PackageSurface, RuntimeCapabilities, SurfaceOperation, SurfaceRequest,
6 SurfaceResponse,
7};
8
9use crate::{F32Tensor, TensorShape};
10
11pub fn package_surface() -> PackageSurface {
13 PackageSurface {
14 library: env!("CARGO_PKG_NAME").to_string(),
15 version: env!("CARGO_PKG_VERSION").to_string(),
16 capabilities: RuntimeCapabilities::pure_rust(),
17 operations: vec![
18 operation(
19 "describe",
20 "Describe package",
21 "Small finite f32 tensor contracts and metadata for video-analysis.",
22 serde_json::json!({"includeOperations": true}),
23 ),
24 operation(
25 "tensor.validate",
26 "Validate tensor",
27 "Validates a finite f32 tensor shape and values payload.",
28 serde_json::json!({"shape": [2, 2], "values": [0.0, 1.0, 2.0, 3.0]}),
29 ),
30 operation(
31 "tensor.summary",
32 "Tensor summary",
33 "Returns shape metadata and scalar summary statistics for a finite f32 tensor.",
34 serde_json::json!({"shape": [2, 2], "values": [0.0, 1.0, 2.0, 3.0], "previewValues": 2}),
35 ),
36 operation(
37 "tensor.reshapePlan",
38 "Tensor reshape plan",
39 "Checks whether a tensor shape can be reshaped without changing element count.",
40 serde_json::json!({"shape": [2, 2], "targetShape": [4, 1]}),
41 ),
42 ],
43 }
44}
45
46fn operation(
47 id: &str,
48 name: &str,
49 description: &str,
50 example_request: serde_json::Value,
51) -> SurfaceOperation {
52 SurfaceOperation {
53 id: OperationId::new(id),
54 name: name.to_string(),
55 description: Some(description.to_string()),
56 input_schema: serde_json::json!({"type": "object", "additionalProperties": true}),
57 output_schema: serde_json::json!({"type": "object"}),
58 example_request,
59 wasm_supported: true,
60 server_supported: true,
61 }
62}
63
64pub fn run_surface_operation(request: SurfaceRequest) -> Result<SurfaceResponse, String> {
66 let operation = request.operation.clone();
67 let value = match request.operation.as_str() {
68 "describe" => describe_value(request.input),
69 "tensor.validate" => validate_value(parse_input(request.input)?)?,
70 "tensor.summary" => summary_value(parse_input(request.input)?)?,
71 "tensor.reshapePlan" => reshape_plan_value(parse_input(request.input)?)?,
72 operation => {
73 return Err(format!(
74 "unsupported operation `{operation}` for {}",
75 env!("CARGO_PKG_NAME")
76 ));
77 }
78 };
79 Ok(response(operation, value))
80}
81
82fn response(operation: OperationId, value: serde_json::Value) -> SurfaceResponse {
83 SurfaceResponse {
84 operation,
85 value,
86 diagnostics: Vec::new(),
87 artifacts: Vec::new(),
88 }
89}
90
91fn describe_value(input: serde_json::Value) -> serde_json::Value {
92 let surface = package_surface();
93 serde_json::json!({
94 "library": surface.library,
95 "version": surface.version,
96 "operationCount": surface.operations.len(),
97 "operations": surface
98 .operations
99 .iter()
100 .map(|operation| operation.id.as_str())
101 .collect::<Vec<_>>(),
102 "input": input
103 })
104}
105
106#[derive(Debug, Deserialize)]
107#[serde(rename_all = "camelCase")]
108struct TensorRequest {
109 shape: Vec<usize>,
110 values: Vec<f32>,
111 #[serde(default = "default_preview_values")]
112 preview_values: usize,
113}
114
115#[derive(Debug, Deserialize)]
116#[serde(rename_all = "camelCase")]
117struct ReshapePlanRequest {
118 shape: Vec<usize>,
119 target_shape: Vec<usize>,
120 #[serde(default)]
121 values: Option<Vec<f32>>,
122}
123
124fn validate_value(request: TensorRequest) -> Result<serde_json::Value, String> {
125 let tensor = tensor_from_request(&request)?;
126 Ok(serde_json::json!({
127 "valid": true,
128 "dtype": "f32",
129 "shape": tensor.shape().dimensions(),
130 "rank": tensor.shape().rank(),
131 "elementCount": tensor.shape().element_count().map_err(|error| error.to_string())?,
132 "valueCount": tensor.values().len(),
133 "metadataKeys": tensor.metadata().keys().collect::<Vec<_>>()
134 }))
135}
136
137fn summary_value(request: TensorRequest) -> Result<serde_json::Value, String> {
138 let tensor = tensor_from_request(&request)?;
139 let values = tensor.values();
140 let min = values
141 .iter()
142 .copied()
143 .reduce(f32::min)
144 .ok_or_else(|| "tensor values must not be empty".to_string())?;
145 let max = values
146 .iter()
147 .copied()
148 .reduce(f32::max)
149 .ok_or_else(|| "tensor values must not be empty".to_string())?;
150 let sum = values.iter().map(|value| f64::from(*value)).sum::<f64>();
151 Ok(serde_json::json!({
152 "dtype": "f32",
153 "shape": tensor.shape().dimensions(),
154 "rank": tensor.shape().rank(),
155 "elementCount": tensor.shape().element_count().map_err(|error| error.to_string())?,
156 "min": min,
157 "max": max,
158 "sum": sum,
159 "mean": sum / values.len() as f64,
160 "preview": values.iter().take(request.preview_values).copied().collect::<Vec<_>>()
161 }))
162}
163
164fn reshape_plan_value(request: ReshapePlanRequest) -> Result<serde_json::Value, String> {
165 let shape = TensorShape::new(request.shape).map_err(|error| error.to_string())?;
166 if let Some(values) = request.values {
167 F32Tensor::new(shape.clone(), values).map_err(|error| error.to_string())?;
168 }
169 let target = shape
170 .reshape(request.target_shape)
171 .map_err(|error| error.to_string())?;
172 Ok(serde_json::json!({
173 "compatible": true,
174 "fromShape": shape.dimensions(),
175 "toShape": target.dimensions(),
176 "rankChange": target.rank() as isize - shape.rank() as isize,
177 "elementCount": shape.element_count().map_err(|error| error.to_string())?,
178 "dataMovement": "metadata-only"
179 }))
180}
181
182fn tensor_from_request(request: &TensorRequest) -> Result<F32Tensor, String> {
183 F32Tensor::from_dims(request.shape.clone(), request.values.clone())
184 .map_err(|error| error.to_string())
185}
186
187fn parse_input<T: for<'de> Deserialize<'de>>(input: serde_json::Value) -> Result<T, String> {
188 serde_json::from_value(input).map_err(|error| format!("invalid request: {error}"))
189}
190
191fn default_preview_values() -> usize {
192 8
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn package_surface_lists_tensor_operations() {
201 let ids = package_surface()
202 .operations
203 .into_iter()
204 .map(|operation| operation.id.0)
205 .collect::<Vec<_>>();
206
207 assert!(ids.contains(&"tensor.validate".to_string()));
208 assert!(ids.contains(&"tensor.summary".to_string()));
209 assert!(ids.contains(&"tensor.reshapePlan".to_string()));
210 }
211
212 #[test]
213 fn describe_operation_returns_surface_summary() {
214 let response = run_surface_operation(SurfaceRequest {
215 operation: OperationId::new("describe"),
216 input: serde_json::json!({"includeOperations": true}),
217 })
218 .expect("describe operation");
219
220 assert_eq!(response.operation.as_str(), "describe");
221 assert_eq!(response.value["library"], env!("CARGO_PKG_NAME"));
222 }
223
224 #[test]
225 fn validate_operation_accepts_matching_shape_and_values() {
226 let response = run_surface_operation(SurfaceRequest {
227 operation: OperationId::new("tensor.validate"),
228 input: serde_json::json!({"shape": [2, 2], "values": [0.0, 1.0, 2.0, 3.0]}),
229 })
230 .expect("validate operation");
231
232 assert_eq!(response.value["valid"], true);
233 assert_eq!(response.value["elementCount"], 4);
234 }
235
236 #[test]
237 fn summary_operation_returns_scalar_stats() {
238 let response = run_surface_operation(SurfaceRequest {
239 operation: OperationId::new("tensor.summary"),
240 input: serde_json::json!({"shape": [2], "values": [1.0, 3.0], "previewValues": 1}),
241 })
242 .expect("summary operation");
243
244 assert_eq!(response.value["mean"], 2.0);
245 assert_eq!(response.value["preview"].as_array().unwrap().len(), 1);
246 }
247
248 #[test]
249 fn reshape_plan_operation_checks_element_count() {
250 let response = run_surface_operation(SurfaceRequest {
251 operation: OperationId::new("tensor.reshapePlan"),
252 input: serde_json::json!({"shape": [2, 2], "targetShape": [4, 1]}),
253 })
254 .expect("reshape plan operation");
255
256 assert_eq!(response.value["compatible"], true);
257 assert_eq!(response.value["elementCount"], 4);
258 }
259
260 #[test]
261 fn invalid_request_parsing_is_reported() {
262 let error = run_surface_operation(SurfaceRequest {
263 operation: OperationId::new("tensor.validate"),
264 input: serde_json::json!({"shape": "not an array", "values": []}),
265 })
266 .expect_err("invalid request");
267
268 assert!(error.contains("invalid request"));
269 }
270
271 #[test]
272 fn unsupported_operation_is_reported() {
273 let error = run_surface_operation(SurfaceRequest {
274 operation: OperationId::new("tensor.missing"),
275 input: serde_json::json!({}),
276 })
277 .expect_err("unsupported operation");
278
279 assert!(error.contains("unsupported operation `tensor.missing`"));
280 }
281}