11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4+ use super :: policies:: ClientRequestIdPolicy ;
45use crate :: http:: { options:: TelemetryOptions , policies:: TelemetryPolicy } ;
5- use std:: { ops:: Deref , sync:: Arc } ;
6+ use std:: {
7+ any:: { Any , TypeId } ,
8+ ops:: Deref ,
9+ sync:: Arc ,
10+ } ;
611use typespec_client_core:: http:: { self , policies:: Policy } ;
712
813/// Execution pipeline.
@@ -12,15 +17,16 @@ use typespec_client_core::http::{self, policies::Policy};
1217/// 1. Client library-specified per-call policies are executed. Per-call policies can fail and bail out of the pipeline
1318/// immediately.
1419/// 2. User-specified per-call policies are executed.
15- /// 3. Telemetry policy.
16- /// 4. Retry policy. It allows to re-execute the following policies.
20+ /// 3. Built-in per-call policies are executed. If a [`ClientRequestIdPolicy`] was not added to `per_call_policies`,
21+ /// the default will be added automatically.
22+ /// 4. The retry policy is executed. It allows to re-execute the following policies.
1723/// 5. Client library-specified per-retry policies. Per-retry polices are always executed at least once but are re-executed
1824/// in case of retries.
1925/// 6. User-specified per-retry policies are executed.
20- /// 7. Authorization policy. Authorization can depend on the HTTP headers and/or the request body so it
26+ /// 7. The authorization policy is executed . Authorization can depend on the HTTP headers and/or the request body so it
2127/// must be executed right before sending the request to the transport. Also, the authorization
2228/// can depend on the current time so it must be executed at every retry.
23- /// 8. Transport policy. Transport policy is always the last policy and is the policy that
29+ /// 8. The transport policy is executed . Transport policy is always the last policy and is the policy that
2430/// actually constructs the `Response` to be passed up the pipeline.
2531///
2632/// A pipeline is immutable. In other words a policy can either succeed and call the following
@@ -45,13 +51,20 @@ impl Pipeline {
4551 ) -> Self {
4652 let mut per_call_policies = per_call_policies. clone ( ) ;
4753
54+ if per_call_policies
55+ . iter ( )
56+ . all ( |policy| TypeId :: of :: < ClientRequestIdPolicy > ( ) != policy. type_id ( ) )
57+ {
58+ per_call_policies. push ( Arc :: new ( ClientRequestIdPolicy :: default ( ) ) ) ;
59+ }
60+
4861 let telemetry_policy = TelemetryPolicy :: new (
4962 crate_name,
5063 crate_version,
5164 // TODO: &options.telemetry.unwrap_or_default(),
5265 & TelemetryOptions :: default ( ) ,
5366 ) ;
54- per_call_policies. insert ( 0 , Arc :: new ( telemetry_policy) ) ;
67+ per_call_policies. push ( Arc :: new ( telemetry_policy) ) ;
5568
5669 Self ( http:: Pipeline :: new (
5770 options,
@@ -68,3 +81,133 @@ impl Deref for Pipeline {
6881 & self . 0
6982 }
7083}
84+
85+ #[ cfg( test) ]
86+ mod tests {
87+ use super :: * ;
88+ use crate :: {
89+ http:: {
90+ headers:: { self , HeaderName , Headers } ,
91+ policies:: Policy ,
92+ request:: options:: ClientRequestId ,
93+ Context , Method , Request , Response , StatusCode , TransportOptions ,
94+ } ,
95+ Bytes ,
96+ } ;
97+ use azure_core_test:: http:: MockHttpClient ;
98+ use futures:: FutureExt as _;
99+ use std:: sync:: Arc ;
100+ use typespec_client_core:: http:: ClientOptions ;
101+
102+ #[ tokio:: test]
103+ async fn pipeline_with_custom_client_request_id_policy ( ) {
104+ // Arrange
105+ const CUSTOM_HEADER_NAME : & str = "x-custom-request-id" ;
106+ const CUSTOM_HEADER : HeaderName = HeaderName :: from_static ( CUSTOM_HEADER_NAME ) ;
107+ const CLIENT_REQUEST_ID : & str = "custom-request-id" ;
108+
109+ let mut ctx = Context :: new ( ) ;
110+ ctx. insert ( ClientRequestId :: new ( CLIENT_REQUEST_ID . to_string ( ) ) ) ;
111+
112+ let transport = TransportOptions :: new ( Arc :: new ( MockHttpClient :: new ( |req| {
113+ async {
114+ // Assert
115+ let header_value = req
116+ . headers ( )
117+ . get_optional_str ( & CUSTOM_HEADER )
118+ . expect ( "Custom header should be present" ) ;
119+ assert_eq ! (
120+ header_value, CLIENT_REQUEST_ID ,
121+ "Custom header value should match the client request ID"
122+ ) ;
123+
124+ Ok ( Response :: from_bytes (
125+ StatusCode :: Ok ,
126+ Headers :: new ( ) ,
127+ Bytes :: new ( ) ,
128+ ) )
129+ }
130+ . boxed ( )
131+ } ) ) ) ;
132+ let options = ClientOptions {
133+ transport : Some ( transport) ,
134+ ..Default :: default ( )
135+ } ;
136+
137+ let per_call_policies: Vec < Arc < dyn Policy > > =
138+ vec ! [
139+ Arc :: new( ClientRequestIdPolicy :: with_header_name( CUSTOM_HEADER_NAME ) )
140+ as Arc <dyn Policy >,
141+ ] ;
142+ let per_retry_policies = vec ! [ ] ;
143+
144+ let pipeline = Pipeline :: new (
145+ Some ( "test-crate" ) ,
146+ Some ( "1.0.0" ) ,
147+ options,
148+ per_call_policies,
149+ per_retry_policies,
150+ ) ;
151+
152+ let mut request = Request :: new ( "https://example.com" . parse ( ) . unwrap ( ) , Method :: Get ) ;
153+
154+ // Act
155+ pipeline
156+ . send :: < ( ) > ( & ctx, & mut request)
157+ . await
158+ . expect ( "Pipeline execution failed" ) ;
159+ }
160+
161+ #[ tokio:: test]
162+ async fn pipeline_without_client_request_id_policy ( ) {
163+ // Arrange
164+ const CLIENT_REQUEST_ID : & str = "default-request-id" ;
165+
166+ let mut ctx = Context :: new ( ) ;
167+ ctx. insert ( ClientRequestId :: new ( CLIENT_REQUEST_ID . to_string ( ) ) ) ;
168+
169+ let transport = TransportOptions :: new ( Arc :: new ( MockHttpClient :: new ( |req| {
170+ async {
171+ // Assert
172+ let header_value = req
173+ . headers ( )
174+ . get_optional_str ( & headers:: CLIENT_REQUEST_ID )
175+ . expect ( "Default header should be present" ) ;
176+ assert_eq ! (
177+ header_value, CLIENT_REQUEST_ID ,
178+ "Default header value should match the client request ID"
179+ ) ;
180+
181+ Ok ( Response :: from_bytes (
182+ StatusCode :: Ok ,
183+ Headers :: new ( ) ,
184+ Bytes :: new ( ) ,
185+ ) )
186+ }
187+ . boxed ( )
188+ } ) ) ) ;
189+ let options = ClientOptions {
190+ transport : Some ( transport) ,
191+ ..Default :: default ( )
192+ } ;
193+
194+ let per_call_policies = vec ! [ ] ; // No ClientRequestIdPolicy added
195+ let per_retry_policies = vec ! [ ] ;
196+
197+ let pipeline = Pipeline :: new (
198+ Some ( "test-crate" ) ,
199+ Some ( "1.0.0" ) ,
200+ options,
201+ per_call_policies,
202+ per_retry_policies,
203+ ) ;
204+
205+ let mut request = Request :: new ( "https://example.com" . parse ( ) . unwrap ( ) , Method :: Get ) ;
206+
207+ // Act
208+ pipeline
209+ . send :: < ( ) > ( & ctx, & mut request)
210+ . await
211+ . expect ( "Pipeline execution failed" ) ;
212+ }
213+ }
0 commit comments