az-device-contract-codegen 2026.5.18

Generate deterministic Modbus C, Markdown, and Tokio client artifacts from typed device contract definitions.
Documentation
use az_device_contract_codegen::{
    generate_bundle, ApiKind, ContractMethod, ContractRenderRequest, ContractService,
    ContractTypedItem, ReadReturnKind, RegisterArea, TransportKind, ValueType,
};

#[test]
fn should_generate_symmetric_artifacts_for_rtu_and_tcp() -> Result<(), Box<dyn std::error::Error>> {
    let request = ContractRenderRequest {
        transports: vec![TransportKind::Rtu, TransportKind::Tcp],
        services: vec![
            ContractService {
                api_kind: ApiKind::StaticRead,
                interface_name: "DeviceStaticReadApi".to_string(),
                interface_summary: Some("静态信息读取".to_string()),
                methods: vec![ContractMethod {
                    summary: Some("读取设备身份".to_string()),
                    method_name: "getDeviceIdentity".to_string(),
                    read_return_kind: Some(ReadReturnKind::Dto),
                    read_return_type_name: Some("DeviceIdentityRegisters".to_string()),
                    read_area: Some(RegisterArea::HoldingRegister),
                    write_area: None,
                    read_fields: vec![
                        ContractTypedItem {
                            name: "macAddress".to_string(),
                            summary: Some("MAC 地址".to_string()),
                            value_type: ValueType::String,
                        },
                        ContractTypedItem {
                            name: "flashCapacityKb".to_string(),
                            summary: Some("Flash 容量".to_string()),
                            value_type: ValueType::Int,
                        },
                    ],
                    parameters: vec![],
                }],
            },
            ContractService {
                api_kind: ApiKind::RuntimeRead,
                interface_name: "DeviceReadApi".to_string(),
                interface_summary: Some("运行态读取".to_string()),
                methods: vec![ContractMethod {
                    summary: Some("读取信号灯".to_string()),
                    method_name: "getSignalLights".to_string(),
                    read_return_kind: Some(ReadReturnKind::Dto),
                    read_return_type_name: Some("SignalLightsRegisters".to_string()),
                    read_area: Some(RegisterArea::DiscreteInput),
                    write_area: None,
                    read_fields: vec![
                        ContractTypedItem {
                            name: "ch1".to_string(),
                            summary: Some("第一路".to_string()),
                            value_type: ValueType::Boolean,
                        },
                        ContractTypedItem {
                            name: "ch2".to_string(),
                            summary: Some("第二路".to_string()),
                            value_type: ValueType::Boolean,
                        },
                    ],
                    parameters: vec![],
                }],
            },
            ContractService {
                api_kind: ApiKind::Write,
                interface_name: "DeviceWriteApi".to_string(),
                interface_summary: Some("写操作".to_string()),
                methods: vec![ContractMethod {
                    summary: Some("设置故障灯".to_string()),
                    method_name: "writeIndicatorLights".to_string(),
                    read_return_kind: None,
                    read_return_type_name: None,
                    read_area: None,
                    write_area: Some(RegisterArea::Coil),
                    read_fields: vec![],
                    parameters: vec![
                        ContractTypedItem {
                            name: "faultLightOn".to_string(),
                            summary: None,
                            value_type: ValueType::Boolean,
                        },
                        ContractTypedItem {
                            name: "runLightOn".to_string(),
                            summary: None,
                            value_type: ValueType::Boolean,
                        },
                    ],
                }],
            },
        ],
    };

    let bundle = generate_bundle(&request)?;

    assert!(
        bundle
            .artifacts
            .iter()
            .any(|artifact| artifact.file_name == "okm_modbus_rtu_tokio_client"),
        "缺少 RTU Rust 客户端产物"
    );
    assert!(
        bundle
            .artifacts
            .iter()
            .any(|artifact| artifact.file_name == "okm_modbus_tcp_tokio_client"),
        "缺少 TCP Rust 客户端产物"
    );
    let markdown = bundle
        .artifacts
        .iter()
        .find(|artifact| artifact.file_name == "modbus-rtu-contract")
        .ok_or("缺少 RTU 协议文档")?;
    assert!(
        markdown.content.contains("getSignalLights"),
        "协议文档中缺少方法映射"
    );
    let dispatch = bundle
        .artifacts
        .iter()
        .find(|artifact| {
            artifact.file_name == "okm_modbus_dispatch" && artifact.extension_name == "c"
        })
        .ok_or("缺少共享 dispatch 源文件")?;
    assert!(
        dispatch.content.contains("okm_bridge_get_signal_lights"),
        "共享 dispatch 未引用桥接函数"
    );
    Ok(())
}