Skip to main content

zenoh_config/
lib.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15//! ⚠️ WARNING ⚠️
16//!
17//! This crate is intended for Zenoh's internal use.
18//!
19//! [Click here for Zenoh's documentation](https://docs.rs/zenoh/latest/zenoh)
20//!
21//! Configuration to pass to `zenoh::open()` and `zenoh::scout()` functions and associated constants.
22#![allow(deprecated)]
23
24pub mod defaults;
25pub mod gateway;
26mod include;
27pub mod qos;
28pub mod wrappers;
29
30#[allow(unused_imports)]
31use std::convert::TryFrom;
32#[allow(unused_imports)]
33use std::str::FromStr;
34// This is a false positive from the rust analyser
35use std::{
36    any::Any,
37    collections::HashSet,
38    fmt,
39    io::Read,
40    net::SocketAddr,
41    num::{NonZeroU16, NonZeroUsize},
42    ops::{self, Bound, Deref, DerefMut, RangeBounds},
43    path::Path,
44    sync::{Arc, Weak},
45};
46
47use include::recursive_include;
48use nonempty_collections::NEVec;
49use qos::{PublisherQoSConfList, QosFilter, QosOverwriteMessage, QosOverwrites};
50use secrecy::{CloneableSecret, DebugSecret, Secret, SerializableSecret, Zeroize};
51use serde::{Deserialize, Serialize};
52use serde_json::{Map, Value};
53use validated_struct::ValidatedMapAssociatedTypes;
54pub use validated_struct::{GetError, ValidatedMap};
55pub use wrappers::ZenohId;
56pub use zenoh_protocol::core::{
57    whatami, EndPoint, EndPoints, Locator, WhatAmI, WhatAmIMatcher, WhatAmIMatcherVisitor,
58};
59use zenoh_protocol::{
60    core::{
61        key_expr::{OwnedKeyExpr, OwnedNonWildKeyExpr},
62        Bits, RegionName,
63    },
64    transport::{BatchSize, TransportSn},
65};
66use zenoh_result::{bail, zerror, ZResult};
67use zenoh_util::{LibLoader, LibSearchDirs};
68
69pub mod mode_dependent;
70pub use mode_dependent::*;
71
72pub mod connection_retry;
73pub use connection_retry::*;
74
75// Wrappers for secrecy of values
76#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
77pub struct SecretString(String);
78
79impl ops::Deref for SecretString {
80    type Target = String;
81
82    fn deref(&self) -> &Self::Target {
83        &self.0
84    }
85}
86
87impl SerializableSecret for SecretString {}
88impl DebugSecret for SecretString {}
89impl CloneableSecret for SecretString {}
90impl Zeroize for SecretString {
91    fn zeroize(&mut self) {
92        self.0 = "".to_string();
93    }
94}
95
96pub type SecretValue = Secret<SecretString>;
97
98#[derive(Debug, Deserialize, Serialize, Clone)]
99pub struct TransportWeight {
100    /// A zid of destination node.
101    pub dst_zid: ZenohId,
102    /// A weight of link from this node to the destination.
103    pub weight: NonZeroU16,
104}
105
106#[derive(Debug, Deserialize, Serialize, Clone, Copy, Eq, PartialEq)]
107#[serde(rename_all = "snake_case")]
108pub enum InterceptorFlow {
109    Egress,
110    Ingress,
111}
112
113/// A category of data message that carries a payload. Used by downsampling, low-pass
114/// filtering, and SHM transport optimization to select which messages a configuration
115/// applies to.
116#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
117#[serde(rename_all = "snake_case")]
118pub enum DataMessage {
119    Put,
120    Delete,
121    Query,
122    Reply,
123}
124
125#[derive(Debug, Deserialize, Serialize, Clone)]
126#[serde(deny_unknown_fields)]
127pub struct DownsamplingRuleConf {
128    /// A list of key-expressions to which the downsampling will be applied.
129    /// Downsampling will be applied for all key extensions if the parameter is None
130    pub key_expr: OwnedKeyExpr,
131    /// The maximum frequency in Hertz;
132    pub freq: f64,
133}
134
135#[derive(Debug, Deserialize, Serialize, Clone)]
136#[serde(deny_unknown_fields)]
137pub struct DownsamplingItemConf {
138    /// Optional identifier for the downsampling configuration item
139    pub id: Option<String>,
140    /// A list of interfaces to which the downsampling will be applied
141    /// Downsampling will be applied for all interfaces if the parameter is None
142    pub interfaces: Option<NEVec<String>>,
143    /// A list of link types, transports having one of those link types will have the downsampling applied
144    /// Downsampling will be applied for all link types if the parameter is None
145    pub link_protocols: Option<NEVec<InterceptorLink>>,
146    // list of message types on which the downsampling will be applied
147    pub messages: NEVec<DataMessage>,
148    /// A list of downsampling rules: key_expression and the maximum frequency in Hertz
149    pub rules: NEVec<DownsamplingRuleConf>,
150    /// Downsampling flow directions: egress and/or ingress
151    pub flows: Option<NEVec<InterceptorFlow>>,
152}
153
154#[derive(Serialize, Debug, Deserialize, Clone)]
155#[serde(deny_unknown_fields)]
156pub struct LowPassFilterConf {
157    pub id: Option<String>,
158    pub interfaces: Option<NEVec<String>>,
159    pub link_protocols: Option<NEVec<InterceptorLink>>,
160    pub flows: Option<NEVec<InterceptorFlow>>,
161    pub messages: NEVec<DataMessage>,
162    pub key_exprs: NEVec<OwnedKeyExpr>,
163    pub size_limit: usize,
164}
165
166#[derive(Serialize, Debug, Deserialize, Clone)]
167#[serde(deny_unknown_fields)]
168pub struct AclConfigRule {
169    pub id: String,
170    pub key_exprs: NEVec<OwnedKeyExpr>,
171    pub messages: NEVec<AclMessage>,
172    pub flows: Option<NEVec<InterceptorFlow>>,
173    pub permission: Permission,
174}
175
176#[derive(Serialize, Debug, Deserialize, Clone)]
177#[serde(deny_unknown_fields)]
178pub struct AclConfigSubjects {
179    pub id: String,
180    pub interfaces: Option<NEVec<Interface>>,
181    pub cert_common_names: Option<NEVec<CertCommonName>>,
182    pub usernames: Option<NEVec<Username>>,
183    pub link_protocols: Option<NEVec<InterceptorLink>>,
184    pub zids: Option<NEVec<ZenohId>>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct ConfRange {
189    start: Option<u64>,
190    end: Option<u64>,
191}
192
193impl ConfRange {
194    pub fn new(start: Option<u64>, end: Option<u64>) -> Self {
195        Self { start, end }
196    }
197}
198
199impl RangeBounds<u64> for ConfRange {
200    fn start_bound(&self) -> Bound<&u64> {
201        match self.start {
202            Some(ref start) => Bound::Included(start),
203            None => Bound::Unbounded,
204        }
205    }
206    fn end_bound(&self) -> Bound<&u64> {
207        match self.end {
208            Some(ref end) => Bound::Included(end),
209            None => Bound::Unbounded,
210        }
211    }
212}
213
214impl serde::Serialize for ConfRange {
215    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
216    where
217        S: serde::Serializer,
218    {
219        serializer.serialize_str(&format!(
220            "{}..{}",
221            self.start.unwrap_or_default(),
222            self.end.unwrap_or_default()
223        ))
224    }
225}
226
227impl<'a> serde::Deserialize<'a> for ConfRange {
228    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
229    where
230        D: serde::Deserializer<'a>,
231    {
232        struct V;
233
234        impl serde::de::Visitor<'_> for V {
235            type Value = ConfRange;
236
237            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
238                formatter.write_str("range string")
239            }
240
241            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
242            where
243                E: serde::de::Error,
244            {
245                let (start, end) = v
246                    .split_once("..")
247                    .ok_or_else(|| serde::de::Error::custom("invalid range"))?;
248                let parse_bound = |bound: &str| {
249                    (!bound.is_empty())
250                        .then(|| bound.parse::<u64>())
251                        .transpose()
252                        .map_err(|_| serde::de::Error::custom("invalid range bound"))
253                };
254                Ok(ConfRange::new(parse_bound(start)?, parse_bound(end)?))
255            }
256        }
257        deserializer.deserialize_str(V)
258    }
259}
260
261#[derive(Debug, Deserialize, Serialize, Clone)]
262#[serde(deny_unknown_fields)]
263pub struct QosOverwriteItemConf {
264    /// Optional identifier for the qos modification configuration item.
265    pub id: Option<String>,
266    /// A list of ZIDs on which qos will be overwritten when communicating with.
267    pub zids: Option<NEVec<ZenohId>>,
268    /// A list of interfaces to which the qos will be applied.
269    /// QosOverwrite will be applied for all interfaces if the parameter is None.
270    pub interfaces: Option<NEVec<String>>,
271    /// A list of link types, transports having one of those link types will have the qos overwrite applied
272    /// Qos overwrite will be applied for all link types if the parameter is None.
273    pub link_protocols: Option<NEVec<InterceptorLink>>,
274    /// List of message types on which the qos overwrite will be applied.
275    pub messages: NEVec<QosOverwriteMessage>,
276    /// List of key expressions to apply qos overwrite.
277    pub key_exprs: Option<NEVec<OwnedKeyExpr>>,
278    // The qos value to overwrite with.
279    pub overwrite: QosOverwrites,
280    /// QosOverwrite flow directions: egress and/or ingress.
281    pub flows: Option<NEVec<InterceptorFlow>>,
282    /// QoS filter to apply to the messages matching this item.
283    pub qos: Option<QosFilter>,
284    /// payload_size range for the messages matching this item.
285    pub payload_size: Option<ConfRange>,
286}
287
288#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
289pub struct Interface(pub String);
290
291impl std::fmt::Display for Interface {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        write!(f, "Interface({})", self.0)
294    }
295}
296
297#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
298pub struct CertCommonName(pub String);
299
300impl std::fmt::Display for CertCommonName {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        write!(f, "CertCommonName({})", self.0)
303    }
304}
305
306#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
307pub struct Username(pub String);
308
309impl std::fmt::Display for Username {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        write!(f, "Username({})", self.0)
312    }
313}
314
315#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
316#[serde(rename_all = "kebab-case")]
317pub enum InterceptorLink {
318    Tcp,
319    Udp,
320    Tls,
321    Quic,
322    Serial,
323    Unixpipe,
324    UnixsockStream,
325    Vsock,
326    Ws,
327}
328
329impl std::fmt::Display for InterceptorLink {
330    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
331        write!(f, "Transport({self:?})")
332    }
333}
334
335#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
336#[serde(deny_unknown_fields)]
337pub struct AclConfigPolicyEntry {
338    pub id: Option<String>,
339    pub rules: Vec<String>,
340    pub subjects: Vec<String>,
341}
342
343#[derive(Clone, Serialize, Debug, Deserialize)]
344#[serde(deny_unknown_fields)]
345pub struct PolicyRule {
346    pub subject_id: usize,
347    pub key_expr: OwnedKeyExpr,
348    pub message: AclMessage,
349    pub permission: Permission,
350    pub flow: InterceptorFlow,
351}
352
353#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
354#[serde(rename_all = "snake_case")]
355pub enum AclMessage {
356    Put,
357    Delete,
358    DeclareSubscriber,
359    Query,
360    DeclareQueryable,
361    Reply,
362    LivelinessToken,
363    DeclareLivelinessSubscriber,
364    LivelinessQuery,
365}
366
367#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
368#[serde(rename_all = "snake_case")]
369pub enum Permission {
370    Allow,
371    Deny,
372}
373
374/// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly.
375#[derive(Default, Clone, Copy, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
376#[serde(rename_all = "kebab-case")]
377pub enum AutoConnectStrategy {
378    /// Always attempt to connect to another node, may result in redundant connection which
379    /// will be then be closed.
380    #[default]
381    Always,
382    /// A node will attempt to connect to another one only if its own zid is greater than the
383    /// other one. If both nodes use this strategy, only one will attempt the connection.
384    /// This strategy may not be suited if one of the node is not reachable by the other one,
385    /// for example because of a private IP.
386    GreaterZid,
387}
388
389#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
390pub struct StatsFilterConfig {
391    pub key: OwnedKeyExpr,
392}
393
394pub trait ConfigValidator: Send + Sync {
395    fn check_config(
396        &self,
397        _plugin_name: &str,
398        _path: &str,
399        _current: &serde_json::Map<String, serde_json::Value>,
400        _new: &serde_json::Map<String, serde_json::Value>,
401    ) -> ZResult<Option<serde_json::Map<String, serde_json::Value>>> {
402        Ok(None)
403    }
404}
405
406// Necessary to allow to set default emplty weak reference value to plugin.validator field
407// because empty weak value is not allowed for Arc<dyn Trait>
408impl ConfigValidator for () {}
409
410/// Creates an empty zenoh net Session configuration.
411pub fn empty() -> Config {
412    Config::default()
413}
414
415/// Creates a default zenoh net Session configuration (equivalent to `peer`).
416pub fn default() -> Config {
417    peer()
418}
419
420/// Creates a default `'peer'` mode zenoh net Session configuration.
421pub fn peer() -> Config {
422    let mut config = Config::default();
423    config.set_mode(Some(WhatAmI::Peer)).unwrap();
424    config
425}
426
427/// Creates a default `'client'` mode zenoh net Session configuration.
428pub fn client<I: IntoIterator<Item = T>, T: Into<EndPoint>>(peers: I) -> Config {
429    let mut config = Config::default();
430    config.set_mode(Some(WhatAmI::Client)).unwrap();
431    config.connect.endpoints = ModeDependentValue::Unique(
432        peers
433            .into_iter()
434            .map(|t| EndPoints::Single(t.into()))
435            .collect(),
436    );
437    config
438}
439
440#[test]
441fn config_keys() {
442    let c = Config::default();
443    dbg!(Vec::from_iter(c.keys()));
444}
445
446/// Deprecated wrapper for `routing.router.peers_failover_brokering`.
447/// Emits a warning on deserialization (both full-config and `--cfg` paths).
448#[derive(Clone, Debug, Default)]
449struct DeprecatedPeersFailoverBrokering(Option<bool>);
450
451impl serde::Serialize for DeprecatedPeersFailoverBrokering {
452    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
453        self.0.serialize(serializer)
454    }
455}
456
457impl<'de> serde::Deserialize<'de> for DeprecatedPeersFailoverBrokering {
458    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
459        tracing::warn!(
460            "`routing.router.peers_failover_brokering` is deprecated and has no effect; \
461            please remove it from your configuration"
462        );
463        Option::<bool>::deserialize(deserializer).map(Self)
464    }
465}
466
467/// Deprecated wrapper for `routing.peer` (and its `mode` / `linkstate` sub-fields).
468/// Emits a warning on deserialization (both full-config and `--cfg` paths).
469#[derive(Clone, Debug, Default)]
470struct DeprecatedRoutingPeer(Option<Value>);
471
472impl serde::Serialize for DeprecatedRoutingPeer {
473    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
474        self.0.serialize(serializer)
475    }
476}
477
478impl<'de> serde::Deserialize<'de> for DeprecatedRoutingPeer {
479    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
480        tracing::warn!(
481            "routing.peer.mode` and `routing.peer.linkstate` are deprecated and have no effect; \
482            please remove them from your configuration"
483        );
484        Option::<Value>::deserialize(deserializer).map(Self)
485    }
486}
487
488validated_struct::validator! {
489    #[derive(Default)]
490    #[recursive_attrs]
491    #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
492    #[serde(default)]
493    #[serde(deny_unknown_fields)]
494    #[doc(hidden)]
495    Config {
496        /// The Zenoh ID of the instance. This ID MUST be unique throughout your Zenoh infrastructure and cannot exceed 16 bytes of length. If left unset, a random u128 will be generated.
497        /// If not specified a random Zenoh ID will be generated upon session creation.
498        id: Option<ZenohId>,
499        /// The metadata of the instance. Arbitrary json data available from the admin space
500        metadata: Value,
501        /// The node's mode ("router" (default value in `zenohd`), "peer" or "client").
502        mode: Option<whatami::WhatAmI>,
503        region_name: Option<RegionName>,
504        pub gateway: gateway::GatewayConf,
505        /// Which zenoh nodes to connect to.
506        pub connect:
507        ConnectConfig {
508            /// global timeout for full connect cycle
509            pub timeout_ms: Option<ModeDependentValue<i64>>,
510            /// The list of endpoints to connect to
511            pub endpoints: ModeDependentValue<Vec<EndPoints>>,
512            /// if connection timeout exceed, exit from application
513            pub exit_on_failure: Option<ModeDependentValue<bool>>,
514            pub retry: Option<connection_retry::ConnectionRetryModeDependentConf>,
515        },
516        /// Which endpoints to listen on.
517        pub listen:
518        ListenConfig {
519            /// global timeout for full listen cycle
520            pub timeout_ms: Option<ModeDependentValue<i64>>,
521            /// The list of endpoints to listen on
522            pub endpoints: ModeDependentValue<Vec<EndPoint>>,
523            /// if connection timeout exceed, exit from application
524            pub exit_on_failure: Option<ModeDependentValue<bool>>,
525            pub retry: Option<connection_retry::ConnectionRetryModeDependentConf>,
526        },
527        /// Configure the session open behavior.
528        pub open: #[derive(Default)]
529        OpenConf {
530            /// Configure the conditions to be met before session open returns.
531            pub return_conditions: #[derive(Default)]
532            ReturnConditionsConf {
533                /// Session open waits to connect to scouted peers and routers before returning.
534                /// When set to false, first publications and queries after session open from peers may be lost.
535                connect_scouted: Option<bool>,
536                /// Session open waits to receive initial declares from connected peers before returning.
537                /// Setting to false may cause extra traffic at startup from peers.
538                declares: Option<bool>,
539            },
540        },
541        pub scouting: #[derive(Default)]
542        ScoutingConf {
543            /// In client mode, the period dedicated to scouting for a router before failing. In milliseconds.
544            timeout: Option<u64>,
545            /// In peer mode, the period dedicated to scouting remote peers before attempting other operations. In milliseconds.
546            delay: Option<u64>,
547            /// The multicast scouting configuration.
548            pub multicast: #[derive(Default)]
549            ScoutingMulticastConf {
550                /// Whether multicast scouting is enabled or not. If left empty, `zenohd` will set it according to the presence of the `--no-multicast-scouting` argument.
551                enabled: Option<bool>,
552                /// The socket which should be used for multicast scouting. `zenohd` will use `224.0.0.224:7446` by default if none is provided.
553                address: Option<SocketAddr>,
554                /// The network interface which should be used for multicast scouting. `zenohd` will automatically select an interface if none is provided.
555                interface: Option<String>,
556                /// The time-to-live on multicast scouting packets. (default: 1)
557                pub ttl: Option<u32>,
558                /// Which type of Zenoh instances to automatically establish sessions with upon discovery through UDP multicast.
559                autoconnect: Option<ModeDependentValue<WhatAmIMatcher>>,
560                /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly.
561                autoconnect_strategy: Option<ModeDependentValue<TargetDependentValue<AutoConnectStrategy>>>,
562                /// Whether or not to listen for scout messages on UDP multicast and reply to them.
563                listen: Option<ModeDependentValue<bool>>,
564            },
565            /// The gossip scouting configuration.
566            pub gossip: #[derive(Default)]
567            GossipConf {
568                /// Whether gossip scouting is enabled or not.
569                enabled: Option<bool>,
570                /// When true, gossip scouting information are propagated multiple hops to all nodes in the local network.
571                /// When false, gossip scouting information are only propagated to the next hop.
572                /// Activating multihop gossip implies more scouting traffic and a lower scalability.
573                /// It mostly makes sense when using "linkstate" routing mode where all nodes in the subsystem don't have
574                /// direct connectivity with each other.
575                multihop: Option<bool>,
576                /// Which type of Zenoh instances to send gossip messages to.
577                target: Option<ModeDependentValue<WhatAmIMatcher>>,
578                /// Which type of Zenoh instances to automatically establish sessions with upon discovery through gossip.
579                autoconnect: Option<ModeDependentValue<WhatAmIMatcher>>,
580                /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly.
581                autoconnect_strategy: Option<ModeDependentValue<TargetDependentValue<AutoConnectStrategy>>>,
582            },
583        },
584
585        /// Configuration of data messages timestamps management.
586        pub timestamping: #[derive(Default)]
587        TimestampingConf {
588            /// Whether data messages should be timestamped if not already.
589            enabled: Option<ModeDependentValue<bool>>,
590            /// Whether data messages with timestamps in the future should be dropped or not.
591            /// If set to false (default), messages with timestamps in the future are retimestamped.
592            /// Timestamps are ignored if timestamping is disabled.
593            drop_future_timestamp: Option<bool>,
594        },
595
596        /// The default timeout to apply to queries in milliseconds.
597        queries_default_timeout: Option<u64>,
598
599        /// The routing strategy to use and it's configuration.
600        pub routing: #[derive(Default)]
601        RoutingConf {
602            /// The routing strategy to use in routers and it's configuration.
603            pub router: #[derive(Default)]
604            RouterRoutingConf {
605                /// Deprecated: this field has no effect and will be removed in a future version.
606                #[serde(default, skip_serializing)]
607                peers_failover_brokering: DeprecatedPeersFailoverBrokering,
608                /// Linkstate mode configuration.
609                pub linkstate: #[derive(Default)]
610                LinkstateConf {
611                    /// Weights of the outgoing links in linkstate mode.
612                    /// If none of the two endpoint nodes of a transport specifies its weight, a weight of 100 is applied.
613                    /// If only one of the two endpoint nodes of a transport specifies its weight, the specified weight is applied.
614                    /// If both endpoint nodes of a transport specify its weight, the greater weight is applied.
615                    pub transport_weights: Vec<TransportWeight>,
616                },
617            },
618            /// Deprecated: these fields have no effect and will be removed in a future version.
619            #[serde(default, skip_serializing)]
620            peer: DeprecatedRoutingPeer,
621            /// The interests-based routing configuration.
622            /// This configuration applies regardless of the mode (router, peer or client).
623            pub interests: #[derive(Default)]
624            InterestsConf {
625                /// The timeout to wait for incoming interests declarations.
626                timeout: Option<u64>,
627            },
628        },
629
630        /// The declarations aggregation strategy.
631        pub aggregation: #[derive(Default)]
632        AggregationConf {
633            /// A list of key-expressions for which all included subscribers will be aggregated into.
634            subscribers: Vec<OwnedKeyExpr>,
635            /// A list of key-expressions for which all included publishers will be aggregated into.
636            publishers: Vec<OwnedKeyExpr>,
637        },
638
639        /// Overwrite QoS options for Zenoh messages by key expression (ignores Zenoh API QoS config)
640        pub qos: #[derive(Default)]
641        QoSConfig {
642            /// A list of QoS configurations for PUT and DELETE messages by key expressions
643            publication: PublisherQoSConfList,
644            /// Configuration of the qos overwrite interceptor rules
645            network: Vec<QosOverwriteItemConf>,
646        },
647
648        pub transport: #[derive(Default)]
649        TransportConf {
650            pub unicast: TransportUnicastConf {
651                /// Timeout in milliseconds when opening a link (default: 10000).
652                open_timeout: u64,
653                /// Timeout in milliseconds when accepting a link (default: 10000).
654                accept_timeout: u64,
655                /// Number of links that may stay pending during accept phase (default: 100).
656                accept_pending: usize,
657                /// Maximum number of unicast sessions (default: 1000)
658                max_sessions: usize,
659                /// Maximum number of unicast incoming links per transport session (default: 1)
660                /// If set to a value greater than 1, multiple outgoing links are also allowed;
661                /// otherwise, only one outgoing link is allowed.
662                /// Issue https://github.com/eclipse-zenoh/zenoh/issues/1533
663                max_links: usize,
664                /// Enables the LowLatency transport (default `false`).
665                /// This option does not make LowLatency transport mandatory, the actual implementation of transport
666                /// used will depend on Establish procedure and other party's settings
667                lowlatency: bool,
668                pub qos: QoSUnicastConf {
669                    /// Whether QoS is enabled or not.
670                    /// If set to `false`, the QoS will be disabled. (default `true`).
671                    enabled: bool
672                },
673                pub compression: CompressionUnicastConf {
674                    /// You must compile zenoh with "transport_compression" feature to be able to enable compression.
675                    /// When enabled is true, batches will be sent compressed. (default `false`).
676                    enabled: bool,
677                },
678            },
679            pub multicast: TransportMulticastConf {
680                /// Link join interval duration in milliseconds (default: 2500)
681                join_interval: Option<u64>,
682                /// Maximum number of multicast sessions (default: 1000)
683                max_sessions: Option<usize>,
684                pub qos: QoSMulticastConf {
685                    /// Whether QoS is enabled or not.
686                    /// If set to `false`, the QoS will be disabled. (default `false`).
687                    enabled: bool
688                },
689                pub compression: CompressionMulticastConf {
690                    /// You must compile zenoh with "transport_compression" feature to be able to enable compression.
691                    /// When enabled is true, batches will be sent compressed. (default `false`).
692                    enabled: bool,
693                },
694            },
695            pub link: #[derive(Default)]
696            TransportLinkConf {
697                // An optional whitelist of protocols to be used for accepting and opening sessions.
698                // If not configured, all the supported protocols are automatically whitelisted.
699                pub protocols: Option<Vec<String>>,
700                pub tx: LinkTxConf {
701                    /// The resolution in bits to be used for the message sequence numbers.
702                    /// When establishing a session with another Zenoh instance, the lowest value of the two instances will be used.
703                    /// Accepted values: 8bit, 16bit, 32bit, 64bit.
704                    sequence_number_resolution: Bits where (sequence_number_resolution_validator),
705                    /// Link lease duration in milliseconds (default: 10000)
706                    lease: u64,
707                    /// Number of keep-alive messages in a link lease duration (default: 4)
708                    keep_alive: usize,
709                    /// Zenoh's MTU equivalent (default: 2^16-1) (max: 2^16-1)
710                    batch_size: BatchSize,
711                    pub queue: #[derive(Default)]
712                    QueueConf {
713                        /// The size of each priority queue indicates the number of batches a given queue can contain.
714                        /// The amount of memory being allocated for each queue is then SIZE_XXX * BATCH_SIZE.
715                        /// In the case of the transport link MTU being smaller than the ZN_BATCH_SIZE,
716                        /// then amount of memory being allocated for each queue is SIZE_XXX * LINK_MTU.
717                        /// If qos is false, then only the DATA priority will be allocated.
718                        pub size: QueueSizeConf {
719                            control: usize,
720                            real_time: usize,
721                            interactive_high: usize,
722                            interactive_low: usize,
723                            data_high: usize,
724                            data: usize,
725                            data_low: usize,
726                            background: usize,
727                        } where (queue_size_validator),
728                        /// Congestion occurs when the queue is empty (no available batch).
729                        /// Using CongestionControl::Block the caller is blocked until a batch is available and re-inserted into the queue.
730                        /// Using CongestionControl::Drop the message might be dropped, depending on conditions configured here.
731                        pub congestion_control: #[derive(Default)]
732                        CongestionControlConf {
733                            /// Behavior pushing CongestionControl::Drop messages to the queue.
734                            pub drop: CongestionControlDropConf {
735                                /// The maximum time in microseconds to wait for an available batch before dropping a droppable message
736                                /// if still no batch is available.
737                                wait_before_drop: i64,
738                                /// The maximum deadline limit for multi-fragment messages.
739                                max_wait_before_drop_fragments: i64,
740                            },
741                            /// Behavior pushing CongestionControl::Block messages to the queue.
742                            pub block: CongestionControlBlockConf {
743                                /// The maximum time in microseconds to wait for an available batch before closing the transport session
744                                /// when sending a blocking message if still no batch is available.
745                                wait_before_close: i64,
746                            },
747                        },
748                        pub batching: BatchingConf {
749                            /// Perform adaptive batching of messages if they are smaller of the batch_size.
750                            /// When the network is detected to not be fast enough to transmit every message individually, many small messages may be
751                            /// batched together and sent all at once on the wire reducing the overall network overhead. This is typically of a high-throughput
752                            /// scenario mainly composed of small messages. In other words, batching is activated by the network back-pressure.
753                            enabled: bool,
754                            /// The maximum time limit (in ms) a message should be retained for batching when back-pressure happens.
755                            time_limit: u64,
756                        },
757                        /// Perform lazy memory allocation of batches in the prioritiey queues. If set to false all batches are initialized at
758                        /// initialization time. If set to true the batches will be allocated when needed up to the maximum number of batches
759                        /// configured in the size configuration parameter.
760                        pub allocation: #[derive(Default, Copy, PartialEq, Eq)]
761                        QueueAllocConf {
762                            pub mode: QueueAllocMode,
763                        },
764                    },
765                    // Number of threads used for TX
766                    threads: usize,
767                },
768                pub rx: LinkRxConf {
769                    /// Receiving buffer size in bytes for each link
770                    /// The default the rx_buffer_size value is the same as the default batch size: 65535.
771                    /// For very high throughput scenarios, the rx_buffer_size can be increased to accommodate
772                    /// more in-flight data. This is particularly relevant when dealing with large messages.
773                    /// E.g. for 16MiB rx_buffer_size set the value to: 16777216.
774                    buffer_size: usize,
775                    /// Maximum size of the defragmentation buffer at receiver end (default: 1GiB).
776                    /// Fragmented messages that are larger than the configured size will be dropped.
777                    max_message_size: usize,
778                },
779                pub tls: #[derive(Default)]
780                TLSConf {
781                    root_ca_certificate: Option<String>,
782                    listen_private_key: Option<String>,
783                    listen_certificate: Option<String>,
784                    enable_mtls: Option<bool>,
785                    connect_private_key: Option<String>,
786                    connect_certificate: Option<String>,
787                    verify_name_on_connect: Option<bool>,
788                    close_link_on_expiration: Option<bool>,
789                    /// Configure TCP write buffer size
790                    pub so_sndbuf: Option<u32>,
791                    /// Configure TCP read buffer size
792                    pub so_rcvbuf: Option<u32>,
793                    // Skip serializing field because they contain secrets
794                    #[serde(skip_serializing)]
795                    root_ca_certificate_base64: Option<SecretValue>,
796                    #[serde(skip_serializing)]
797                    listen_private_key_base64:  Option<SecretValue>,
798                    #[serde(skip_serializing)]
799                    listen_certificate_base64: Option<SecretValue>,
800                    #[serde(skip_serializing)]
801                    connect_private_key_base64 :  Option<SecretValue>,
802                    #[serde(skip_serializing)]
803                    connect_certificate_base64 :  Option<SecretValue>,
804                },
805                pub tcp: #[derive(Default)]
806                TcpConf {
807                    /// Configure TCP write buffer size
808                    pub so_sndbuf: Option<u32>,
809                    /// Configure TCP read buffer size
810                    pub so_rcvbuf: Option<u32>,
811                },
812                pub unixpipe: #[derive(Default)]
813                UnixPipeConf {
814                    file_access_mask: Option<u32>
815                },
816            },
817            pub shared_memory:
818            ShmConf {
819                /// Whether shared memory is enabled or not.
820                /// If set to `true`, the SHM buffer optimization support will be announced to other parties. (default `true`).
821                /// This option doesn't make SHM buffer optimization mandatory, the real support depends on other party setting
822                /// A probing procedure for shared memory is performed upon session opening. To enable zenoh to operate
823                /// over shared memory (and to not fallback on network mode), shared memory needs to be enabled also on the
824                /// subscriber side. By doing so, the probing procedure will succeed and shared memory will operate as expected.
825                enabled: bool,
826                /// SHM resources initialization mode (default "lazy").
827                /// - "lazy": SHM subsystem internals will be initialized lazily upon the first SHM buffer
828                /// allocation or reception. This setting provides better startup time and optimizes resource usage,
829                /// but produces extra latency at the first SHM buffer interaction.
830                /// - "init": SHM subsystem internals will be initialized upon Session opening. This setting sacrifices
831                /// startup time, but guarantees no latency impact when first SHM buffer is processed.
832                mode: ShmInitMode,
833                pub transport_optimization:
834                LargeMessageTransportOpt {
835                    /// Enables transport optimization for large messages (default `true`).
836                    /// Implicitly puts large messages into shared memory for transports with SHM-compatible connection.
837                    enabled: bool,
838                    /// SHM arena size in bytes used for transport optimization (default `16 * 1024 * 1024`).
839                    pool_size: NonZeroUsize,
840                    /// Allow optimization for messages equal or larger than this threshold in bytes (default `3072`).
841                    message_size_threshold: usize,
842                    /// The categories of messages the *implicit* SHM optimization is applied
843                    /// to, i.e. for which a large enough regular payload is automatically
844                    /// copied into shared memory (default: `put`, `query`, `reply`).
845                    /// Payloads the application already allocated in shared memory are always
846                    /// sent over SHM when the peer supports it, regardless of this list.
847                    messages: Vec<DataMessage>,
848                },
849            },
850            pub auth: #[derive(Default)]
851            AuthConf {
852                /// The configuration of authentication.
853                /// A password implies a username is required.
854                pub usrpwd: #[derive(Default)]
855                UsrPwdConf {
856                    user: Option<String>,
857                    password: Option<String>,
858                    /// The path to a file containing the user password dictionary, a file containing `<user>:<password>`
859                    dictionary_file: Option<String>,
860                } where (user_conf_validator),
861                pub pubkey: #[derive(Default)]
862                PubKeyConf {
863                    public_key_pem: Option<String>,
864                    private_key_pem: Option<String>,
865                    public_key_file: Option<String>,
866                    private_key_file: Option<String>,
867                    key_size: Option<usize>,
868                    known_keys_file: Option<String>,
869                },
870            },
871
872        },
873        /// Configuration of the admin space.
874        pub adminspace: #[derive(Default)]
875        /// <div class="stab unstable">
876        ///   <span class="emoji">🔬</span>
877        ///   This API has been marked as unstable: it works as advertised, but we may change it in a future release.
878        ///   To use it, you must enable zenoh's <code>unstable</code> feature flag.
879        /// </div>
880        AdminSpaceConf {
881            /// Enable the admin space
882            #[serde(default = "set_false")]
883            pub enabled: bool,
884            /// Permissions on the admin space
885            pub permissions:
886            PermissionsConf {
887                /// Whether the admin space replies to queries (true by default).
888                #[serde(default = "set_true")]
889                pub read: bool,
890                /// Whether the admin space accepts config changes at runtime (false by default).
891                #[serde(default = "set_false")]
892                pub write: bool,
893            },
894
895        },
896
897        /// Namespace prefix.
898        /// If not None, all outgoing key expressions will be
899        /// automatically prefixed with specified string,
900        /// and all incoming key expressions will be stripped
901        /// of specified prefix.
902        /// Namespace is applied to the session.
903        /// E. g. if session has a namespace of "1" then session.put("my/keyexpr", message),
904        /// will put a message into "1/my/keyexpr". Same applies to all other operations within this session.
905        pub namespace: Option<OwnedNonWildKeyExpr>,
906
907        /// Configuration of the downsampling.
908        downsampling: Vec<DownsamplingItemConf>,
909
910        /// Configuration of the access control (ACL)
911        pub access_control: AclConfig {
912            pub enabled: bool,
913            pub default_permission: Permission,
914            pub rules: Option<Vec<AclConfigRule>>,
915            pub subjects: Option<Vec<AclConfigSubjects>>,
916            pub policies: Option<Vec<AclConfigPolicyEntry>>,
917        },
918
919        /// Configuration of the low-pass filter
920        pub low_pass_filter: Vec<LowPassFilterConf>,
921
922        /// Configuration of the stats per keyexpr
923        pub stats: #[derive(Default, PartialEq, Eq)] StatsConfig {
924            filters: Vec<StatsFilterConfig>,
925        },
926
927        /// A list of directories where plugins may be searched for if no `__path__` was specified for them.
928        /// The executable's current directory will be added to the search paths.
929        pub plugins_loading: #[derive(Default)]
930        PluginsLoading {
931            pub enabled: bool,
932            pub search_dirs: LibSearchDirs,
933        },
934        #[validated(recursive_accessors)]
935        /// The configuration for plugins.
936        ///
937        /// Please refer to [`PluginsConfig`]'s documentation for further details.
938        plugins: PluginsConfig,
939    }
940}
941
942#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
943#[serde(rename_all = "snake_case")]
944pub enum QueueAllocMode {
945    Init,
946    #[default]
947    Lazy,
948}
949
950#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
951#[serde(rename_all = "snake_case")]
952pub enum ShmInitMode {
953    Init,
954    #[default]
955    Lazy,
956}
957
958impl Default for PermissionsConf {
959    fn default() -> Self {
960        PermissionsConf {
961            read: true,
962            write: false,
963        }
964    }
965}
966
967fn set_true() -> bool {
968    true
969}
970fn set_false() -> bool {
971    false
972}
973
974#[test]
975fn config_deser() {
976    let config = Config::from_deserializer(
977        &mut json5::Deserializer::from_str(
978            r#"{
979        scouting: {
980          multicast: {
981            enabled: false,
982            autoconnect: ["peer", "router"]
983          }
984        }
985      }"#,
986        )
987        .unwrap(),
988    )
989    .unwrap();
990    assert_eq!(*config.scouting().multicast().enabled(), Some(false));
991    assert_eq!(
992        config.scouting().multicast().autoconnect().router(),
993        Some(&WhatAmIMatcher::empty().router().peer())
994    );
995    assert_eq!(
996        config.scouting().multicast().autoconnect().peer(),
997        Some(&WhatAmIMatcher::empty().router().peer())
998    );
999    assert_eq!(
1000        config.scouting().multicast().autoconnect().client(),
1001        Some(&WhatAmIMatcher::empty().router().peer())
1002    );
1003    let config = Config::from_deserializer(
1004        &mut json5::Deserializer::from_str(
1005            r#"{
1006        scouting: {
1007          multicast: {
1008            enabled: false,
1009            autoconnect: {router: [], peer: ["peer", "router"]}
1010          }
1011        }
1012      }"#,
1013        )
1014        .unwrap(),
1015    )
1016    .unwrap();
1017    assert_eq!(*config.scouting().multicast().enabled(), Some(false));
1018    assert_eq!(
1019        config.scouting().multicast().autoconnect().router(),
1020        Some(&WhatAmIMatcher::empty())
1021    );
1022    assert_eq!(
1023        config.scouting().multicast().autoconnect().peer(),
1024        Some(&WhatAmIMatcher::empty().router().peer())
1025    );
1026    assert_eq!(config.scouting().multicast().autoconnect().client(), None);
1027    let config = Config::from_deserializer(
1028        &mut json5::Deserializer::from_str(
1029            r#"{transport: { auth: { usrpwd: { user: null, password: null, dictionary_file: "file" }}}}"#,
1030        )
1031            .unwrap(),
1032    )
1033        .unwrap();
1034    assert_eq!(
1035        config
1036            .transport()
1037            .auth()
1038            .usrpwd()
1039            .dictionary_file()
1040            .as_ref()
1041            .map(|s| s.as_ref()),
1042        Some("file")
1043    );
1044    std::mem::drop(Config::from_deserializer(
1045        &mut json5::Deserializer::from_str(
1046            r#"{transport: { auth: { usrpwd: { user: null, password: null, user_password_dictionary: "file" }}}}"#,
1047        )
1048            .unwrap(),
1049    )
1050        .unwrap_err());
1051
1052    let config = Config::from_deserializer(
1053        &mut json5::Deserializer::from_str(
1054            r#"{
1055              qos: {
1056                network: [
1057                  {
1058                    messages: ["put"],
1059                    overwrite: {
1060                      priority: "foo",
1061                    },
1062                  },
1063                ],
1064              }
1065            }"#,
1066        )
1067        .unwrap(),
1068    );
1069    assert!(config.is_err());
1070
1071    let config = Config::from_deserializer(
1072        &mut json5::Deserializer::from_str(
1073            r#"{
1074              qos: {
1075                network: [
1076                  {
1077                    messages: ["put"],
1078                    overwrite: {
1079                      priority: +8,
1080                    },
1081                  },
1082                ],
1083              }
1084            }"#,
1085        )
1086        .unwrap(),
1087    );
1088    assert!(config.is_err());
1089
1090    let config = Config::from_deserializer(
1091        &mut json5::Deserializer::from_str(
1092            r#"{
1093              qos: {
1094                network: [
1095                  {
1096                    messages: ["put"],
1097                    overwrite: {
1098                      priority: "data_high",
1099                    },
1100                  },
1101                ],
1102              }
1103            }"#,
1104        )
1105        .unwrap(),
1106    )
1107    .unwrap();
1108    assert_eq!(
1109        config.qos().network().first().unwrap().overwrite.priority,
1110        Some(qos::PriorityUpdateConf::Priority(
1111            qos::PriorityConf::DataHigh
1112        ))
1113    );
1114
1115    let config = Config::from_deserializer(
1116        &mut json5::Deserializer::from_str(
1117            r#"{
1118              qos: {
1119                network: [
1120                  {
1121                    messages: ["put"],
1122                    overwrite: {
1123                      priority: +1,
1124                    },
1125                  },
1126                ],
1127              }
1128            }"#,
1129        )
1130        .unwrap(),
1131    )
1132    .unwrap();
1133    assert_eq!(
1134        config.qos().network().first().unwrap().overwrite.priority,
1135        Some(qos::PriorityUpdateConf::Increment(1))
1136    );
1137
1138    let config = Config::from_deserializer(
1139        &mut json5::Deserializer::from_str(
1140            r#"{
1141              qos: {
1142                network: [
1143                  {
1144                    messages: ["put"],
1145                    payload_size: "0..99",
1146                    overwrite: {},
1147                  },
1148                ],
1149              }
1150            }"#,
1151        )
1152        .unwrap(),
1153    )
1154    .unwrap();
1155    assert_eq!(
1156        config
1157            .qos()
1158            .network()
1159            .first()
1160            .unwrap()
1161            .payload_size
1162            .as_ref()
1163            .map(|r| (r.start_bound(), r.end_bound())),
1164        Some((Bound::Included(&0), Bound::Included(&99)))
1165    );
1166
1167    let config = Config::from_deserializer(
1168        &mut json5::Deserializer::from_str(
1169            r#"{
1170              qos: {
1171                network: [
1172                  {
1173                    messages: ["put"],
1174                    payload_size: "100..",
1175                    overwrite: {},
1176                  },
1177                ],
1178              }
1179            }"#,
1180        )
1181        .unwrap(),
1182    )
1183    .unwrap();
1184    assert_eq!(
1185        config
1186            .qos()
1187            .network()
1188            .first()
1189            .unwrap()
1190            .payload_size
1191            .as_ref()
1192            .map(|r| (r.start_bound(), r.end_bound())),
1193        Some((Bound::Included(&100), Bound::Unbounded))
1194    );
1195
1196    let config = Config::from_deserializer(
1197        &mut json5::Deserializer::from_str(
1198            r#"{
1199              qos: {
1200                network: [
1201                  {
1202                    messages: ["put"],
1203                    qos: {
1204                      congestion_control: "drop",
1205                      priority: "data",
1206                      express: true,
1207                      reliability: "reliable",
1208                    },
1209                    overwrite: {},
1210                  },
1211                ],
1212              }
1213            }"#,
1214        )
1215        .unwrap(),
1216    )
1217    .unwrap();
1218    assert_eq!(
1219        config.qos().network().first().unwrap().qos,
1220        Some(QosFilter {
1221            congestion_control: Some(qos::CongestionControlConf::Drop),
1222            priority: Some(qos::PriorityConf::Data),
1223            express: Some(true),
1224            reliability: Some(qos::ReliabilityConf::Reliable),
1225        })
1226    );
1227
1228    let config = Config::from_deserializer(
1229        &mut json5::Deserializer::from_str(
1230            r#"{
1231                mode: "client",
1232                connect: {
1233                    endpoints: [
1234                        { strategy: "allOf", locators: ["tcp/127.0.0.1:7447?rel=0", "tcp/127.0.0.1:7448?rel=1"] },
1235                    ]
1236                }
1237            }"#,
1238        )
1239        .unwrap(),
1240    )
1241    .unwrap();
1242    assert_eq!(*config.mode(), Some(WhatAmI::Client));
1243    let endpoints = config.connect().endpoints().client().unwrap();
1244    assert_eq!(endpoints.len(), 1);
1245    assert_eq!(
1246        endpoints[0],
1247        EndPoints::Locators(zenoh_protocol::core::Locators {
1248            strategy: zenoh_protocol::core::LocatorsStrategy::AllOf,
1249            locators: vec![
1250                EndPoint::from_str("tcp/127.0.0.1:7447?rel=0").unwrap(),
1251                EndPoint::from_str("tcp/127.0.0.1:7448?rel=1").unwrap()
1252            ]
1253        })
1254    );
1255
1256    dbg!(Config::from_file("../../DEFAULT_CONFIG.json5").unwrap());
1257}
1258
1259impl Config {
1260    pub fn insert<'d, D: serde::Deserializer<'d>>(
1261        &mut self,
1262        key: &str,
1263        value: D,
1264    ) -> Result<(), validated_struct::InsertionError>
1265    where
1266        validated_struct::InsertionError: From<D::Error>,
1267    {
1268        <Self as ValidatedMap>::insert(self, key, value)
1269    }
1270
1271    pub fn get(
1272        &self,
1273        key: &str,
1274    ) -> Result<<Self as ValidatedMapAssociatedTypes<'_>>::Accessor, GetError> {
1275        <Self as ValidatedMap>::get(self, key)
1276    }
1277
1278    pub fn get_json(&self, key: &str) -> Result<String, GetError> {
1279        <Self as ValidatedMap>::get_json(self, key)
1280    }
1281
1282    pub fn insert_json5(
1283        &mut self,
1284        key: &str,
1285        value: &str,
1286    ) -> Result<(), validated_struct::InsertionError> {
1287        <Self as ValidatedMap>::insert_json5(self, key, value)
1288    }
1289
1290    /// Tries to insert or update a JSON5 object in an array using a field filter
1291    /// in the last key segment.
1292    ///
1293    /// A key of the form `<array-key>/<field-name>=<field-value>` addresses an
1294    /// object inside the array stored at `<array-key>`. The `<field-name>` part
1295    /// is not a config child key; it is a filter applied to objects contained in
1296    /// the array. For example, `qos/network/id=rule1` loads the array at
1297    /// `qos/network` and matches objects whose `id` field is the string `rule1`.
1298    ///
1299    /// When a field filter is present, `value` must be a single JSON5 object
1300    /// containing the same string field value. The object replaces the first
1301    /// matching array element, or is appended if none exists.
1302    ///
1303    /// Returns `true` if the field-filter operation was applied. If `key` does
1304    /// not contain a field filter, this returns `false` and leaves the config
1305    /// unchanged.
1306    pub fn try_insert_json5_array_item(
1307        &mut self,
1308        key: &str,
1309        value: &str,
1310    ) -> Result<bool, validated_struct::InsertionError> {
1311        let Some((prefix, field_value)) = key.split_once('=') else {
1312            return Ok(false);
1313        };
1314        let (array_key, field_name) =
1315            prefix
1316                .rsplit_once('/')
1317                .ok_or(validated_struct::InsertionError::Str(
1318                    "missing field filter",
1319                ))?;
1320        let new_item = json5::from_str::<serde_json::Value>(value)?;
1321        if new_item
1322            .as_object()
1323            .and_then(|map| map.get(field_name))
1324            .and_then(|v| v.as_str())
1325            != Some(field_value)
1326        {
1327            return Err(validated_struct::InsertionError::String(format!(
1328                "field filter mismatch: value must be an object containing {field_name}=\"{field_value}\""
1329            )));
1330        }
1331        let current = serde_json::from_str::<serde_json::Value>(
1332            &self
1333                .get_json(array_key)
1334                .map_err(|err| validated_struct::InsertionError::String(err.to_string()))?,
1335        )?;
1336        let serde_json::Value::Array(mut list) = current else {
1337            return Err(validated_struct::InsertionError::Str("not an array"));
1338        };
1339        let mut new_item = Some(new_item);
1340        for item in list.iter_mut() {
1341            let serde_json::Value::Object(map) = item else {
1342                return Err(validated_struct::InsertionError::Str(
1343                    "array item is not an object",
1344                ));
1345            };
1346
1347            if map.get(field_name).and_then(|v| v.as_str()) == Some(field_value) {
1348                *item = new_item.take().unwrap();
1349                break;
1350            }
1351        }
1352        if let Some(new_item) = new_item {
1353            list.push(new_item);
1354        }
1355        <Self as ValidatedMap>::insert_json5(self, array_key, &serde_json::to_string(&list)?)?;
1356        Ok(true)
1357    }
1358
1359    pub fn keys(&self) -> impl Iterator<Item = String> {
1360        <Self as ValidatedMap>::keys(self).into_iter()
1361    }
1362
1363    pub fn set_plugin_validator<T: ConfigValidator + 'static>(&mut self, validator: Weak<T>) {
1364        self.plugins.validator = validator;
1365    }
1366
1367    pub fn plugin(&self, name: &str) -> Option<&Value> {
1368        self.plugins.values.get(name)
1369    }
1370
1371    pub fn sift_privates(&self) -> Self {
1372        let mut copy = self.clone();
1373        copy.plugins.sift_privates();
1374        copy
1375    }
1376
1377    pub fn remove<K: AsRef<str>>(&mut self, key: K) -> ZResult<()> {
1378        let key = key.as_ref();
1379
1380        let key = key.strip_prefix('/').unwrap_or(key);
1381        if !key.starts_with("plugins/") {
1382            bail!(
1383                "Removal of values from Config is only supported for keys starting with `plugins/`"
1384            )
1385        }
1386        self.plugins.remove(&key["plugins/".len()..])
1387    }
1388
1389    /// Tries to remove objects from an array using a field filter in the last
1390    /// key segment.
1391    ///
1392    /// A key of the form `<array-key>/<field-name>=<field-value>` removes every
1393    /// object from the array stored at `<array-key>` whose `<field-name>` field
1394    /// is the string `<field-value>`. The `<field-name>` part is not a config
1395    /// child key; it is a filter applied to objects contained in the array. For
1396    /// example, `qos/network/id=rule1` removes objects from `qos/network` where
1397    /// `id == "rule1"`.
1398    ///
1399    /// Returns `true` if the field-filter operation was applied. If `key` does
1400    /// not contain a field filter, this returns `false` and leaves the config
1401    /// unchanged.
1402    pub fn try_remove_json5_array_item<K: AsRef<str>>(&mut self, key: K) -> ZResult<bool> {
1403        let key = key.as_ref();
1404        let Some((prefix, field_value)) = key.split_once('=') else {
1405            return Ok(false);
1406        };
1407        let (array_key, field_name) = prefix.rsplit_once('/').ok_or("missing field filter")?;
1408        let current = serde_json::from_str::<serde_json::Value>(
1409            &self.get_json(array_key).map_err(|err| zerror!("{err}"))?,
1410        )?;
1411        let serde_json::Value::Array(mut list) = current else {
1412            bail!("not an array")
1413        };
1414        let prev_len = list.len();
1415        list.retain(|item| match item {
1416            serde_json::Value::Object(map) => {
1417                map.get(field_name).and_then(|v| v.as_str()) != Some(field_value)
1418            }
1419            _ => true,
1420        });
1421        if list.len() != prev_len {
1422            self.insert_json5(array_key, &serde_json::to_string(&list)?)?;
1423        }
1424        Ok(true)
1425    }
1426
1427    pub fn get_retry_config(
1428        &self,
1429        endpoint: Option<&EndPoint>,
1430        listen: bool,
1431    ) -> ConnectionRetryConf {
1432        get_retry_config(self, endpoint, listen)
1433    }
1434}
1435
1436#[derive(Debug)]
1437pub enum ConfigOpenErr {
1438    IoError(std::io::Error),
1439    JsonParseErr(json5::Error),
1440    InvalidConfiguration(Box<Config>),
1441}
1442impl std::fmt::Display for ConfigOpenErr {
1443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1444        match self {
1445            ConfigOpenErr::IoError(e) => write!(f, "Couldn't open file: {e}"),
1446            ConfigOpenErr::JsonParseErr(e) => write!(f, "JSON5 parsing error {e}"),
1447            ConfigOpenErr::InvalidConfiguration(c) => write!(
1448                f,
1449                "Invalid configuration {}",
1450                serde_json::to_string(c).unwrap()
1451            ),
1452        }
1453    }
1454}
1455impl std::error::Error for ConfigOpenErr {}
1456impl Config {
1457    pub fn from_file<P: AsRef<Path>>(path: P) -> ZResult<Self> {
1458        let path = path.as_ref();
1459        let mut config = Self::_from_file(path)?;
1460        config.plugins.load_external_configs()?;
1461        Ok(config)
1462    }
1463
1464    fn _from_file(path: &Path) -> ZResult<Config> {
1465        match std::fs::File::open(path) {
1466            Ok(mut f) => {
1467                let mut content = String::new();
1468                if let Err(e) = f.read_to_string(&mut content) {
1469                    bail!(e)
1470                }
1471                if content.is_empty() {
1472                    bail!("Empty config file");
1473                }
1474                match path
1475                    .extension()
1476                    .map(|s| s.to_str().unwrap())
1477                {
1478                    Some("json") | Some("json5") => match json5::Deserializer::from_str(&content) {
1479                        Ok(mut d) => Config::from_deserializer(&mut d).map_err(|e| match e {
1480                            Ok(c) => zerror!("Invalid configuration: {}", c).into(),
1481                            Err(e) => zerror!("JSON error: {:?}", e).into(),
1482                        }),
1483                        Err(e) => bail!(e),
1484                    },
1485                    Some("yaml") | Some("yml") => Config::from_deserializer(serde_yaml::Deserializer::from_str(&content)).map_err(|e| match e {
1486                        Ok(c) => zerror!("Invalid configuration: {}", c).into(),
1487                        Err(e) => zerror!("YAML error: {:?}", e).into(),
1488                    }),
1489                    #[cfg(feature = "unstable")]
1490                    Some("toml") => {
1491                        tracing::warn!("The TOML configuration format is unstable and may be removed in a future release");
1492                        match toml::Deserializer::parse(&content) {
1493                            Ok(de) => Config::from_deserializer(de).map_err(|e| match e {
1494                                Ok(c) => zerror!("Invalid configuration: {}", c).into(),
1495                                Err(e) => zerror!("TOML deserization error: {:?}", e).into(),
1496                            }),
1497                            Err(e) => bail!("TOML parsing error: {:?}", e),
1498                        }
1499                    },
1500                    Some(other) => bail!("Unsupported file type '.{}' (.json, .json5 and .yaml are supported)", other),
1501                    None => bail!("Unsupported file type. Configuration files must have an extension (.json, .json5 and .yaml supported)")
1502                }
1503            }
1504            Err(e) => bail!(e),
1505        }
1506    }
1507
1508    pub fn libloader(&self) -> LibLoader {
1509        if self.plugins_loading.enabled {
1510            LibLoader::new(self.plugins_loading.search_dirs().clone())
1511        } else {
1512            LibLoader::empty()
1513        }
1514    }
1515
1516    /// Expands the config with missing but required fields.
1517    ///
1518    /// This method should be called before a user-supplied config is used in the runtime.
1519    ///
1520    /// ## Invariants
1521    ///
1522    /// 1. All getter methods on [`ExpandedConfig`] are infallible (e.g. [`ExpandedConfig::id`] vs [`Config::id`]).
1523    pub fn expanded(mut self) -> ExpandedConfig {
1524        if self.id.is_none() {
1525            self.set_id(Some(ZenohId::default())).unwrap();
1526        }
1527
1528        if self.mode.is_none() {
1529            self.set_mode(Some(WhatAmI::default())).unwrap();
1530        }
1531
1532        ExpandedConfig(self)
1533    }
1534}
1535
1536#[doc(hidden)]
1537#[derive(Debug, Clone)]
1538pub struct ExpandedConfig(Config);
1539
1540impl ExpandedConfig {
1541    pub fn id(&self) -> ZenohId {
1542        self.0.id.unwrap()
1543    }
1544
1545    pub fn mode(&self) -> WhatAmI {
1546        self.0.mode.unwrap()
1547    }
1548}
1549
1550impl Deref for ExpandedConfig {
1551    type Target = Config;
1552
1553    fn deref(&self) -> &Self::Target {
1554        &self.0
1555    }
1556}
1557
1558impl DerefMut for ExpandedConfig {
1559    fn deref_mut(&mut self) -> &mut Self::Target {
1560        &mut self.0
1561    }
1562}
1563
1564impl std::fmt::Display for Config {
1565    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1566        serde_json::to_value(self)
1567            .map(|mut json| {
1568                sift_privates(&mut json);
1569                write!(f, "{json}")
1570            })
1571            .map_err(|e| {
1572                _ = write!(f, "{e:?}");
1573                fmt::Error
1574            })?
1575    }
1576}
1577
1578#[test]
1579fn config_from_json() {
1580    let from_str = serde_json::Deserializer::from_str;
1581    let mut config = Config::from_deserializer(&mut from_str(r#"{}"#)).unwrap();
1582    config
1583        .insert("transport/link/tx/lease", &mut from_str("168"))
1584        .unwrap();
1585    dbg!(std::mem::size_of_val(&config));
1586    println!("{}", serde_json::to_string_pretty(&config).unwrap());
1587}
1588
1589fn sequence_number_resolution_validator(b: &Bits) -> bool {
1590    b <= &Bits::from(TransportSn::MAX)
1591}
1592
1593fn queue_size_validator(q: &QueueSizeConf) -> bool {
1594    fn check(size: &usize) -> bool {
1595        (QueueSizeConf::MIN..=QueueSizeConf::MAX).contains(size)
1596    }
1597
1598    let QueueSizeConf {
1599        control,
1600        real_time,
1601        interactive_low,
1602        interactive_high,
1603        data_high,
1604        data,
1605        data_low,
1606        background,
1607    } = q;
1608    check(control)
1609        && check(real_time)
1610        && check(interactive_low)
1611        && check(interactive_high)
1612        && check(data_high)
1613        && check(data)
1614        && check(data_low)
1615        && check(background)
1616}
1617
1618fn user_conf_validator(u: &UsrPwdConf) -> bool {
1619    (u.password().is_none() && u.user().is_none()) || (u.password().is_some() && u.user().is_some())
1620}
1621
1622/// This part of the configuration is highly dynamic (any [`serde_json::Value`] may be put in there), but should follow this scheme:
1623/// ```javascript
1624/// plugins: {
1625///     // `plugin_name` must be unique per configuration, and will be used to find the appropriate
1626///     // dynamic library to load if no `__path__` is specified
1627///     [plugin_name]: {
1628///         // Defaults to `false`. Setting this to `true` does 2 things:
1629///         // * If `zenohd` fails to locate the requested plugin, it will crash instead of logging an error.
1630///         // * Plugins are expected to check this value to set their panic-behaviour: plugins are encouraged
1631///         //   to panic upon non-recoverable errors if their `__required__` flag is set to `true`, and to
1632///         //   simply log them otherwise
1633///         __required__: bool,
1634///         // The path(s) where the plugin is expected to be located.
1635///         // If none is specified, `zenohd` will search for a `<dylib_prefix>zenoh_plugin_<plugin_name>.<dylib_suffix>` file in the search directories.
1636///         // If any path is specified, file-search will be disabled, and the first path leading to
1637///         // an existing file will be used
1638///         __path__: string | [string],
1639///         // [plugin_name] may require additional configuration
1640///         ...
1641///     }
1642/// }
1643/// ```
1644#[derive(Clone)]
1645pub struct PluginsConfig {
1646    values: Value,
1647    validator: std::sync::Weak<dyn ConfigValidator>,
1648}
1649fn sift_privates(value: &mut serde_json::Value) {
1650    match value {
1651        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => {}
1652        Value::Array(a) => a.iter_mut().for_each(sift_privates),
1653        Value::Object(o) => {
1654            o.remove("private");
1655            o.values_mut().for_each(sift_privates);
1656        }
1657    }
1658}
1659
1660fn load_external_plugin_config(title: &str, value: &mut Value) -> ZResult<()> {
1661    let Some(values) = value.as_object_mut() else {
1662        bail!("{} must be object", title);
1663    };
1664    recursive_include(title, values, HashSet::new(), "__config__", ".")
1665}
1666
1667#[derive(Debug, Clone)]
1668pub struct PluginLoad {
1669    pub id: String,
1670    pub name: String,
1671    pub paths: Option<Vec<String>>,
1672    pub required: bool,
1673}
1674impl PluginsConfig {
1675    pub fn sift_privates(&mut self) {
1676        sift_privates(&mut self.values);
1677    }
1678    fn load_external_configs(&mut self) -> ZResult<()> {
1679        let Some(values) = self.values.as_object_mut() else {
1680            bail!("plugins configuration must be an object")
1681        };
1682        for (name, value) in values.iter_mut() {
1683            load_external_plugin_config(format!("plugins.{}", name.as_str()).as_str(), value)?;
1684        }
1685        Ok(())
1686    }
1687    pub fn load_requests(&'_ self) -> impl Iterator<Item = PluginLoad> + '_ {
1688        self.values.as_object().unwrap().iter().map(|(id, value)| {
1689            let value = value.as_object().expect("Plugin configurations must be objects");
1690            let required = match value.get("__required__") {
1691                None => false,
1692                Some(Value::Bool(b)) => *b,
1693                _ => panic!("Plugin '{id}' has an invalid '__required__' configuration property (must be a boolean)")
1694            };
1695            let name = match value.get("__plugin__") {
1696                Some(Value::String(p)) => p,
1697                _ => id,
1698            };
1699
1700            if let Some(paths) = value.get("__path__") {
1701                let paths = match paths {
1702                    Value::String(s) => vec![s.clone()],
1703                    Value::Array(a) => a.iter().map(|s| if let Value::String(s) = s { s.clone() } else { panic!("Plugin '{id}' has an invalid '__path__' configuration property (must be either string or array of strings)") }).collect(),
1704                    _ => panic!("Plugin '{id}' has an invalid '__path__' configuration property (must be either string or array of strings)")
1705                };
1706                PluginLoad { id: id.clone(), name: name.clone(), paths: Some(paths), required }
1707            } else {
1708                PluginLoad { id: id.clone(), name: name.clone(), paths: None, required }
1709            }
1710        })
1711    }
1712    pub fn remove(&mut self, key: &str) -> ZResult<()> {
1713        let mut split = key.split('/');
1714        let plugin = split.next().unwrap();
1715        let mut current = match split.next() {
1716            Some(first_in_plugin) => first_in_plugin,
1717            None => {
1718                self.values.as_object_mut().unwrap().remove(plugin);
1719                return Ok(());
1720            }
1721        };
1722        let (old_conf, mut new_conf) = match self.values.get_mut(plugin) {
1723            Some(plugin) => {
1724                let clone = plugin.clone();
1725                (plugin, clone)
1726            }
1727            None => bail!("No plugin {} to edit", plugin),
1728        };
1729        let mut remove_from = &mut new_conf;
1730        for next in split {
1731            match remove_from {
1732                Value::Object(o) => match o.get_mut(current) {
1733                    Some(v) => {
1734                        remove_from = unsafe {
1735                            std::mem::transmute::<&mut serde_json::Value, &mut serde_json::Value>(v)
1736                        }
1737                    }
1738                    None => bail!("{:?} has no {} property", o, current),
1739                },
1740                Value::Array(a) => {
1741                    let index: usize = current.parse()?;
1742                    if a.len() <= index {
1743                        bail!("{:?} cannot be indexed at {}", a, index)
1744                    }
1745                    remove_from = &mut a[index];
1746                }
1747                other => bail!("{} cannot be indexed", other),
1748            }
1749            current = next
1750        }
1751        match remove_from {
1752            Value::Object(o) => {
1753                if o.remove(current).is_none() {
1754                    bail!("{:?} has no {} property", o, current)
1755                }
1756            }
1757            Value::Array(a) => {
1758                let index: usize = current.parse()?;
1759                if a.len() <= index {
1760                    bail!("{:?} cannot be indexed at {}", a, index)
1761                }
1762                a.remove(index);
1763            }
1764            other => bail!("{} cannot be indexed", other),
1765        }
1766        let new_conf = if let Some(validator) = self.validator.upgrade() {
1767            match validator.check_config(
1768                plugin,
1769                &key[("plugins/".len() + plugin.len())..],
1770                old_conf.as_object().unwrap(),
1771                new_conf.as_object().unwrap(),
1772            )? {
1773                None => new_conf,
1774                Some(new_conf) => Value::Object(new_conf),
1775            }
1776        } else {
1777            new_conf
1778        };
1779        *old_conf = new_conf;
1780        Ok(())
1781    }
1782}
1783impl serde::Serialize for PluginsConfig {
1784    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1785    where
1786        S: serde::Serializer,
1787    {
1788        let mut value = self.values.clone();
1789        sift_privates(&mut value);
1790        value.serialize(serializer)
1791    }
1792}
1793impl Default for PluginsConfig {
1794    fn default() -> Self {
1795        Self {
1796            values: Value::Object(Default::default()),
1797            validator: std::sync::Weak::<()>::new(),
1798        }
1799    }
1800}
1801impl<'a> serde::Deserialize<'a> for PluginsConfig {
1802    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1803    where
1804        D: serde::Deserializer<'a>,
1805    {
1806        Ok(PluginsConfig {
1807            values: serde::Deserialize::deserialize(deserializer)?,
1808            validator: std::sync::Weak::<()>::new(),
1809        })
1810    }
1811}
1812
1813impl std::fmt::Debug for PluginsConfig {
1814    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1815        let mut values: Value = self.values.clone();
1816        sift_privates(&mut values);
1817        write!(f, "{values:?}")
1818    }
1819}
1820
1821trait PartialMerge: Sized {
1822    fn merge(self, path: &str, value: Self) -> Result<Self, validated_struct::InsertionError>;
1823}
1824impl PartialMerge for serde_json::Value {
1825    fn merge(
1826        mut self,
1827        path: &str,
1828        new_value: Self,
1829    ) -> Result<Self, validated_struct::InsertionError> {
1830        let mut value = &mut self;
1831        let mut key = path;
1832        let key_not_found = || {
1833            Err(validated_struct::InsertionError::String(format!(
1834                "{path} not found"
1835            )))
1836        };
1837        while !key.is_empty() {
1838            let (current, new_key) = validated_struct::split_once(key, '/');
1839            key = new_key;
1840            if current.is_empty() {
1841                continue;
1842            }
1843            value = match value {
1844                Value::Bool(_) | Value::Number(_) | Value::String(_) => return key_not_found(),
1845                Value::Null => match current {
1846                    "0" | "+" => {
1847                        *value = Value::Array(vec![Value::Null]);
1848                        &mut value[0]
1849                    }
1850                    _ => {
1851                        *value = Value::Object(Default::default());
1852                        value
1853                            .as_object_mut()
1854                            .unwrap()
1855                            .entry(current)
1856                            .or_insert(Value::Null)
1857                    }
1858                },
1859                Value::Array(a) => match current {
1860                    "+" => {
1861                        a.push(Value::Null);
1862                        a.last_mut().unwrap()
1863                    }
1864                    "0" if a.is_empty() => {
1865                        a.push(Value::Null);
1866                        a.last_mut().unwrap()
1867                    }
1868                    _ => match current.parse::<usize>() {
1869                        Ok(i) => match a.get_mut(i) {
1870                            Some(r) => r,
1871                            None => return key_not_found(),
1872                        },
1873                        Err(_) => return key_not_found(),
1874                    },
1875                },
1876                Value::Object(v) => v.entry(current).or_insert(Value::Null),
1877            }
1878        }
1879        *value = new_value;
1880        Ok(self)
1881    }
1882}
1883impl<'a> validated_struct::ValidatedMapAssociatedTypes<'a> for PluginsConfig {
1884    type Accessor = &'a dyn Any;
1885}
1886impl validated_struct::ValidatedMap for PluginsConfig {
1887    fn insert<'d, D: serde::Deserializer<'d>>(
1888        &mut self,
1889        key: &str,
1890        deserializer: D,
1891    ) -> Result<(), validated_struct::InsertionError>
1892    where
1893        validated_struct::InsertionError: From<D::Error>,
1894    {
1895        let (plugin, key) = validated_struct::split_once(key, '/');
1896        let new_value: Value = serde::Deserialize::deserialize(deserializer)?;
1897        let value = self
1898            .values
1899            .as_object_mut()
1900            .unwrap()
1901            .entry(plugin)
1902            .or_insert(Value::Null);
1903        let new_value = value.clone().merge(key, new_value)?;
1904        *value = if let Some(validator) = self.validator.upgrade() {
1905            // New plugin configuration for compare with original configuration.
1906            // Return error if it's not an object.
1907            // Note: it's ok if original "new_value" is not an object: this can be some subkey of the plugin configuration. But the result of the merge should be an object.
1908            // Error occurs  if the original plugin configuration is not an object itself (e.g. null).
1909            let Some(new_plugin_config) = new_value.as_object() else {
1910                return Err(format!(
1911                    "Attempt to provide non-object value as configuration for plugin `{plugin}`"
1912                )
1913                .into());
1914            };
1915            // Original plugin configuration for compare with new configuration.
1916            // If for some reason it's not defined or not an object, we default to an empty object.
1917            // Usually this happens when no plugin with this name defined. Reject then should be performed by the validator with `plugin not found` error.
1918            let empty_config = Map::new();
1919            let current_plugin_config = value.as_object().unwrap_or(&empty_config);
1920            match validator.check_config(plugin, key, current_plugin_config, new_plugin_config) {
1921                // Validator made changes to the proposed configuration, take these changes
1922                Ok(Some(val)) => Value::Object(val),
1923                // Validator accepted the proposed configuration as is
1924                Ok(None) => new_value,
1925                // Validator rejected the proposed configuration
1926                Err(e) => return Err(format!("{e}").into()),
1927            }
1928        } else {
1929            new_value
1930        };
1931        Ok(())
1932    }
1933    fn get<'a>(&'a self, mut key: &str) -> Result<&'a dyn Any, GetError> {
1934        let (current, new_key) = validated_struct::split_once(key, '/');
1935        key = new_key;
1936        let mut value = match self.values.get(current) {
1937            Some(matched) => matched,
1938            None => return Err(GetError::NoMatchingKey),
1939        };
1940        while !key.is_empty() {
1941            let (current, new_key) = validated_struct::split_once(key, '/');
1942            key = new_key;
1943            let matched = match value {
1944                serde_json::Value::Null
1945                | serde_json::Value::Bool(_)
1946                | serde_json::Value::Number(_)
1947                | serde_json::Value::String(_) => return Err(GetError::NoMatchingKey),
1948                serde_json::Value::Array(a) => a.get(match current.parse::<usize>() {
1949                    Ok(i) => i,
1950                    Err(_) => return Err(GetError::NoMatchingKey),
1951                }),
1952                serde_json::Value::Object(v) => v.get(current),
1953            };
1954            value = match matched {
1955                Some(matched) => matched,
1956                None => return Err(GetError::NoMatchingKey),
1957            }
1958        }
1959        Ok(value)
1960    }
1961
1962    type Keys = Vec<String>;
1963    fn keys(&self) -> Self::Keys {
1964        self.values.as_object().unwrap().keys().cloned().collect()
1965    }
1966
1967    fn get_json(&self, mut key: &str) -> Result<String, GetError> {
1968        let (current, new_key) = validated_struct::split_once(key, '/');
1969        key = new_key;
1970        let mut value = match self.values.get(current) {
1971            Some(matched) => matched,
1972            None => return Err(GetError::NoMatchingKey),
1973        };
1974        while !key.is_empty() {
1975            let (current, new_key) = validated_struct::split_once(key, '/');
1976            key = new_key;
1977            let matched = match value {
1978                serde_json::Value::Null
1979                | serde_json::Value::Bool(_)
1980                | serde_json::Value::Number(_)
1981                | serde_json::Value::String(_) => return Err(GetError::NoMatchingKey),
1982                serde_json::Value::Array(a) => a.get(match current.parse::<usize>() {
1983                    Ok(i) => i,
1984                    Err(_) => return Err(GetError::NoMatchingKey),
1985                }),
1986                serde_json::Value::Object(v) => v.get(current),
1987            };
1988            value = match matched {
1989                Some(matched) => matched,
1990                None => return Err(GetError::NoMatchingKey),
1991            }
1992        }
1993        Ok(serde_json::to_string(value).unwrap())
1994    }
1995}
1996
1997#[macro_export]
1998macro_rules! unwrap_or_default {
1999    ($val:ident$(.$field:ident($($param:ident)?))*) => {
2000        $val$(.$field($($param)?))*.clone().unwrap_or(zenoh_config::defaults$(::$field$(($param))?)*.into())
2001    };
2002}
2003
2004pub trait IConfig: Send + Sync {
2005    fn get(&self, key: &str) -> ZResult<String>;
2006    fn queries_default_timeout_ms(&self) -> u64;
2007    fn insert_json5(&self, key: &str, value: &str) -> ZResult<()>;
2008    fn to_json(&self) -> String;
2009}
2010
2011pub struct GenericConfig(Arc<dyn IConfig>);
2012
2013impl std::fmt::Debug for GenericConfig {
2014    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2015        f.debug_tuple("GenericConfig").field(&"..").finish()
2016    }
2017}
2018
2019impl Deref for GenericConfig {
2020    type Target = Arc<dyn IConfig>;
2021
2022    fn deref(&self) -> &Self::Target {
2023        &self.0
2024    }
2025}
2026
2027impl GenericConfig {
2028    pub fn new(value: Arc<dyn IConfig>) -> Self {
2029        GenericConfig(value)
2030    }
2031
2032    pub fn get_typed<T: for<'a> Deserialize<'a>>(&self, key: &str) -> ZResult<T> {
2033        self.0
2034            .get(key)
2035            .and_then(|v| serde_json::from_str::<T>(&v).map_err(|e| e.into()))
2036    }
2037
2038    pub fn get_plugin_config(&self, plugin_name: &str) -> ZResult<Value> {
2039        self.get(&("plugins/".to_owned() + plugin_name))
2040            .and_then(|v| serde_json::from_str(&v).map_err(|e| e.into()))
2041    }
2042}
2043
2044impl fmt::Display for GenericConfig {
2045    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2046        f.write_str(&self.0.to_json())
2047    }
2048}
2049
2050#[cfg(test)]
2051mod tests {
2052    use std::{env, fs::File, io::Write, str::FromStr, time::SystemTime};
2053
2054    use zenoh_protocol::core::{EndPoint, WhatAmI};
2055
2056    use crate::{Config, ModeDependentValue, ZenohId};
2057
2058    #[test]
2059    fn test_toml_config_format() {
2060        const FILE_CONTENTS: &str = r#"
2061            id = "abc"
2062            mode = "router"
2063
2064            [listen]
2065            endpoints = ["tcp/localhost:7448"]
2066
2067            [adminspace]
2068            enabled = true
2069        "#;
2070
2071        let timestamp = SystemTime::now()
2072            .duration_since(SystemTime::UNIX_EPOCH)
2073            .unwrap()
2074            .as_secs();
2075
2076        let path = env::temp_dir().join(format!("{timestamp}.test.config.toml"));
2077
2078        {
2079            let mut tmp = File::create(&path).unwrap();
2080            tmp.write_all(FILE_CONTENTS.as_bytes()).unwrap();
2081            tmp.flush().unwrap();
2082        }
2083
2084        let expected_config = {
2085            let mut c = Config::default();
2086            c.set_id(Some(ZenohId::from_str("abc").unwrap())).unwrap();
2087            c.set_mode(Some(WhatAmI::Router)).unwrap();
2088            c.listen
2089                .set_endpoints(ModeDependentValue::Unique(vec![EndPoint::from_str(
2090                    "tcp/localhost:7448",
2091                )
2092                .unwrap()]))
2093                .unwrap();
2094            c.adminspace.set_enabled(true).unwrap();
2095            c
2096        };
2097
2098        assert_eq!(
2099            Config::from_file(&path).unwrap().to_string(),
2100            expected_config.to_string()
2101        );
2102    }
2103
2104    #[test]
2105    fn insert_remove_json5_array_item_by_id() {
2106        let mut config = Config::default();
2107
2108        assert!(config
2109            .try_insert_json5_array_item(
2110                "qos/network/id=item1",
2111                r#"{
2112                        id: "item1",
2113                        messages: ["put"],
2114                        key_exprs: ["**"],
2115                        overwrite: { priority: "data" },
2116                        flows: ["egress"]
2117                    }"#,
2118            )
2119            .unwrap());
2120        assert!(config
2121            .try_insert_json5_array_item(
2122                "qos/network/id=item1",
2123                r#"{
2124                        id: "item1",
2125                        messages: ["put"],
2126                        key_exprs: ["**"],
2127                        overwrite: { priority: "data_high" },
2128                        flows: ["egress"]
2129                    }"#,
2130            )
2131            .unwrap());
2132
2133        let items: serde_json::Value =
2134            serde_json::from_str(&config.get_json("qos/network").unwrap()).unwrap();
2135        assert_eq!(items.as_array().unwrap().len(), 1);
2136        assert_eq!(items[0]["id"], "item1");
2137        assert_eq!(items[0]["overwrite"]["priority"], "data_high");
2138
2139        assert!(config
2140            .try_remove_json5_array_item("qos/network/id=item1")
2141            .unwrap());
2142        let items: serde_json::Value =
2143            serde_json::from_str(&config.get_json("qos/network").unwrap()).unwrap();
2144        assert_eq!(items.as_array().unwrap().len(), 0);
2145    }
2146}