bacnet_objects/property_metadata.rs
1//! Executable property metadata and compatibility projections.
2
3use std::borrow::Cow;
4
5use bacnet_types::enums::PropertyIdentifier;
6
7/// A property's base conformance code in its Clause 12 object table.
8///
9/// Conditions attached to a base code are represented separately by
10/// [`PropertyPresenceCondition`]. This keeps the table code available to RPM
11/// and PICS while preserving why an implemented conditional row is present.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum PropertyConformance {
14 /// Required and readable (`R`).
15 RequiredRead,
16 /// Required and writable (`W`).
17 RequiredWrite,
18 /// Optional (`O`), including an implemented conditionally present row.
19 Optional,
20}
21
22impl PropertyConformance {
23 /// Whether the table code classifies this property as required.
24 pub const fn is_required(self) -> bool {
25 matches!(self, Self::RequiredRead | Self::RequiredWrite)
26 }
27}
28
29/// Why a conditionally present property row is implemented.
30///
31/// Metadata describes effective rows that are already present. These values
32/// retain the conformance reason; they are not predicates evaluated at runtime.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum PropertyPresenceCondition {
36 /// The row is present because the object is commandable.
37 Commandable,
38 /// The row is present because the object implements intrinsic reporting.
39 IntrinsicReporting,
40 /// The paired Active_Text and Inactive_Text option is implemented.
41 PairedText,
42}
43
44/// The write capability implemented by an object's `write_property` route.
45///
46/// A conditional capability remains writable for PICS purposes even when the
47/// current object state causes a particular request to be denied.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum PropertyWriteCapability {
51 /// No network property-write route is implemented.
52 ReadOnly,
53 /// The property-write route is available without an object-state gate.
54 Always,
55 /// The property-write route is available only while Out_Of_Service is true.
56 WhenOutOfService,
57}
58
59impl PropertyWriteCapability {
60 /// Whether any network property-write route is implemented.
61 pub const fn is_writable(self) -> bool {
62 !matches!(self, Self::ReadOnly)
63 }
64}
65
66/// Canonical metadata for one effective property row on a BACnet object.
67///
68/// A migrated object's canonical set includes `PROPERTY_LIST` itself. The
69/// legacy [`BACnetObject::property_list`](crate::traits::BACnetObject::property_list)
70/// projection omits that identifier, and the encoded BACnet Property_List
71/// value additionally omits Object_Identifier, Object_Name, and Object_Type.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73#[non_exhaustive]
74pub struct PropertyMetadata {
75 /// The standard property identifier.
76 pub property_identifier: PropertyIdentifier,
77 /// The base `R`, `W`, or `O` table classification.
78 pub conformance: PropertyConformance,
79 /// The reason a conditionally present row is implemented.
80 pub presence_condition: Option<PropertyPresenceCondition>,
81 /// The write route implemented for this property.
82 pub write_capability: PropertyWriteCapability,
83}
84
85impl PropertyMetadata {
86 /// Construct one canonical metadata row.
87 ///
88 /// This constructor is the stable construction path as the non-exhaustive
89 /// metadata model gains conditions needed by later object migrations.
90 pub const fn new(
91 property_identifier: PropertyIdentifier,
92 conformance: PropertyConformance,
93 presence_condition: Option<PropertyPresenceCondition>,
94 write_capability: PropertyWriteCapability,
95 ) -> Self {
96 Self {
97 property_identifier,
98 conformance,
99 presence_condition,
100 write_capability,
101 }
102 }
103}
104
105/// Derive the legacy object property-list projection from canonical metadata.
106///
107/// The projection preserves metadata order and omits only `PROPERTY_LIST`.
108pub fn property_list_from_metadata(
109 metadata: &[PropertyMetadata],
110) -> Cow<'static, [PropertyIdentifier]> {
111 Cow::Owned(
112 metadata
113 .iter()
114 .filter_map(|row| {
115 (row.property_identifier != PropertyIdentifier::PROPERTY_LIST)
116 .then_some(row.property_identifier)
117 })
118 .collect(),
119 )
120}
121
122/// Derive all table-required identifiers from canonical metadata.
123///
124/// Unlike the legacy property-list projection, this includes `PROPERTY_LIST`.
125/// Consumers such as RPM apply their own service-specific exclusion.
126pub fn required_properties_from_metadata(
127 metadata: &[PropertyMetadata],
128) -> Cow<'static, [PropertyIdentifier]> {
129 Cow::Owned(
130 metadata
131 .iter()
132 .filter_map(|row| {
133 row.conformance
134 .is_required()
135 .then_some(row.property_identifier)
136 })
137 .collect(),
138 )
139}
140
141/// Project implemented write capability for one property identifier.
142pub fn is_writable_in_metadata(
143 metadata: &[PropertyMetadata],
144 property_identifier: PropertyIdentifier,
145) -> bool {
146 metadata.iter().any(|row| {
147 row.property_identifier == property_identifier && row.write_capability.is_writable()
148 })
149}