1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum AccessControlEntryAuthMode {
21 Pase = 1,
23 Case = 2,
25 Group = 3,
27}
28
29impl AccessControlEntryAuthMode {
30 pub fn from_u8(value: u8) -> Option<Self> {
32 match value {
33 1 => Some(AccessControlEntryAuthMode::Pase),
34 2 => Some(AccessControlEntryAuthMode::Case),
35 3 => Some(AccessControlEntryAuthMode::Group),
36 _ => None,
37 }
38 }
39
40 pub fn to_u8(self) -> u8 {
42 self as u8
43 }
44}
45
46impl From<AccessControlEntryAuthMode> for u8 {
47 fn from(val: AccessControlEntryAuthMode) -> Self {
48 val as u8
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
53#[repr(u8)]
54pub enum AccessControlEntryPrivilege {
55 View = 1,
57 Proxyview = 2,
58 Operate = 3,
60 Manage = 4,
62 Administer = 5,
64}
65
66impl AccessControlEntryPrivilege {
67 pub fn from_u8(value: u8) -> Option<Self> {
69 match value {
70 1 => Some(AccessControlEntryPrivilege::View),
71 2 => Some(AccessControlEntryPrivilege::Proxyview),
72 3 => Some(AccessControlEntryPrivilege::Operate),
73 4 => Some(AccessControlEntryPrivilege::Manage),
74 5 => Some(AccessControlEntryPrivilege::Administer),
75 _ => None,
76 }
77 }
78
79 pub fn to_u8(self) -> u8 {
81 self as u8
82 }
83}
84
85impl From<AccessControlEntryPrivilege> for u8 {
86 fn from(val: AccessControlEntryPrivilege) -> Self {
87 val as u8
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
92#[repr(u8)]
93pub enum AccessRestrictionType {
94 Attributeaccessforbidden = 0,
96 Attributewriteforbidden = 1,
98 Commandforbidden = 2,
100 Eventforbidden = 3,
102}
103
104impl AccessRestrictionType {
105 pub fn from_u8(value: u8) -> Option<Self> {
107 match value {
108 0 => Some(AccessRestrictionType::Attributeaccessforbidden),
109 1 => Some(AccessRestrictionType::Attributewriteforbidden),
110 2 => Some(AccessRestrictionType::Commandforbidden),
111 3 => Some(AccessRestrictionType::Eventforbidden),
112 _ => None,
113 }
114 }
115
116 pub fn to_u8(self) -> u8 {
118 self as u8
119 }
120}
121
122impl From<AccessRestrictionType> for u8 {
123 fn from(val: AccessRestrictionType) -> Self {
124 val as u8
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
129#[repr(u8)]
130pub enum ChangeType {
131 Changed = 0,
133 Added = 1,
135 Removed = 2,
137}
138
139impl ChangeType {
140 pub fn from_u8(value: u8) -> Option<Self> {
142 match value {
143 0 => Some(ChangeType::Changed),
144 1 => Some(ChangeType::Added),
145 2 => Some(ChangeType::Removed),
146 _ => None,
147 }
148 }
149
150 pub fn to_u8(self) -> u8 {
152 self as u8
153 }
154}
155
156impl From<ChangeType> for u8 {
157 fn from(val: ChangeType) -> Self {
158 val as u8
159 }
160}
161
162#[derive(Debug, serde::Serialize)]
165pub struct AccessControlEntry {
166 pub privilege: Option<AccessControlEntryPrivilege>,
167 pub auth_mode: Option<AccessControlEntryAuthMode>,
168 pub subjects: Option<Vec<u64>>,
169 pub targets: Option<Vec<AccessControlTarget>>,
170}
171
172#[derive(Debug, serde::Serialize)]
173pub struct AccessControlExtension {
174 #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
175 pub data: Option<Vec<u8>>,
176}
177
178#[derive(Debug, serde::Serialize)]
179pub struct AccessControlTarget {
180 pub cluster: Option<u32>,
181 pub endpoint: Option<u16>,
182 pub device_type: Option<u32>,
183}
184
185#[derive(Debug, serde::Serialize)]
186pub struct AccessRestrictionEntry {
187 pub endpoint: Option<u16>,
188 pub cluster: Option<u32>,
189 pub restrictions: Option<Vec<AccessRestriction>>,
190}
191
192#[derive(Debug, serde::Serialize)]
193pub struct AccessRestriction {
194 pub type_: Option<AccessRestrictionType>,
195 pub id: Option<u32>,
196}
197
198#[derive(Debug, serde::Serialize)]
199pub struct CommissioningAccessRestrictionEntry {
200 pub endpoint: Option<u16>,
201 pub cluster: Option<u32>,
202 pub restrictions: Option<Vec<AccessRestriction>>,
203}
204
205pub fn encode_review_fabric_restrictions(arl: Vec<CommissioningAccessRestrictionEntry>) -> anyhow::Result<Vec<u8>> {
209 let tlv = tlv::TlvItemEnc {
210 tag: 0,
211 value: tlv::TlvItemValueEnc::StructInvisible(vec![
212 (0, tlv::TlvItemValueEnc::Array(arl.into_iter().map(|v| {
213 let mut fields = Vec::new();
214 if let Some(x) = v.endpoint { fields.push((0, tlv::TlvItemValueEnc::UInt16(x)).into()); }
215 if let Some(x) = v.cluster { fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
216 if let Some(listv) = v.restrictions {
217 let inner_vec: Vec<_> = listv.into_iter().map(|inner| {
218 let mut nested_fields = Vec::new();
219 if let Some(x) = inner.type_ { nested_fields.push((0, tlv::TlvItemValueEnc::UInt8(x.to_u8())).into()); }
220 if let Some(x) = inner.id { nested_fields.push((1, tlv::TlvItemValueEnc::UInt32(x)).into()); }
221 (0, tlv::TlvItemValueEnc::StructAnon(nested_fields)).into()
222 }).collect();
223 fields.push((2, tlv::TlvItemValueEnc::Array(inner_vec)).into());
224 }
225 (0, tlv::TlvItemValueEnc::StructAnon(fields)).into()
226 }).collect())).into(),
227 ]),
228 };
229 Ok(tlv.encode()?)
230}
231
232pub fn decode_acl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlEntry>> {
236 let mut res = Vec::new();
237 if let tlv::TlvItemValue::List(v) = inp {
238 for item in v {
239 res.push(AccessControlEntry {
240 privilege: item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
241 auth_mode: item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
242 subjects: {
243 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[3]) {
244 let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
245 Some(items)
246 } else {
247 None
248 }
249 },
250 targets: {
251 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[4]) {
252 let mut items = Vec::new();
253 for list_item in l {
254 items.push(AccessControlTarget {
255 cluster: list_item.get_int(&[0]).map(|v| v as u32),
256 endpoint: list_item.get_int(&[1]).map(|v| v as u16),
257 device_type: list_item.get_int(&[2]).map(|v| v as u32),
258 });
259 }
260 Some(items)
261 } else {
262 None
263 }
264 },
265 });
266 }
267 }
268 Ok(res)
269}
270
271pub fn decode_extension(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessControlExtension>> {
273 let mut res = Vec::new();
274 if let tlv::TlvItemValue::List(v) = inp {
275 for item in v {
276 res.push(AccessControlExtension {
277 data: item.get_octet_string_owned(&[1]),
278 });
279 }
280 }
281 Ok(res)
282}
283
284pub fn decode_subjects_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
286 if let tlv::TlvItemValue::Int(v) = inp {
287 Ok(*v as u16)
288 } else {
289 Err(anyhow::anyhow!("Expected UInt16"))
290 }
291}
292
293pub fn decode_targets_per_access_control_entry(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
295 if let tlv::TlvItemValue::Int(v) = inp {
296 Ok(*v as u16)
297 } else {
298 Err(anyhow::anyhow!("Expected UInt16"))
299 }
300}
301
302pub fn decode_access_control_entries_per_fabric(inp: &tlv::TlvItemValue) -> anyhow::Result<u16> {
304 if let tlv::TlvItemValue::Int(v) = inp {
305 Ok(*v as u16)
306 } else {
307 Err(anyhow::anyhow!("Expected UInt16"))
308 }
309}
310
311pub fn decode_commissioning_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<CommissioningAccessRestrictionEntry>> {
313 let mut res = Vec::new();
314 if let tlv::TlvItemValue::List(v) = inp {
315 for item in v {
316 res.push(CommissioningAccessRestrictionEntry {
317 endpoint: item.get_int(&[0]).map(|v| v as u16),
318 cluster: item.get_int(&[1]).map(|v| v as u32),
319 restrictions: {
320 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
321 let mut items = Vec::new();
322 for list_item in l {
323 items.push(AccessRestriction {
324 type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
325 id: list_item.get_int(&[1]).map(|v| v as u32),
326 });
327 }
328 Some(items)
329 } else {
330 None
331 }
332 },
333 });
334 }
335 }
336 Ok(res)
337}
338
339pub fn decode_arl(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<AccessRestrictionEntry>> {
341 let mut res = Vec::new();
342 if let tlv::TlvItemValue::List(v) = inp {
343 for item in v {
344 res.push(AccessRestrictionEntry {
345 endpoint: item.get_int(&[0]).map(|v| v as u16),
346 cluster: item.get_int(&[1]).map(|v| v as u32),
347 restrictions: {
348 if let Some(tlv::TlvItemValue::List(l)) = item.get(&[2]) {
349 let mut items = Vec::new();
350 for list_item in l {
351 items.push(AccessRestriction {
352 type_: list_item.get_int(&[0]).and_then(|v| AccessRestrictionType::from_u8(v as u8)),
353 id: list_item.get_int(&[1]).map(|v| v as u32),
354 });
355 }
356 Some(items)
357 } else {
358 None
359 }
360 },
361 });
362 }
363 }
364 Ok(res)
365}
366
367
368pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
380 if cluster_id != 0x001F {
382 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x001F, got {}\"}}", cluster_id);
383 }
384
385 match attribute_id {
386 0x0000 => {
387 match decode_acl(tlv_value) {
388 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
389 Err(e) => format!("{{\"error\": \"{}\"}}", e),
390 }
391 }
392 0x0001 => {
393 match decode_extension(tlv_value) {
394 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
395 Err(e) => format!("{{\"error\": \"{}\"}}", e),
396 }
397 }
398 0x0002 => {
399 match decode_subjects_per_access_control_entry(tlv_value) {
400 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
401 Err(e) => format!("{{\"error\": \"{}\"}}", e),
402 }
403 }
404 0x0003 => {
405 match decode_targets_per_access_control_entry(tlv_value) {
406 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
407 Err(e) => format!("{{\"error\": \"{}\"}}", e),
408 }
409 }
410 0x0004 => {
411 match decode_access_control_entries_per_fabric(tlv_value) {
412 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
413 Err(e) => format!("{{\"error\": \"{}\"}}", e),
414 }
415 }
416 0x0005 => {
417 match decode_commissioning_arl(tlv_value) {
418 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
419 Err(e) => format!("{{\"error\": \"{}\"}}", e),
420 }
421 }
422 0x0006 => {
423 match decode_arl(tlv_value) {
424 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
425 Err(e) => format!("{{\"error\": \"{}\"}}", e),
426 }
427 }
428 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
429 }
430}
431
432pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
437 vec![
438 (0x0000, "ACL"),
439 (0x0001, "Extension"),
440 (0x0002, "SubjectsPerAccessControlEntry"),
441 (0x0003, "TargetsPerAccessControlEntry"),
442 (0x0004, "AccessControlEntriesPerFabric"),
443 (0x0005, "CommissioningARL"),
444 (0x0006, "ARL"),
445 ]
446}
447
448pub fn get_command_list() -> Vec<(u32, &'static str)> {
451 vec![
452 (0x00, "ReviewFabricRestrictions"),
453 ]
454}
455
456pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
457 match cmd_id {
458 0x00 => Some("ReviewFabricRestrictions"),
459 _ => None,
460 }
461}
462
463pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
464 match cmd_id {
465 0x00 => Some(vec![
466 crate::clusters::codec::CommandField { tag: 0, name: "arl", kind: crate::clusters::codec::FieldKind::List { entry_type: "CommissioningAccessRestrictionEntryStruct" }, optional: false, nullable: false },
467 ]),
468 _ => None,
469 }
470}
471
472pub fn encode_command_json(cmd_id: u32, _args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
473 match cmd_id {
474 0x00 => Err(anyhow::anyhow!("command \"ReviewFabricRestrictions\" has complex args: use raw mode")),
475 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
476 }
477}
478
479#[derive(Debug, serde::Serialize)]
480pub struct ReviewFabricRestrictionsResponse {
481 pub token: Option<u64>,
482}
483
484pub fn decode_review_fabric_restrictions_response(inp: &tlv::TlvItemValue) -> anyhow::Result<ReviewFabricRestrictionsResponse> {
488 if let tlv::TlvItemValue::List(_fields) = inp {
489 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
490 Ok(ReviewFabricRestrictionsResponse {
491 token: item.get_int(&[0]),
492 })
493 } else {
494 Err(anyhow::anyhow!("Expected struct fields"))
495 }
496}
497
498pub async fn review_fabric_restrictions(conn: &crate::controller::Connection, endpoint: u16, arl: Vec<CommissioningAccessRestrictionEntry>) -> anyhow::Result<ReviewFabricRestrictionsResponse> {
502 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_CMD_ID_REVIEWFABRICRESTRICTIONS, &encode_review_fabric_restrictions(arl)?).await?;
503 decode_review_fabric_restrictions_response(&tlv)
504}
505
506pub async fn read_acl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessControlEntry>> {
508 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ACL).await?;
509 decode_acl(&tlv)
510}
511
512pub async fn read_extension(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessControlExtension>> {
514 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_EXTENSION).await?;
515 decode_extension(&tlv)
516}
517
518pub async fn read_subjects_per_access_control_entry(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
520 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_SUBJECTSPERACCESSCONTROLENTRY).await?;
521 decode_subjects_per_access_control_entry(&tlv)
522}
523
524pub async fn read_targets_per_access_control_entry(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
526 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_TARGETSPERACCESSCONTROLENTRY).await?;
527 decode_targets_per_access_control_entry(&tlv)
528}
529
530pub async fn read_access_control_entries_per_fabric(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u16> {
532 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ACCESSCONTROLENTRIESPERFABRIC).await?;
533 decode_access_control_entries_per_fabric(&tlv)
534}
535
536pub async fn read_commissioning_arl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<CommissioningAccessRestrictionEntry>> {
538 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_COMMISSIONINGARL).await?;
539 decode_commissioning_arl(&tlv)
540}
541
542pub async fn read_arl(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<AccessRestrictionEntry>> {
544 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_ACCESS_CONTROL, crate::clusters::defs::CLUSTER_ACCESS_CONTROL_ATTR_ID_ARL).await?;
545 decode_arl(&tlv)
546}
547
548#[derive(Debug, serde::Serialize)]
549pub struct AccessControlEntryChangedEvent {
550 pub admin_node_id: Option<u64>,
551 pub admin_passcode_id: Option<u16>,
552 pub change_type: Option<ChangeType>,
553 pub latest_value: Option<AccessControlEntry>,
554}
555
556#[derive(Debug, serde::Serialize)]
557pub struct AccessControlExtensionChangedEvent {
558 pub admin_node_id: Option<u64>,
559 pub admin_passcode_id: Option<u16>,
560 pub change_type: Option<ChangeType>,
561 pub latest_value: Option<AccessControlExtension>,
562}
563
564#[derive(Debug, serde::Serialize)]
565pub struct FabricRestrictionReviewUpdateEvent {
566 pub token: Option<u64>,
567 pub instruction: Option<String>,
568 pub arl_request_flow_url: Option<String>,
569}
570
571pub fn decode_access_control_entry_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlEntryChangedEvent> {
575 if let tlv::TlvItemValue::List(_fields) = inp {
576 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
577 Ok(AccessControlEntryChangedEvent {
578 admin_node_id: item.get_int(&[1]),
579 admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
580 change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
581 latest_value: {
582 if let Some(nested_tlv) = item.get(&[4]) {
583 if let tlv::TlvItemValue::List(_) = nested_tlv {
584 let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
585 Some(AccessControlEntry {
586 privilege: nested_item.get_int(&[1]).and_then(|v| AccessControlEntryPrivilege::from_u8(v as u8)),
587 auth_mode: nested_item.get_int(&[2]).and_then(|v| AccessControlEntryAuthMode::from_u8(v as u8)),
588 subjects: {
589 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[3]) {
590 let items: Vec<u64> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Some(*v) } else { None } }).collect();
591 Some(items)
592 } else {
593 None
594 }
595 },
596 targets: {
597 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[4]) {
598 let mut items = Vec::new();
599 for list_item in l {
600 items.push(AccessControlTarget {
601 cluster: list_item.get_int(&[0]).map(|v| v as u32),
602 endpoint: list_item.get_int(&[1]).map(|v| v as u16),
603 device_type: list_item.get_int(&[2]).map(|v| v as u32),
604 });
605 }
606 Some(items)
607 } else {
608 None
609 }
610 },
611 })
612 } else {
613 None
614 }
615 } else {
616 None
617 }
618 },
619 })
620 } else {
621 Err(anyhow::anyhow!("Expected struct fields"))
622 }
623}
624
625pub fn decode_access_control_extension_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<AccessControlExtensionChangedEvent> {
627 if let tlv::TlvItemValue::List(_fields) = inp {
628 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
629 Ok(AccessControlExtensionChangedEvent {
630 admin_node_id: item.get_int(&[1]),
631 admin_passcode_id: item.get_int(&[2]).map(|v| v as u16),
632 change_type: item.get_int(&[3]).and_then(|v| ChangeType::from_u8(v as u8)),
633 latest_value: {
634 if let Some(nested_tlv) = item.get(&[4]) {
635 if let tlv::TlvItemValue::List(_) = nested_tlv {
636 let nested_item = tlv::TlvItem { tag: 4, value: nested_tlv.clone() };
637 Some(AccessControlExtension {
638 data: nested_item.get_octet_string_owned(&[1]),
639 })
640 } else {
641 None
642 }
643 } else {
644 None
645 }
646 },
647 })
648 } else {
649 Err(anyhow::anyhow!("Expected struct fields"))
650 }
651}
652
653pub fn decode_fabric_restriction_review_update_event(inp: &tlv::TlvItemValue) -> anyhow::Result<FabricRestrictionReviewUpdateEvent> {
655 if let tlv::TlvItemValue::List(_fields) = inp {
656 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
657 Ok(FabricRestrictionReviewUpdateEvent {
658 token: item.get_int(&[0]),
659 instruction: item.get_string_owned(&[1]),
660 arl_request_flow_url: item.get_string_owned(&[2]),
661 })
662 } else {
663 Err(anyhow::anyhow!("Expected struct fields"))
664 }
665}
666