|
1 | 1 | use std::str::FromStr; |
2 | 2 |
|
3 | | -use clap::Parser; |
| 3 | +use clap::{ArgGroup, Parser}; |
4 | 4 | use crates_io_fastly::Fastly; |
5 | 5 | use secrecy::SecretString; |
6 | 6 |
|
7 | 7 | /// Arguments accepted by the purge example. |
8 | 8 | #[derive(Debug, Parser)] |
9 | | -#[command(about = "Purge cached content using the crates.io Fastly client")] |
| 9 | +#[command( |
| 10 | + about = "Purge cached content using the crates.io Fastly client", |
| 11 | + group( |
| 12 | + ArgGroup::new("operation") |
| 13 | + .required(true) |
| 14 | + .args(["url", "service_id"]) |
| 15 | + ) |
| 16 | +)] |
10 | 17 | struct Options { |
11 | 18 | /// Fastly API token used to authenticate the purge request. |
12 | 19 | #[arg(long, env = "FASTLY_API_TOKEN", hide_env_values = true)] |
13 | 20 | api_token: SecretString, |
14 | 21 |
|
15 | 22 | /// URL to purge, without the scheme. |
16 | 23 | #[arg(long, value_name = "DOMAIN/PATH")] |
17 | | - url: PurgeUrl, |
| 24 | + url: Option<PurgeUrl>, |
| 25 | + |
| 26 | + /// Fastly service containing the objects associated with the key. |
| 27 | + #[arg(long, value_name = "ID", requires = "key")] |
| 28 | + service_id: Option<String>, |
| 29 | + |
| 30 | + /// Surrogate key to purge. |
| 31 | + #[arg(value_name = "KEY", requires = "service_id")] |
| 32 | + key: Option<String>, |
18 | 33 | } |
19 | 34 |
|
20 | 35 | /// Domain and path extracted from a URL argument. |
@@ -53,12 +68,17 @@ async fn main() -> Result<(), crates_io_fastly::Error> { |
53 | 68 | let options = Options::parse(); |
54 | 69 | let fastly = Fastly::new(options.api_token); |
55 | 70 |
|
56 | | - fastly.purge(&options.url.domain, &options.url.path).await |
| 71 | + match (options.url, options.service_id, options.key) { |
| 72 | + (Some(url), None, None) => fastly.purge(&url.domain, &url.path).await, |
| 73 | + (None, Some(service_id), Some(key)) => fastly.purge_surrogate_key(&service_id, &key).await, |
| 74 | + _ => unreachable!("clap validates exactly one complete purge operation"), |
| 75 | + } |
57 | 76 | } |
58 | 77 |
|
59 | 78 | #[cfg(test)] |
60 | 79 | mod tests { |
61 | 80 | use super::*; |
| 81 | + use clap::error::ErrorKind; |
62 | 82 |
|
63 | 83 | #[test] |
64 | 84 | fn rejects_url_with_http_scheme() { |
@@ -86,10 +106,60 @@ mod tests { |
86 | 106 |
|
87 | 107 | assert_eq!( |
88 | 108 | options.url, |
89 | | - PurgeUrl { |
| 109 | + Some(PurgeUrl { |
90 | 110 | domain: "static.crates.io".into(), |
91 | 111 | path: "crates/serde/serde-1.0.0.crate".into(), |
92 | | - } |
| 112 | + }) |
93 | 113 | ); |
| 114 | + assert_eq!(options.service_id, None); |
| 115 | + assert_eq!(options.key, None); |
| 116 | + } |
| 117 | + |
| 118 | + #[test] |
| 119 | + fn parses_surrogate_key_purge() { |
| 120 | + let options = Options::try_parse_from([ |
| 121 | + "purge", |
| 122 | + "--api-token", |
| 123 | + "test-token", |
| 124 | + "--service-id", |
| 125 | + "static-service-id", |
| 126 | + "release:serde@1.0.0+metadata", |
| 127 | + ]) |
| 128 | + .unwrap(); |
| 129 | + |
| 130 | + assert_eq!(options.url, None); |
| 131 | + assert_eq!(options.service_id.as_deref(), Some("static-service-id")); |
| 132 | + assert_eq!(options.key.as_deref(), Some("release:serde@1.0.0+metadata")); |
| 133 | + } |
| 134 | + |
| 135 | + #[test] |
| 136 | + fn rejects_service_id_without_key() { |
| 137 | + let error = Options::try_parse_from([ |
| 138 | + "purge", |
| 139 | + "--api-token", |
| 140 | + "test-token", |
| 141 | + "--service-id", |
| 142 | + "static-service-id", |
| 143 | + ]) |
| 144 | + .unwrap_err(); |
| 145 | + |
| 146 | + assert_eq!(error.kind(), ErrorKind::MissingRequiredArgument); |
| 147 | + } |
| 148 | + |
| 149 | + #[test] |
| 150 | + fn rejects_multiple_operations() { |
| 151 | + let error = Options::try_parse_from([ |
| 152 | + "purge", |
| 153 | + "--api-token", |
| 154 | + "test-token", |
| 155 | + "--url", |
| 156 | + "static.crates.io/crates/serde/serde-1.0.0.crate", |
| 157 | + "--service-id", |
| 158 | + "static-service-id", |
| 159 | + "crate:serde", |
| 160 | + ]) |
| 161 | + .unwrap_err(); |
| 162 | + |
| 163 | + assert_eq!(error.kind(), ErrorKind::ArgumentConflict); |
94 | 164 | } |
95 | 165 | } |
0 commit comments