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::GetFunction, name);
379        }
380        ["function", name, "describe"] if m == Method::GET => {
381            return ok_with_name(Operation::DescribeFunction, name);
382        }
383        ["function", name] if m == Method::DELETE => {
384            return ok_with_name(Operation::DeleteFunction, name);
385        }
386        ["function", name] if m == Method::PUT => {
387            return ok_with_name(Operation::UpdateFunction, name);
388        }
389        ["function", name, "publish"] if m == Method::POST => {
390            return ok_with_name(Operation::PublishFunction, name);
391        }
392        ["function", name, "test"] if m == Method::POST => {
393            return ok_with_name(Operation::TestFunction, name);
394        }
395        ["function", name, "development"] if m == Method::POST => {
396            return ok_with_name(Operation::GetFunction, name);
397        }
398
399        // ----- FLE -----
400        ["field-level-encryption"] if m == Method::POST => {
401            Operation::CreateFieldLevelEncryptionConfig
402        }
403        ["field-level-encryption"] if m == Method::GET => {
404            Operation::ListFieldLevelEncryptionConfigs
405        }
406        ["field-level-encryption", id] if m == Method::GET => {
407            return ok_with_id(Operation::GetFieldLevelEncryption, id);
408        }
409        ["field-level-encryption", id] if m == Method::DELETE => {
410            return ok_with_id(Operation::DeleteFieldLevelEncryptionConfig, id);
411        }
412        ["field-level-encryption", id, "config"] if m == Method::GET => {
413            return ok_with_id(Operation::GetFieldLevelEncryptionConfig, id);
414        }
415        ["field-level-encryption", id, "config"] if m == Method::PUT => {
416            return ok_with_id(Operation::UpdateFieldLevelEncryptionConfig, id);
417        }
418        ["field-level-encryption-profile"] if m == Method::POST => {
419            Operation::CreateFieldLevelEncryptionProfile
420        }
421        ["field-level-encryption-profile"] if m == Method::GET => {
422            Operation::ListFieldLevelEncryptionProfiles
423        }
424        ["field-level-encryption-profile", id] if m == Method::GET => {
425            return ok_with_id(Operation::GetFieldLevelEncryptionProfile, id);
426        }
427        ["field-level-encryption-profile", id] if m == Method::DELETE => {
428            return ok_with_id(Operation::DeleteFieldLevelEncryptionProfile, id);
429        }
430        ["field-level-encryption-profile", id, "config"] if m == Method::GET => {
431            return ok_with_id(Operation::GetFieldLevelEncryptionProfileConfig, id);
432        }
433        ["field-level-encryption-profile", id, "config"] if m == Method::PUT => {
434            return ok_with_id(Operation::UpdateFieldLevelEncryptionProfile, id);
435        }
436
437        // ----- Monitoring subscription -----
438        ["distributions", dist_id, "monitoring-subscription"] if m == Method::POST => {
439            return ok_with_id(Operation::CreateMonitoringSubscription, dist_id);
440        }
441        ["distributions", dist_id, "monitoring-subscription"] if m == Method::GET => {
442            return ok_with_id(Operation::GetMonitoringSubscription, dist_id);
443        }
444        ["distributions", dist_id, "monitoring-subscription"] if m == Method::DELETE => {
445            return ok_with_id(Operation::DeleteMonitoringSubscription, dist_id);
446        }
447
448        // ----- KVS -----
449        ["key-value-store"] if m == Method::POST => Operation::CreateKeyValueStore,
450        ["key-value-store"] if m == Method::GET => Operation::ListKeyValueStores,
451        ["key-value-store", id] if m == Method::GET => {
452            return ok_with_id(Operation::DescribeKeyValueStore, id);
453        }
454        ["key-value-store", id] if m == Method::DELETE => {
455            return ok_with_id(Operation::DeleteKeyValueStore, id);
456        }
457        ["key-value-store", id] if m == Method::PUT => {
458            return ok_with_id(Operation::UpdateKeyValueStore, id);
459        }
460
461        // ----- Realtime log -----
462        ["realtime-log-config"] if m == Method::POST => Operation::CreateRealtimeLogConfig,
463        ["realtime-log-config"] if m == Method::GET => Operation::ListRealtimeLogConfigs,
464        ["realtime-log-config"] if m == Method::PUT => Operation::UpdateRealtimeLogConfig,
465        ["realtime-log-config"] if m == Method::DELETE => Operation::DeleteRealtimeLogConfig,
466        ["get-realtime-log-config"] if m == Method::POST => Operation::GetRealtimeLogConfig,
467
468        // ----- Phase 4 stubs: keep the shapes so SDKs don't fail -----
469        ["distributions-by-cache-policy-id", id] if m == Method::GET => {
470            return ok_with_id(Operation::ListDistributionsByCachePolicyId, id);
471        }
472        ["distributions-by-key-group", id] if m == Method::GET => {
473            return ok_with_id(Operation::ListDistributionsByKeyGroup, id);
474        }
475        ["distributions-by-origin-request-policy-id", id] if m == Method::GET => {
476            return ok_with_id(Operation::ListDistributionsByOriginRequestPolicyId, id);
477        }
478        ["distributions-by-realtime-log-config"] if m == Method::POST => {
479            Operation::ListDistributionsByRealtimeLogConfig
480        }
481        ["distributions-by-response-headers-policy-id", id] if m == Method::GET => {
482            return ok_with_id(Operation::ListDistributionsByResponseHeadersPolicyId, id);
483        }
484        ["distributions-by-vpc-origin-id", id] if m == Method::GET => {
485            return ok_with_id(Operation::ListDistributionsByVpcOriginId, id);
486        }
487        ["distributions-by-web-acl-id", id] if m == Method::GET => {
488            return ok_with_id(Operation::ListDistributionsByWebACLId, id);
489        }
490        ["distributions-by-anycast-ip-list-id", id] if m == Method::GET => {
491            return ok_with_id(Operation::ListDistributionsByAnycastIpListId, id);
492        }
493        ["distribution", id, "associate-alias"] if m == Method::PUT => {
494            return ok_with_id(Operation::AssociateAlias, id);
495        }
496        ["conflicting-alias"] if m == Method::GET => Operation::ListConflictingAliases,
497        ["resource-policy", "arn"] if m == Method::GET => Operation::GetResourcePolicy,
498        ["resource-policy", "arn"] if m == Method::PUT => Operation::PutResourcePolicy,
499        ["resource-policy", "arn"] if m == Method::DELETE => Operation::DeleteResourcePolicy,
500        ["managed-certificate-details"] if m == Method::GET => {
501            Operation::GetManagedCertificateDetails
502        }
503        ["verify-dns-configuration"] if m == Method::POST => Operation::VerifyDnsConfiguration,
504        ["continuous-deployment-policy"] if m == Method::POST => {
505            Operation::CreateContinuousDeploymentPolicy
506        }
507        ["continuous-deployment-policy"] if m == Method::GET => {
508            Operation::ListContinuousDeploymentPolicies
509        }
510        ["continuous-deployment-policy", id] if m == Method::GET => {
511            return ok_with_id(Operation::GetContinuousDeploymentPolicy, id);
512        }
513        ["continuous-deployment-policy", id] if m == Method::DELETE => {
514            return ok_with_id(Operation::DeleteContinuousDeploymentPolicy, id);
515        }
516        ["continuous-deployment-policy", id, "config"] if m == Method::GET => {
517            return ok_with_id(Operation::GetContinuousDeploymentPolicyConfig, id);
518        }
519        ["continuous-deployment-policy", id, "config"] if m == Method::PUT => {
520            return ok_with_id(Operation::UpdateContinuousDeploymentPolicy, id);
521        }
522        ["streaming-distribution"] if m == Method::POST => {
523            if query_has(query, "WithTags") {
524                Operation::CreateStreamingDistributionWithTags
525            } else {
526                Operation::CreateStreamingDistribution
527            }
528        }
529        ["streaming-distribution"] if m == Method::GET => Operation::ListStreamingDistributions,
530        ["streaming-distribution", id] if m == Method::GET => {
531            return ok_with_id(Operation::GetStreamingDistribution, id);
532        }
533        ["streaming-distribution", id] if m == Method::DELETE => {
534            return ok_with_id(Operation::DeleteStreamingDistribution, id);
535        }
536        ["streaming-distribution", id, "config"] if m == Method::GET => {
537            return ok_with_id(Operation::GetStreamingDistributionConfig, id);
538        }
539        ["streaming-distribution", id, "config"] if m == Method::PUT => {
540            return ok_with_id(Operation::UpdateStreamingDistribution, id);
541        }
542        ["anycast-ip-list"] if m == Method::POST => Operation::CreateAnycastIpList,
543        ["anycast-ip-list"] if m == Method::GET => Operation::ListAnycastIpLists,
544        ["anycast-ip-list", id] if m == Method::GET => {
545            return ok_with_id(Operation::GetAnycastIpList, id);
546        }
547        ["anycast-ip-list", id] if m == Method::DELETE => {
548            return ok_with_id(Operation::DeleteAnycastIpList, id);
549        }
550        ["anycast-ip-list", id] if m == Method::PUT => {
551            return ok_with_id(Operation::UpdateAnycastIpList, id);
552        }
553        ["vpc-origin"] if m == Method::POST => Operation::CreateVpcOrigin,
554        ["vpc-origin"] if m == Method::GET => Operation::ListVpcOrigins,
555        ["vpc-origin", id] if m == Method::GET => {
556            return ok_with_id(Operation::GetVpcOrigin, id);
557        }
558        ["vpc-origin", id] if m == Method::DELETE => {
559            return ok_with_id(Operation::DeleteVpcOrigin, id);
560        }
561        ["vpc-origin", id] if m == Method::PUT => {
562            return ok_with_id(Operation::UpdateVpcOrigin, id);
563        }
564        ["truststore"] if m == Method::POST => Operation::CreateTrustStore,
565        ["truststore"] if m == Method::GET => Operation::ListTrustStores,
566        ["truststore", id] if m == Method::GET => {
567            return ok_with_id(Operation::GetTrustStore, id);
568        }
569        ["truststore", id] if m == Method::DELETE => {
570            return ok_with_id(Operation::DeleteTrustStore, id);
571        }
572        ["truststore", id] if m == Method::PUT => {
573            return ok_with_id(Operation::UpdateTrustStore, id);
574        }
575        ["domain-conflicts"] if m == Method::POST => Operation::ListDomainConflicts,
576        ["domain-association"] if m == Method::POST => Operation::UpdateDomainAssociation,
577        ["distribution", id, "web-acl"] if m == Method::PUT => {
578            return ok_with_id(Operation::AssociateDistributionWebACL, id);
579        }
580        ["distribution", id, "web-acl"] if m == Method::DELETE => {
581            return ok_with_id(Operation::DisassociateDistributionWebACL, id);
582        }
583
584        _ => {
585            return Err(CloudFrontError::InvalidArgument(format!(
586                "No route matched {method} {path}"
587            )));
588        }
589    };
590
591    Ok(RouteMatch {
592        operation: op,
593        path_params: PathParams::default(),
594    })
595}
596
597fn ok_with_id(op: Operation, id: &str) -> Result<RouteMatch, CloudFrontError> {
598    Ok(RouteMatch {
599        operation: op,
600        path_params: PathParams {
601            id: id.to_owned(),
602            ..PathParams::default()
603        },
604    })
605}
606
607fn ok_with_two(op: Operation, id: &str, sec: &str) -> Result<RouteMatch, CloudFrontError> {
608    Ok(RouteMatch {
609        operation: op,
610        path_params: PathParams {
611            id: id.to_owned(),
612            secondary_id: sec.to_owned(),
613            ..PathParams::default()
614        },
615    })
616}
617
618fn ok_with_name(op: Operation, name: &str) -> Result<RouteMatch, CloudFrontError> {
619    Ok(RouteMatch {
620        operation: op,
621        path_params: PathParams {
622            name: name.to_owned(),
623            ..PathParams::default()
624        },
625    })
626}
627
628fn query_has(q: &str, key: &str) -> bool {
629    q.split('&')
630        .any(|kv| kv == key || kv.starts_with(&format!("{key}=")))
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636
637    fn parse_url(method: &str, uri: &str) -> RouteMatch {
638        let m = http::Method::from_bytes(method.as_bytes()).unwrap();
639        let u: http::Uri = uri.parse().unwrap();
640        resolve(&m, &u).expect("route")
641    }
642
643    #[test]
644    fn test_create_distribution() {
645        let r = parse_url("POST", "/2020-05-31/distribution");
646        assert_eq!(r.operation, Operation::CreateDistribution);
647    }
648
649    #[test]
650    fn test_create_distribution_with_tags() {
651        let r = parse_url("POST", "/2020-05-31/distribution?WithTags");
652        assert_eq!(r.operation, Operation::CreateDistributionWithTags);
653    }
654
655    #[test]
656    fn test_update_distribution() {
657        let r = parse_url("PUT", "/2020-05-31/distribution/E1ABC/config");
658        assert_eq!(r.operation, Operation::UpdateDistribution);
659        assert_eq!(r.path_params.id, "E1ABC");
660    }
661
662    #[test]
663    fn test_get_invalidation() {
664        let r = parse_url("GET", "/2020-05-31/distribution/E1ABC/invalidation/I42");
665        assert_eq!(r.operation, Operation::GetInvalidation);
666        assert_eq!(r.path_params.id, "E1ABC");
667        assert_eq!(r.path_params.secondary_id, "I42");
668    }
669
670    #[test]
671    fn test_tag_resource() {
672        let r = parse_url(
673            "POST",
674            "/2020-05-31/tagging?Operation=Tag&Resource=arn:aws:cloudfront::000:dist/E1",
675        );
676        assert_eq!(r.operation, Operation::TagResource);
677    }
678
679    #[test]
680    fn test_describe_function_route() {
681        let r = parse_url(
682            "GET",
683            "/2020-05-31/function/rustack-pulumi-cf-fn/describe?Stage=DEVELOPMENT",
684        );
685        assert_eq!(r.operation, Operation::DescribeFunction);
686        assert_eq!(r.path_params.name, "rustack-pulumi-cf-fn");
687    }
688
689    #[test]
690    fn test_get_function_route() {
691        let r = parse_url(
692            "GET",
693            "/2020-05-31/function/rustack-pulumi-cf-fn?Stage=DEVELOPMENT",
694        );
695        assert_eq!(r.operation, Operation::GetFunction);
696        assert_eq!(r.path_params.name, "rustack-pulumi-cf-fn");
697    }
698}