Skip to main content

tensor_data/
surface.rs

1//! Library-owned runtime surface for `tensor-data`.
2
3use serde::Deserialize;
4use video_analysis_core::runtime::{
5    OperationId, PackageSurface, RuntimeCapabilities, SurfaceOperation, SurfaceRequest,
6    SurfaceResponse,
7};
8
9use crate::{F32Tensor, TensorShape};
10
11/// Describes the tensor operations exposed by transport wrappers.
12pub 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
64/// Runs one library-owned operation.
65pub 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}