mssf_core/client/
svc_mgmt_client.rs

1// ------------------------------------------------------------
2// Copyright (c) Microsoft Corporation.  All rights reserved.
3// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
4// ------------------------------------------------------------
5
6use std::{ffi::c_void, time::Duration};
7
8use crate::{PCWSTR, WString, runtime::executor::BoxedCancelToken, types::Uri};
9use mssf_com::{
10    FabricClient::{IFabricResolvedServicePartitionResult, IFabricServiceManagementClient6},
11    FabricTypes::{
12        FABRIC_PARTITION_KEY_TYPE, FABRIC_PARTITION_KEY_TYPE_INT64,
13        FABRIC_PARTITION_KEY_TYPE_INVALID, FABRIC_PARTITION_KEY_TYPE_NONE,
14        FABRIC_PARTITION_KEY_TYPE_STRING, FABRIC_REMOVE_REPLICA_DESCRIPTION,
15        FABRIC_RESOLVED_SERVICE_ENDPOINT, FABRIC_RESTART_REPLICA_DESCRIPTION,
16        FABRIC_SERVICE_DESCRIPTION, FABRIC_SERVICE_ENDPOINT_ROLE,
17        FABRIC_SERVICE_NOTIFICATION_FILTER_DESCRIPTION, FABRIC_SERVICE_PARTITION_KIND,
18        FABRIC_SERVICE_PARTITION_KIND_INT64_RANGE, FABRIC_SERVICE_PARTITION_KIND_INVALID,
19        FABRIC_SERVICE_PARTITION_KIND_NAMED, FABRIC_SERVICE_PARTITION_KIND_SINGLETON,
20        FABRIC_SERVICE_ROLE_INVALID, FABRIC_SERVICE_ROLE_STATEFUL_PRIMARY,
21        FABRIC_SERVICE_ROLE_STATEFUL_SECONDARY, FABRIC_SERVICE_ROLE_STATELESS,
22        FABRIC_SERVICE_UPDATE_DESCRIPTION, FABRIC_URI,
23    },
24};
25
26use crate::sync::{FabricReceiver, fabric_begin_end_proxy};
27
28use crate::{
29    iter::{FabricIter, FabricListAccessor},
30    strings::WStringWrap,
31    types::{
32        RemoveReplicaDescription, RestartReplicaDescription, ServiceNotificationFilterDescription,
33    },
34};
35
36// Service Management Client
37#[derive(Debug, Clone)]
38pub struct ServiceManagementClient {
39    com: IFabricServiceManagementClient6,
40}
41
42impl ServiceManagementClient {
43    pub fn get_com(&self) -> IFabricServiceManagementClient6 {
44        self.com.clone()
45    }
46}
47// internal implementation block
48
49impl ServiceManagementClient {
50    fn resolve_service_partition_internal(
51        &self,
52        name: FABRIC_URI,
53        partition_key_type: FABRIC_PARTITION_KEY_TYPE,
54        partition_key: Option<*const ::core::ffi::c_void>,
55        previous_result: Option<&IFabricResolvedServicePartitionResult>, // This is different from generated code
56        timeout_milliseconds: u32,
57        cancellation_token: Option<BoxedCancelToken>,
58    ) -> FabricReceiver<crate::WinResult<IFabricResolvedServicePartitionResult>> {
59        let com1 = &self.com;
60        let com2 = self.com.clone();
61        fabric_begin_end_proxy(
62            move |callback| unsafe {
63                com1.BeginResolveServicePartition(
64                    name,
65                    partition_key_type,
66                    partition_key.unwrap_or(std::ptr::null()),
67                    previous_result,
68                    timeout_milliseconds,
69                    callback,
70                )
71            },
72            move |ctx| unsafe { com2.EndResolveServicePartition(ctx) },
73            cancellation_token,
74        )
75    }
76
77    fn restart_replica_internal(
78        &self,
79        desc: &FABRIC_RESTART_REPLICA_DESCRIPTION,
80        timeout_milliseconds: u32,
81        cancellation_token: Option<BoxedCancelToken>,
82    ) -> FabricReceiver<crate::WinResult<()>> {
83        let com1 = &self.com;
84        let com2 = self.com.clone();
85        fabric_begin_end_proxy(
86            move |callback| unsafe {
87                com1.BeginRestartReplica(desc, timeout_milliseconds, callback)
88            },
89            move |ctx| unsafe { com2.EndRestartReplica(ctx) },
90            cancellation_token,
91        )
92    }
93
94    fn remove_replica_internal(
95        &self,
96        desc: &FABRIC_REMOVE_REPLICA_DESCRIPTION,
97        timeout_milliseconds: u32,
98        cancellation_token: Option<BoxedCancelToken>,
99    ) -> FabricReceiver<crate::WinResult<()>> {
100        let com1 = &self.com;
101        let com2 = self.com.clone();
102        fabric_begin_end_proxy(
103            move |callback| unsafe {
104                com1.BeginRemoveReplica(desc, timeout_milliseconds, callback)
105            },
106            move |ctx| unsafe { com2.EndRemoveReplica(ctx) },
107            cancellation_token,
108        )
109    }
110
111    fn register_service_notification_filter_internal(
112        &self,
113        desc: &FABRIC_SERVICE_NOTIFICATION_FILTER_DESCRIPTION,
114        timeout_milliseconds: u32,
115        cancellation_token: Option<BoxedCancelToken>,
116    ) -> FabricReceiver<crate::WinResult<i64>> {
117        let com1 = &self.com;
118        let com2 = self.com.clone();
119        fabric_begin_end_proxy(
120            move |callback| unsafe {
121                com1.BeginRegisterServiceNotificationFilter(desc, timeout_milliseconds, callback)
122            },
123            move |ctx| unsafe { com2.EndRegisterServiceNotificationFilter(ctx) },
124            cancellation_token,
125        )
126    }
127
128    fn unregister_service_notification_filter_internal(
129        &self,
130        filterid: i64,
131        timeout_milliseconds: u32,
132        cancellation_token: Option<BoxedCancelToken>,
133    ) -> FabricReceiver<crate::WinResult<()>> {
134        let com1 = &self.com;
135        let com2 = self.com.clone();
136        fabric_begin_end_proxy(
137            move |callback| unsafe {
138                com1.BeginUnregisterServiceNotificationFilter(
139                    filterid,
140                    timeout_milliseconds,
141                    callback,
142                )
143            },
144            move |ctx| unsafe { com2.EndUnregisterServiceNotificationFilter(ctx) },
145            cancellation_token,
146        )
147    }
148
149    fn create_service_internal(
150        &self,
151        desc: &FABRIC_SERVICE_DESCRIPTION,
152        timeout_milliseconds: u32,
153        cancellation_token: Option<BoxedCancelToken>,
154    ) -> FabricReceiver<crate::WinResult<()>> {
155        let com1 = &self.com;
156        let com2 = self.com.clone();
157        fabric_begin_end_proxy(
158            move |callback| unsafe {
159                com1.BeginCreateService(desc, timeout_milliseconds, callback)
160            },
161            move |ctx| unsafe { com2.EndCreateService(ctx) },
162            cancellation_token,
163        )
164    }
165
166    fn update_service_internal(
167        &self,
168        name: FABRIC_URI,
169        desc: &FABRIC_SERVICE_UPDATE_DESCRIPTION,
170        timeout_milliseconds: u32,
171        cancellation_token: Option<BoxedCancelToken>,
172    ) -> FabricReceiver<crate::WinResult<()>> {
173        let com1 = &self.com;
174        let com2 = self.com.clone();
175        fabric_begin_end_proxy(
176            move |callback| unsafe {
177                com1.BeginUpdateService(name, desc, timeout_milliseconds, callback)
178            },
179            move |ctx| unsafe { com2.EndUpdateService(ctx) },
180            cancellation_token,
181        )
182    }
183
184    fn delete_service_internal(
185        &self,
186        name: FABRIC_URI,
187        timeout_milliseconds: u32,
188        cancellation_token: Option<BoxedCancelToken>,
189    ) -> FabricReceiver<crate::WinResult<()>> {
190        let com1 = &self.com;
191        let com2 = self.com.clone();
192        fabric_begin_end_proxy(
193            move |callback| unsafe {
194                com1.BeginDeleteService(name, timeout_milliseconds, callback)
195            },
196            move |ctx| unsafe { com2.EndDeleteService(ctx) },
197            cancellation_token,
198        )
199    }
200}
201
202impl From<IFabricServiceManagementClient6> for ServiceManagementClient {
203    fn from(com: IFabricServiceManagementClient6) -> Self {
204        Self { com }
205    }
206}
207
208impl From<ServiceManagementClient> for IFabricServiceManagementClient6 {
209    fn from(value: ServiceManagementClient) -> Self {
210        value.com
211    }
212}
213
214// public implementation block - tokio required
215
216impl ServiceManagementClient {
217    // Resolve service partition
218    pub async fn resolve_service_partition(
219        &self,
220        name: &Uri,
221        key_type: &PartitionKeyType,
222        prev: Option<&ResolvedServicePartition>,
223        timeout: Duration,
224        cancellation_token: Option<BoxedCancelToken>,
225    ) -> crate::Result<ResolvedServicePartition> {
226        let com = {
227            let uri = name.as_raw();
228            // supply prev as null if not present
229            let prev_opt = prev.map(|x| &x.com);
230
231            let part_key_opt = key_type.get_raw_opt();
232
233            self.resolve_service_partition_internal(
234                uri,
235                key_type.into(),
236                part_key_opt,
237                prev_opt,
238                timeout.as_millis().try_into().unwrap(),
239                cancellation_token,
240            )
241        }
242        .await??;
243        let res = ResolvedServicePartition::from(com);
244        Ok(res)
245    }
246
247    /// Simulates a service replica failure by restarting a persisted service replica,
248    /// closing the replica, and then reopening it. Use this to test your service for problems
249    /// along the replica reopen path. This helps simulate the report fault temporary path through client APIs.
250    /// This is only valid for replicas that belong to stateful persisted services.
251    pub async fn restart_replica(
252        &self,
253        desc: &RestartReplicaDescription,
254        timeout: Duration,
255        cancellation_token: Option<BoxedCancelToken>,
256    ) -> crate::Result<()> {
257        {
258            let raw: FABRIC_RESTART_REPLICA_DESCRIPTION = desc.into();
259            self.restart_replica_internal(&raw, timeout.as_millis() as u32, cancellation_token)
260        }
261        .await?
262        .map_err(crate::Error::from)
263    }
264
265    /// This API gives a running replica the chance to cleanup its state and be gracefully shutdown.
266    /// WARNING: There are no safety checks performed when this API is used.
267    /// Incorrect use of this API can lead to data loss for stateful services.
268    /// Remarks:
269    /// For stateless services, Instance Abort is called.
270    pub async fn remove_replica(
271        &self,
272        desc: &RemoveReplicaDescription,
273        timeout: Duration,
274        cancellation_token: Option<BoxedCancelToken>,
275    ) -> crate::Result<()> {
276        {
277            let raw: FABRIC_REMOVE_REPLICA_DESCRIPTION = desc.into();
278            self.remove_replica_internal(&raw, timeout.as_millis() as u32, cancellation_token)
279        }
280        .await?
281        .map_err(crate::Error::from)
282    }
283
284    /// Remarks:
285    /// There is a cache of service endpoints in the client that gets updated by notifications
286    /// and this same cache is used to satisfy complaint based resolution requests
287    /// (see resolve_service_partition())). Applications that both register for notifications
288    /// and use complaint based resolution on the same client instance typically only need to
289    /// pass null for the ResolvedServicePartition argument during resolution.
290    /// This will always return the endpoints in the client cache updated by the latest notification.
291    /// The notification mechanism itself will keep the client cache updated when service endpoints change.
292    ///
293    /// Notification callback is delivered on `FabricClientBuilder::with_on_service_notification` as well.
294    /// The callback contains minimum info only as a signal, user can call resolve_service_partition()
295    /// again to retrieve full info from the cache.
296    ///
297    /// This is observed to have 1~4 secs delay compared with brute force complaint based resolve.
298    pub async fn register_service_notification_filter(
299        &self,
300        desc: &ServiceNotificationFilterDescription,
301        timeout: Duration,
302        cancellation_token: Option<BoxedCancelToken>,
303    ) -> crate::Result<FilterIdHandle> {
304        let id = {
305            let raw: FABRIC_SERVICE_NOTIFICATION_FILTER_DESCRIPTION = desc.into();
306            self.register_service_notification_filter_internal(
307                &raw,
308                timeout.as_millis() as u32,
309                cancellation_token,
310            )
311        }
312        .await??;
313        Ok(FilterIdHandle { id })
314    }
315
316    /// It's not necessary to unregister individual filters if the client itself
317    /// will no longer be used since all ServiceNotificationFilterDescription
318    /// objects registered by the FabricClient will be automatically unregistered when client is disposed.
319    pub async fn unregister_service_notification_filter(
320        &self,
321        filter_id_handle: FilterIdHandle,
322        timeout: Duration,
323        cancellation_token: Option<BoxedCancelToken>,
324    ) -> crate::Result<()> {
325        self.unregister_service_notification_filter_internal(
326            filter_id_handle.id,
327            timeout.as_millis() as u32,
328            cancellation_token,
329        )
330        .await?
331        .map_err(crate::Error::from)
332    }
333
334    pub async fn create_service(
335        &self,
336        desc: &crate::types::ServiceDescription,
337        timeout: Duration,
338        cancellation_token: Option<BoxedCancelToken>,
339    ) -> crate::Result<()> {
340        {
341            let desc_raw = desc.build_raw();
342            let ffi_raw = desc_raw.as_ffi();
343            self.create_service_internal(&ffi_raw, timeout.as_millis() as u32, cancellation_token)
344        }
345        .await?
346        .map_err(crate::Error::from)
347    }
348
349    pub async fn update_service(
350        &self,
351        name: &Uri,
352        desc: &crate::types::ServiceUpdateDescription,
353        timeout: Duration,
354        cancellation_token: Option<BoxedCancelToken>,
355    ) -> crate::Result<()> {
356        {
357            let desc_raw = desc.build_raw();
358            let ffi_raw = desc_raw.as_ffi();
359            self.update_service_internal(
360                name.as_raw(),
361                &ffi_raw,
362                timeout.as_millis() as u32,
363                cancellation_token,
364            )
365        }
366        .await?
367        .map_err(crate::Error::from)
368    }
369
370    pub async fn delete_service(
371        &self,
372        name: &Uri,
373        timeout: Duration,
374        cancellation_token: Option<BoxedCancelToken>,
375    ) -> crate::Result<()> {
376        self.delete_service_internal(
377            name.as_raw(),
378            timeout.as_millis() as u32,
379            cancellation_token,
380        )
381        .await?
382        .map_err(crate::Error::from)
383    }
384}
385
386// Handle to the registered service notification filter
387
388#[derive(Debug, PartialEq)]
389pub struct FilterIdHandle {
390    pub(crate) id: i64,
391}
392
393// see ComFabricClient.cpp for conversion details in cpp
394#[derive(Debug, PartialEq)]
395pub enum PartitionKeyType {
396    Int64(i64),
397    Invalid,
398    None,
399    String(WString),
400}
401
402impl PartitionKeyType {
403    fn from_raw_svc_part(svc: ServicePartitionKind, data: *const c_void) -> PartitionKeyType {
404        match svc {
405            ServicePartitionKind::Int64Range => {
406                let x = data as *mut i64;
407                assert!(!x.is_null());
408                PartitionKeyType::Int64(unsafe { *x })
409            }
410            ServicePartitionKind::Invalid => PartitionKeyType::Invalid,
411            ServicePartitionKind::Singleton => PartitionKeyType::None,
412            ServicePartitionKind::Named => {
413                let x = data as *mut u16;
414                assert!(!x.is_null());
415                let s = WStringWrap::from(PCWSTR::from_raw(x)).into();
416                PartitionKeyType::String(s)
417            }
418        }
419    }
420}
421
422impl From<&PartitionKeyType> for FABRIC_PARTITION_KEY_TYPE {
423    fn from(value: &PartitionKeyType) -> Self {
424        match value {
425            PartitionKeyType::Int64(_) => FABRIC_PARTITION_KEY_TYPE_INT64,
426            PartitionKeyType::Invalid => FABRIC_PARTITION_KEY_TYPE_INVALID,
427            PartitionKeyType::None => FABRIC_PARTITION_KEY_TYPE_NONE,
428            PartitionKeyType::String(_) => FABRIC_PARTITION_KEY_TYPE_STRING,
429        }
430    }
431}
432
433impl PartitionKeyType {
434    // get raw ptr to pass in com api
435    fn get_raw_opt(&self) -> Option<*const c_void> {
436        match self {
437            // Not sure if this is ok for i64
438            PartitionKeyType::Int64(x) => Some(x as *const i64 as *const c_void),
439            PartitionKeyType::Invalid => None,
440            PartitionKeyType::None => None,
441            PartitionKeyType::String(x) => {
442                Some(PCWSTR::from_raw(x.as_ptr()).as_ptr() as *const c_void)
443            }
444        }
445    }
446}
447
448#[derive(Clone, Copy, Debug, PartialEq)]
449pub enum ServicePartitionKind {
450    Int64Range,
451    Invalid,
452    Named,
453    Singleton,
454}
455
456impl From<&ServicePartitionKind> for FABRIC_SERVICE_PARTITION_KIND {
457    fn from(value: &ServicePartitionKind) -> Self {
458        match value {
459            ServicePartitionKind::Int64Range => FABRIC_SERVICE_PARTITION_KIND_INT64_RANGE,
460            ServicePartitionKind::Invalid => FABRIC_SERVICE_PARTITION_KIND_INVALID,
461            ServicePartitionKind::Named => FABRIC_SERVICE_PARTITION_KIND_NAMED,
462            ServicePartitionKind::Singleton => FABRIC_SERVICE_PARTITION_KIND_SINGLETON,
463        }
464    }
465}
466
467impl From<FABRIC_SERVICE_PARTITION_KIND> for ServicePartitionKind {
468    fn from(value: FABRIC_SERVICE_PARTITION_KIND) -> Self {
469        match value {
470            FABRIC_SERVICE_PARTITION_KIND_INT64_RANGE => ServicePartitionKind::Int64Range,
471            FABRIC_SERVICE_PARTITION_KIND_INVALID => ServicePartitionKind::Invalid,
472            FABRIC_SERVICE_PARTITION_KIND_NAMED => ServicePartitionKind::Named,
473            FABRIC_SERVICE_PARTITION_KIND_SINGLETON => ServicePartitionKind::Singleton,
474            _ => {
475                if cfg!(debug_assertions) {
476                    panic!("unknown type: {value:?}");
477                } else {
478                    ServicePartitionKind::Invalid
479                }
480            }
481        }
482    }
483}
484
485#[derive(Debug, Clone)]
486pub struct ResolvedServicePartition {
487    com: IFabricResolvedServicePartitionResult,
488}
489
490impl From<IFabricResolvedServicePartitionResult> for ResolvedServicePartition {
491    fn from(com: IFabricResolvedServicePartitionResult) -> Self {
492        Self { com }
493    }
494}
495
496#[derive(Debug)]
497pub struct ResolvedServicePartitionInfo {
498    pub service_name: Uri,
499    pub service_partition_kind: ServicePartitionKind,
500    pub partition_key_type: PartitionKeyType,
501}
502
503impl ResolvedServicePartition {
504    // Get the service partition info/metadata
505    pub fn get_info(&self) -> ResolvedServicePartitionInfo {
506        let raw = unsafe { self.com.get_Partition().as_ref().unwrap() };
507        let service_name = Uri::from(raw.ServiceName);
508        let kind_raw = raw.Info.Kind;
509        let val = raw.Info.Value;
510        let service_partition_kind: ServicePartitionKind = kind_raw.into();
511        let partition_key_type = PartitionKeyType::from_raw_svc_part(service_partition_kind, val);
512        ResolvedServicePartitionInfo {
513            service_name,
514            service_partition_kind,
515            partition_key_type,
516        }
517    }
518
519    // Get the list of endpoints
520    pub fn get_endpoint_list(&self) -> ResolvedServiceEndpointList {
521        ResolvedServiceEndpointList::from(self.com.clone())
522    }
523
524    // If compared with different partition error is returned.
525    // to enable the user to identify which RSP is more
526    // up-to-date. A returned value of 0 indicates that the two RSPs have the same version. 1 indicates that the other RSP has an older version.
527    // -1 indicates that the other RSP has a newer version.
528    pub fn compare_version(&self, other: &ResolvedServicePartition) -> crate::Result<i32> {
529        unsafe { self.com.CompareVersion(&other.com) }.map_err(crate::Error::from)
530    }
531}
532
533impl PartialEq for ResolvedServicePartition {
534    fn eq(&self, other: &Self) -> bool {
535        match self.compare_version(other) {
536            Ok(i) => i == 0,
537            Err(_) => false, // error comparing different services
538        }
539    }
540}
541
542impl PartialOrd for ResolvedServicePartition {
543    /// Compare the version of the resolved result.
544    /// a > b means partial_cmp(a,b) == Some(Greater) i.e. a.compare_version(b) > 0.
545    /// a is newer and up to date.
546    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
547        match self.compare_version(other) {
548            Ok(i) => Some(i.cmp(&0)),
549            // If you compare version of different service you get error
550            Err(_) => None,
551        }
552    }
553}
554
555#[derive(Debug, PartialEq, Clone)]
556pub enum ServiceEndpointRole {
557    Invalid,
558    StatefulPrimary,
559    StatefulSecondary,
560    Stateless,
561}
562
563impl From<FABRIC_SERVICE_ENDPOINT_ROLE> for ServiceEndpointRole {
564    fn from(value: FABRIC_SERVICE_ENDPOINT_ROLE) -> Self {
565        match value {
566            FABRIC_SERVICE_ROLE_INVALID => ServiceEndpointRole::Invalid,
567            FABRIC_SERVICE_ROLE_STATEFUL_PRIMARY => ServiceEndpointRole::StatefulPrimary,
568            FABRIC_SERVICE_ROLE_STATEFUL_SECONDARY => ServiceEndpointRole::StatefulSecondary,
569            FABRIC_SERVICE_ROLE_STATELESS => ServiceEndpointRole::Stateless,
570            _ => {
571                if cfg!(debug_assertions) {
572                    panic!("unknown type: {value:?}");
573                } else {
574                    ServiceEndpointRole::Invalid
575                }
576            }
577        }
578    }
579}
580
581pub struct ResolvedServiceEndpointList {
582    com: IFabricResolvedServicePartitionResult,
583}
584
585impl From<IFabricResolvedServicePartitionResult> for ResolvedServiceEndpointList {
586    fn from(com: IFabricResolvedServicePartitionResult) -> Self {
587        Self { com }
588    }
589}
590
591impl ResolvedServiceEndpointList {
592    // Get iterator for the list
593    pub fn iter(&self) -> ResolvedServiceEndpointListIter<'_> {
594        ResolvedServiceEndpointListIter::new(self, self)
595    }
596}
597
598impl FabricListAccessor<FABRIC_RESOLVED_SERVICE_ENDPOINT> for ResolvedServiceEndpointList {
599    fn get_count(&self) -> u32 {
600        let raw = unsafe { self.com.get_Partition().as_ref().unwrap() };
601        raw.EndpointCount
602    }
603
604    fn get_first_item(&self) -> *const FABRIC_RESOLVED_SERVICE_ENDPOINT {
605        let raw = unsafe { self.com.get_Partition().as_ref().unwrap() };
606        raw.Endpoints
607    }
608}
609
610#[derive(Debug, Clone, PartialEq)]
611pub struct ResolvedServiceEndpoint {
612    pub address: WString,
613    pub role: ServiceEndpointRole,
614}
615
616type ResolvedServiceEndpointListIter<'a> = FabricIter<
617    'a,
618    FABRIC_RESOLVED_SERVICE_ENDPOINT,
619    ResolvedServiceEndpoint,
620    ResolvedServiceEndpointList,
621>;
622
623impl From<&FABRIC_RESOLVED_SERVICE_ENDPOINT> for ResolvedServiceEndpoint {
624    fn from(value: &FABRIC_RESOLVED_SERVICE_ENDPOINT) -> Self {
625        let raw = value;
626        Self {
627            address: WStringWrap::from(raw.Address).into(),
628            role: raw.Role.into(),
629        }
630    }
631}
632
633#[cfg(test)]
634mod tests {
635    use crate::{PCWSTR, WString};
636
637    use super::{PartitionKeyType, ServicePartitionKind};
638
639    #[test]
640    fn test_conversion_int() {
641        let k = PartitionKeyType::Int64(99);
642        // check the raw ptr is ok
643        let raw = k.get_raw_opt();
644        let i = unsafe { (raw.unwrap() as *const i64).as_ref().unwrap() };
645        assert_eq!(*i, 99);
646
647        let service_type = ServicePartitionKind::Int64Range;
648        // restore the key
649        let k2 = PartitionKeyType::from_raw_svc_part(service_type, raw.unwrap());
650        assert_eq!(k, k2);
651    }
652
653    #[test]
654    fn test_conversion_string() {
655        let src = WString::from("mystr");
656        let k = PartitionKeyType::String(src.clone());
657        // check the raw ptr is ok
658        let raw = k.get_raw_opt();
659        let s = WString::from(PCWSTR::from_raw(raw.unwrap() as *const u16));
660        assert_eq!(s, src);
661
662        let service_type = ServicePartitionKind::Named;
663        // restore the key
664        let k2 = PartitionKeyType::from_raw_svc_part(service_type, raw.unwrap());
665        assert_eq!(k, k2);
666    }
667}