1use crate::errors::{MirrorConfigError, RepositoryParseError, RepositoryUrlParseError};
6use fidl_fuchsia_pkg as fidl;
7use http::Uri;
8use http_uri_ext::HttpUriExt as _;
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeSet;
12use std::{fmt, mem};
13use thiserror::Error;
14
15#[derive(
17 Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
18)]
19#[serde(rename_all = "snake_case")]
20pub enum RepositoryStorageType {
21 Ephemeral,
24
25 Persistent,
28}
29
30#[derive(
32 Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
33)]
34#[serde(rename_all = "snake_case")]
35pub enum RepositoryRegistrationAliasConflictMode {
36 ErrorOut,
38
39 Replace,
42}
43
44#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
46#[serde(rename_all = "lowercase", tag = "type", content = "value", deny_unknown_fields)]
47pub enum RepositoryKey {
48 Ed25519(#[serde(with = "hex_serde")] Vec<u8>),
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Hash)]
53pub struct MirrorConfig {
54 mirror_url: http::Uri,
55 subscribe: bool,
56 blob_mirror_url: http::Uri,
57}
58
59impl MirrorConfig {
60 pub fn mirror_url(&self) -> &http::Uri {
62 &self.mirror_url
63 }
64 pub fn subscribe(&self) -> bool {
65 self.subscribe
66 }
67
68 pub fn blob_mirror_url(&self) -> &http::Uri {
70 &self.blob_mirror_url
71 }
72}
73
74impl serde::Serialize for MirrorConfig {
76 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
77 where
78 S: serde::Serializer,
79 {
80 #[derive(Serialize)]
81 pub struct SerMirrorConfig<'a> {
82 #[serde(with = "uri_serde")]
83 mirror_url: &'a http::Uri,
84 subscribe: bool,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 blob_mirror_url: Option<&'a str>,
87 }
88
89 let blob_mirror_url = normalize_blob_mirror_url(&self.mirror_url, &self.blob_mirror_url);
90 let blob_mirror_string: String;
91 let blob_mirror_url = if let Some(blob_mirror_url) = blob_mirror_url {
92 blob_mirror_string = blob_mirror_url.to_string();
93 Some(blob_mirror_string.as_ref())
94 } else {
95 None
96 };
97 SerMirrorConfig { mirror_url: &self.mirror_url, subscribe: self.subscribe, blob_mirror_url }
98 .serialize(serializer)
99 }
100}
101
102impl<'de> serde::Deserialize<'de> for MirrorConfig {
104 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105 where
106 D: serde::Deserializer<'de>,
107 {
108 #[derive(Deserialize)]
109 pub struct DeMirrorConfig {
110 #[serde(with = "uri_serde")]
111 mirror_url: http::Uri,
112 subscribe: bool,
113 blob_mirror_url: Option<String>,
114 }
115
116 let DeMirrorConfig { mirror_url, subscribe, blob_mirror_url } =
117 DeMirrorConfig::deserialize(deserializer)?;
118
119 if mirror_url.scheme_str().is_none() {
120 return Err(serde::de::Error::custom(format!(
121 "mirror_url must have a scheme: {:?}",
122 mirror_url
123 )));
124 }
125
126 let blob_mirror_url = if let Some(blob_mirror_url) = blob_mirror_url {
127 blob_mirror_url.parse::<Uri>().map_err(|e| {
128 serde::de::Error::custom(format!("bad uri string: {:?}: {}", blob_mirror_url, e))
129 })?
130 } else {
131 blob_mirror_url_from_mirror_url(&mirror_url)
132 };
133
134 if blob_mirror_url.scheme_str().is_none() {
135 return Err(serde::de::Error::custom(format!(
136 "blob_mirror_url must have a scheme: {:?}",
137 blob_mirror_url
138 )));
139 }
140
141 Ok(Self { mirror_url, subscribe, blob_mirror_url })
142 }
143}
144
145#[derive(Clone, Debug)]
147pub struct MirrorConfigBuilder {
148 config: MirrorConfig,
149}
150
151impl MirrorConfigBuilder {
152 pub fn new(mirror_url: impl Into<http::Uri>) -> Result<Self, MirrorConfigError> {
153 let mirror_url = mirror_url.into();
154 if mirror_url.scheme().is_none() {
155 return Err(MirrorConfigError::MirrorUrlMissingScheme);
156 }
157 let blob_mirror_url = blob_mirror_url_from_mirror_url(&mirror_url);
158 Ok(MirrorConfigBuilder {
159 config: MirrorConfig { mirror_url, subscribe: false, blob_mirror_url },
160 })
161 }
162
163 #[allow(clippy::result_large_err)] pub fn mirror_url(
165 mut self,
166 mirror_url: impl Into<http::Uri>,
167 ) -> Result<Self, (Self, MirrorConfigError)> {
168 self.config.mirror_url = mirror_url.into();
169 if self.config.mirror_url.scheme().is_none() {
170 return Err((self, MirrorConfigError::MirrorUrlMissingScheme));
171 }
172 Ok(self)
173 }
174
175 #[allow(clippy::result_large_err)] pub fn blob_mirror_url(
177 mut self,
178 blob_mirror_url: impl Into<http::Uri>,
179 ) -> Result<Self, (Self, MirrorConfigError)> {
180 self.config.blob_mirror_url = blob_mirror_url.into();
181 if self.config.blob_mirror_url.scheme().is_none() {
182 return Err((self, MirrorConfigError::BlobMirrorUrlMissingScheme));
183 }
184 Ok(self)
185 }
186
187 pub fn subscribe(mut self, subscribe: bool) -> Self {
188 self.config.subscribe = subscribe;
189 self
190 }
191
192 pub fn build(self) -> MirrorConfig {
193 self.config
194 }
195}
196
197impl From<MirrorConfigBuilder> for MirrorConfig {
198 fn from(builder: MirrorConfigBuilder) -> Self {
199 builder.build()
200 }
201}
202
203impl TryFrom<fidl::MirrorConfig> for MirrorConfig {
204 type Error = RepositoryParseError;
205 fn try_from(other: fidl::MirrorConfig) -> Result<Self, RepositoryParseError> {
206 let mirror_url =
207 other.mirror_url.ok_or(RepositoryParseError::MirrorUrlMissing)?.parse::<Uri>()?;
208 if mirror_url.scheme().is_none() {
209 Err(MirrorConfigError::MirrorUrlMissingScheme)?
210 }
211 let blob_mirror_url = match other.blob_mirror_url {
212 None => blob_mirror_url_from_mirror_url(&mirror_url),
213 Some(s) => {
214 let url = s.parse::<http::Uri>()?;
215 if url.scheme().is_none() {
216 Err(MirrorConfigError::BlobMirrorUrlMissingScheme)?
217 }
218 url
219 }
220 };
221
222 Ok(Self {
223 mirror_url,
224 subscribe: other.subscribe.ok_or(RepositoryParseError::SubscribeMissing)?,
225 blob_mirror_url,
226 })
227 }
228}
229
230impl From<MirrorConfig> for fidl::MirrorConfig {
231 fn from(config: MirrorConfig) -> Self {
232 let blob_mirror_url =
233 normalize_blob_mirror_url(&config.mirror_url, &config.blob_mirror_url)
234 .map(|url| url.to_string());
235 Self {
236 mirror_url: Some(config.mirror_url.to_string()),
237 subscribe: Some(config.subscribe),
238 blob_mirror_url,
239 ..Default::default()
240 }
241 }
242}
243
244impl From<fidl::RepositoryStorageType> for RepositoryStorageType {
245 fn from(other: fidl::RepositoryStorageType) -> Self {
246 match other {
247 fidl::RepositoryStorageType::Ephemeral => RepositoryStorageType::Ephemeral,
248 fidl::RepositoryStorageType::Persistent => RepositoryStorageType::Persistent,
249 }
250 }
251}
252
253impl From<RepositoryStorageType> for fidl::RepositoryStorageType {
254 fn from(storage_type: RepositoryStorageType) -> Self {
255 match storage_type {
256 RepositoryStorageType::Ephemeral => fidl::RepositoryStorageType::Ephemeral,
257 RepositoryStorageType::Persistent => fidl::RepositoryStorageType::Persistent,
258 }
259 }
260}
261
262#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
263pub struct RepositoryTarget {
264 pub repo_name: String,
265 pub target_identifier: Option<String>,
266 pub aliases: Option<BTreeSet<String>>,
267 pub storage_type: Option<RepositoryStorageType>,
268}
269
270#[derive(Debug, Error)]
271pub enum RepositoryError {
272 #[error("the repository name is missing")]
273 MissingRepositoryName,
274
275 #[error("repository does not exist")]
276 NoMatchingRepository,
277
278 #[error("error communicating with target device")]
279 TargetCommunicationFailure,
280
281 #[error("error interacting with the target's RepositoryManager")]
282 RepositoryManagerError,
283
284 #[error("error iteracting with the target's RewriteEngine")]
285 RewriteEngineError,
286
287 #[error("unknown repository spec type")]
288 UnknownRepositorySpec,
289
290 #[error("repository spec is missing a required field")]
291 MissingRepositorySpecField,
292
293 #[error("some unspecified error during I/O")]
294 IoError,
295
296 #[error("some unspecified internal error")]
297 InternalError,
298
299 #[error("repository metadata is expired")]
300 ExpiredRepositoryMetadata,
301
302 #[error("repository registration does not exist")]
303 NoMatchingRegistration,
304
305 #[error("repository server is not running")]
306 ServerNotRunning,
307
308 #[error("invalid url")]
309 InvalidUrl,
310
311 #[error("repository server address already in use")]
312 ServerAddressAlreadyInUse,
313
314 #[error("package does not exist")]
315 NoMatchingPackage,
316
317 #[error("repository registration conflict")]
318 ConflictingRegistration,
319
320 #[error("repository metadata cannot be found")]
321 NoRepositoryMetadata,
322}
323
324fn blob_mirror_url_from_mirror_url(mirror_url: &http::Uri) -> http::Uri {
325 mirror_url.to_owned().extend_dir_with_path("blobs").unwrap()
327}
328
329fn is_default_blob_mirror_url(mirror_url: &http::Uri, blob_mirror_url: &http::Uri) -> bool {
330 blob_mirror_url == &blob_mirror_url_from_mirror_url(mirror_url)
331}
332
333fn normalize_blob_mirror_url<'a>(
334 mirror_url: &http::Uri,
335 blob_mirror_url: &'a http::Uri,
336) -> Option<&'a http::Uri> {
337 if is_default_blob_mirror_url(mirror_url, blob_mirror_url) {
338 None
339 } else {
340 Some(blob_mirror_url)
341 }
342}
343
344#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
346pub struct RepositoryConfig {
347 repo_url: fuchsia_url::RepositoryUrl,
348 #[serde(default = "default_root_version")]
349 root_version: u32,
350 #[serde(default = "default_root_threshold")]
351 root_threshold: u32,
352 root_keys: Vec<RepositoryKey>,
353 mirrors: Vec<MirrorConfig>,
354 #[serde(default = "default_use_local_mirror")]
355 use_local_mirror: bool,
356 #[serde(default = "default_storage_type")]
357 repo_storage_type: RepositoryStorageType,
358}
359
360fn default_root_version() -> u32 {
361 1
362}
363
364fn default_root_threshold() -> u32 {
365 1
366}
367
368fn default_use_local_mirror() -> bool {
369 false
370}
371
372fn default_storage_type() -> RepositoryStorageType {
373 RepositoryStorageType::Ephemeral
374}
375
376impl RepositoryConfig {
377 pub fn repo_url(&self) -> &fuchsia_url::RepositoryUrl {
378 &self.repo_url
379 }
380
381 pub fn insert_mirror(&mut self, mut mirror: MirrorConfig) -> Option<MirrorConfig> {
383 if let Some(m) = self.mirrors.iter_mut().find(|m| m.mirror_url == mirror.mirror_url) {
384 mem::swap(m, &mut mirror);
385 Some(mirror)
386 } else {
387 self.mirrors.push(mirror);
388 None
389 }
390 }
391
392 pub fn remove_mirror(&mut self, mirror_url: &http::Uri) -> Option<MirrorConfig> {
394 if let Some(pos) = self.mirrors.iter().position(|m| &m.mirror_url == mirror_url) {
395 Some(self.mirrors.remove(pos))
396 } else {
397 None
398 }
399 }
400
401 pub fn mirrors(&self) -> &[MirrorConfig] {
403 &self.mirrors
404 }
405
406 pub fn root_version(&self) -> u32 {
408 self.root_version
409 }
410
411 pub fn root_threshold(&self) -> u32 {
414 self.root_threshold
415 }
416
417 pub fn root_keys(&self) -> &[RepositoryKey] {
419 &self.root_keys
420 }
421
422 pub fn use_local_mirror(&self) -> bool {
423 self.use_local_mirror
424 }
425
426 pub fn repo_storage_type(&self) -> &RepositoryStorageType {
428 &self.repo_storage_type
429 }
430}
431
432impl TryFrom<fidl::RepositoryConfig> for RepositoryConfig {
433 type Error = RepositoryParseError;
434
435 fn try_from(other: fidl::RepositoryConfig) -> Result<Self, RepositoryParseError> {
436 let repo_url: fuchsia_url::RepositoryUrl = other
437 .repo_url
438 .ok_or(RepositoryParseError::RepoUrlMissing)?
439 .parse()
440 .map_err(|err| RepositoryParseError::InvalidRepoUrl(err))?;
441
442 let root_version = if let Some(root_version) = other.root_version {
443 if root_version < 1 {
444 return Err(RepositoryParseError::InvalidRootVersion(root_version));
445 }
446 root_version
447 } else {
448 1
449 };
450
451 let root_threshold = if let Some(root_threshold) = other.root_threshold {
452 if root_threshold < 1 {
453 return Err(RepositoryParseError::InvalidRootThreshold(root_threshold));
454 }
455 root_threshold
456 } else {
457 1
458 };
459
460 let storage_type =
461 other.storage_type.map(|r| r.into()).unwrap_or(RepositoryStorageType::Ephemeral);
462
463 Ok(Self {
464 repo_url: repo_url,
465 root_version: root_version,
466 root_threshold: root_threshold,
467 root_keys: other
468 .root_keys
469 .unwrap_or(vec![])
470 .into_iter()
471 .map(RepositoryKey::try_from)
472 .collect::<Result<_, _>>()?,
473 mirrors: other
474 .mirrors
475 .unwrap_or(vec![])
476 .into_iter()
477 .map(MirrorConfig::try_from)
478 .collect::<Result<_, _>>()?,
479 use_local_mirror: other.use_local_mirror.unwrap_or(false),
480 repo_storage_type: storage_type.into(),
481 })
482 }
483}
484
485impl From<RepositoryConfig> for fidl::RepositoryConfig {
486 fn from(config: RepositoryConfig) -> Self {
487 Self {
488 repo_url: Some(config.repo_url.to_string()),
489 root_version: Some(config.root_version),
490 root_threshold: Some(config.root_threshold),
491 root_keys: Some(config.root_keys.into_iter().map(RepositoryKey::into).collect()),
492 mirrors: Some(config.mirrors.into_iter().map(MirrorConfig::into).collect()),
493 use_local_mirror: Some(config.use_local_mirror),
494 storage_type: Some(RepositoryStorageType::into(config.repo_storage_type)),
495 ..Default::default()
496 }
497 }
498}
499
500impl From<RepositoryConfig> for RepositoryConfigBuilder {
501 fn from(config: RepositoryConfig) -> Self {
502 Self { config }
503 }
504}
505
506#[derive(Clone, Debug)]
508pub struct RepositoryConfigBuilder {
509 config: RepositoryConfig,
510}
511
512impl RepositoryConfigBuilder {
513 pub fn new(repo_url: fuchsia_url::RepositoryUrl) -> Self {
514 RepositoryConfigBuilder {
515 config: RepositoryConfig {
516 repo_url,
517 root_version: 1,
518 root_threshold: 1,
519 root_keys: vec![],
520 mirrors: vec![],
521 use_local_mirror: false,
522 repo_storage_type: RepositoryStorageType::Ephemeral,
523 },
524 }
525 }
526
527 pub fn repo_url(mut self, repo_url: fuchsia_url::RepositoryUrl) -> Self {
528 self.config.repo_url = repo_url;
529 self
530 }
531
532 pub fn root_version(mut self, root_version: u32) -> Self {
533 self.config.root_version = root_version;
534 self
535 }
536
537 pub fn root_threshold(mut self, root_threshold: u32) -> Self {
538 self.config.root_threshold = root_threshold;
539 self
540 }
541
542 pub fn add_root_key(mut self, key: RepositoryKey) -> Self {
543 self.config.root_keys.push(key);
544 self
545 }
546
547 pub fn add_mirror(mut self, mirror: impl Into<MirrorConfig>) -> Self {
548 self.config.mirrors.push(mirror.into());
549 self
550 }
551
552 pub fn use_local_mirror(mut self, use_local_mirror: bool) -> Self {
553 self.config.use_local_mirror = use_local_mirror;
554 self
555 }
556
557 pub fn repo_storage_type(mut self, repo_storage_type: RepositoryStorageType) -> Self {
558 self.config.repo_storage_type = repo_storage_type;
559 self
560 }
561
562 pub fn build(self) -> RepositoryConfig {
563 self.config
564 }
565}
566
567impl From<RepositoryConfigBuilder> for RepositoryConfig {
568 fn from(builder: RepositoryConfigBuilder) -> Self {
569 builder.build()
570 }
571}
572
573#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
575#[serde(tag = "version", content = "content", deny_unknown_fields)]
576pub enum RepositoryConfigs {
577 #[serde(rename = "1")]
578 Version1(Vec<RepositoryConfig>),
579}
580
581impl TryFrom<fidl::RepositoryKeyConfig> for RepositoryKey {
582 type Error = RepositoryParseError;
583 fn try_from(id: fidl::RepositoryKeyConfig) -> Result<Self, RepositoryParseError> {
584 match id {
585 fidl::RepositoryKeyConfig::Ed25519Key(key) => Ok(RepositoryKey::Ed25519(key)),
586 _ => Err(RepositoryParseError::UnsupportedKeyType),
587 }
588 }
589}
590
591impl From<RepositoryKey> for fidl::RepositoryKeyConfig {
592 fn from(key: RepositoryKey) -> Self {
593 match key {
594 RepositoryKey::Ed25519(key) => fidl::RepositoryKeyConfig::Ed25519Key(key),
595 }
596 }
597}
598
599impl fmt::Debug for RepositoryKey {
600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601 let RepositoryKey::Ed25519(ref value) = self;
602 f.debug_tuple("Ed25519").field(&hex::encode(value)).finish()
603 }
604}
605
606#[derive(Debug, PartialEq, Clone)]
607pub struct RepositoryUrl {
609 url: fuchsia_url::RepositoryUrl,
610}
611
612impl RepositoryUrl {
613 pub fn url(&self) -> &fuchsia_url::RepositoryUrl {
614 &self.url
615 }
616}
617
618impl From<fuchsia_url::RepositoryUrl> for RepositoryUrl {
619 fn from(url: fuchsia_url::RepositoryUrl) -> Self {
620 Self { url }
621 }
622}
623
624impl TryFrom<&fidl::RepositoryUrl> for RepositoryUrl {
625 type Error = RepositoryUrlParseError;
626
627 fn try_from(other: &fidl::RepositoryUrl) -> Result<Self, RepositoryUrlParseError> {
628 Ok(Self { url: other.url.parse().map_err(RepositoryUrlParseError::InvalidRepoUrl)? })
629 }
630}
631
632impl From<RepositoryUrl> for fidl::RepositoryUrl {
633 fn from(url: RepositoryUrl) -> Self {
634 Self { url: url.url.to_string() }
635 }
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
641 use assert_matches::assert_matches;
642 use proptest::prelude::*;
643 use serde_json::json;
644 fn verify_json_serde<T>(expected_value: T, expected_json: serde_json::Value)
645 where
646 T: PartialEq + std::fmt::Debug,
647 T: serde::Serialize,
648 for<'de> T: serde::Deserialize<'de>,
649 {
650 let actual_json = serde_json::to_value(&expected_value).expect("value to serialize");
651 assert_eq!(actual_json, expected_json);
652 let actual_value = serde_json::from_value::<T>(actual_json).expect("json to deserialize");
653 assert_eq!(actual_value, expected_value);
654 }
655
656 #[test]
657 fn test_repository_key_serde() {
658 verify_json_serde(
659 RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),
660 json!({
661 "type": "ed25519",
662 "value": "f10f1003",
663 }),
664 );
665 verify_json_serde(
666 RepositoryKey::Ed25519(vec![0, 1, 2, 3]),
667 json!({
668 "type": "ed25519",
669 "value": "00010203",
670 }),
671 );
672 }
673
674 #[test]
675 fn test_repository_key_deserialize_with_bad_type() {
676 let json = r#"{"type":"bogus","value":"00010203"}"#;
677 let result = serde_json::from_str::<RepositoryKey>(json);
678 let error_message = result.unwrap_err().to_string();
679 assert!(
680 error_message.contains("unknown variant `bogus`"),
681 r#"Error message did not contain "unknown variant `bogus`", was "{}""#,
682 error_message
683 );
684 }
685
686 #[test]
687 fn test_repository_key_into_fidl() {
688 let key = RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]);
689 let as_fidl: fidl::RepositoryKeyConfig = key.into();
690 assert_eq!(as_fidl, fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3]))
691 }
692
693 #[test]
694 fn test_repository_key_from_fidl() {
695 let as_fidl = fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3]);
696 assert_matches!(
697 RepositoryKey::try_from(as_fidl),
698 Ok(RepositoryKey::Ed25519(v)) if v == vec![0xf1, 15, 16, 3]
699 );
700 }
701
702 #[test]
703 fn test_repository_key_from_fidl_with_bad_type() {
704 let as_fidl = fidl::RepositoryKeyConfig::unknown_variant_for_testing();
705 assert_matches!(
706 RepositoryKey::try_from(as_fidl),
707 Err(RepositoryParseError::UnsupportedKeyType)
708 );
709 }
710
711 #[test]
712 fn test_repository_key_into_from_fidl_roundtrip() {
713 let key = RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]);
714 let as_fidl: fidl::RepositoryKeyConfig = key.clone().into();
715 assert_eq!(RepositoryKey::try_from(as_fidl).unwrap(), key,)
716 }
717
718 #[test]
719 fn test_mirror_config_serde() {
720 verify_json_serde(
721 MirrorConfigBuilder::new("http://example.com/".parse::<Uri>().unwrap())
722 .unwrap()
723 .build(),
724 json!({
725 "mirror_url": "http://example.com/",
726 "subscribe": false,
727 }),
728 );
729
730 verify_json_serde(
731 MirrorConfigBuilder::new("http://example.com".parse::<Uri>().unwrap())
732 .unwrap()
733 .blob_mirror_url("http://example.com/subdir".parse::<Uri>().unwrap())
734 .unwrap()
735 .build(),
736 json!({
737 "mirror_url": "http://example.com/",
738 "blob_mirror_url": "http://example.com/subdir",
739 "subscribe": false,
740 }),
741 );
742
743 verify_json_serde(
744 MirrorConfigBuilder::new("http://example.com".parse::<Uri>().unwrap())
745 .unwrap()
746 .blob_mirror_url("http://example.com/blobs".parse::<Uri>().unwrap())
747 .unwrap()
748 .build(),
749 json!({
750 "mirror_url": "http://example.com/",
751 "subscribe": false,
752 }),
753 );
754
755 verify_json_serde(
756 MirrorConfigBuilder::new("http://example.com".parse::<Uri>().unwrap())
757 .unwrap()
758 .subscribe(true)
759 .build(),
760 json!({
761 "mirror_url": "http://example.com/",
762 "subscribe": true,
763 }),
764 );
765
766 {
767 let json = json!({
769 "mirror_url": "http://example.com/",
770 "blob_key": {
771 "type": "aes",
772 "value": "00010203",
773 },
774 "subscribe": false,
775 });
776 assert_eq!(
777 serde_json::from_value::<MirrorConfig>(json).expect("json to deserialize"),
778 MirrorConfigBuilder::new("http://example.com".parse::<Uri>().unwrap())
779 .unwrap()
780 .build()
781 );
782 }
783 }
784
785 #[test]
786 fn test_mirror_config_deserialize_rejects_urls_without_schemes() {
787 let j = json!({
788 "mirror_url": "example.com",
789 "subscribe": false,
790 });
791 assert_matches!(
792 serde_json::from_value::<MirrorConfig>(j),
793 Err(e) if e.to_string().starts_with("mirror_url must have a scheme")
794 );
795
796 let j = json!({
797 "mirror_url": "https://example.com",
798 "subscribe": false,
799 "blob_mirror_url": Some("example.com")
800 });
801 assert_matches!(
802 serde_json::from_value::<MirrorConfig>(j),
803 Err(e) if e.to_string().starts_with("blob_mirror_url must have a scheme")
804 );
805 }
806
807 #[test]
808 fn test_mirror_config_into_fidl() {
809 let config = MirrorConfig {
810 mirror_url: "http://example.com/tuf/repo".parse::<Uri>().unwrap(),
811 subscribe: true,
812 blob_mirror_url: "http://example.com/tuf/repo/subdir/blobs".parse::<Uri>().unwrap(),
813 };
814 let as_fidl: fidl::MirrorConfig = config.into();
815 assert_eq!(
816 as_fidl,
817 fidl::MirrorConfig {
818 mirror_url: Some("http://example.com/tuf/repo".into()),
819 subscribe: Some(true),
820 blob_mirror_url: Some("http://example.com/tuf/repo/subdir/blobs".into()),
821 ..Default::default()
822 }
823 );
824 }
825
826 #[test]
827 fn test_mirror_config_into_fidl_normalizes_blob_mirror_url() {
828 let config = MirrorConfig {
829 mirror_url: "http://example.com/tuf/repo".parse::<Uri>().unwrap(),
830 subscribe: false,
831 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
832 };
833 let as_fidl: fidl::MirrorConfig = config.into();
834 assert_eq!(
835 as_fidl,
836 fidl::MirrorConfig {
837 mirror_url: Some("http://example.com/tuf/repo".into()),
838 subscribe: Some(false),
839 blob_mirror_url: None,
840 ..Default::default()
841 }
842 );
843 }
844
845 #[test]
846 fn test_mirror_config_from_fidl() {
847 let as_fidl = fidl::MirrorConfig {
848 mirror_url: Some("http://example.com/tuf/repo".into()),
849 subscribe: Some(true),
850 blob_mirror_url: Some("http://example.com/tuf/repo/subdir/blobs".into()),
851 ..Default::default()
852 };
853 assert_matches!(
854 MirrorConfig::try_from(as_fidl),
855 Ok(mirror_config) if mirror_config == MirrorConfig {
856 mirror_url: "http://example.com/tuf/repo".parse::<Uri>().unwrap(),
857 subscribe: true,
858 blob_mirror_url: "http://example.com/tuf/repo/subdir/blobs".parse::<Uri>().unwrap(),
859 }
860 );
861 }
862
863 #[test]
864 fn test_mirror_config_from_fidl_rejects_urls_without_schemes() {
865 let as_fidl = fidl::MirrorConfig {
866 mirror_url: Some("example.com".into()),
867 subscribe: Some(false),
868 blob_mirror_url: None,
869 ..Default::default()
870 };
871 assert_matches!(
872 MirrorConfig::try_from(as_fidl),
873 Err(RepositoryParseError::MirrorConfig(MirrorConfigError::MirrorUrlMissingScheme))
874 );
875
876 let as_fidl = fidl::MirrorConfig {
877 mirror_url: Some("https://example.com".into()),
878 subscribe: Some(false),
879 blob_mirror_url: Some("example.com".into()),
880 ..Default::default()
881 };
882 assert_matches!(
883 MirrorConfig::try_from(as_fidl),
884 Err(RepositoryParseError::MirrorConfig(MirrorConfigError::BlobMirrorUrlMissingScheme))
885 );
886 }
887
888 #[test]
889 fn test_mirror_config_from_fidl_populates_blob_mirror_url() {
890 let as_fidl = fidl::MirrorConfig {
891 mirror_url: Some("http://example.com/tuf/repo/".into()),
892 subscribe: Some(false),
893 blob_mirror_url: None,
894 ..Default::default()
895 };
896 assert_matches!(
897 MirrorConfig::try_from(as_fidl),
898 Ok(mirror_config) if mirror_config == MirrorConfig {
899 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
900 subscribe: false,
901 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
902 }
903 );
904 }
905
906 prop_compose! {
907 fn uri_with_adversarial_path()(path in "[p/]{0,6}") -> http::Uri
908 {
909 let mut parts = http::uri::Parts::default();
910 parts.scheme = Some(http::uri::Scheme::HTTP);
911 parts.authority = Some(http::uri::Authority::from_static("example.com"));
912 parts.path_and_query = Some(path.parse().unwrap());
913 http::Uri::from_parts(parts).unwrap()
914 }
915 }
916
917 proptest! {
918 #![proptest_config(ProptestConfig{
919 failure_persistence: None,
922 .. ProptestConfig::default()
923 })]
924
925 #[test]
926 fn blob_mirror_url_from_mirror_url_produces_default_blob_mirror_urls(
927 mirror_url in uri_with_adversarial_path()
928 ) {
929 let blob_mirror_url = blob_mirror_url_from_mirror_url(&mirror_url);
930 prop_assert!(is_default_blob_mirror_url(&mirror_url, &blob_mirror_url));
931 }
932
933 #[test]
934 fn normalize_blob_mirror_url_detects_default_blob_mirror_url(
935 mirror_url in uri_with_adversarial_path()
936 ) {
937 let blob_mirror_url = blob_mirror_url_from_mirror_url(&mirror_url);
938 prop_assert_eq!(normalize_blob_mirror_url(&mirror_url, &blob_mirror_url), None);
939 prop_assert_ne!(normalize_blob_mirror_url(&blob_mirror_url, &mirror_url), None);
941 }
942 }
943
944 #[test]
945 fn test_mirror_config_into_from_fidl_roundtrip() {
946 let config = MirrorConfig {
947 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
948 subscribe: true,
949 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
950 };
951 let as_fidl: fidl::MirrorConfig = config.clone().into();
952 assert_eq!(MirrorConfig::try_from(as_fidl).unwrap(), config);
953 }
954
955 #[test]
956 fn test_mirror_config_builder() {
957 let builder =
958 MirrorConfigBuilder::new("http://example.com/".parse::<Uri>().unwrap()).unwrap();
959 assert_eq!(
960 builder.clone().build(),
961 MirrorConfig {
962 mirror_url: "http://example.com/".parse::<Uri>().unwrap(),
963 subscribe: false,
964 blob_mirror_url: "http://example.com/blobs".parse::<Uri>().unwrap(),
965 }
966 );
967 assert_eq!(
968 builder
969 .clone()
970 .blob_mirror_url("http://example.com/a/b".parse::<Uri>().unwrap())
971 .unwrap()
972 .build(),
973 MirrorConfig {
974 mirror_url: "http://example.com/".parse::<Uri>().unwrap(),
975 subscribe: false,
976 blob_mirror_url: "http://example.com/a/b".parse::<Uri>().unwrap(),
977 }
978 );
979 assert_eq!(
980 builder.clone().mirror_url("http://127.0.0.1".parse::<Uri>().unwrap()).unwrap().build(),
981 MirrorConfig {
982 mirror_url: "http://127.0.0.1".parse::<Uri>().unwrap(),
983 subscribe: false,
984 blob_mirror_url: "http://example.com/blobs".parse::<Uri>().unwrap(),
985 }
986 );
987 assert_eq!(
988 builder.subscribe(true).build(),
989 MirrorConfig {
990 mirror_url: "http://example.com".parse::<Uri>().unwrap(),
991 subscribe: true,
992 blob_mirror_url: "http://example.com/blobs".parse::<Uri>().unwrap(),
993 }
994 );
995 }
996
997 #[test]
998 fn test_mirror_config_builder_rejects_urls_without_schemes() {
999 assert_matches!(
1000 MirrorConfigBuilder::new("example.com".parse::<Uri>().unwrap()),
1001 Err(MirrorConfigError::MirrorUrlMissingScheme)
1002 );
1003
1004 let builder =
1005 MirrorConfigBuilder::new("http://example.com/".parse::<Uri>().unwrap()).unwrap();
1006 assert_matches!(
1007 builder.mirror_url("example.com".parse::<Uri>().unwrap()),
1008 Err((_, MirrorConfigError::MirrorUrlMissingScheme))
1009 );
1010
1011 let builder =
1012 MirrorConfigBuilder::new("http://example.com/".parse::<Uri>().unwrap()).unwrap();
1013 assert_matches!(
1014 builder.blob_mirror_url("example.com".parse::<Uri>().unwrap()),
1015 Err((_, MirrorConfigError::BlobMirrorUrlMissingScheme))
1016 );
1017 }
1018
1019 #[test]
1020 fn test_mirror_config_bad_uri() {
1021 let as_fidl = fidl::MirrorConfig {
1022 mirror_url: None,
1023 subscribe: Some(false),
1024 blob_mirror_url: None,
1025 ..Default::default()
1026 };
1027 assert_matches!(
1028 MirrorConfig::try_from(as_fidl),
1029 Err(RepositoryParseError::MirrorUrlMissing)
1030 );
1031 }
1032
1033 #[test]
1034 fn test_repository_config_builder() {
1035 let repo_url = fuchsia_url::RepositoryUrl::parse("fuchsia-pkg://fuchsia.com").unwrap();
1036 let builder = RepositoryConfigBuilder::new(repo_url.clone());
1037 assert_eq!(
1038 builder.clone().build(),
1039 RepositoryConfig {
1040 repo_url: repo_url.clone(),
1041 root_version: 1,
1042 root_threshold: 1,
1043 root_keys: vec![],
1044 mirrors: vec![],
1045 use_local_mirror: false,
1046 repo_storage_type: RepositoryStorageType::Ephemeral,
1047 }
1048 );
1049
1050 assert_eq!(
1051 builder.clone().repo_storage_type(RepositoryStorageType::Persistent).build(),
1052 RepositoryConfig {
1053 repo_url: repo_url.clone(),
1054 root_version: 1,
1055 root_threshold: 1,
1056 root_keys: vec![],
1057 mirrors: vec![],
1058 use_local_mirror: false,
1059 repo_storage_type: RepositoryStorageType::Persistent,
1060 }
1061 );
1062 }
1063
1064 #[test]
1065 fn test_repository_config_into_fidl() {
1066 let config = RepositoryConfig {
1067 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1068 root_version: 2,
1069 root_threshold: 2,
1070 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3])],
1071 mirrors: vec![MirrorConfig {
1072 mirror_url: "http://example.com/tuf/repo".parse::<Uri>().unwrap(),
1073 subscribe: true,
1074 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1075 }],
1076 use_local_mirror: true,
1077 repo_storage_type: RepositoryStorageType::Ephemeral,
1078 };
1079 let as_fidl: fidl::RepositoryConfig = config.into();
1080 assert_eq!(
1081 as_fidl,
1082 fidl::RepositoryConfig {
1083 repo_url: Some("fuchsia-pkg://fuchsia.com".parse().unwrap()),
1084 root_version: Some(2),
1085 root_threshold: Some(2),
1086 root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
1087 mirrors: Some(vec![fidl::MirrorConfig {
1088 mirror_url: Some("http://example.com/tuf/repo".into()),
1089 subscribe: Some(true),
1090 blob_mirror_url: None,
1091 ..Default::default()
1092 }]),
1093 use_local_mirror: Some(true),
1094 storage_type: Some(fidl::RepositoryStorageType::Ephemeral),
1095 ..Default::default()
1096 }
1097 );
1098 }
1099
1100 #[test]
1101 fn test_repository_config_from_fidl_without_storage_type() {
1102 let as_fidl = fidl::RepositoryConfig {
1103 repo_url: Some("fuchsia-pkg://fuchsia.com".parse().unwrap()),
1104 root_version: Some(1),
1105 root_threshold: Some(1),
1106 root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
1107 mirrors: Some(vec![fidl::MirrorConfig {
1108 mirror_url: Some("http://example.com/tuf/repo/".into()),
1109 subscribe: Some(true),
1110 blob_mirror_url: None,
1111 ..Default::default()
1112 }]),
1113 use_local_mirror: None,
1114 storage_type: None,
1115 ..Default::default()
1116 };
1117 assert_matches!(
1118 RepositoryConfig::try_from(as_fidl),
1119 Ok(repository_config) if repository_config == RepositoryConfig {
1120 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1121 root_version: 1,
1122 root_threshold: 1,
1123 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),],
1124 mirrors: vec![MirrorConfig {
1125 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
1126 subscribe: true,
1127 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1128 },],
1129 use_local_mirror: false,
1130 repo_storage_type: RepositoryStorageType::Ephemeral,
1131 }
1132 );
1133 }
1134
1135 #[test]
1136 fn test_repository_config_from_fidl_with_storage_type() {
1137 let as_fidl = fidl::RepositoryConfig {
1138 repo_url: Some("fuchsia-pkg://fuchsia.com".parse().unwrap()),
1139 root_version: Some(1),
1140 root_threshold: Some(1),
1141 root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
1142 mirrors: Some(vec![fidl::MirrorConfig {
1143 mirror_url: Some("http://example.com/tuf/repo/".into()),
1144 subscribe: Some(true),
1145 blob_mirror_url: None,
1146 ..Default::default()
1147 }]),
1148 use_local_mirror: None,
1149 storage_type: Some(fidl::RepositoryStorageType::Persistent),
1150 ..Default::default()
1151 };
1152 assert_matches!(
1153 RepositoryConfig::try_from(as_fidl),
1154 Ok(repository_config) if repository_config == RepositoryConfig {
1155 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1156 root_version: 1,
1157 root_threshold: 1,
1158 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),],
1159 mirrors: vec![MirrorConfig {
1160 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
1161 subscribe: true,
1162 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1163 },],
1164 use_local_mirror: false,
1165 repo_storage_type: RepositoryStorageType::Persistent,
1166 }
1167 );
1168 }
1169
1170 #[test]
1171 fn test_repository_config_from_fidl_without_version_and_threshold_and_use_local_mirror() {
1172 let as_fidl = fidl::RepositoryConfig {
1173 repo_url: Some("fuchsia-pkg://fuchsia.com".parse().unwrap()),
1174 root_version: None,
1175 root_threshold: None,
1176 root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
1177 mirrors: Some(vec![fidl::MirrorConfig {
1178 mirror_url: Some("http://example.com/tuf/repo/".into()),
1179 subscribe: Some(true),
1180 blob_mirror_url: None,
1181 ..Default::default()
1182 }]),
1183 use_local_mirror: None,
1184 storage_type: None,
1185 ..Default::default()
1186 };
1187 assert_matches!(
1188 RepositoryConfig::try_from(as_fidl),
1189 Ok(repository_config) if repository_config == RepositoryConfig {
1190 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1191 root_version: 1,
1192 root_threshold: 1,
1193 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),],
1194 mirrors: vec![MirrorConfig {
1195 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
1196 subscribe: true,
1197 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1198 },],
1199 use_local_mirror: false,
1200 repo_storage_type: RepositoryStorageType::Ephemeral,
1201 }
1202 );
1203 }
1204
1205 #[test]
1206 fn test_repository_config_from_fidl_with_version_and_threshold_and_use_local_mirror() {
1207 let as_fidl = fidl::RepositoryConfig {
1208 repo_url: Some("fuchsia-pkg://fuchsia.com".parse().unwrap()),
1209 root_version: Some(2),
1210 root_threshold: Some(2),
1211 root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
1212 mirrors: Some(vec![fidl::MirrorConfig {
1213 mirror_url: Some("http://example.com/tuf/repo/".into()),
1214 subscribe: Some(true),
1215 blob_mirror_url: None,
1216 ..Default::default()
1217 }]),
1218 use_local_mirror: Some(true),
1219 storage_type: None,
1220 ..Default::default()
1221 };
1222 assert_matches!(
1223 RepositoryConfig::try_from(as_fidl),
1224 Ok(repository_config) if repository_config == RepositoryConfig {
1225 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1226 root_version: 2,
1227 root_threshold: 2,
1228 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),],
1229 mirrors: vec![MirrorConfig {
1230 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
1231 subscribe: true,
1232 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1233 },],
1234 use_local_mirror: true,
1235 repo_storage_type: RepositoryStorageType::Ephemeral,
1236 }
1237 );
1238 }
1239
1240 #[test]
1241 fn test_repository_config_from_fidl_repo_url_missing() {
1242 let as_fidl = fidl::RepositoryConfig {
1243 repo_url: None,
1244 root_version: None,
1245 root_threshold: None,
1246 root_keys: Some(vec![]),
1247 mirrors: Some(vec![]),
1248 use_local_mirror: None,
1249 storage_type: None,
1250 ..Default::default()
1251 };
1252 assert_matches!(
1253 RepositoryConfig::try_from(as_fidl),
1254 Err(RepositoryParseError::RepoUrlMissing)
1255 );
1256 }
1257
1258 #[test]
1259 fn test_repository_config_into_from_fidl_roundtrip() {
1260 let config = RepositoryConfig {
1261 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1262 root_version: 2,
1263 root_threshold: 2,
1264 root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3])],
1265 mirrors: vec![MirrorConfig {
1266 mirror_url: "http://example.com/tuf/repo/".parse::<Uri>().unwrap(),
1267 subscribe: true,
1268 blob_mirror_url: "http://example.com/tuf/repo/blobs".parse::<Uri>().unwrap(),
1269 }],
1270 use_local_mirror: true,
1271 repo_storage_type: RepositoryStorageType::Ephemeral,
1272 };
1273 let as_fidl: fidl::RepositoryConfig = config.clone().into();
1274 assert_eq!(RepositoryConfig::try_from(as_fidl).unwrap(), config);
1275 }
1276
1277 #[test]
1278 fn test_repository_config_deserialize_missing_root_version_and_threshold_and_use_local_mirror()
1279 {
1280 let json_value = json!({
1281 "repo_url": "fuchsia-pkg://fuchsia.com",
1282 "root_keys": [],
1283 "mirrors": [],
1284 });
1285 let actual_config: RepositoryConfig = serde_json::from_value(json_value).unwrap();
1286
1287 assert_eq!(
1288 actual_config,
1289 RepositoryConfig {
1290 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1291 root_version: 1,
1292 root_threshold: 1,
1293 root_keys: vec![],
1294 mirrors: vec![],
1295 use_local_mirror: false,
1296 repo_storage_type: RepositoryStorageType::Ephemeral,
1297 },
1298 );
1299 }
1300
1301 #[test]
1304 fn test_repository_config_deserialize_ignores_update_package_url() {
1305 let json_value = json!({
1306 "repo_url": "fuchsia-pkg://fuchsia.com",
1307 "root_keys": [],
1308 "mirrors": [],
1309 "update_package_url": "ignored-value",
1310 });
1311 let actual_config: RepositoryConfig = serde_json::from_value(json_value).unwrap();
1312
1313 assert_eq!(
1314 actual_config,
1315 RepositoryConfig {
1316 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1317 root_version: 1,
1318 root_threshold: 1,
1319 root_keys: vec![],
1320 mirrors: vec![],
1321 use_local_mirror: false,
1322 repo_storage_type: RepositoryStorageType::Ephemeral,
1323 },
1324 );
1325 }
1326
1327 #[test]
1328 fn test_repository_configs_serde_simple() {
1329 verify_json_serde(
1330 RepositoryConfigs::Version1(vec![RepositoryConfig {
1331 repo_url: "fuchsia-pkg://fuchsia.com".parse().unwrap(),
1332 root_version: 1,
1333 root_threshold: 1,
1334 root_keys: vec![],
1335 mirrors: vec![],
1336 use_local_mirror: true,
1337 repo_storage_type: RepositoryStorageType::Ephemeral,
1338 }]),
1339 json!({
1340 "version": "1",
1341 "content": [{
1342 "repo_url": "fuchsia-pkg://fuchsia.com",
1343 "root_version": 1,
1344 "root_threshold": 1,
1345 "root_keys": [],
1346 "mirrors": [],
1347 "use_local_mirror": true,
1348 "repo_storage_type": "ephemeral",
1349 }],
1350 }),
1351 );
1352 }
1353
1354 #[test]
1355 fn test_repository_url_into_fidl() {
1356 let url = RepositoryUrl { url: "fuchsia-pkg://fuchsia.com".parse().unwrap() };
1357 let as_fidl: fidl::RepositoryUrl = url.into();
1358 assert_eq!(as_fidl, fidl::RepositoryUrl { url: "fuchsia-pkg://fuchsia.com".to_owned() });
1359 }
1360
1361 #[test]
1362 fn test_repository_url_from_fidl() {
1363 let as_fidl = fidl::RepositoryUrl { url: "fuchsia-pkg://fuchsia.com".to_owned() };
1364 assert_matches!(
1365 RepositoryUrl::try_from(&as_fidl),
1366 Ok(RepositoryUrl { url }) if url == "fuchsia-pkg://fuchsia.com".parse().unwrap()
1367 );
1368 }
1369
1370 #[test]
1371 fn test_repository_url_from_fidl_with_bad_url() {
1372 let as_fidl = fidl::RepositoryUrl { url: "invalid-scheme://fuchsia.com".to_owned() };
1373 assert_matches!(
1374 RepositoryUrl::try_from(&as_fidl),
1375 Err(RepositoryUrlParseError::InvalidRepoUrl(fuchsia_url::ParseError::InvalidScheme))
1376 );
1377 }
1378
1379 #[test]
1380 fn test_repository_url_into_from_fidl_roundtrip() {
1381 let url = RepositoryUrl { url: "fuchsia-pkg://fuchsia.com".parse().unwrap() };
1382 let as_fidl: fidl::RepositoryUrl = url.clone().into();
1383 assert_eq!(RepositoryUrl::try_from(&as_fidl).unwrap(), url);
1384 }
1385}
1386
1387mod hex_serde {
1388 use serde::Deserialize;
1389
1390 pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
1391 where
1392 S: serde::Serializer,
1393 {
1394 let s = hex::encode(bytes);
1395 serializer.serialize_str(&s)
1396 }
1397
1398 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
1399 where
1400 D: serde::Deserializer<'de>,
1401 {
1402 let value = String::deserialize(deserializer)?;
1403 hex::decode(value.as_bytes())
1404 .map_err(|e| serde::de::Error::custom(format!("bad hex value: {:?}: {}", value, e)))
1405 }
1406}
1407
1408mod uri_serde {
1409 use http::Uri;
1410 use serde::Deserialize;
1411
1412 pub fn serialize<S>(uri: &http::Uri, serializer: S) -> Result<S::Ok, S::Error>
1413 where
1414 S: serde::Serializer,
1415 {
1416 let s = uri.to_string();
1417 serializer.serialize_str(&s)
1418 }
1419
1420 pub fn deserialize<'de, D>(deserializer: D) -> Result<http::Uri, D::Error>
1421 where
1422 D: serde::Deserializer<'de>,
1423 {
1424 let value = String::deserialize(deserializer)?;
1425 value
1426 .parse::<Uri>()
1427 .map_err(|e| serde::de::Error::custom(format!("bad uri value: {:?}: {}", value, e)))
1428 }
1429}