Files
harmony/opnsense-api/src/wire.rs
Jean-Gabriel Gill-Couture a7f9b1037a
Some checks failed
Run Check Script / check (pull_request) Failing after 19s
refactor: push harmony_types enums all the way down to opnsense-api
Move vendor-neutral IaC enums to harmony_types::firewall. Add From impls
in opnsense-api::wire converting harmony_types to generated OPNsense
types. Add typed methods in opnsense-config that accept harmony_types
enums and handle wire conversion internally.

Score layer no longer builds serde_json::json!() bodies — it passes
harmony_types enums directly to opnsense-config typed methods:
  ensure_filter_rule(&FirewallAction, &Direction, &IpProtocol, ...)
  ensure_snat_rule_from(&IpProtocol, &NetworkProtocol, ...)
  ensure_dnat_rule(&IpProtocol, &NetworkProtocol, ...)
  ensure_vip_from(&VipMode, ...)
  ensure_lagg(..., &LaggProtocol, ...)

Type flow: harmony_types → Score → opnsense-config → From<> → generated → wire
No strings cross layer boundaries for typed fields.
2026-03-26 11:07:49 -04:00

195 lines
6.7 KiB
Rust

//! Conversions from vendor-neutral `harmony_types` enums to OPNsense generated API types.
//!
//! Each `From` impl maps a harmony IaC type to the corresponding auto-generated
//! OPNsense enum. The generated types handle wire-format serde serialization.
use harmony_types::firewall::{
Direction, FirewallAction, IpProtocol, LaggProtocol, NetworkProtocol, VipMode,
};
use crate::generated::firewall_filter::{
FirewallFilterRulesRuleAction, FirewallFilterRulesRuleDirection,
FirewallFilterRulesRuleIpprotocol, FirewallFilterSnatrulesRuleIpprotocol,
};
use crate::generated::lagg::LaggProto;
use crate::generated::vip::VirtualipVipMode;
// ── FirewallAction ──────────────────────────────────────────────────
impl From<&FirewallAction> for FirewallFilterRulesRuleAction {
fn from(value: &FirewallAction) -> Self {
match value {
FirewallAction::Pass => Self::Pass,
FirewallAction::Block => Self::Block,
FirewallAction::Reject => Self::Reject,
}
}
}
// ── Direction ───────────────────────────────────────────────────────
impl From<&Direction> for FirewallFilterRulesRuleDirection {
fn from(value: &Direction) -> Self {
match value {
Direction::In => Self::In,
Direction::Out => Self::Out,
}
}
}
// ── IpProtocol → filter rules ───────────────────────────────────────
impl From<&IpProtocol> for FirewallFilterRulesRuleIpprotocol {
fn from(value: &IpProtocol) -> Self {
match value {
IpProtocol::Inet => Self::IPv4,
IpProtocol::Inet6 => Self::IPv6,
}
}
}
// ── IpProtocol → SNAT rules ────────────────────────────────────────
impl From<&IpProtocol> for FirewallFilterSnatrulesRuleIpprotocol {
fn from(value: &IpProtocol) -> Self {
match value {
IpProtocol::Inet => Self::IPv4,
IpProtocol::Inet6 => Self::IPv6,
}
}
}
// ── NetworkProtocol ─────────────────────────────────────────────────
// The OPNsense protocol field is a free-form ProtocolField (String),
// not a generated enum. This is the only case requiring a string conversion.
/// Convert a `NetworkProtocol` to the OPNsense wire string.
///
/// This is a function (not a `From` impl) because OPNsense's ProtocolField
/// is a free-form string, not a generated enum. Orphan rules prevent
/// `impl From<&NetworkProtocol> for String`.
pub fn network_protocol_to_opnsense(value: &NetworkProtocol) -> String {
match value {
NetworkProtocol::Tcp => "TCP".to_string(),
NetworkProtocol::Udp => "UDP".to_string(),
NetworkProtocol::TcpUdp => "TCP/UDP".to_string(),
NetworkProtocol::Icmp => "ICMP".to_string(),
NetworkProtocol::Any => "any".to_string(),
}
}
// ── VipMode ─────────────────────────────────────────────────────────
impl From<&VipMode> for VirtualipVipMode {
fn from(value: &VipMode) -> Self {
match value {
VipMode::IpAlias => Self::IpAlias,
VipMode::Carp => Self::Carp,
VipMode::ProxyArp => Self::ProxyArp,
}
}
}
// ── LaggProtocol ────────────────────────────────────────────────────
impl From<&LaggProtocol> for LaggProto {
fn from(value: &LaggProtocol) -> Self {
match value {
LaggProtocol::Lacp => Self::Lacp,
LaggProtocol::Failover => Self::Failover,
LaggProtocol::LoadBalance => Self::Loadbalance,
LaggProtocol::RoundRobin => Self::Roundrobin,
LaggProtocol::None => Self::None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn firewall_action_to_generated() {
assert_eq!(
FirewallFilterRulesRuleAction::from(&FirewallAction::Pass),
FirewallFilterRulesRuleAction::Pass
);
assert_eq!(
FirewallFilterRulesRuleAction::from(&FirewallAction::Block),
FirewallFilterRulesRuleAction::Block
);
}
#[test]
fn direction_to_generated() {
assert_eq!(
FirewallFilterRulesRuleDirection::from(&Direction::In),
FirewallFilterRulesRuleDirection::In
);
assert_eq!(
FirewallFilterRulesRuleDirection::from(&Direction::Out),
FirewallFilterRulesRuleDirection::Out
);
}
#[test]
fn ip_protocol_to_generated() {
assert_eq!(
FirewallFilterRulesRuleIpprotocol::from(&IpProtocol::Inet),
FirewallFilterRulesRuleIpprotocol::IPv4
);
assert_eq!(
FirewallFilterRulesRuleIpprotocol::from(&IpProtocol::Inet6),
FirewallFilterRulesRuleIpprotocol::IPv6
);
}
#[test]
fn ip_protocol_to_generated_snat() {
assert_eq!(
FirewallFilterSnatrulesRuleIpprotocol::from(&IpProtocol::Inet),
FirewallFilterSnatrulesRuleIpprotocol::IPv4
);
}
#[test]
fn network_protocol_to_wire_string() {
assert_eq!(network_protocol_to_opnsense(&NetworkProtocol::Tcp), "TCP");
assert_eq!(network_protocol_to_opnsense(&NetworkProtocol::Any), "any");
assert_eq!(
network_protocol_to_opnsense(&NetworkProtocol::TcpUdp),
"TCP/UDP"
);
}
#[test]
fn vip_mode_to_generated() {
assert_eq!(
VirtualipVipMode::from(&VipMode::IpAlias),
VirtualipVipMode::IpAlias
);
assert_eq!(
VirtualipVipMode::from(&VipMode::Carp),
VirtualipVipMode::Carp
);
assert_eq!(
VirtualipVipMode::from(&VipMode::ProxyArp),
VirtualipVipMode::ProxyArp
);
}
#[test]
fn lagg_protocol_to_generated() {
assert_eq!(LaggProto::from(&LaggProtocol::Lacp), LaggProto::Lacp);
assert_eq!(
LaggProto::from(&LaggProtocol::Failover),
LaggProto::Failover
);
assert_eq!(
LaggProto::from(&LaggProtocol::LoadBalance),
LaggProto::Loadbalance
);
assert_eq!(LaggProto::from(&LaggProtocol::None), LaggProto::None);
}
}