rover_nexus_core 0.2.0

Wire-format message types (Cap'n Proto + JSON) for communication between robotic vehicles and a robot orchestration server: Rover Nexus
Documentation
// Copyright 2026 Rottinghaus Dynamics
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Cap'n Proto message serialization for spatial directive query messages.

use crate::capnp_messages::{read_spatial_directive, write_spatial_directive};
use crate::spatial_directives_query::{GetSpatialDirectivesRequest, GetSpatialDirectivesResponse};

use ::capnp::message::Builder;
use ::capnp::serialize;

use std::io::Cursor;

/// Serialize a GetSpatialDirectivesRequest to Cap'n Proto bytes
pub fn serialize_get_spatial_directives_request(
    request: &GetSpatialDirectivesRequest,
) -> Result<Vec<u8>, ::capnp::Error> {
    let mut message = Builder::new_default();
    let mut builder = message
        .init_root::<crate::spatial_directives_query_capnp::get_spatial_directives_request::Builder>();

    let mut ids_builder = builder.reborrow().init_ids(request.ids.len() as u32);
    for (i, id) in request.ids.iter().enumerate() {
        ids_builder.set(i as u32, id);
    }

    let mut buf = Vec::new();
    serialize::write_message(&mut buf, &message)?;
    Ok(buf)
}

/// Deserialize a GetSpatialDirectivesRequest from Cap'n Proto bytes
pub fn deserialize_get_spatial_directives_request(
    data: &[u8],
) -> Result<GetSpatialDirectivesRequest, ::capnp::Error> {
    let cursor = Cursor::new(data);
    let message_reader = serialize::read_message(cursor, ::capnp::message::ReaderOptions::new())?;
    let reader = message_reader
        .get_root::<crate::spatial_directives_query_capnp::get_spatial_directives_request::Reader>(
    )?;

    let ids_reader = reader.get_ids()?;
    let mut ids = Vec::with_capacity(ids_reader.len() as usize);
    for i in 0..ids_reader.len() {
        ids.push(ids_reader.get(i)?.to_str()?.to_string());
    }

    Ok(GetSpatialDirectivesRequest { ids })
}

/// Serialize a GetSpatialDirectivesResponse to Cap'n Proto bytes
pub fn serialize_get_spatial_directives_response(
    response: &GetSpatialDirectivesResponse,
) -> Result<Vec<u8>, ::capnp::Error> {
    let mut message = Builder::new_default();
    let mut builder = message
        .init_root::<crate::spatial_directives_query_capnp::get_spatial_directives_response::Builder>();

    let mut items_builder = builder.reborrow().init_items(response.items.len() as u32);
    for (i, directive) in response.items.iter().enumerate() {
        let mut item_builder = items_builder.reborrow().get(i as u32);
        write_spatial_directive(&mut item_builder, directive);
    }

    let mut buf = Vec::new();
    serialize::write_message(&mut buf, &message)?;
    Ok(buf)
}

/// Deserialize a GetSpatialDirectivesResponse from Cap'n Proto bytes
pub fn deserialize_get_spatial_directives_response(
    data: &[u8],
) -> Result<GetSpatialDirectivesResponse, ::capnp::Error> {
    let cursor = Cursor::new(data);
    let message_reader = serialize::read_message(cursor, ::capnp::message::ReaderOptions::new())?;
    let reader = message_reader
        .get_root::<crate::spatial_directives_query_capnp::get_spatial_directives_response::Reader>(
    )?;

    let items_reader = reader.get_items()?;
    let mut items = Vec::with_capacity(items_reader.len() as usize);
    for i in 0..items_reader.len() {
        items.push(read_spatial_directive(items_reader.get(i))?);
    }

    Ok(GetSpatialDirectivesResponse { items })
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core_model::{AssetIdentity, Feature, LayerRole, ScopeType, SpatialDirective};
    use crate::spatial_types::{GeoPoint, SpatialData, Wgs84Shape};

    fn sample_directive(id: &str) -> SpatialDirective {
        SpatialDirective {
            directive_id: id.to_string(),
            name: "Test Directive".to_string(),
            start_time_ms: 1000,
            expire_time_ms: Some(2000),
            zone_parameters: vec![],
            capabilities: vec!["cap.a".to_string()],
            spatial: Feature {
                identity: AssetIdentity {
                    id: "shape-1".to_string(),
                    name: "Shape 1".to_string(),
                    rev: 1,
                    hash: String::new(),
                },
                layer_role: LayerRole::Keepout,
                scope: ScopeType::Fleet,
                data: SpatialData::Wgs84Shape(Wgs84Shape::Point(GeoPoint {
                    lon: -122.0,
                    lat: 37.0,
                })),
                expires_at_ms: 0,
                min_z_m: 0.0,
                max_z_m: 10.0,
                level_id: 0,
            },
        }
    }

    #[test]
    fn test_get_spatial_directives_request_roundtrip() {
        let original = GetSpatialDirectivesRequest {
            ids: vec!["dir-1".to_string(), "dir-2".to_string()],
        };

        let serialized =
            serialize_get_spatial_directives_request(&original).expect("Failed to serialize");
        let deserialized =
            deserialize_get_spatial_directives_request(&serialized).expect("Failed to deserialize");

        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_get_spatial_directives_response_roundtrip() {
        let original = GetSpatialDirectivesResponse {
            items: vec![sample_directive("dir-1"), sample_directive("dir-2")],
        };

        let serialized =
            serialize_get_spatial_directives_response(&original).expect("Failed to serialize");
        let deserialized = deserialize_get_spatial_directives_response(&serialized)
            .expect("Failed to deserialize");

        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_empty_request_roundtrip() {
        let original = GetSpatialDirectivesRequest { ids: vec![] };

        let serialized =
            serialize_get_spatial_directives_request(&original).expect("Failed to serialize");
        let deserialized =
            deserialize_get_spatial_directives_request(&serialized).expect("Failed to deserialize");

        assert_eq!(original, deserialized);
    }

    #[test]
    fn test_empty_response_roundtrip() {
        let original = GetSpatialDirectivesResponse { items: vec![] };

        let serialized =
            serialize_get_spatial_directives_response(&original).expect("Failed to serialize");
        let deserialized = deserialize_get_spatial_directives_response(&serialized)
            .expect("Failed to deserialize");

        assert_eq!(original, deserialized);
    }
}