Skip to main content

switchback_codec_pb/convert/
mod.rs

1//! Bidirectional mapping between seam model types and protobuf messages.
2
3/// Contract, group, and companion mapping.
4mod contract;
5/// Entity bodies and stored entity mapping.
6mod entity;
7/// Intra-links, structural references, and link targets.
8mod link;
9/// Manual, document, module, and source mapping.
10mod manual;
11/// Protocol attachment mapping.
12mod protocol;
13
14use switchback_traits::{ReferenceManual, Result, SwitchbackError};
15
16use crate::pb;
17
18/// Wire schema version written by [`crate::ProtobufCodec`].
19pub const WIRE_VERSION: &str = "v1alpha1";
20
21/// Convert a [`ReferenceManual`] to its protobuf representation.
22///
23/// Rejects manuals containing [`switchback_traits::LinkTarget::Unresolved`] intra-links.
24pub fn to_proto(manual: &ReferenceManual) -> Result<pb::ReferenceManual> {
25    validate_resolved_links(manual)?;
26    manual::reference_manual_to_proto(manual)
27}
28
29/// Convert a protobuf [`ReferenceManual`] into the seam model.
30///
31/// Rejects wire values whose `switchback_version` does not start with [`WIRE_VERSION`].
32pub fn from_proto(manual: pb::ReferenceManual) -> Result<ReferenceManual> {
33    validate_wire_version(&manual.switchback_version)?;
34    manual::reference_manual_from_proto(manual)
35}
36
37fn validate_wire_version(version: &str) -> Result<()> {
38    if version.starts_with(WIRE_VERSION) {
39        Ok(())
40    } else {
41        Err(SwitchbackError::codec(format!(
42            "unsupported switchback_version {version:?}; expected prefix {WIRE_VERSION:?}"
43        )))
44    }
45}
46
47fn validate_resolved_links(manual: &ReferenceManual) -> Result<()> {
48    for module in &manual.modules {
49        for contract in &module.contracts {
50            for group in &contract.groups {
51                for entity in &group.entities {
52                    for link in &entity.intra_links {
53                        if matches!(link.target, switchback_traits::LinkTarget::Unresolved) {
54                            return Err(SwitchbackError::codec(
55                                "cannot serialize manual with unresolved intra-link",
56                            ));
57                        }
58                    }
59                }
60            }
61        }
62    }
63    Ok(())
64}
65
66pub(crate) fn opt_string(value: &Option<String>) -> String {
67    value.clone().unwrap_or_default()
68}
69
70pub(crate) fn string_opt(value: String) -> Option<String> {
71    if value.is_empty() { None } else { Some(value) }
72}
73
74pub(crate) fn codec_err(message: impl Into<String>) -> SwitchbackError {
75    SwitchbackError::codec(message)
76}
77
78pub(crate) fn missing_link_target() -> SwitchbackError {
79    codec_err("link target missing on wire")
80}
81
82pub(crate) fn missing_entity_body() -> SwitchbackError {
83    codec_err("entity body missing on wire")
84}