1use crate::io::{Directory, RemoteDirectory};
6use anyhow::Context;
7use cm_rust::{ComponentDecl, FidlIntoNative};
8use fidl::endpoints::{create_proxy, ServerEnd};
9use fuchsia_async::TimeoutExt;
10use futures::TryFutureExt;
11use moniker::{Moniker, MonikerError};
12use thiserror::Error;
13use {fidl_fuchsia_component_decl as fcdecl, fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys};
14
15static DIR_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(1);
24
25#[cfg(feature = "serde")]
26use {schemars::JsonSchema, serde::Serialize};
27
28#[derive(Debug, Error)]
29pub enum ParseError {
30 #[error("{struct_name} FIDL is missing a field: {field_name}")]
31 MissingField { struct_name: &'static str, field_name: &'static str },
32
33 #[error("moniker could not be parsed successfully: {0}")]
34 BadMoniker(#[from] MonikerError),
35
36 #[error("{struct_name} FIDL enum is set to an unknown value")]
37 UnknownEnumValue { struct_name: &'static str },
38}
39
40#[derive(Debug, Error)]
41pub enum GetInstanceError {
42 #[error("instance {0} could not be found")]
43 InstanceNotFound(Moniker),
44
45 #[error("component manager could not parse {0}")]
46 BadMoniker(Moniker),
47
48 #[error(transparent)]
49 ParseError(#[from] ParseError),
50
51 #[error("component manager responded with an unknown error code")]
52 UnknownError,
53
54 #[error(transparent)]
55 Fidl(#[from] fidl::Error),
56}
57
58#[derive(Debug, Error)]
59pub enum GetAllInstancesError {
60 #[error("scoped root instance could not be found")]
61 InstanceNotFound,
62
63 #[error(transparent)]
64 ParseError(#[from] ParseError),
65
66 #[error("component manager responded with an unknown error code")]
67 UnknownError,
68
69 #[error("FIDL error: {0}")]
70 Fidl(#[from] fidl::Error),
71}
72
73#[derive(Debug, Error)]
74pub enum GetRuntimeError {
75 #[error(transparent)]
76 Fidl(#[from] fidl::Error),
77
78 #[error("Component manager could not open runtime dir: {0:?}")]
79 OpenError(fsys::OpenError),
80
81 #[error("timed out parsing dir")]
82 Timeout,
83
84 #[error("error parsing dir: {0}")]
85 ParseError(#[source] anyhow::Error),
86}
87
88#[derive(Debug, Error)]
89pub enum GetOutgoingCapabilitiesError {
90 #[error(transparent)]
91 Fidl(#[from] fidl::Error),
92
93 #[error("Component manager could not open outgoing dir: {0:?}")]
94 OpenError(fsys::OpenError),
95
96 #[error("timed out parsing dir")]
97 Timeout,
98
99 #[error("error parsing dir: {0}")]
100 ParseError(#[source] anyhow::Error),
101}
102
103#[derive(Debug, Error)]
104pub enum GetMerkleRootError {
105 #[error(transparent)]
106 Fidl(#[from] fidl::Error),
107
108 #[error("Component manager could not open pacakage dir: {0:?}")]
109 OpenError(fsys::OpenError),
110
111 #[error("error reading meta file: {0}")]
112 ReadError(#[from] fuchsia_fs::file::ReadError),
113}
114
115#[derive(Debug, Error)]
116pub enum GetDeclarationError {
117 #[error(transparent)]
118 Fidl(#[from] fidl::Error),
119
120 #[error("instance {0} could not be found")]
121 InstanceNotFound(Moniker),
122
123 #[error("instance {0} is not resolved")]
124 InstanceNotResolved(Moniker),
125
126 #[error("component manager could not parse {0}")]
127 BadMoniker(Moniker),
128
129 #[error("component manager failed to encode the manifest")]
130 EncodeFailed,
131
132 #[error("component does not have {_0:?} as a valid location for a child")]
133 BadChildLocation(fsys::ChildLocation),
134
135 #[error("could not resolve component from URL {_0}")]
136 BadUrl(String),
137
138 #[error("component manifest could not be validated")]
139 InvalidManifest(#[from] cm_fidl_validator::error::ErrorList),
140
141 #[error("component manager responded with an unknown error code")]
142 UnknownError,
143}
144
145#[derive(Debug, Error)]
146pub enum GetStructuredConfigError {
147 #[error(transparent)]
148 Fidl(#[from] fidl::Error),
149
150 #[error(transparent)]
151 ParseError(#[from] ParseError),
152
153 #[error("instance {0} could not be found")]
154 InstanceNotFound(Moniker),
155
156 #[error("instance {0} is not resolved")]
157 InstanceNotResolved(Moniker),
158
159 #[error("component manager could not parse {0}")]
160 BadMoniker(Moniker),
161
162 #[error("component manager responded with an unknown error code")]
163 UnknownError,
164}
165
166#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
167#[derive(Debug, Clone)]
168pub struct Instance {
169 pub moniker: Moniker,
171
172 pub url: String,
174
175 pub environment: Option<String>,
177
178 pub instance_id: Option<String>,
180
181 pub resolved_info: Option<ResolvedInfo>,
183}
184
185impl TryFrom<fsys::Instance> for Instance {
186 type Error = ParseError;
187
188 fn try_from(instance: fsys::Instance) -> Result<Self, Self::Error> {
189 let moniker = instance
190 .moniker
191 .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "moniker" })?;
192 let moniker = Moniker::parse_str(&moniker)?;
193 let url = instance
194 .url
195 .ok_or(ParseError::MissingField { struct_name: "Instance", field_name: "url" })?;
196 let resolved_info = instance.resolved_info.map(|i| i.try_into()).transpose()?;
197
198 Ok(Self {
199 moniker,
200 url,
201 environment: instance.environment,
202 instance_id: instance.instance_id,
203 resolved_info,
204 })
205 }
206}
207
208#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
210#[derive(Debug, Clone)]
211pub struct ResolvedInfo {
212 pub resolved_url: String,
213 pub execution_info: Option<ExecutionInfo>,
214}
215
216impl TryFrom<fsys::ResolvedInfo> for ResolvedInfo {
217 type Error = ParseError;
218
219 fn try_from(resolved: fsys::ResolvedInfo) -> Result<Self, Self::Error> {
220 let resolved_url = resolved.resolved_url.ok_or(ParseError::MissingField {
221 struct_name: "ResolvedInfo",
222 field_name: "resolved_url",
223 })?;
224 let execution_info = resolved.execution_info.map(|i| i.try_into()).transpose()?;
225
226 Ok(Self { resolved_url, execution_info })
227 }
228}
229
230#[cfg_attr(feature = "serde", derive(JsonSchema, Serialize))]
232#[derive(Debug, Clone)]
233pub struct ExecutionInfo {
234 pub start_reason: String,
235}
236
237impl TryFrom<fsys::ExecutionInfo> for ExecutionInfo {
238 type Error = ParseError;
239
240 fn try_from(info: fsys::ExecutionInfo) -> Result<Self, Self::Error> {
241 let start_reason = info.start_reason.ok_or(ParseError::MissingField {
242 struct_name: "ExecutionInfo",
243 field_name: "start_reason",
244 })?;
245 Ok(Self { start_reason })
246 }
247}
248
249#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
251#[derive(Debug, PartialEq)]
252pub struct ConfigField {
253 pub key: String,
254 pub value: String,
255}
256
257impl TryFrom<fcdecl::ResolvedConfigField> for ConfigField {
258 type Error = ParseError;
259
260 fn try_from(field: fcdecl::ResolvedConfigField) -> Result<Self, Self::Error> {
261 let value = match &field.value {
262 fcdecl::ConfigValue::Vector(value) => format!("{:#?}", value),
263 fcdecl::ConfigValue::Single(value) => format!("{:?}", value),
264 _ => {
265 return Err(ParseError::UnknownEnumValue {
266 struct_name: "fuchsia.component.config.Value",
267 })
268 }
269 };
270 Ok(ConfigField { key: field.key, value })
271 }
272}
273
274#[cfg_attr(feature = "serde", derive(Serialize, JsonSchema))]
275#[derive(Debug)]
276pub enum Runtime {
277 Elf {
278 job_id: u64,
279 process_id: Option<u64>,
280 process_start_time: Option<i64>,
281 process_start_time_utc_estimate: Option<String>,
282 },
283 Unknown,
284}
285
286#[cfg_attr(feature = "serde", derive(Serialize))]
287#[derive(Debug, PartialEq)]
288pub enum Durability {
289 Transient,
290 SingleRun,
291}
292
293impl std::fmt::Display for Durability {
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 match self {
296 Self::Transient => write!(f, "Transient"),
297 Self::SingleRun => write!(f, "Single-run"),
298 }
299 }
300}
301
302impl From<fcdecl::Durability> for Durability {
303 fn from(value: fcdecl::Durability) -> Self {
304 match value {
305 fcdecl::Durability::SingleRun => Durability::SingleRun,
306 fcdecl::Durability::Transient => Durability::Transient,
307 }
308 }
309}
310
311pub async fn get_all_instances(
312 query: &fsys::RealmQueryProxy,
313) -> Result<Vec<Instance>, GetAllInstancesError> {
314 let result = query.get_all_instances().await?;
315
316 let iterator = match result {
317 Ok(iterator) => iterator,
318 Err(fsys::GetAllInstancesError::InstanceNotFound) => {
319 return Err(GetAllInstancesError::InstanceNotFound)
320 }
321 Err(_) => return Err(GetAllInstancesError::UnknownError),
322 };
323
324 let iterator = iterator.into_proxy();
325 let mut instances = vec![];
326
327 loop {
328 let mut batch = iterator.next().await?;
329 if batch.is_empty() {
330 break;
331 }
332 instances.append(&mut batch);
333 }
334
335 let instances: Result<Vec<Instance>, ParseError> =
336 instances.into_iter().map(|i| Instance::try_from(i)).collect();
337 Ok(instances?)
338}
339
340pub async fn get_resolved_declaration(
341 moniker: &Moniker,
342 realm_query: &fsys::RealmQueryProxy,
343) -> Result<ComponentDecl, GetDeclarationError> {
344 let moniker_str = moniker.to_string();
345 let iterator = match realm_query.get_manifest(&moniker_str).await? {
347 Ok(iterator) => Ok(iterator),
348 Err(fsys::GetDeclarationError::InstanceNotFound) => {
349 Err(GetDeclarationError::InstanceNotFound(moniker.clone()))
350 }
351 Err(fsys::GetDeclarationError::InstanceNotResolved) => {
352 Err(GetDeclarationError::InstanceNotResolved(moniker.clone()))
353 }
354 Err(fsys::GetDeclarationError::BadMoniker) => {
355 Err(GetDeclarationError::BadMoniker(moniker.clone()))
356 }
357 Err(fsys::GetDeclarationError::EncodeFailed) => Err(GetDeclarationError::EncodeFailed),
358 Err(_) => Err(GetDeclarationError::UnknownError),
359 }?;
360
361 let bytes = drain_manifest_bytes_iterator(iterator.into_proxy()).await?;
362 let manifest = fidl::unpersist::<fcdecl::Component>(&bytes)?;
363 let manifest = manifest.fidl_into_native();
364 Ok(manifest)
365}
366
367async fn drain_manifest_bytes_iterator(
368 iterator: fsys::ManifestBytesIteratorProxy,
369) -> Result<Vec<u8>, fidl::Error> {
370 let mut bytes = vec![];
371
372 loop {
373 let mut batch = iterator.next().await?;
374 if batch.is_empty() {
375 break;
376 }
377 bytes.append(&mut batch);
378 }
379
380 Ok(bytes)
381}
382
383pub async fn resolve_declaration(
384 realm_query: &fsys::RealmQueryProxy,
385 parent: &Moniker,
386 child_location: &fsys::ChildLocation,
387 url: &str,
388) -> Result<ComponentDecl, GetDeclarationError> {
389 let iterator = realm_query
390 .resolve_declaration(&parent.to_string(), child_location, url)
391 .await?
392 .map_err(|e| match e {
393 fsys::GetDeclarationError::InstanceNotFound => {
394 GetDeclarationError::InstanceNotFound(parent.clone())
395 }
396 fsys::GetDeclarationError::InstanceNotResolved => {
397 GetDeclarationError::InstanceNotResolved(parent.clone())
398 }
399 fsys::GetDeclarationError::BadMoniker => {
400 GetDeclarationError::BadMoniker(parent.clone())
401 }
402 fsys::GetDeclarationError::EncodeFailed => GetDeclarationError::EncodeFailed,
403 fsys::GetDeclarationError::BadChildLocation => {
404 GetDeclarationError::BadChildLocation(child_location.to_owned())
405 }
406 fsys::GetDeclarationError::BadUrl => GetDeclarationError::BadUrl(url.to_owned()),
407 _ => GetDeclarationError::UnknownError,
408 })?;
409
410 let bytes = drain_manifest_bytes_iterator(iterator.into_proxy()).await?;
411 let manifest = fidl::unpersist::<fcdecl::Component>(&bytes)?;
412 cm_fidl_validator::validate(&manifest)?;
413 let manifest = manifest.fidl_into_native();
414 Ok(manifest)
415}
416
417pub async fn get_config_fields(
418 moniker: &Moniker,
419 realm_query: &fsys::RealmQueryProxy,
420) -> Result<Option<Vec<ConfigField>>, GetStructuredConfigError> {
421 let moniker_str = moniker.to_string();
423 match realm_query.get_structured_config(&moniker_str).await? {
424 Ok(config) => {
425 let fields: Result<Vec<ConfigField>, ParseError> =
426 config.fields.into_iter().map(|f| f.try_into()).collect();
427 let fields = fields?;
428 Ok(Some(fields))
429 }
430 Err(fsys::GetStructuredConfigError::InstanceNotFound) => {
431 Err(GetStructuredConfigError::InstanceNotFound(moniker.clone()))
432 }
433 Err(fsys::GetStructuredConfigError::InstanceNotResolved) => {
434 Err(GetStructuredConfigError::InstanceNotResolved(moniker.clone()))
435 }
436 Err(fsys::GetStructuredConfigError::NoConfig) => Ok(None),
437 Err(fsys::GetStructuredConfigError::BadMoniker) => {
438 Err(GetStructuredConfigError::BadMoniker(moniker.clone()))
439 }
440 Err(_) => Err(GetStructuredConfigError::UnknownError),
441 }
442}
443
444pub async fn get_runtime(
445 moniker: &Moniker,
446 realm_query: &fsys::RealmQueryProxy,
447) -> Result<Runtime, GetRuntimeError> {
448 let moniker_str = moniker.to_string();
450 let (runtime_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
451 let runtime_dir = RemoteDirectory::from_proxy(runtime_dir);
452 let server_end = ServerEnd::new(server_end.into_channel());
453 realm_query
454 .deprecated_open(
455 &moniker_str,
456 fsys::OpenDirType::RuntimeDir,
457 fio::OpenFlags::RIGHT_READABLE,
458 fio::ModeType::empty(),
459 ".",
460 server_end,
461 )
462 .await?
463 .map_err(|e| GetRuntimeError::OpenError(e))?;
464 parse_runtime_from_dir(runtime_dir)
465 .map_err(|e| GetRuntimeError::ParseError(e))
466 .on_timeout(DIR_TIMEOUT, || Err(GetRuntimeError::Timeout))
467 .await
468}
469
470async fn parse_runtime_from_dir(runtime_dir: RemoteDirectory) -> Result<Runtime, anyhow::Error> {
471 if let Ok(true) = runtime_dir.exists("elf").await {
474 let elf_dir = runtime_dir.open_dir_readonly("elf")?;
475
476 let (job_id, process_id, process_start_time, process_start_time_utc_estimate) = futures::join!(
477 elf_dir.read_file("job_id"),
478 elf_dir.read_file("process_id"),
479 elf_dir.read_file("process_start_time"),
480 elf_dir.read_file("process_start_time_utc_estimate"),
481 );
482
483 let job_id = job_id?.parse::<u64>().context("Job ID is not u64")?;
484
485 let process_id = match process_id {
486 Ok(id) => Some(id.parse::<u64>().context("Process ID is not u64")?),
487 Err(_) => None,
488 };
489
490 let process_start_time =
491 process_start_time.ok().map(|time_string| time_string.parse::<i64>().ok()).flatten();
492
493 let process_start_time_utc_estimate = process_start_time_utc_estimate.ok();
494
495 Ok(Runtime::Elf { job_id, process_id, process_start_time, process_start_time_utc_estimate })
496 } else {
497 Ok(Runtime::Unknown)
498 }
499}
500
501pub async fn get_instance(
502 moniker: &Moniker,
503 realm_query: &fsys::RealmQueryProxy,
504) -> Result<Instance, GetInstanceError> {
505 let moniker_str = moniker.to_string();
506 match realm_query.get_instance(&moniker_str).await? {
507 Ok(instance) => {
508 let instance = instance.try_into()?;
509 Ok(instance)
510 }
511 Err(fsys::GetInstanceError::InstanceNotFound) => {
512 Err(GetInstanceError::InstanceNotFound(moniker.clone()))
513 }
514 Err(fsys::GetInstanceError::BadMoniker) => {
515 Err(GetInstanceError::BadMoniker(moniker.clone()))
516 }
517 Err(_) => Err(GetInstanceError::UnknownError),
518 }
519}
520
521pub async fn get_outgoing_capabilities(
522 moniker: &Moniker,
523 realm_query: &fsys::RealmQueryProxy,
524) -> Result<Vec<String>, GetOutgoingCapabilitiesError> {
525 let moniker_str = moniker.to_string();
526 let (out_dir, server_end) = create_proxy::<fio::DirectoryMarker>();
527 let out_dir = RemoteDirectory::from_proxy(out_dir);
528 let server_end = ServerEnd::new(server_end.into_channel());
529 realm_query
530 .deprecated_open(
531 &moniker_str,
532 fsys::OpenDirType::OutgoingDir,
533 fio::OpenFlags::RIGHT_READABLE,
534 fio::ModeType::empty(),
535 ".",
536 server_end,
537 )
538 .await?
539 .map_err(|e| GetOutgoingCapabilitiesError::OpenError(e))?;
540 get_capabilities(out_dir)
541 .map_err(|e| GetOutgoingCapabilitiesError::ParseError(e))
542 .on_timeout(DIR_TIMEOUT, || Err(GetOutgoingCapabilitiesError::Timeout))
543 .await
544}
545
546pub async fn get_merkle_root(
547 moniker: &Moniker,
548 realm_query: &fsys::RealmQueryProxy,
549) -> Result<String, GetMerkleRootError> {
550 let moniker_str = moniker.to_string();
551 let (meta_file, server_end) = create_proxy::<fio::FileMarker>();
552 let server_end = ServerEnd::new(server_end.into_channel());
553 realm_query
554 .deprecated_open(
555 &moniker_str,
556 fsys::OpenDirType::PackageDir,
557 fio::OpenFlags::RIGHT_READABLE,
558 fio::ModeType::empty(),
559 "meta",
560 server_end,
561 )
562 .await?
563 .map_err(|e| GetMerkleRootError::OpenError(e))?;
564 let merkle_root = fuchsia_fs::file::read_to_string(&meta_file).await?;
565 Ok(merkle_root)
566}
567
568async fn get_capabilities(capability_dir: RemoteDirectory) -> Result<Vec<String>, anyhow::Error> {
571 let mut entries = capability_dir.entry_names().await?;
572
573 for (index, name) in entries.iter().enumerate() {
574 if name == "svc" {
575 entries.remove(index);
576 let svc_dir = capability_dir.open_dir_readonly("svc")?;
577 let mut svc_entries = svc_dir.entry_names().await?;
578 entries.append(&mut svc_entries);
579 break;
580 }
581 }
582
583 entries.sort_unstable();
584 Ok(entries)
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use crate::test_utils::*;
591 use fidl_fuchsia_component_decl as fdecl;
592 use std::collections::HashMap;
593 use tempfile::TempDir;
594
595 #[fuchsia::test]
596 async fn test_get_all_instances() {
597 let query = serve_realm_query_instances(vec![fsys::Instance {
598 moniker: Some("./my_foo".to_string()),
599 url: Some("#meta/foo.cm".to_string()),
600 instance_id: Some("1234567890".to_string()),
601 resolved_info: Some(fsys::ResolvedInfo {
602 resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
603 execution_info: Some(fsys::ExecutionInfo {
604 start_reason: Some("Debugging Workflow".to_string()),
605 ..Default::default()
606 }),
607 ..Default::default()
608 }),
609 ..Default::default()
610 }]);
611
612 let mut instances = get_all_instances(&query).await.unwrap();
613 assert_eq!(instances.len(), 1);
614 let instance = instances.remove(0);
615
616 let moniker = Moniker::parse_str("/my_foo").unwrap();
617 assert_eq!(instance.moniker, moniker);
618 assert_eq!(instance.url, "#meta/foo.cm");
619 assert_eq!(instance.instance_id.unwrap(), "1234567890");
620 assert!(instance.resolved_info.is_some());
621
622 let resolved = instance.resolved_info.unwrap();
623 assert_eq!(resolved.resolved_url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
624
625 let execution_info = resolved.execution_info.unwrap();
626 assert_eq!(execution_info.start_reason, "Debugging Workflow".to_string());
627 }
628
629 #[fuchsia::test]
630 async fn test_get_manifest() {
631 let query = serve_realm_query(
632 vec![],
633 HashMap::from([(
634 "./my_foo".to_string(),
635 fdecl::Component {
636 uses: Some(vec![fdecl::Use::Protocol(fdecl::UseProtocol {
637 source: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
638 source_name: Some("fuchsia.foo.bar".to_string()),
639 target_path: Some("/svc/fuchsia.foo.bar".to_string()),
640 dependency_type: Some(fdecl::DependencyType::Strong),
641 availability: Some(fdecl::Availability::Required),
642 ..Default::default()
643 })]),
644 exposes: Some(vec![fdecl::Expose::Protocol(fdecl::ExposeProtocol {
645 source: Some(fdecl::Ref::Self_(fdecl::SelfRef)),
646 source_name: Some("fuchsia.bar.baz".to_string()),
647 target: Some(fdecl::Ref::Parent(fdecl::ParentRef)),
648 target_name: Some("fuchsia.bar.baz".to_string()),
649 ..Default::default()
650 })]),
651 capabilities: Some(vec![fdecl::Capability::Protocol(fdecl::Protocol {
652 name: Some("fuchsia.bar.baz".to_string()),
653 source_path: Some("/svc/fuchsia.bar.baz".to_string()),
654 ..Default::default()
655 })]),
656 ..Default::default()
657 },
658 )]),
659 HashMap::new(),
660 HashMap::new(),
661 );
662
663 let moniker = Moniker::parse_str("/my_foo").unwrap();
664 let manifest = get_resolved_declaration(&moniker, &query).await.unwrap();
665
666 assert_eq!(manifest.uses.len(), 1);
667 assert_eq!(manifest.exposes.len(), 1);
668 }
669
670 pub fn create_pkg_dir() -> TempDir {
671 let temp_dir = TempDir::new_in("/tmp").unwrap();
672 let root = temp_dir.path();
673
674 std::fs::write(root.join("meta"), "1234").unwrap();
675
676 temp_dir
677 }
678
679 #[fuchsia::test]
680 async fn test_get_merkle_root() {
681 let pkg_dir = create_pkg_dir();
682 let query = serve_realm_query(
683 vec![],
684 HashMap::new(),
685 HashMap::new(),
686 HashMap::from([(("./my_foo".to_string(), fsys::OpenDirType::PackageDir), pkg_dir)]),
687 );
688
689 let moniker = Moniker::parse_str("/my_foo").unwrap();
690 let merkle_root = get_merkle_root(&moniker, &query).await.unwrap();
691
692 assert_eq!(merkle_root, "1234");
693 }
694
695 #[fuchsia::test]
696 async fn test_get_instance() {
697 let realm_query = serve_realm_query_instances(vec![fsys::Instance {
698 moniker: Some("./my_foo".to_string()),
699 url: Some("#meta/foo.cm".to_string()),
700 instance_id: Some("1234567890".to_string()),
701 resolved_info: Some(fsys::ResolvedInfo {
702 resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
703 execution_info: Some(fsys::ExecutionInfo {
704 start_reason: Some("Debugging Workflow".to_string()),
705 ..Default::default()
706 }),
707 ..Default::default()
708 }),
709 ..Default::default()
710 }]);
711
712 let moniker = Moniker::parse_str("/my_foo").unwrap();
713 let instance = get_instance(&moniker, &realm_query).await.unwrap();
714
715 assert_eq!(instance.moniker, moniker);
716 assert_eq!(instance.url, "#meta/foo.cm");
717 assert_eq!(instance.instance_id.unwrap(), "1234567890");
718 assert!(instance.resolved_info.is_some());
719
720 let resolved = instance.resolved_info.unwrap();
721 assert_eq!(resolved.resolved_url, "fuchsia-pkg://fuchsia.com/foo#meta/foo.cm");
722
723 let execution_info = resolved.execution_info.unwrap();
724 assert_eq!(execution_info.start_reason, "Debugging Workflow".to_string());
725 }
726}