Skip to main content

rustack_cloudfront_http/
router.rs

1//! Route CloudFront REST paths to operation identifiers.
2//!
3//! CloudFront uses a 2020-05-31 path-versioned REST API, disambiguated by
4//! HTTP method + path + optional query flag. The table here covers every
5//! operation shipped across all phases.
6
7use rustack_cloudfront_model::CloudFrontError;
8
9/// An identified CloudFront operation.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct RouteMatch {
12    /// Operation name (matching the Smithy operation name).
13    pub operation: Operation,
14    /// Path-bound parameters extracted by the router.
15    pub path_params: PathParams,
16}
17
18/// Path parameters extracted from the URL.
19#[derive(Debug, Clone, Default, PartialEq, Eq)]
20pub struct PathParams {
21    /// Resource ID parameter (distribution, OAC, policy, etc.).
22    pub id: String,
23    /// Secondary resource ID (e.g. invalidation ID within a distribution).
24    pub secondary_id: String,
25    /// Resource name parameter (e.g. function name).
26    pub name: String,
27}
28
29/// Identified operation.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[allow(missing_docs)]
32pub enum Operation {
33    // Distribution
34    CreateDistribution,
35    CreateDistributionWithTags,
36    GetDistribution,
37    GetDistributionConfig,
38    UpdateDistribution,
39    DeleteDistribution,
40    ListDistributions,
41    CopyDistribution,
42    // Invalidation
43    CreateInvalidation,
44    GetInvalidation,
45    ListInvalidations,
46    // OAC
47    CreateOriginAccessControl,
48    GetOriginAccessControl,
49    GetOriginAccessControlConfig,
50    UpdateOriginAccessControl,
51    DeleteOriginAccessControl,
52    ListOriginAccessControls,
53    // OAI
54    CreateCloudFrontOriginAccessIdentity,
55    GetCloudFrontOriginAccessIdentity,
56    GetCloudFrontOriginAccessIdentityConfig,
57    UpdateCloudFrontOriginAccessIdentity,
58    DeleteCloudFrontOriginAccessIdentity,
59    ListCloudFrontOriginAccessIdentities,
60    // Cache policy
61    CreateCachePolicy,
62    GetCachePolicy,
63    GetCachePolicyConfig,
64    UpdateCachePolicy,
65    DeleteCachePolicy,
66    ListCachePolicies,
67    // Origin request policy
68    CreateOriginRequestPolicy,
69    GetOriginRequestPolicy,
70    GetOriginRequestPolicyConfig,
71    UpdateOriginRequestPolicy,
72    DeleteOriginRequestPolicy,
73    ListOriginRequestPolicies,
74    // Response headers policy
75    CreateResponseHeadersPolicy,
76    GetResponseHeadersPolicy,
77    GetResponseHeadersPolicyConfig,
78    UpdateResponseHeadersPolicy,
79    DeleteResponseHeadersPolicy,
80    ListResponseHeadersPolicies,
81    // Key group
82    CreateKeyGroup,
83    GetKeyGroup,
84    GetKeyGroupConfig,
85    UpdateKeyGroup,
86    DeleteKeyGroup,
87    ListKeyGroups,
88    // Public key
89    CreatePublicKey,
90    GetPublicKey,
91    GetPublicKeyConfig,
92    UpdatePublicKey,
93    DeletePublicKey,
94    ListPublicKeys,
95    // Functions
96    CreateFunction,
97    DescribeFunction,
98    GetFunction,
99    UpdateFunction,
100    DeleteFunction,
101    PublishFunction,
102    TestFunction,
103    ListFunctions,
104    // FLE
105    CreateFieldLevelEncryptionConfig,
106    GetFieldLevelEncryption,
107    GetFieldLevelEncryptionConfig,
108    UpdateFieldLevelEncryptionConfig,
109    DeleteFieldLevelEncryptionConfig,
110    ListFieldLevelEncryptionConfigs,
111    CreateFieldLevelEncryptionProfile,
112    GetFieldLevelEncryptionProfile,
113    GetFieldLevelEncryptionProfileConfig,
114    UpdateFieldLevelEncryptionProfile,
115    DeleteFieldLevelEncryptionProfile,
116    ListFieldLevelEncryptionProfiles,
117    // Monitoring subscription
118    CreateMonitoringSubscription,
119    GetMonitoringSubscription,
120    DeleteMonitoringSubscription,
121    // KVS
122    CreateKeyValueStore,
123    DescribeKeyValueStore,
124    UpdateKeyValueStore,
125    DeleteKeyValueStore,
126    ListKeyValueStores,
127    // Realtime log
128    CreateRealtimeLogConfig,
129    GetRealtimeLogConfig,
130    UpdateRealtimeLogConfig,
131    DeleteRealtimeLogConfig,
132    ListRealtimeLogConfigs,
133    // Tagging
134    TagResource,
135    UntagResource,
136    ListTagsForResource,
137    // Phase 4 stubs
138    AssociateAlias,
139    ListConflictingAliases,
140    UpdateDistributionWithStagingConfig,
141    GetResourcePolicy,
142    PutResourcePolicy,
143    DeleteResourcePolicy,
144    GetManagedCertificateDetails,
145    VerifyDnsConfiguration,
146    ListDistributionsByCachePolicyId,
147    ListDistributionsByKeyGroup,
148    ListDistributionsByOriginRequestPolicyId,
149    ListDistributionsByRealtimeLogConfig,
150    ListDistributionsByResponseHeadersPolicyId,
151    ListDistributionsByVpcOriginId,
152    ListDistributionsByWebACLId,
153    ListDistributionsByAnycastIpListId,
154    AssociateDistributionWebACL,
155    DisassociateDistributionWebACL,
156    CreateContinuousDeploymentPolicy,
157    GetContinuousDeploymentPolicy,
158    GetContinuousDeploymentPolicyConfig,
159    UpdateContinuousDeploymentPolicy,
160    DeleteContinuousDeploymentPolicy,
161    ListContinuousDeploymentPolicies,
162    CreateStreamingDistribution,
163    CreateStreamingDistributionWithTags,
164    GetStreamingDistribution,
165    GetStreamingDistributionConfig,
166    UpdateStreamingDistribution,
167    DeleteStreamingDistribution,
168    ListStreamingDistributions,
169    CreateAnycastIpList,
170    GetAnycastIpList,
171    UpdateAnycastIpList,
172    DeleteAnycastIpList,
173    ListAnycastIpLists,
174    CreateVpcOrigin,
175    GetVpcOrigin,
176    UpdateVpcOrigin,
177    DeleteVpcOrigin,
178    ListVpcOrigins,
179    CreateTrustStore,
180    GetTrustStore,
181    UpdateTrustStore,
182    DeleteTrustStore,
183    ListTrustStores,
184    ListDomainConflicts,
185    UpdateDomainAssociation,
186}
187
188/// Resolve an HTTP request to a CloudFront operation.
189#[allow(clippy::too_many_lines)]
190pub fn resolve(method: &http::Method, uri: &http::Uri) -> Result<RouteMatch, CloudFrontError> {
191    let path = uri.path();
192    let query = uri.query().unwrap_or("");
193    let segments: Vec<&str> = path.trim_matches('/').split('/').collect();
194
195    // Every CloudFront URL begins with the API version.
196    if segments.first().copied() != Some("2020-05-31") {
197        return Err(CloudFrontError::InvalidArgument(format!(
198            "Unknown path {path}"
199        )));
200    }
201    let rest: Vec<&str> = segments.iter().skip(1).copied().collect();
202
203    use http::Method;
204    let m = method.clone();
205
206    let op = match rest.as_slice() {
207        // ----- Tagging -----
208        ["tagging"] if m == Method::POST => {
209            if query_has(query, "Operation=Tag") {
210                Operation::TagResource
211            } else if query_has(query, "Operation=Untag") {
212                Operation::UntagResource
213            } else {
214                return Err(CloudFrontError::InvalidArgument(
215                    "tagging requires Operation=Tag or Operation=Untag".into(),
216                ));
217            }
218        }
219        ["tagging"] if m == Method::GET => Operation::ListTagsForResource,
220
221        // ----- Distribution -----
222        ["distribution"] if m == Method::POST => {
223            if query_has(query, "WithTags") {
224                Operation::CreateDistributionWithTags
225            } else {
226                Operation::CreateDistribution
227            }
228        }
229        ["distribution"] if m == Method::GET => Operation::ListDistributions,
230        ["distribution", id] if m == Method::GET => {
231            return ok_with_id(Operation::GetDistribution, id);
232        }
233        ["distribution", id] if m == Method::DELETE => {
234            return ok_with_id(Operation::DeleteDistribution, id);
235        }
236        ["distribution", id, "config"] if m == Method::GET => {
237            return ok_with_id(Operation::GetDistributionConfig, id);
238        }
239        ["distribution", id, "config"] if m == Method::PUT => {
240            return ok_with_id(Operation::UpdateDistribution, id);
241        }
242        ["distribution", id, "copy"] if m == Method::POST => {
243            return ok_with_id(Operation::CopyDistribution, id);
244        }
245        ["distribution", id, "promote-staging-config"] if m == Method::POST => {
246            return ok_with_id(Operation::UpdateDistributionWithStagingConfig, id);
247        }
248        ["distribution", id, "invalidation"] if m == Method::POST => {
249            return ok_with_id(Operation::CreateInvalidation, id);
250        }
251        ["distribution", id, "invalidation"] if m == Method::GET => {
252            return ok_with_id(Operation::ListInvalidations, id);
253        }
254        ["distribution", id, "invalidation", inv_id] if m == Method::GET => {
255            return ok_with_two(Operation::GetInvalidation, id, inv_id);
256        }
257
258        // ----- OAC -----
259        ["origin-access-control"] if m == Method::POST => Operation::CreateOriginAccessControl,
260        ["origin-access-control"] if m == Method::GET => Operation::ListOriginAccessControls,
261        ["origin-access-control", id] if m == Method::GET => {
262            return ok_with_id(Operation::GetOriginAccessControl, id);
263        }
264        ["origin-access-control", id] if m == Method::DELETE => {
265            return ok_with_id(Operation::DeleteOriginAccessControl, id);
266        }
267        ["origin-access-control", id, "config"] if m == Method::GET => {
268            return ok_with_id(Operation::GetOriginAccessControlConfig, id);
269        }
270        ["origin-access-control", id, "config"] if m == Method::PUT => {
271            return ok_with_id(Operation::UpdateOriginAccessControl, id);
272        }
273
274        // ----- OAI -----
275        ["origin-access-identity", "cloudfront"] if m == Method::POST => {
276            Operation::CreateCloudFrontOriginAccessIdentity
277        }
278        ["origin-access-identity", "cloudfront"] if m == Method::GET => {
279            Operation::ListCloudFrontOriginAccessIdentities
280        }
281        ["origin-access-identity", "cloudfront", id] if m == Method::GET => {
282            return ok_with_id(Operation::GetCloudFrontOriginAccessIdentity, id);
283        }
284        ["origin-access-identity", "cloudfront", id] if m == Method::DELETE => {
285            return ok_with_id(Operation::DeleteCloudFrontOriginAccessIdentity, id);
286        }
287        ["origin-access-identity", "cloudfront", id, "config"] if m == Method::GET => {
288            return ok_with_id(Operation::GetCloudFrontOriginAccessIdentityConfig, id);
289        }
290        ["origin-access-identity", "cloudfront", id, "config"] if m == Method::PUT => {
291            return ok_with_id(Operation::UpdateCloudFrontOriginAccessIdentity, id);
292        }
293
294        // ----- Policies: cache-policy -----
295        ["cache-policy"] if m == Method::POST => Operation::CreateCachePolicy,
296        ["cache-policy"] if m == Method::GET => Operation::ListCachePolicies,
297        ["cache-policy", id] if m == Method::GET => {
298            return ok_with_id(Operation::GetCachePolicy, id);
299        }
300        ["cache-policy", id] if m == Method::DELETE => {
301            return ok_with_id(Operation::DeleteCachePolicy, id);
302        }
303        ["cache-policy", id, "config"] if m == Method::GET => {
304            return ok_with_id(Operation::GetCachePolicyConfig, id);
305        }
306        ["cache-policy", id, "config"] if m == Method::PUT => {
307            return ok_with_id(Operation::UpdateCachePolicy, id);
308        }
309
310        // ----- origin-request-policy -----
311        ["origin-request-policy"] if m == Method::POST => Operation::CreateOriginRequestPolicy,
312        ["origin-request-policy"] if m == Method::GET => Operation::ListOriginRequestPolicies,
313        ["origin-request-policy", id] if m == Method::GET => {
314            return ok_with_id(Operation::GetOriginRequestPolicy, id);
315        }
316        ["origin-request-policy", id] if m == Method::DELETE => {
317            return ok_with_id(Operation::DeleteOriginRequestPolicy, id);
318        }
319        ["origin-request-policy", id, "config"] if m == Method::GET => {
320            return ok_with_id(Operation::GetOriginRequestPolicyConfig, id);
321        }
322        ["origin-request-policy", id, "config"] if m == Method::PUT => {
323            return ok_with_id(Operation::UpdateOriginRequestPolicy, id);
324        }
325
326        // ----- response-headers-policy -----
327        ["response-headers-policy"] if m == Method::POST => Operation::CreateResponseHeadersPolicy,
328        ["response-headers-policy"] if m == Method::GET => Operation::ListResponseHeadersPolicies,
329        ["response-headers-policy", id] if m == Method::GET => {
330            return ok_with_id(Operation::GetResponseHeadersPolicy, id);
331        }
332        ["response-headers-policy", id] if m == Method::DELETE => {
333            return ok_with_id(Operation::DeleteResponseHeadersPolicy, id);
334        }
335        ["response-headers-policy", id, "config"] if m == Method::GET => {
336            return ok_with_id(Operation::GetResponseHeadersPolicyConfig, id);
337        }
338        ["response-headers-policy", id, "config"] if m == Method::PUT => {
339            return ok_with_id(Operation::UpdateResponseHeadersPolicy, id);
340        }
341
342        // ----- key-group -----
343        ["key-group"] if m == Method::POST => Operation::CreateKeyGroup,
344        ["key-group"] if m == Method::GET => Operation::ListKeyGroups,
345        ["key-group", id] if m == Method::GET => {
346            return ok_with_id(Operation::GetKeyGroup, id);
347        }
348        ["key-group", id] if m == Method::DELETE => {
349            return ok_with_id(Operation::DeleteKeyGroup, id);
350        }
351        ["key-group", id, "config"] if m == Method::GET => {
352            return ok_with_id(Operation::GetKeyGroupConfig, id);
353        }
354        ["key-group", id, "config"] if m == Method::PUT => {
355            return ok_with_id(Operation::UpdateKeyGroup, id);
356        }
357
358        // ----- public-key -----
359        ["public-key"] if m == Method::POST => Operation::CreatePublicKey,
360        ["public-key"] if m == Method::GET => Operation::ListPublicKeys,
361        ["public-key", id] if m == Method::GET => {
362            return ok_with_id(Operation::GetPublicKey, id);
363        }
364        ["public-key", id] if m == Method::DELETE => {
365            return ok_with_id(Operation::DeletePublicKey, id);
366        }
367        ["public-key", id, "config"] if m == Method::GET => {
368            return ok_with_id(Operation::GetPublicKeyConfig, id);
369        }
370        ["public-key", id, "config"] if m == Method::PUT => {
371            return ok_with_id(Operation::UpdatePublicKey, id);
372        }
373
374        // ----- function -----
375        ["function"] if m == Method::POST => Operation::CreateFunction,
376        ["function"] if m == Method::GET => Operation::ListFunctions,
377        ["function", name] if m == Method::GET => {
378            return ok_with_name(Operation::DescribeFunction, name);
379        }
380        ["function", name] if m == Method::DELETE => {
381            return ok_with_name(Operation::DeleteFunction, name);
382        }
383        ["function", name] if m == Method::PUT => {
384            return ok_with_name(Operation::UpdateFunction, name);
385        }
386        ["function", name, "publish"] if m == Method::POST => {
387            return ok_with_name(Operation::PublishFunction, name);
388        }
389        ["function", name, "test"] if m == Method::POST => {
390            return ok_with_name(Operation::TestFunction, name);
391        }
392        ["function", name, "development"] if m == Method::POST => {
393            return ok_with_name(Operation::GetFunction, name);
394        }
395
396        // ----- FLE -----
397        ["field-level-encryption"] if m == Method::POST => {
398            Operation::CreateFieldLevelEncryptionConfig
399        }
400        ["field-level-encryption"] if m == Method::GET => {
401            Operation::ListFieldLevelEncryptionConfigs
402        }
403        ["field-level-encryption", id] if m == Method::GET => {
404            return ok_with_id(Operation::GetFieldLevelEncryption, id);
405        }
406        ["field-level-encryption", id] if m == Method::DELETE => {
407            return ok_with_id(Operation::DeleteFieldLevelEncryptionConfig, id);
408        }
409        ["field-level-encryption", id, "config"] if m == Method::GET => {
410            return ok_with_id(Operation::GetFieldLevelEncryptionConfig, id);
411        }
412        ["field-level-encryption", id, "config"] if m == Method::PUT => {
413            return ok_with_id(Operation::UpdateFieldLevelEncryptionConfig, id);
414        }
415        ["field-level-encryption-profile"] if m == Method::POST => {
416            Operation::CreateFieldLevelEncryptionProfile
417        }
418        ["field-level-encryption-profile"] if m == Method::GET => {
419            Operation::ListFieldLevelEncryptionProfiles
420        }
421        ["field-level-encryption-profile", id] if m == Method::GET => {
422            return ok_with_id(Operation::GetFieldLevelEncryptionProfile, id);
423        }
424        ["field-level-encryption-profile", id] if m == Method::DELETE => {
425            return ok_with_id(Operation::DeleteFieldLevelEncryptionProfile, id);
426        }
427        ["field-level-encryption-profile", id, "config"] if m == Method::GET => {
428            return ok_with_id(Operation::GetFieldLevelEncryptionProfileConfig, id);
429        }
430        ["field-level-encryption-profile", id, "config"] if m == Method::PUT => {
431            return ok_with_id(Operation::UpdateFieldLevelEncryptionProfile, id);
432        }
433
434        // ----- Monitoring subscription -----
435        ["distributions", dist_id, "monitoring-subscription"] if m == Method::POST => {
436            return ok_with_id(Operation::CreateMonitoringSubscription, dist_id);
437        }
438        ["distributions", dist_id, "monitoring-subscription"] if m == Method::GET => {
439            return ok_with_id(Operation::GetMonitoringSubscription, dist_id);
440        }
441        ["distributions", dist_id, "monitoring-subscription"] if m == Method::DELETE => {
442            return ok_with_id(Operation::DeleteMonitoringSubscription, dist_id);
443        }
444
445        // ----- KVS -----
446        ["key-value-store"] if m == Method::POST => Operation::CreateKeyValueStore,
447        ["key-value-store"] if m == Method::GET => Operation::ListKeyValueStores,
448        ["key-value-store", id] if m == Method::GET => {
449            return ok_with_id(Operation::DescribeKeyValueStore, id);
450        }
451        ["key-value-store", id] if m == Method::DELETE => {
452            return ok_with_id(Operation::DeleteKeyValueStore, id);
453        }
454        ["key-value-store", id] if m == Method::PUT => {
455            return ok_with_id(Operation::UpdateKeyValueStore, id);
456        }
457
458        // ----- Realtime log -----
459        ["realtime-log-config"] if m == Method::POST => Operation::CreateRealtimeLogConfig,
460        ["realtime-log-config"] if m == Method::GET => Operation::ListRealtimeLogConfigs,
461        ["realtime-log-config"] if m == Method::PUT => Operation::UpdateRealtimeLogConfig,
462        ["realtime-log-config"] if m == Method::DELETE => Operation::DeleteRealtimeLogConfig,
463        ["get-realtime-log-config"] if m == Method::POST => Operation::GetRealtimeLogConfig,
464
465        // ----- Phase 4 stubs: keep the shapes so SDKs don't fail -----
466        ["distributions-by-cache-policy-id", id] if m == Method::GET => {
467            return ok_with_id(Operation::ListDistributionsByCachePolicyId, id);
468        }
469        ["distributions-by-key-group", id] if m == Method::GET => {
470            return ok_with_id(Operation::ListDistributionsByKeyGroup, id);
471        }
472        ["distributions-by-origin-request-policy-id", id] if m == Method::GET => {
473            return ok_with_id(Operation::ListDistributionsByOriginRequestPolicyId, id);
474        }
475        ["distributions-by-realtime-log-config"] if m == Method::POST => {
476            Operation::ListDistributionsByRealtimeLogConfig
477        }
478        ["distributions-by-response-headers-policy-id", id] if m == Method::GET => {
479            return ok_with_id(Operation::ListDistributionsByResponseHeadersPolicyId, id);
480        }
481        ["distributions-by-vpc-origin-id", id] if m == Method::GET => {
482            return ok_with_id(Operation::ListDistributionsByVpcOriginId, id);
483        }
484        ["distributions-by-web-acl-id", id] if m == Method::GET => {
485            return ok_with_id(Operation::ListDistributionsByWebACLId, id);
486        }
487        ["distributions-by-anycast-ip-list-id", id] if m == Method::GET => {
488            return ok_with_id(Operation::ListDistributionsByAnycastIpListId, id);
489        }
490        ["distribution", id, "associate-alias"] if m == Method::PUT => {
491            return ok_with_id(Operation::AssociateAlias, id);
492        }
493        ["conflicting-alias"] if m == Method::GET => Operation::ListConflictingAliases,
494        ["resource-policy", "arn"] if m == Method::GET => Operation::GetResourcePolicy,
495        ["resource-policy", "arn"] if m == Method::PUT => Operation::PutResourcePolicy,
496        ["resource-policy", "arn"] if m == Method::DELETE => Operation::DeleteResourcePolicy,
497        ["managed-certificate-details"] if m == Method::GET => {
498            Operation::GetManagedCertificateDetails
499        }
500        ["verify-dns-configuration"] if m == Method::POST => Operation::VerifyDnsConfiguration,
501        ["continuous-deployment-policy"] if m == Method::POST => {
502            Operation::CreateContinuousDeploymentPolicy
503        }
504        ["continuous-deployment-policy"] if m == Method::GET => {
505            Operation::ListContinuousDeploymentPolicies
506        }
507        ["continuous-deployment-policy", id] if m == Method::GET => {
508            return ok_with_id(Operation::GetContinuousDeploymentPolicy, id);
509        }
510        ["continuous-deployment-policy", id] if m == Method::DELETE => {
511            return ok_with_id(Operation::DeleteContinuousDeploymentPolicy, id);
512        }
513        ["continuous-deployment-policy", id, "config"] if m == Method::GET => {
514            return ok_with_id(Operation::GetContinuousDeploymentPolicyConfig, id);
515        }
516        ["continuous-deployment-policy", id, "config"] if m == Method::PUT => {
517            return ok_with_id(Operation::UpdateContinuousDeploymentPolicy, id);
518        }
519        ["streaming-distribution"] if m == Method::POST => {
520            if query_has(query, "WithTags") {
521                Operation::CreateStreamingDistributionWithTags
522            } else {
523                Operation::CreateStreamingDistribution
524            }
525        }
526        ["streaming-distribution"] if m == Method::GET => Operation::ListStreamingDistributions,
527        ["streaming-distribution", id] if m == Method::GET => {
528            return ok_with_id(Operation::GetStreamingDistribution, id);
529        }
530        ["streaming-distribution", id] if m == Method::DELETE => {
531            return ok_with_id(Operation::DeleteStreamingDistribution, id);
532        }
533        ["streaming-distribution", id, "config"] if m == Method::GET => {
534            return ok_with_id(Operation::GetStreamingDistributionConfig, id);
535        }
536        ["streaming-distribution", id, "config"] if m == Method::PUT => {
537            return ok_with_id(Operation::UpdateStreamingDistribution, id);
538        }
539        ["anycast-ip-list"] if m == Method::POST => Operation::CreateAnycastIpList,
540        ["anycast-ip-list"] if m == Method::GET => Operation::ListAnycastIpLists,
541        ["anycast-ip-list", id] if m == Method::GET => {
542            return ok_with_id(Operation::GetAnycastIpList, id);
543        }
544        ["anycast-ip-list", id] if m == Method::DELETE => {
545            return ok_with_id(Operation::DeleteAnycastIpList, id);
546        }
547        ["anycast-ip-list", id] if m == Method::PUT => {
548            return ok_with_id(Operation::UpdateAnycastIpList, id);
549        }
550        ["vpc-origin"] if m == Method::POST => Operation::CreateVpcOrigin,
551        ["vpc-origin"] if m == Method::GET => Operation::ListVpcOrigins,
552        ["vpc-origin", id] if m == Method::GET => {
553            return ok_with_id(Operation::GetVpcOrigin, id);
554        }
555        ["vpc-origin", id] if m == Method::DELETE => {
556            return ok_with_id(Operation::DeleteVpcOrigin, id);
557        }
558        ["vpc-origin", id] if m == Method::PUT => {
559            return ok_with_id(Operation::UpdateVpcOrigin, id);
560        }
561        ["truststore"] if m == Method::POST => Operation::CreateTrustStore,
562        ["truststore"] if m == Method::GET => Operation::ListTrustStores,
563        ["truststore", id] if m == Method::GET => {
564            return ok_with_id(Operation::GetTrustStore, id);
565        }
566        ["truststore", id] if m == Method::DELETE => {
567            return ok_with_id(Operation::DeleteTrustStore, id);
568        }
569        ["truststore", id] if m == Method::PUT => {
570            return ok_with_id(Operation::UpdateTrustStore, id);
571        }
572        ["domain-conflicts"] if m == Method::POST => Operation::ListDomainConflicts,
573        ["domain-association"] if m == Method::POST => Operation::UpdateDomainAssociation,
574        ["distribution", id, "web-acl"] if m == Method::PUT => {
575            return ok_with_id(Operation::AssociateDistributionWebACL, id);
576        }
577        ["distribution", id, "web-acl"] if m == Method::DELETE => {
578            return ok_with_id(Operation::DisassociateDistributionWebACL, id);
579        }
580
581        _ => {
582            return Err(CloudFrontError::InvalidArgument(format!(
583                "No route matched {method} {path}"
584            )));
585        }
586    };
587
588    Ok(RouteMatch {
589        operation: op,
590        path_params: PathParams::default(),
591    })
592}
593
594fn ok_with_id(op: Operation, id: &str) -> Result<RouteMatch, CloudFrontError> {
595    Ok(RouteMatch {
596        operation: op,
597        path_params: PathParams {
598            id: id.to_owned(),
599            ..PathParams::default()
600        },
601    })
602}
603
604fn ok_with_two(op: Operation, id: &str, sec: &str) -> Result<RouteMatch, CloudFrontError> {
605    Ok(RouteMatch {
606        operation: op,
607        path_params: PathParams {
608            id: id.to_owned(),
609            secondary_id: sec.to_owned(),
610            ..PathParams::default()
611        },
612    })
613}
614
615fn ok_with_name(op: Operation, name: &str) -> Result<RouteMatch, CloudFrontError> {
616    Ok(RouteMatch {
617        operation: op,
618        path_params: PathParams {
619            name: name.to_owned(),
620            ..PathParams::default()
621        },
622    })
623}
624
625fn query_has(q: &str, key: &str) -> bool {
626    q.split('&')
627        .any(|kv| kv == key || kv.starts_with(&format!("{key}=")))
628}
629
630#[cfg(test)]
631mod tests {
632    use super::*;
633
634    fn parse_url(method: &str, uri: &str) -> RouteMatch {
635        let m = http::Method::from_bytes(method.as_bytes()).unwrap();
636        let u: http::Uri = uri.parse().unwrap();
637        resolve(&m, &u).expect("route")
638    }
639
640    #[test]
641    fn test_create_distribution() {
642        let r = parse_url("POST", "/2020-05-31/distribution");
643        assert_eq!(r.operation, Operation::CreateDistribution);
644    }
645
646    #[test]
647    fn test_create_distribution_with_tags() {
648        let r = parse_url("POST", "/2020-05-31/distribution?WithTags");
649        assert_eq!(r.operation, Operation::CreateDistributionWithTags);
650    }
651
652    #[test]
653    fn test_update_distribution() {
654        let r = parse_url("PUT", "/2020-05-31/distribution/E1ABC/config");
655        assert_eq!(r.operation, Operation::UpdateDistribution);
656        assert_eq!(r.path_params.id, "E1ABC");
657    }
658
659    #[test]
660    fn test_get_invalidation() {
661        let r = parse_url("GET", "/2020-05-31/distribution/E1ABC/invalidation/I42");
662        assert_eq!(r.operation, Operation::GetInvalidation);
663        assert_eq!(r.path_params.id, "E1ABC");
664        assert_eq!(r.path_params.secondary_id, "I42");
665    }
666
667    #[test]
668    fn test_tag_resource() {
669        let r = parse_url(
670            "POST",
671            "/2020-05-31/tagging?Operation=Tag&Resource=arn:aws:cloudfront::000:dist/E1",
672        );
673        assert_eq!(r.operation, Operation::TagResource);
674    }
675}