• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TEN-framework / ten-framework / 20008373563

07 Dec 2025 06:18PM UTC coverage: 55.723% (+1.1%) from 54.616%
20008373563

push

github

web-flow
fix: coveralls app arguments (#1835)

* fix: coveralls app arguments

* fix: path consistency in lcov and work env

---------

Co-authored-by: Nie Zhihe <niezhihe@shengwang.cn>

45848 of 82278 relevant lines covered (55.72%)

795052.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.16
/core/src/ten_rust/src/graph/node/mod.rs
1
//
2
// Copyright © 2025 Agora
3
// This file is part of TEN Framework, an open source project.
4
// Licensed under the Apache License, Version 2.0, with certain conditions.
5
// Refer to the "LICENSE" file in the root directory for more information.
6
//
7
use anyhow::Result;
8
use serde::{Deserialize, Serialize};
9

10
use crate::{
11
    constants::{
12
        ERR_MSG_GRAPH_LOCALHOST_FORBIDDEN_IN_MULTI_APP_MODE,
13
        ERR_MSG_GRAPH_LOCALHOST_FORBIDDEN_IN_SINGLE_APP_MODE,
14
    },
15
    graph::{connection::GraphLoc, is_app_default_loc_or_none, AppUriDeclarationState},
16
    pkg_info::localhost,
17
};
18

19
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
20
#[serde(rename_all = "lowercase")]
21
pub enum GraphNodeType {
22
    Extension,
23
    Subgraph,
24
    Selector,
25
}
26

27
/// Represents an extension node in the graph
28
#[derive(Serialize, Deserialize, Debug, Clone)]
29
pub struct ExtensionNode {
30
    pub name: String,
31
    pub addon: String,
32

33
    /// The extension group this node belongs to. Extension group nodes
34
    /// themselves do not contain this field, as they define groups rather
35
    /// than belong to them.
36
    #[serde(skip_serializing_if = "Option::is_none")]
37
    pub extension_group: Option<String>,
38

39
    #[serde(skip_serializing_if = "is_app_default_loc_or_none")]
40
    pub app: Option<String>,
41

42
    #[serde(skip_serializing_if = "Option::is_none")]
43
    pub property: Option<serde_json::Value>,
44
}
45

46
/// Represents a subgraph node in the graph
47
#[derive(Serialize, Deserialize, Debug, Clone)]
48
pub struct SubgraphNode {
49
    pub name: String,
50

51
    #[serde(skip_serializing_if = "Option::is_none")]
52
    pub property: Option<serde_json::Value>,
53

54
    pub graph: GraphResource,
55
}
56

57
#[derive(Serialize, Deserialize, Debug, Clone)]
58
pub struct GraphResource {
59
    pub import_uri: String,
60
}
61

62
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
63
pub enum FilterOperator {
64
    #[serde(rename = "exact")]
65
    Exact,
66
    #[serde(rename = "regex")]
67
    Regex,
68
}
69

70
#[derive(Serialize, Deserialize, Debug, Clone)]
71
pub struct AtomicFilter {
72
    pub field: String,
73
    pub operator: FilterOperator,
74
    pub value: String,
75
}
76

77
#[derive(Serialize, Deserialize, Debug, Clone)]
78
#[serde(untagged)]
79
pub enum Filter {
80
    Atomic(AtomicFilter),
81
    And { and: Vec<Filter> },
82
    Or { or: Vec<Filter> },
83
}
84

85
impl Filter {
86
    pub fn as_atomic_filter(&self) -> Option<&AtomicFilter> {
5✔
87
        match self {
5✔
88
            Filter::Atomic(atomic) => Some(atomic),
5✔
89
            _ => None,
×
90
        }
91
    }
5✔
92

93
    pub fn as_and_filter(&self) -> Option<&[Filter]> {
1✔
94
        match self {
1✔
95
            Filter::And {
96
                and,
1✔
97
            } => Some(and),
1✔
98
            _ => None,
×
99
        }
100
    }
1✔
101

102
    pub fn as_or_filter(&self) -> Option<&[Filter]> {
1✔
103
        match self {
1✔
104
            Filter::Or {
105
                or,
1✔
106
            } => Some(or),
1✔
107
            _ => None,
×
108
        }
109
    }
1✔
110
}
111

112
/// Represents a selector node in the graph
113
#[derive(Serialize, Deserialize, Debug, Clone)]
114
pub struct SelectorNode {
115
    pub name: String,
116

117
    pub filter: Filter,
118
}
119

120
/// Represents a node in a graph. This enum represents different types of nodes
121
/// that can exist in the graph.
122
#[derive(Serialize, Deserialize, Debug, Clone)]
123
#[serde(tag = "type", rename_all = "lowercase")]
124
pub enum GraphNode {
125
    Extension {
126
        #[serde(flatten)]
127
        content: ExtensionNode,
128
    },
129
    Subgraph {
130
        #[serde(flatten)]
131
        content: SubgraphNode,
132
    },
133
    Selector {
134
        #[serde(flatten)]
135
        content: SelectorNode,
136
    },
137
}
138

139
impl GraphNode {
140
    pub fn new_extension_node(
59✔
141
        name: String,
59✔
142
        addon: String,
59✔
143
        extension_group: Option<String>,
59✔
144
        app: Option<String>,
59✔
145
        property: Option<serde_json::Value>,
59✔
146
    ) -> Self {
59✔
147
        Self::Extension {
59✔
148
            content: ExtensionNode {
59✔
149
                name,
59✔
150
                addon,
59✔
151
                extension_group,
59✔
152
                app,
59✔
153
                property,
59✔
154
            },
59✔
155
        }
59✔
156
    }
59✔
157

158
    pub fn new_subgraph_node(
14✔
159
        name: String,
14✔
160
        property: Option<serde_json::Value>,
14✔
161
        graph: GraphResource,
14✔
162
    ) -> Self {
14✔
163
        Self::Subgraph {
14✔
164
            content: SubgraphNode {
14✔
165
                name,
14✔
166
                property,
14✔
167
                graph,
14✔
168
            },
14✔
169
        }
14✔
170
    }
14✔
171

172
    /// Validates and completes a graph node by ensuring it has all required
173
    /// fields and follows the app declaration rules of the graph.
174
    ///
175
    /// For graphs spanning multiple apps, no node can have 'localhost' as its
176
    /// app field value, as other apps would not know how to connect to the
177
    /// app that node belongs to. For consistency, single app graphs also do
178
    /// not allow 'localhost' as an explicit app field value. Instead,
179
    /// 'localhost' is used as the internal default value when no app field is
180
    /// specified.
181
    pub fn validate_and_complete(
4,587✔
182
        &mut self,
4,587✔
183
        app_uri_declaration_state: &AppUriDeclarationState,
4,587✔
184
    ) -> Result<()> {
4,587✔
185
        match self {
4,587✔
186
            GraphNode::Extension {
187
                content,
4,583✔
188
            } => {
189
                // Validate app URI if provided
190
                if let Some(app) = &content.app {
4,583✔
191
                    // Disallow 'localhost' as an app URI in graph definitions.
192
                    if app.as_str() == localhost() {
4,061✔
193
                        let err_msg = if app_uri_declaration_state.is_single_app_graph() {
2✔
194
                            ERR_MSG_GRAPH_LOCALHOST_FORBIDDEN_IN_SINGLE_APP_MODE
1✔
195
                        } else {
196
                            ERR_MSG_GRAPH_LOCALHOST_FORBIDDEN_IN_MULTI_APP_MODE
1✔
197
                        };
198
                        return Err(anyhow::anyhow!(err_msg));
2✔
199
                    }
4,059✔
200
                }
522✔
201
                Ok(())
4,581✔
202
            }
203
            GraphNode::Subgraph {
204
                ..
205
            } => Ok(()),
1✔
206
            GraphNode::Selector {
207
                ..
208
            } => Ok(()),
3✔
209
        }
210
    }
4,587✔
211

212
    pub fn get_loc(&self) -> GraphLoc {
×
213
        match self {
×
214
            GraphNode::Extension {
215
                content,
×
216
            } => GraphLoc::with_app_and_type_and_name(
×
217
                content.app.clone(),
×
218
                GraphNodeType::Extension,
×
219
                content.name.clone(),
×
220
            )
221
            .unwrap(),
×
222
            GraphNode::Subgraph {
223
                content,
×
224
            } => GraphLoc::with_app_and_type_and_name(
×
225
                None,
×
226
                GraphNodeType::Subgraph,
×
227
                content.name.clone(),
×
228
            )
229
            .unwrap(),
×
230
            GraphNode::Selector {
231
                content,
×
232
            } => GraphLoc::with_app_and_type_and_name(
×
233
                None,
×
234
                GraphNodeType::Selector,
×
235
                content.name.clone(),
×
236
            )
237
            .unwrap(),
×
238
        }
239
    }
×
240

241
    pub fn get_app_uri(&self) -> &Option<String> {
6,527✔
242
        match self {
6,527✔
243
            GraphNode::Extension {
244
                content,
6,522✔
245
            } => &content.app,
6,522✔
246
            GraphNode::Subgraph {
247
                ..
248
            } => &None,
2✔
249
            GraphNode::Selector {
250
                ..
251
            } => &None,
3✔
252
        }
253
    }
6,527✔
254

255
    pub fn get_type(&self) -> GraphNodeType {
13,384✔
256
        match self {
13,384✔
257
            GraphNode::Extension {
258
                ..
259
            } => GraphNodeType::Extension,
13,347✔
260
            GraphNode::Subgraph {
261
                ..
262
            } => GraphNodeType::Subgraph,
27✔
263
            GraphNode::Selector {
264
                ..
265
            } => GraphNodeType::Selector,
10✔
266
        }
267
    }
13,384✔
268

269
    pub fn get_name(&self) -> &str {
2,030✔
270
        match self {
2,030✔
271
            GraphNode::Extension {
272
                content,
2,014✔
273
            } => &content.name,
2,014✔
274
            GraphNode::Subgraph {
275
                content,
1✔
276
            } => &content.name,
1✔
277
            GraphNode::Selector {
278
                content,
15✔
279
            } => &content.name,
15✔
280
        }
281
    }
2,030✔
282

283
    pub fn set_name(&mut self, name: String) {
×
284
        match self {
×
285
            GraphNode::Extension {
286
                content,
×
287
            } => content.name = name,
×
288
            GraphNode::Subgraph {
289
                content,
×
290
            } => content.name = name,
×
291
            GraphNode::Selector {
292
                content,
×
293
            } => content.name = name,
×
294
        }
295
    }
×
296

297
    pub fn get_field(&self, field: &str) -> Option<&str> {
45✔
298
        match self {
45✔
299
            GraphNode::Extension {
300
                content,
27✔
301
            } => match field {
27✔
302
                "name" => Some(&content.name),
27✔
303
                "type" => Some("extension"),
4✔
304
                "app" => content.app.as_deref(),
4✔
305
                "addon" => Some(&content.addon),
×
306
                _ => None,
×
307
            },
308
            GraphNode::Subgraph {
309
                content,
×
310
            } => match field {
×
311
                "name" => Some(&content.name),
×
312
                "type" => Some("subgraph"),
×
313
                _ => None,
×
314
            },
315
            GraphNode::Selector {
316
                content,
18✔
317
            } => match field {
18✔
318
                "name" => Some(&content.name),
18✔
319
                "type" => Some("selector"),
×
320
                _ => None,
×
321
            },
322
        }
323
    }
45✔
324

325
    pub fn as_selector_node(&self) -> Option<&SelectorNode> {
13✔
326
        match self {
13✔
327
            GraphNode::Selector {
328
                content,
9✔
329
            } => Some(content),
9✔
330
            _ => None,
4✔
331
        }
332
    }
13✔
333

334
    pub fn as_extension_node(&self) -> Option<&ExtensionNode> {
×
335
        match self {
×
336
            GraphNode::Extension {
337
                content,
×
338
            } => Some(content),
×
339
            _ => None,
×
340
        }
341
    }
×
342

343
    pub fn as_subgraph_node(&self) -> Option<&SubgraphNode> {
×
344
        match self {
×
345
            GraphNode::Subgraph {
346
                content,
×
347
            } => Some(content),
×
348
            _ => None,
×
349
        }
350
    }
×
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc