graph_rs_sdk/client/
graph.rs

1use crate::admin::AdminApiClient;
2use crate::agreement_acceptances::{
3    AgreementAcceptancesApiClient, AgreementAcceptancesIdApiClient,
4};
5use crate::agreements::{AgreementsApiClient, AgreementsIdApiClient};
6use crate::api_default_imports::*;
7use crate::app_catalogs::AppCatalogsApiClient;
8use crate::applications::{ApplicationsApiClient, ApplicationsIdApiClient};
9use crate::audit_logs::AuditLogsApiClient;
10use crate::authentication_method_configurations::{
11    AuthenticationMethodConfigurationsApiClient, AuthenticationMethodConfigurationsIdApiClient,
12};
13use crate::authentication_methods_policy::AuthenticationMethodsPolicyApiClient;
14use crate::batch::BatchApiClient;
15use crate::branding::BrandingApiClient;
16use crate::certificate_based_auth_configuration::{
17    CertificateBasedAuthConfigurationApiClient, CertificateBasedAuthConfigurationIdApiClient,
18};
19use crate::chats::{ChatsApiClient, ChatsIdApiClient};
20use crate::communications::CommunicationsApiClient;
21use crate::contracts::{ContractsApiClient, ContractsIdApiClient};
22use crate::data_policy_operations::DataPolicyOperationsApiClient;
23use crate::default_drive::DefaultDriveApiClient;
24use crate::device_app_management::DeviceAppManagementApiClient;
25use crate::device_management::DeviceManagementApiClient;
26use crate::devices::{DevicesApiClient, DevicesIdApiClient};
27use crate::directory::DirectoryApiClient;
28use crate::directory_objects::{DirectoryObjectsApiClient, DirectoryObjectsIdApiClient};
29use crate::directory_role_templates::{
30    DirectoryRoleTemplatesApiClient, DirectoryRoleTemplatesIdApiClient,
31};
32use crate::directory_roles::{DirectoryRolesApiClient, DirectoryRolesIdApiClient};
33use crate::domain_dns_records::{DomainDnsRecordsApiClient, DomainDnsRecordsIdApiClient};
34use crate::domains::{DomainsApiClient, DomainsIdApiClient};
35use crate::drives::{DrivesApiClient, DrivesIdApiClient};
36use crate::education::EducationApiClient;
37use crate::group_lifecycle_policies::{
38    GroupLifecyclePoliciesApiClient, GroupLifecyclePoliciesIdApiClient,
39};
40use crate::groups::{GroupsApiClient, GroupsIdApiClient};
41use crate::identity::{
42    AllowedHostValidator, AuthorizationCodeAssertionCredential,
43    AuthorizationCodeCertificateCredential, AuthorizationCodeCredential, BearerTokenCredential,
44    ClientAssertionCredential, ClientCertificateCredential, ClientSecretCredential,
45    ConfidentialClientApplication, DeviceCodeCredential, HostIs, OpenIdCredential,
46    PublicClientApplication, ResourceOwnerPasswordCredential, Token,
47};
48use crate::identity_access::IdentityApiClient;
49use crate::identity_governance::IdentityGovernanceApiClient;
50use crate::identity_providers::{IdentityProvidersApiClient, IdentityProvidersIdApiClient};
51use crate::invitations::InvitationsApiClient;
52use crate::me::MeApiClient;
53use crate::oauth2_permission_grants::{
54    Oauth2PermissionGrantsApiClient, Oauth2PermissionGrantsIdApiClient,
55};
56use crate::organization::{OrganizationApiClient, OrganizationIdApiClient};
57use crate::permission_grants::{PermissionGrantsApiClient, PermissionGrantsIdApiClient};
58use crate::places::PlacesApiClient;
59use crate::planner::PlannerApiClient;
60use crate::policies::PoliciesApiClient;
61use crate::reports::ReportsApiClient;
62use crate::schema_extensions::{SchemaExtensionsApiClient, SchemaExtensionsIdApiClient};
63use crate::service_principals::{ServicePrincipalsApiClient, ServicePrincipalsIdApiClient};
64use crate::sites::{SitesApiClient, SitesIdApiClient};
65use crate::solutions::SolutionsApiClient;
66use crate::subscribed_skus::SubscribedSkusApiClient;
67use crate::subscriptions::{SubscriptionsApiClient, SubscriptionsIdApiClient};
68use crate::teams::{TeamsApiClient, TeamsIdApiClient};
69use crate::teams_templates::{TeamsTemplatesApiClient, TeamsTemplatesIdApiClient};
70use crate::teamwork::TeamworkApiClient;
71use crate::users::{UsersApiClient, UsersIdApiClient};
72use crate::{GRAPH_URL, GRAPH_URL_BETA};
73use graph_core::identity::ForceTokenRefresh;
74use graph_oauth::AuthorizationCodeSpaCredential;
75use lazy_static::lazy_static;
76
77lazy_static! {
78    static ref PARSED_GRAPH_URL: Url = Url::parse(GRAPH_URL).expect("Unable to set v1 endpoint");
79    static ref PARSED_GRAPH_URL_BETA: Url =
80        Url::parse(GRAPH_URL_BETA).expect("Unable to set beta endpoint");
81}
82
83// For backwards compatibility.
84pub type Graph = GraphClient;
85
86#[derive(Debug, Clone)]
87pub struct GraphClient {
88    client: Client,
89    endpoint: Url,
90    allowed_host_validator: AllowedHostValidator,
91}
92
93impl GraphClient {
94    pub fn new<AT: ToString>(access_token: AT) -> GraphClient {
95        GraphClient {
96            client: Client::new(BearerTokenCredential::from(access_token.to_string())),
97            endpoint: PARSED_GRAPH_URL.clone(),
98            allowed_host_validator: AllowedHostValidator::default(),
99        }
100    }
101
102    pub fn from_client_app<CA: ClientApplication + 'static>(client_app: CA) -> GraphClient {
103        GraphClient {
104            client: Client::new(client_app),
105            endpoint: PARSED_GRAPH_URL.clone(),
106            allowed_host_validator: AllowedHostValidator::default(),
107        }
108    }
109
110    /// Use the v1 endpoint for the Microsoft Graph API. This is the default
111    /// endpoint used by the client.
112    ///
113    /// # Example
114    /// ```rust,ignore
115    /// # use graph_rs_sdk::GRAPH_URL;
116    /// use graph_rs_sdk::Graph;
117    ///
118    /// let mut client = Graph::new("ACCESS_TOKEN");
119    ///
120    /// client.v1()
121    ///     .me()
122    ///     .get_user()
123    ///     .send()
124    ///     .await?;
125    /// ```
126    pub fn v1(&mut self) -> &mut GraphClient {
127        self.endpoint = PARSED_GRAPH_URL.clone();
128        self
129    }
130
131    /// Use the v1 endpoint for the Microsoft Graph API. Same as calling
132    /// `v1()` but takes a mutable reference to self and does not return
133    /// self.
134    ///
135    /// # Example
136    /// ```rust
137    /// # use graph_rs_sdk::GRAPH_URL;
138    /// use graph_rs_sdk::Graph;
139    ///
140    /// let mut client = Graph::new("ACCESS_TOKEN");
141    /// client.use_v1();
142    ///
143    /// assert_eq!(client.url().to_string(), GRAPH_URL.to_string())
144    /// ```
145    pub fn use_v1(&mut self) {
146        self.endpoint = PARSED_GRAPH_URL.clone();
147    }
148
149    /// Use the beta endpoint for the Microsoft Graph API
150    ///
151    /// # Example
152    /// ```rust,ignore
153    /// # use graph_rs_sdk::GRAPH_URL_BETA;
154    /// use graph_rs_sdk::Graph;
155    ///
156    /// let mut client = Graph::new("ACCESS_TOKEN");
157    ///
158    /// client.beta()
159    ///     .me()
160    ///     .get_user()
161    ///     .send()
162    ///     .await?;
163    /// ```
164    pub fn beta(&mut self) -> &mut GraphClient {
165        self.endpoint = PARSED_GRAPH_URL_BETA.clone();
166        self
167    }
168
169    /// Use the beta endpoint for the Microsoft Graph API. Same as calling
170    /// `beta()` but takes a mutable reference to self and does not return
171    /// self.
172    ///
173    /// Example
174    /// ```rust
175    /// # use graph_rs_sdk::GRAPH_URL_BETA;
176    /// use graph_rs_sdk::Graph;
177    ///
178    /// let mut client = Graph::new("ACCESS_TOKEN");
179    /// client.use_beta();
180    ///
181    /// assert_eq!(client.url().to_string(), GRAPH_URL_BETA.to_string())
182    /// ```
183    pub fn use_beta(&mut self) {
184        self.endpoint = PARSED_GRAPH_URL_BETA.clone();
185    }
186
187    pub fn url(&self) -> &Url {
188        &self.endpoint
189    }
190
191    pub fn with_force_token_refresh(
192        &mut self,
193        force_token_refresh: ForceTokenRefresh,
194    ) -> &mut Self {
195        self.client.with_force_token_refresh(force_token_refresh);
196        self
197    }
198
199    pub fn use_force_token_refresh(&mut self, force_token_refresh: ForceTokenRefresh) {
200        self.client.with_force_token_refresh(force_token_refresh);
201    }
202
203    /// Set a custom endpoint for the Microsoft Graph API. Provide the scheme and host with an
204    /// optional path. The path is not set by the sdk when using a custom endpoint.
205    ///
206    /// The scheme must be https:// and any other provided scheme will cause a panic.
207    /// See [Microsoft Graph Service Root Endpoints](https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints)
208    ///
209    /// Attempting to use an invalid host will cause the client to panic. This is done
210    /// for increased security.
211    ///
212    /// Do not use a government host endpoint without authorization and any necessary clearances.
213    /// Using any government host endpoint means you should expect every API call will be monitored
214    /// and recorded.
215    ///
216    /// You should also assume China's Graph API operated by 21Vianet is being monitored
217    /// by the Chinese government who is well known for the control it has over Chinese companies
218    /// and for its surveillance state of Chinese citizens.
219    /// And, according to Microsoft, **These services are subject to Chinese laws**. See
220    /// [Microsoft 365 operated by 21Vianet](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-platform-service-description/microsoft-365-operated-by-21vianet)
221    ///
222    /// Valid Hosts:
223    /// * graph.microsoft.com (Default public endpoint worldwide)
224    /// * graph.microsoft.us (U.S. Government)
225    /// * dod-graph.microsoft.us (U.S. Department Of Defense)
226    /// * graph.microsoft.de
227    /// * microsoftgraph.chinacloudapi.cn (operated by 21Vianet)
228    /// * canary.graph.microsoft.com
229    ///
230    /// Example
231    /// ```rust,ignore
232    /// use graph_rs_sdk::Graph;
233    ///
234    /// let mut client = Graph::new("ACCESS_TOKEN");
235    ///
236    /// client.custom_endpoint("https://graph.microsoft.com/v1.0")
237    ///     .me()
238    ///     .get_user()
239    ///     .send()
240    ///     .await?;
241    /// ```
242    pub fn custom_endpoint(&mut self, url: &Url) -> &mut GraphClient {
243        self.use_endpoint(url);
244        self
245    }
246
247    /// Set a custom endpoint for the Microsoft Graph API. Provide the scheme and host with an
248    /// optional path. The path is not set by the sdk when using a custom endpoint.
249    ///
250    /// The scheme must be https:// and any other provided scheme will cause a panic.
251    /// See [Microsoft Graph Service Root Endpoints](https://learn.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints)
252    ///
253    /// Attempting to use an invalid host will cause the client to panic. This is done
254    /// for increased security.
255    ///
256    /// Do not use a government host endpoint without authorization and any necessary clearances.
257    /// Using any government host endpoint means you should expect every API call will be monitored
258    /// and recorded.
259    ///
260    /// You should also assume China's Graph API operated by 21Vianet is being monitored
261    /// by the Chinese government who is well known for the control it has over Chinese companies
262    /// and for its surveillance state of Chinese citizens.
263    /// And, according to Microsoft, **These services are subject to Chinese laws**. See
264    /// [Microsoft 365 operated by 21Vianet](https://learn.microsoft.com/en-us/office365/servicedescriptions/office-365-platform-service-description/microsoft-365-operated-by-21vianet)
265    ///
266    /// Valid Hosts:
267    /// * graph.microsoft.com (Default public endpoint worldwide)
268    /// * graph.microsoft.us (U.S. Government)
269    /// * dod-graph.microsoft.us (U.S. Department Of Defense)
270    /// * graph.microsoft.de
271    /// * microsoftgraph.chinacloudapi.cn (operated by 21Vianet)
272    /// * canary.graph.microsoft.com
273    ///
274    /// Example
275    /// ```rust
276    /// use url::Url;
277    /// use graph_rs_sdk::Graph;
278    ///
279    /// let mut client = Graph::new("ACCESS_TOKEN");
280    /// client.use_endpoint(&Url::parse("https://graph.microsoft.com/v1.0").unwrap());
281    ///
282    /// assert_eq!(client.url().to_string(), "https://graph.microsoft.com/v1.0".to_string())
283    /// ```
284    pub fn use_endpoint(&mut self, url: &Url) {
285        if url.query().is_some() {
286            panic!(
287                "Invalid query - provide only the scheme, host, and optional path of the Uri such as https://graph.microsoft.com/v1.0"
288            );
289        }
290
291        match self.allowed_host_validator.validate_url(url) {
292            HostIs::Valid => {
293                self.endpoint = url.clone();
294            }
295            HostIs::Invalid => panic!("Invalid host"),
296        }
297    }
298
299    #[cfg(feature = "test-util")]
300    pub fn use_test_endpoint(&mut self, url: &Url) {
301        self.endpoint = url.clone();
302    }
303
304    api_client_impl!(admin, AdminApiClient);
305
306    api_client_impl!(app_catalogs, AppCatalogsApiClient);
307
308    api_client_impl!(
309        agreement_acceptances,
310        AgreementAcceptancesApiClient,
311        agreement_acceptance,
312        AgreementAcceptancesIdApiClient
313    );
314
315    api_client_impl!(
316        agreements,
317        AgreementsApiClient,
318        agreement,
319        AgreementsIdApiClient
320    );
321
322    api_client_impl!(
323        applications,
324        ApplicationsApiClient,
325        application,
326        ApplicationsIdApiClient
327    );
328
329    api_client_impl!(audit_logs, AuditLogsApiClient);
330
331    api_client_impl!(
332        authentication_method_configurations,
333        AuthenticationMethodConfigurationsApiClient,
334        authentication_method_configuration,
335        AuthenticationMethodConfigurationsIdApiClient
336    );
337
338    api_client_impl!(
339        authentication_methods_policy,
340        AuthenticationMethodsPolicyApiClient
341    );
342
343    api_client_impl!(branding, BrandingApiClient);
344
345    api_client_impl!(
346        certificate_based_auth_configurations,
347        CertificateBasedAuthConfigurationApiClient,
348        certificate_based_auth_configuration,
349        CertificateBasedAuthConfigurationIdApiClient
350    );
351
352    api_client_impl!(chats, ChatsApiClient, chat, ChatsIdApiClient);
353
354    api_client_impl!(communications, CommunicationsApiClient);
355
356    api_client_impl!(
357        contracts,
358        ContractsApiClient,
359        contract,
360        ContractsIdApiClient
361    );
362
363    api_client_impl!(data_policy_operations, DataPolicyOperationsApiClient);
364
365    api_client_impl!(device_app_management, DeviceAppManagementApiClient);
366
367    api_client_impl!(device_management, DeviceManagementApiClient);
368
369    api_client_impl!(devices, DevicesApiClient, device, DevicesIdApiClient);
370
371    api_client_impl!(directory, DirectoryApiClient);
372
373    api_client_impl!(
374        directory_objects,
375        DirectoryObjectsApiClient,
376        directory_object,
377        DirectoryObjectsIdApiClient
378    );
379
380    api_client_impl!(
381        directory_role_templates,
382        DirectoryRoleTemplatesApiClient,
383        directory_role_template,
384        DirectoryRoleTemplatesIdApiClient
385    );
386
387    api_client_impl!(
388        directory_roles,
389        DirectoryRolesApiClient,
390        directory_role,
391        DirectoryRolesIdApiClient
392    );
393
394    api_client_impl!(
395        domain_dns_records,
396        DomainDnsRecordsApiClient,
397        domain_dns_record,
398        DomainDnsRecordsIdApiClient
399    );
400
401    api_client_impl!(domains, DomainsApiClient, domain, DomainsIdApiClient);
402
403    api_client_impl!(drives, DrivesApiClient, drive, DrivesIdApiClient);
404
405    api_client_impl_link!(default_drive, DefaultDriveApiClient);
406
407    api_client_impl_link!(education, EducationApiClient);
408
409    api_client_impl!(groups, GroupsApiClient, group, GroupsIdApiClient);
410
411    api_client_impl!(
412        group_lifecycle_policies,
413        GroupLifecyclePoliciesApiClient,
414        group_lifecycle_policy,
415        GroupLifecyclePoliciesIdApiClient
416    );
417
418    api_client_impl_link!(identity, IdentityApiClient);
419
420    api_client_impl!(identity_governance, IdentityGovernanceApiClient);
421
422    api_client_impl!(
423        identity_providers,
424        IdentityProvidersApiClient,
425        identity_provider,
426        IdentityProvidersIdApiClient
427    );
428
429    api_client_impl!(invitations, InvitationsApiClient);
430
431    api_client_impl_link!(me, MeApiClient);
432
433    api_client_impl!(
434        oauth2_permission_grants,
435        Oauth2PermissionGrantsApiClient,
436        oauth2_permission_grant,
437        Oauth2PermissionGrantsIdApiClient
438    );
439
440    api_client_impl!(
441        organizations,
442        OrganizationApiClient,
443        organization,
444        OrganizationIdApiClient
445    );
446
447    api_client_impl!(places, PlacesApiClient);
448
449    api_client_impl!(
450        permission_grants,
451        PermissionGrantsApiClient,
452        permission_grant,
453        PermissionGrantsIdApiClient
454    );
455
456    api_client_impl!(planner, PlannerApiClient);
457
458    api_client_impl!(policies, PoliciesApiClient);
459
460    api_client_impl!(reports, ReportsApiClient);
461
462    api_client_impl!(
463        schema_extensions,
464        SchemaExtensionsApiClient,
465        schema_extension,
466        SchemaExtensionsIdApiClient
467    );
468
469    api_client_impl!(
470        service_principals,
471        ServicePrincipalsApiClient,
472        service_principal,
473        ServicePrincipalsIdApiClient
474    );
475
476    api_client_impl!(sites, SitesApiClient, site, SitesIdApiClient);
477
478    api_client_impl!(solutions, SolutionsApiClient);
479
480    api_client_impl!(
481        subscribed_skus,
482        SubscribedSkusApiClient,
483        subscribed_sku,
484        SubscriptionsIdApiClient
485    );
486
487    api_client_impl!(
488        subscriptions,
489        SubscriptionsApiClient,
490        subscription,
491        SubscriptionsIdApiClient
492    );
493
494    api_client_impl!(teams, TeamsApiClient, team, TeamsIdApiClient);
495
496    api_client_impl!(
497        teams_templates,
498        TeamsTemplatesApiClient,
499        teams_template,
500        TeamsTemplatesIdApiClient
501    );
502
503    api_client_impl_link!(teamwork, TeamworkApiClient);
504
505    api_client_impl!(users, UsersApiClient, user, UsersIdApiClient);
506
507    pub fn custom(&self, method: Method, body: Option<BodyRead>) -> RequestHandler {
508        let body_result = body.map(|body| body.into_body());
509        if let Some(b) = body_result {
510            if let Err(err) = b {
511                return RequestHandler::new(
512                    self.client.clone(),
513                    RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
514                    Some(err),
515                    None,
516                );
517            } else if let Ok(body_read) = b {
518                return RequestHandler::new(
519                    self.client.clone(),
520                    RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
521                    None,
522                    Some(body_read),
523                );
524            }
525        }
526
527        RequestHandler::new(
528            self.client.clone(),
529            RequestComponents::new(ResourceIdentity::Custom, self.endpoint.clone(), method),
530            None,
531            None,
532        )
533    }
534
535    pub fn batch<B: serde::Serialize>(&self, batch: &B) -> RequestHandler {
536        BatchApiClient::new(
537            self.client.clone(),
538            ResourceProvisioner::resource_config_with_url(
539                self.endpoint.clone(),
540                ResourceIdentity::Batch,
541            ),
542            Handlebars::new(),
543        )
544        .batch(batch)
545    }
546}
547
548impl From<&str> for GraphClient {
549    fn from(token: &str) -> Self {
550        GraphClient::from_client_app(BearerTokenCredential::from(token.to_string()))
551    }
552}
553
554impl From<String> for GraphClient {
555    fn from(token: String) -> Self {
556        GraphClient::from_client_app(BearerTokenCredential::from(token))
557    }
558}
559
560impl From<&Token> for GraphClient {
561    fn from(token: &Token) -> Self {
562        GraphClient::from_client_app(BearerTokenCredential::from(token.access_token.clone()))
563    }
564}
565
566impl From<GraphClientConfiguration> for GraphClient {
567    fn from(graph_client_builder: GraphClientConfiguration) -> Self {
568        GraphClient {
569            client: Client::from(graph_client_builder),
570            endpoint: PARSED_GRAPH_URL.clone(),
571            allowed_host_validator: AllowedHostValidator::default(),
572        }
573    }
574}
575
576impl From<&ConfidentialClientApplication<AuthorizationCodeCredential>> for GraphClient {
577    fn from(value: &ConfidentialClientApplication<AuthorizationCodeCredential>) -> Self {
578        GraphClient::from_client_app(value.clone())
579    }
580}
581
582impl From<&ConfidentialClientApplication<AuthorizationCodeAssertionCredential>> for GraphClient {
583    fn from(value: &ConfidentialClientApplication<AuthorizationCodeAssertionCredential>) -> Self {
584        GraphClient::from_client_app(value.clone())
585    }
586}
587
588impl From<&ConfidentialClientApplication<AuthorizationCodeCertificateCredential>> for GraphClient {
589    fn from(value: &ConfidentialClientApplication<AuthorizationCodeCertificateCredential>) -> Self {
590        GraphClient::from_client_app(value.clone())
591    }
592}
593
594impl From<&ConfidentialClientApplication<ClientSecretCredential>> for GraphClient {
595    fn from(value: &ConfidentialClientApplication<ClientSecretCredential>) -> Self {
596        GraphClient::from_client_app(value.clone())
597    }
598}
599
600impl From<&ConfidentialClientApplication<ClientCertificateCredential>> for GraphClient {
601    fn from(value: &ConfidentialClientApplication<ClientCertificateCredential>) -> Self {
602        GraphClient::from_client_app(value.clone())
603    }
604}
605
606impl From<&ConfidentialClientApplication<ClientAssertionCredential>> for GraphClient {
607    fn from(value: &ConfidentialClientApplication<ClientAssertionCredential>) -> Self {
608        GraphClient::from_client_app(value.clone())
609    }
610}
611
612impl From<&ConfidentialClientApplication<OpenIdCredential>> for GraphClient {
613    fn from(value: &ConfidentialClientApplication<OpenIdCredential>) -> Self {
614        GraphClient::from_client_app(value.clone())
615    }
616}
617
618impl From<&PublicClientApplication<DeviceCodeCredential>> for GraphClient {
619    fn from(value: &PublicClientApplication<DeviceCodeCredential>) -> Self {
620        GraphClient::from_client_app(value.clone())
621    }
622}
623
624impl From<&PublicClientApplication<ResourceOwnerPasswordCredential>> for GraphClient {
625    fn from(value: &PublicClientApplication<ResourceOwnerPasswordCredential>) -> Self {
626        GraphClient::from_client_app(value.clone())
627    }
628}
629
630impl From<&PublicClientApplication<AuthorizationCodeSpaCredential>> for GraphClient {
631    fn from(value: &PublicClientApplication<AuthorizationCodeSpaCredential>) -> Self {
632        GraphClient::from_client_app(value.clone())
633    }
634}
635
636#[cfg(test)]
637mod test {
638    use super::*;
639
640    #[test]
641    #[should_panic]
642    fn try_invalid_host() {
643        let mut client = GraphClient::new("token");
644        client.custom_endpoint(&Url::parse("https://example.org").unwrap());
645    }
646
647    #[test]
648    #[should_panic]
649    fn try_invalid_http_scheme() {
650        let mut client = GraphClient::new("token");
651        client.custom_endpoint(&Url::parse("http://example.org").unwrap());
652    }
653
654    #[test]
655    #[should_panic]
656    fn try_invalid_query() {
657        let mut client = GraphClient::new("token");
658        client.custom_endpoint(&Url::parse("https://example.org?user=name").unwrap());
659    }
660
661    #[test]
662    #[should_panic]
663    fn try_invalid_path() {
664        let mut client = GraphClient::new("token");
665        client.custom_endpoint(&Url::parse("https://example.org/v1").unwrap());
666    }
667
668    #[test]
669    #[should_panic]
670    fn try_invalid_host2() {
671        let mut client = GraphClient::new("token");
672        client.use_endpoint(&Url::parse("https://example.org").unwrap());
673    }
674
675    #[test]
676    #[should_panic]
677    fn try_invalid_scheme2() {
678        let mut client = GraphClient::new("token");
679        client.use_endpoint(&Url::parse("http://example.org").unwrap());
680    }
681
682    #[test]
683    #[should_panic]
684    fn try_invalid_query2() {
685        let mut client = GraphClient::new("token");
686        client.use_endpoint(&Url::parse("https://example.org?user=name").unwrap());
687    }
688
689    #[test]
690    #[should_panic]
691    fn try_invalid_path2() {
692        let mut client = GraphClient::new("token");
693        client.use_endpoint(&Url::parse("https://example.org/v1").unwrap());
694    }
695
696    #[test]
697    fn try_valid_hosts() {
698        let urls = [
699            "https://graph.microsoft.com/v1.0",
700            "https://graph.microsoft.us",
701            "https://dod-graph.microsoft.us",
702            "https://graph.microsoft.de",
703            "https://microsoftgraph.chinacloudapi.cn",
704            "https://canary.graph.microsoft.com",
705        ];
706
707        let mut client = Graph::new("token");
708
709        for url in urls.iter() {
710            client.custom_endpoint(&Url::parse(url).unwrap());
711            assert_eq!(client.url().clone(), Url::parse(url).unwrap());
712        }
713    }
714}
715
716#[cfg(test)]
717#[cfg(feature = "test-util")]
718mod test_util_feature {
719    use crate::{http::Url, Graph, GraphClientConfiguration, ODataQuery};
720    use wiremock::matchers::{bearer_token, method, path, query_param};
721    use wiremock::{Mock, MockServer, ResponseTemplate};
722
723    /// Tests the test-util feature and setting https-only to false.
724    #[tokio::test]
725    async fn can_set_test_endpoint() {
726        let mock_server = MockServer::start().await;
727
728        Mock::given(method("GET"))
729            .and(path("/users"))
730            .and(query_param("$top", "10"))
731            .and(bearer_token("token"))
732            .respond_with(ResponseTemplate::new(200))
733            .mount(&mock_server)
734            .await;
735
736        let graph_client_configuration = GraphClientConfiguration::new()
737            .access_token("token")
738            .https_only(false);
739
740        let mut client = Graph::from(graph_client_configuration);
741        let uri = mock_server.uri();
742        client.use_test_endpoint(&Url::parse(uri.as_str()).unwrap());
743
744        let response = client.users().list_user().top("10").send().await.unwrap();
745        let status = response.status();
746        assert_eq!(status.as_u16(), 200);
747    }
748
749    #[tokio::test]
750    #[should_panic]
751    async fn test_util_feature_use_endpoint_panics() {
752        let mock_server = MockServer::start().await;
753
754        Mock::given(method("GET"))
755            .and(path("/users"))
756            .and(query_param("$top", "10"))
757            .and(bearer_token("token"))
758            .respond_with(ResponseTemplate::new(200))
759            .mount(&mock_server)
760            .await;
761
762        let graph_client_configuration = GraphClientConfiguration::new()
763            .access_token("token")
764            .https_only(false);
765
766        let mut client = Graph::from(graph_client_configuration);
767        let uri = mock_server.uri();
768        client.use_endpoint(&Url::parse(uri.as_str()).unwrap());
769
770        let response = client.users().list_user().top("10").send().await.unwrap();
771        let status = response.status();
772        assert_eq!(status.as_u16(), 200);
773    }
774}