1#![deny(clippy::all)]
2mod error;
3pub mod resources;
4pub mod retry;
5
6use chrono::{DateTime, Utc};
7use http::{header::ACCEPT, Method};
8use log::debug;
9use once_cell::sync::Lazy;
10use reqwest::{
11 blocking::{
12 multipart::{Form, Part},
13 Client as HttpClient, Response as HttpResponse,
14 },
15 header::{self, HeaderMap, HeaderValue},
16 IntoUrl, Proxy, Result as ReqwestResult,
17};
18use resources::{
19 attachments::UploadAttachmentResponse,
20 auth::{RefreshUserPermissionsRequest, RefreshUserPermissionsResponse},
21 bucket::{
22 GetKeyedSyncStateIdsRequest, GetKeyedSyncStateIdsResponse, GetKeyedSyncStatesResponse,
23 KeyedSyncState, KeyedSyncStateId,
24 },
25 bucket_statistics::GetBucketStatisticsResponse,
26 comment::{AttachmentReference, CommentTimestampFilter},
27 dataset::{
28 CreateIxpDatasetRequest, CreateIxpDatasetResponse, GetAllModelsInDatasetRequest,
29 GetAllModelsInDatasetRespone, IxpDatasetNew, QueryRequestParams, QueryResponse,
30 StatisticsRequestParams as DatasetStatisticsRequestParams, SummaryRequestParams,
31 SummaryResponse, UploadIxpDocumentResponse, UserModelMetadata,
32 },
33 documents::{Document, SyncRawEmailsRequest, SyncRawEmailsResponse},
34 email::{Email, GetEmailResponse},
35 integration::{
36 GetIntegrationResponse, GetIntegrationsResponse, Integration, NewIntegration,
37 PostIntegrationRequest, PostIntegrationResponse, PutIntegrationRequest,
38 PutIntegrationResponse,
39 },
40 label_def::{CreateOrUpdateLabelDefsBulkRequest, CreateOrUpdateLabelDefsBulkResponse},
41 project::ForceDeleteProject,
42 quota::{GetQuotasResponse, Quota},
43 source::StatisticsRequestParams as SourceStatisticsRequestParams,
44 stream::{GetStreamResponse, NewStream, PutStreamRequest, PutStreamResponse},
45 tenant_id::UiPathTenantId,
46 validation::{
47 LabelValidation, LabelValidationRequest, LabelValidationResponse, ValidationResponse,
48 },
49};
50use serde::{Deserialize, Serialize};
51use serde_json::json;
52use std::{
53 cell::Cell,
54 fmt::{Debug, Display},
55 io::Read,
56 path::{Path, PathBuf},
57 time::Duration,
58};
59use url::Url;
60
61use crate::resources::{
62 audit::{AuditQueryFilter, AuditQueryRequest, AuditQueryResponse},
63 bucket::{
64 CreateRequest as CreateBucketRequest, CreateResponse as CreateBucketResponse,
65 GetAvailableResponse as GetAvailableBucketsResponse, GetResponse as GetBucketResponse,
66 },
67 bucket_statistics::Statistics as BucketStatistics,
68 comment::{
69 GetAnnotationsResponse, GetCommentResponse, GetLabellingsAfter, GetPredictionsResponse,
70 GetRecentRequest, PutCommentsRequest, PutCommentsResponse, RecentCommentsPage,
71 SyncCommentsRequest, UpdateAnnotationsRequest,
72 },
73 dataset::{
74 CreateRequest as CreateDatasetRequest, CreateResponse as CreateDatasetResponse,
75 GetAvailableResponse as GetAvailableDatasetsResponse, GetResponse as GetDatasetResponse,
76 UpdateRequest as UpdateDatasetRequest, UpdateResponse as UpdateDatasetResponse,
77 },
78 email::{PutEmailsRequest, PutEmailsResponse},
79 project::{
80 CreateProjectRequest, CreateProjectResponse, GetProjectResponse, GetProjectsResponse,
81 UpdateProjectRequest, UpdateProjectResponse,
82 },
83 quota::{CreateQuota, TenantQuotaKind},
84 source::{
85 CreateRequest as CreateSourceRequest, CreateResponse as CreateSourceResponse,
86 GetAvailableResponse as GetAvailableSourcesResponse, GetResponse as GetSourceResponse,
87 UpdateRequest as UpdateSourceRequest, UpdateResponse as UpdateSourceResponse,
88 },
89 statistics::GetResponse as GetStatisticsResponse,
90 stream::{
91 AdvanceRequest as StreamAdvanceRequest, FetchRequest as StreamFetchRequest,
92 GetStreamsResponse, ResetRequest as StreamResetRequest,
93 TagExceptionsRequest as TagStreamExceptionsRequest,
94 },
95 tenant_id::TenantId,
96 user::{
97 CreateRequest as CreateUserRequest, CreateResponse as CreateUserResponse,
98 GetAvailableResponse as GetAvailableUsersResponse,
99 GetCurrentResponse as GetCurrentUserResponse, GetResponse as GetUserResponse,
100 PostUserRequest, PostUserResponse, WelcomeEmailResponse,
101 },
102 EmptySuccess, Response,
103};
104
105use crate::retry::{Retrier, RetryConfig};
106
107pub use crate::{
108 error::{Error, Result},
109 resources::{
110 bucket::{
111 Bucket, BucketType, FullName as BucketFullName, Id as BucketId,
112 Identifier as BucketIdentifier, Name as BucketName, NewBucket,
113 },
114 comment::{
115 AnnotatedComment, Comment, CommentFilter, CommentPredictionsThreshold,
116 CommentsIterPage, Continuation, EitherLabelling, Entities, Entity,
117 GetCommentPredictionsRequest, HasAnnotations, Id as CommentId, Label, Labelling,
118 Message, MessageBody, MessageSignature, MessageSubject, NewAnnotatedComment,
119 NewComment, NewEntities, NewLabelling, NewMoonForm, PredictedLabel, Prediction,
120 PropertyMap, PropertyValue, Sentiment, SyncCommentsResponse, TriggerLabelThreshold,
121 Uid as CommentUid,
122 },
123 dataset::{
124 Dataset, FullName as DatasetFullName, Id as DatasetId, Identifier as DatasetIdentifier,
125 ModelVersion, Name as DatasetName, NewDataset, UpdateDataset,
126 },
127 email::{
128 Continuation as EmailContinuation, EmailsIterPage, EmailsQueryPage, Id as EmailId,
129 Mailbox, MimeContent, NewEmail,
130 },
131 entity_def::{EntityDef, Id as EntityDefId, Name as EntityName, NewEntityDef},
132 integration::FullName as IntegrationFullName,
133 label_def::{
134 LabelDef, LabelDefPretrained, MoonFormFieldDef, Name as LabelName, NewLabelDef,
135 NewLabelDefPretrained, PretrainedId as LabelDefPretrainedId,
136 },
137 label_group::{
138 LabelGroup, Name as LabelGroupName, NewLabelGroup, DEFAULT_LABEL_GROUP_NAME,
139 },
140 project::{NewProject, Project, ProjectName, UpdateProject},
141 source::{
142 FullName as SourceFullName, Id as SourceId, Identifier as SourceIdentifier,
143 Name as SourceName, NewSource, Source, SourceKind, TransformTag, UpdateSource,
144 },
145 statistics::Statistics as CommentStatistics,
146 stream::{
147 Batch as StreamBatch, FullName as StreamFullName, SequenceId as StreamSequenceId,
148 Stream, StreamException, StreamExceptionMetadata,
149 },
150 user::{
151 Email as UserEmail, GlobalPermission, Id as UserId, Identifier as UserIdentifier,
152 ModifiedPermissions, NewUser, ProjectPermission, UpdateUser, User, Username,
153 },
154 },
155};
156
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct Token(pub String);
159
160pub trait SplittableRequest {
161 fn split(self) -> impl Iterator<Item = Self>
162 where
163 Self: Sized;
164
165 fn count(&self) -> usize;
166}
167
168pub struct SplitableRequestResponse<ResponseT>
169where
170 for<'de> ResponseT: Deserialize<'de> + ReducibleResponse,
171{
172 pub response: ResponseT,
173 pub num_failed: usize,
174}
175
176pub trait ReducibleResponse {
177 fn merge(self, _b: Self) -> Self
178 where
179 Self: std::default::Default,
180 {
181 Default::default()
182 }
183
184 fn empty() -> Self
185 where
186 Self: std::default::Default,
187 {
188 Default::default()
189 }
190}
191
192pub struct Config {
193 pub endpoint: Url,
194 pub token: Token,
195 pub accept_invalid_certificates: bool,
196 pub proxy: Option<Url>,
197 pub retry_config: Option<RetryConfig>,
200}
201
202impl Default for Config {
203 fn default() -> Self {
204 Config {
205 endpoint: DEFAULT_ENDPOINT.clone(),
206 token: Token("".to_owned()),
207 accept_invalid_certificates: false,
208 proxy: None,
209 retry_config: None,
210 }
211 }
212}
213
214#[derive(Debug)]
215pub struct Client {
216 endpoints: Endpoints,
217 http_client: HttpClient,
218 headers: HeaderMap,
219 retrier: Option<Retrier>,
220}
221
222#[derive(Serialize)]
223pub struct GetLabellingsInBulk<'a> {
224 pub source_id: &'a SourceId,
225 pub return_predictions: &'a bool,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
228 pub after: &'a Option<GetLabellingsAfter>,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub limit: &'a Option<usize>,
232}
233
234#[derive(Serialize)]
235pub struct GetCommentsIterPageQuery<'a> {
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub from_timestamp: Option<DateTime<Utc>>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub to_timestamp: Option<DateTime<Utc>>,
240 #[serde(skip_serializing_if = "Option::is_none")]
241 pub after: Option<&'a Continuation>,
242 pub limit: usize,
243 pub include_markup: bool,
244}
245
246#[derive(Serialize)]
247pub struct GetEmailsIterPageQuery<'a> {
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub continuation: Option<&'a EmailContinuation>,
250 pub limit: usize,
251}
252
253#[derive(Serialize)]
254pub struct QueryEmailsPageRequest<'a> {
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub continuation: Option<&'a EmailContinuation>,
257 pub limit: usize,
258 #[serde(skip_serializing_if = "Option::is_none")]
259 pub from_timestamp: Option<DateTime<Utc>>,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub to_timestamp: Option<DateTime<Utc>>,
262 #[serde(skip_serializing_if = "Option::is_none")]
263 pub mailbox_name: Option<&'a str>,
264}
265
266#[derive(Serialize)]
267pub struct GetCommentQuery {
268 pub include_markup: bool,
269}
270
271#[derive(Serialize)]
272pub struct GetEmailQuery {
273 pub id: String,
274}
275
276impl Client {
277 pub fn new(config: Config) -> Result<Client> {
279 let http_client = build_http_client(&config)?;
280 let headers = build_headers(&config)?;
281 let endpoints = Endpoints::new(config.endpoint)?;
282 let retrier = config.retry_config.map(Retrier::new);
283 Ok(Client {
284 endpoints,
285 http_client,
286 headers,
287 retrier,
288 })
289 }
290
291 pub fn base_url(&self) -> &Url {
293 &self.endpoints.base
294 }
295
296 pub fn get_sources(&self) -> Result<Vec<Source>> {
298 Ok(self
299 .get::<_, GetAvailableSourcesResponse>(self.endpoints.sources.clone())?
300 .sources)
301 }
302
303 pub fn get_user(&self, user: impl Into<UserIdentifier>) -> Result<User> {
305 Ok(match user.into() {
306 UserIdentifier::Id(user_id) => {
307 self.get::<_, GetUserResponse>(self.endpoints.user_by_id(&user_id)?)?
308 .user
309 }
310 })
311 }
312
313 pub fn get_source(&self, source: impl Into<SourceIdentifier>) -> Result<Source> {
315 Ok(match source.into() {
316 SourceIdentifier::Id(source_id) => {
317 self.get::<_, GetSourceResponse>(self.endpoints.source_by_id(&source_id)?)?
318 .source
319 }
320 SourceIdentifier::FullName(source_name) => {
321 self.get::<_, GetSourceResponse>(self.endpoints.source_by_name(&source_name)?)?
322 .source
323 }
324 })
325 }
326
327 pub fn create_label_defs_bulk(
328 &self,
329 dataset_name: &DatasetFullName,
330 label_group: LabelGroupName,
331 label_defs: Vec<NewLabelDef>,
332 ) -> Result<()> {
333 self.put::<_, _, CreateOrUpdateLabelDefsBulkResponse>(
334 self.endpoints.label_group(dataset_name, label_group)?,
335 CreateOrUpdateLabelDefsBulkRequest { label_defs },
336 )?;
337 Ok(())
338 }
339
340 pub fn create_source(
342 &self,
343 source_name: &SourceFullName,
344 options: NewSource<'_>,
345 ) -> Result<Source> {
346 Ok(self
347 .put::<_, _, CreateSourceResponse>(
348 self.endpoints.source_by_name(source_name)?,
349 CreateSourceRequest { source: options },
350 )?
351 .source)
352 }
353
354 pub fn update_source(
356 &self,
357 source_name: &SourceFullName,
358 options: UpdateSource<'_>,
359 ) -> Result<Source> {
360 Ok(self
361 .post::<_, _, UpdateSourceResponse>(
362 self.endpoints.source_by_name(source_name)?,
363 UpdateSourceRequest { source: options },
364 Retry::Yes,
365 )?
366 .source)
367 }
368
369 pub fn delete_source(&self, source: impl Into<SourceIdentifier>) -> Result<()> {
371 let source_id = match source.into() {
372 SourceIdentifier::Id(source_id) => source_id,
373 source @ SourceIdentifier::FullName(_) => self.get_source(source)?.id,
374 };
375 self.delete(self.endpoints.source_by_id(&source_id)?)
376 }
377
378 pub fn create_quota(
380 &self,
381 target_tenant_id: &TenantId,
382 tenant_quota_kind: TenantQuotaKind,
383 options: CreateQuota,
384 ) -> Result<()> {
385 self.post(
386 self.endpoints.quota(target_tenant_id, tenant_quota_kind)?,
387 options,
388 Retry::Yes,
389 )
390 }
391
392 pub fn get_quotas(&self, tenant_id: &Option<UiPathTenantId>) -> Result<Vec<Quota>> {
394 Ok(self
395 .get::<_, GetQuotasResponse>(self.endpoints.quotas(tenant_id)?)?
396 .quotas)
397 }
398
399 pub fn delete_user(&self, user: impl Into<UserIdentifier>) -> Result<()> {
401 let UserIdentifier::Id(user_id) = user.into();
402 self.delete(self.endpoints.user_by_id(&user_id)?)
403 }
404
405 pub fn delete_comments(
407 &self,
408 source: impl Into<SourceIdentifier>,
409 comments: &[CommentId],
410 ) -> Result<()> {
411 let source_full_name = match source.into() {
412 source @ SourceIdentifier::Id(_) => self.get_source(source)?.full_name(),
413 SourceIdentifier::FullName(source_full_name) => source_full_name,
414 };
415 self.delete_query(
416 self.endpoints.comments_v1(&source_full_name)?,
417 Some(&id_list_query(comments.iter().map(|uid| &uid.0))),
418 )
419 }
420
421 pub fn delete_emails(
423 &self,
424 bucket: impl Into<BucketIdentifier>,
425 emails: &[EmailId],
426 ) -> Result<()> {
427 let bucket_full_name = match bucket.into() {
428 bucket @ BucketIdentifier::Id(_) => self.get_bucket(bucket)?.full_name(),
429 BucketIdentifier::FullName(bucket_full_name) => bucket_full_name,
430 };
431 self.delete_query(
432 self.endpoints.delete_emails(&bucket_full_name)?,
433 Some(&id_list_query(emails.iter().map(|id| &id.0))),
434 )
435 }
436
437 pub fn get_comments_iter_page(
439 &self,
440 source_name: &SourceFullName,
441 continuation: Option<&ContinuationKind>,
442 to_timestamp: Option<DateTime<Utc>>,
443 limit: usize,
444 ) -> Result<CommentsIterPage> {
445 let (from_timestamp, after) = match continuation {
448 Some(ContinuationKind::Timestamp(from_timestamp)) => (Some(*from_timestamp), None),
451 Some(ContinuationKind::Continuation(after)) => (None, Some(after)),
454 None => (None, None),
457 };
458 let query_params = GetCommentsIterPageQuery {
459 from_timestamp,
460 to_timestamp,
461 after,
462 limit,
463 include_markup: true,
464 };
465 self.get_query(self.endpoints.comments(source_name)?, Some(&query_params))
466 }
467
468 pub fn get_dataset_query_iter<'a>(
470 &'a self,
471 dataset_name: &'a DatasetFullName,
472 params: &'a mut QueryRequestParams,
473 ) -> DatasetQueryIter<'a> {
474 DatasetQueryIter::new(self, dataset_name, params)
475 }
476
477 pub fn get_comments_iter<'a>(
479 &'a self,
480 source_name: &'a SourceFullName,
481 page_size: Option<usize>,
482 timerange: CommentsIterTimerange,
483 ) -> CommentsIter<'a> {
484 CommentsIter::new(self, source_name, page_size, timerange)
485 }
486
487 pub fn get_keyed_sync_state_ids(
488 &self,
489 bucket_id: &BucketId,
490 request: &GetKeyedSyncStateIdsRequest,
491 ) -> Result<Vec<KeyedSyncStateId>> {
492 Ok(self
493 .post::<_, _, GetKeyedSyncStateIdsResponse>(
494 self.endpoints.query_keyed_sync_state_ids(bucket_id)?,
495 Some(&request),
496 Retry::Yes,
497 )?
498 .keyed_sync_state_ids)
499 }
500
501 pub fn delete_keyed_sync_state(
502 &self,
503 bucket_id: &BucketId,
504 id: &KeyedSyncStateId,
505 ) -> Result<()> {
506 self.delete(self.endpoints.keyed_sync_state(bucket_id, id)?)
507 }
508
509 pub fn get_keyed_sync_states(&self, bucket_id: &BucketId) -> Result<Vec<KeyedSyncState>> {
510 Ok(self
511 .get::<_, GetKeyedSyncStatesResponse>(self.endpoints.keyed_sync_states(bucket_id)?)?
512 .keyed_sync_states)
513 }
514
515 pub fn get_email(&self, bucket_name: &BucketFullName, id: EmailId) -> Result<Vec<Email>> {
517 let query_params = GetEmailQuery { id: id.0 };
518 Ok(self
519 .get_query::<_, _, GetEmailResponse>(
520 self.endpoints.get_emails(bucket_name)?,
521 Some(&query_params),
522 )?
523 .emails)
524 }
525
526 pub fn get_emails_iter_page(
528 &self,
529 bucket_name: &BucketFullName,
530 continuation: Option<&EmailContinuation>,
531 limit: usize,
532 ) -> Result<EmailsIterPage> {
533 let query_params = GetEmailsIterPageQuery {
534 continuation,
535 limit,
536 };
537 self.post(
538 self.endpoints.get_emails(bucket_name)?,
539 Some(&query_params),
540 Retry::Yes,
541 )
542 }
543
544 pub fn get_emails_iter<'a>(
546 &'a self,
547 bucket_name: &'a BucketFullName,
548 page_size: Option<usize>,
549 ) -> EmailsIter<'a> {
550 EmailsIter::new(self, bucket_name, page_size)
551 }
552
553 pub fn query_emails_iter_page(
555 &self,
556 bucket_name: &BucketFullName,
557 filter: &EmailsQueryFilter,
558 continuation: Option<&EmailContinuation>,
559 limit: usize,
560 ) -> Result<EmailsQueryPage> {
561 let request = QueryEmailsPageRequest {
562 continuation,
563 limit,
564 from_timestamp: filter.from_timestamp,
565 to_timestamp: filter.to_timestamp,
566 mailbox_name: filter.mailbox_name.as_deref(),
567 };
568 self.post(
569 self.endpoints.query_emails(bucket_name)?,
570 Some(&request),
571 Retry::Yes,
572 )
573 }
574
575 pub fn query_emails_iter<'a>(
577 &'a self,
578 bucket_name: &'a BucketFullName,
579 filter: EmailsQueryFilter,
580 page_size: Option<usize>,
581 ) -> EmailsQueryIter<'a> {
582 EmailsQueryIter::new(self, bucket_name, filter, page_size)
583 }
584
585 pub fn get_comment<'a>(
587 &'a self,
588 source_name: &'a SourceFullName,
589 comment_id: &'a CommentId,
590 ) -> Result<Comment> {
591 let query_params = GetCommentQuery {
592 include_markup: true,
593 };
594 Ok(self
595 .get_query::<_, _, GetCommentResponse>(
596 self.endpoints.comment_by_id(source_name, comment_id)?,
597 Some(&query_params),
598 )?
599 .comment)
600 }
601 pub fn post_integration(
602 &self,
603 name: &IntegrationFullName,
604 integration: &NewIntegration,
605 ) -> Result<PostIntegrationResponse> {
606 self.request(
607 &Method::POST,
608 &self.endpoints.integration(name)?,
609 &Some(PostIntegrationRequest {
610 integration: integration.clone(),
611 }),
612 &None::<()>,
613 &Retry::No,
614 )
615 }
616
617 pub fn put_integration(
618 &self,
619 name: &IntegrationFullName,
620 integration: &NewIntegration,
621 ) -> Result<PutIntegrationResponse> {
622 self.request(
623 &Method::PUT,
624 &self.endpoints.integration(name)?,
625 &Some(PutIntegrationRequest {
626 integration: integration.clone(),
627 }),
628 &None::<()>,
629 &Retry::No,
630 )
631 }
632
633 pub fn put_comments_split_on_failure(
634 &self,
635 source_name: &SourceFullName,
636 comments: Vec<NewComment>,
637 no_charge: bool,
638 ) -> SplitableRequestResponse<PutCommentsResponse> {
639 self.splitable_request(
643 Method::PUT,
644 self.endpoints
645 .put_comments(source_name)
646 .expect("Could not get put_comments endpoint"),
647 PutCommentsRequest { comments },
648 Some(NoChargeQuery { no_charge }),
649 Retry::Yes,
650 )
651 }
652
653 pub fn put_comments(
654 &self,
655 source_name: &SourceFullName,
656 comments: Vec<NewComment>,
657 no_charge: bool,
658 ) -> Result<PutCommentsResponse> {
659 self.request(
662 &Method::PUT,
663 &self.endpoints.put_comments(source_name)?,
664 &Some(PutCommentsRequest { comments }),
665 &Some(NoChargeQuery { no_charge }),
666 &Retry::Yes,
667 )
668 }
669
670 pub fn put_stream(
671 &self,
672 dataset_name: &DatasetFullName,
673 stream: &NewStream,
674 ) -> Result<PutStreamResponse> {
675 self.put(
676 self.endpoints.streams(dataset_name)?,
677 Some(PutStreamRequest { stream }),
678 )
679 }
680
681 pub fn get_audit_events(
682 &self,
683 minimum_timestamp: Option<DateTime<Utc>>,
684 maximum_timestamp: Option<DateTime<Utc>>,
685 continuation: Option<Continuation>,
686 ) -> Result<AuditQueryResponse> {
687 self.post::<_, _, AuditQueryResponse>(
688 self.endpoints.audit_events_query()?,
689 AuditQueryRequest {
690 continuation,
691 filter: AuditQueryFilter {
692 timestamp: CommentTimestampFilter {
693 minimum: minimum_timestamp,
694 maximum: maximum_timestamp,
695 },
696 },
697 },
698 Retry::Yes,
699 )
700 }
701 pub fn get_latest_validation(
702 &self,
703 dataset_name: &DatasetFullName,
704 ) -> Result<ValidationResponse> {
705 self.get::<_, ValidationResponse>(self.endpoints.latest_validation(dataset_name)?)
706 }
707
708 pub fn get_validation(
709 &self,
710 dataset_name: &DatasetFullName,
711 model_version: &ModelVersion,
712 ) -> Result<ValidationResponse> {
713 self.get::<_, ValidationResponse>(self.endpoints.validation(dataset_name, model_version)?)
714 }
715
716 pub fn get_labellers(&self, dataset_name: &DatasetFullName) -> Result<Vec<UserModelMetadata>> {
717 Ok(self
718 .post::<_, _, GetAllModelsInDatasetRespone>(
719 self.endpoints.labellers(dataset_name)?,
720 GetAllModelsInDatasetRequest {},
721 Retry::Yes,
722 )?
723 .labellers)
724 }
725
726 pub fn get_label_validation(
727 &self,
728 label: &LabelName,
729 dataset_name: &DatasetFullName,
730 model_version: &ModelVersion,
731 ) -> Result<LabelValidation> {
732 Ok(self
733 .post::<_, _, LabelValidationResponse>(
734 self.endpoints
735 .label_validation(dataset_name, model_version)?,
736 LabelValidationRequest {
737 label: label.clone(),
738 },
739 Retry::Yes,
740 )?
741 .label_validation)
742 }
743
744 pub fn sync_comments(
745 &self,
746 source_name: &SourceFullName,
747 comments: Vec<NewComment>,
748 no_charge: bool,
749 ) -> Result<SyncCommentsResponse> {
750 self.request(
751 &Method::POST,
752 &self.endpoints.sync_comments(source_name)?,
753 &Some(SyncCommentsRequest { comments }),
754 &Some(NoChargeQuery { no_charge }),
755 &Retry::Yes,
756 )
757 }
758
759 pub fn sync_comments_split_on_failure(
760 &self,
761 source_name: &SourceFullName,
762 comments: Vec<NewComment>,
763 no_charge: bool,
764 ) -> SplitableRequestResponse<SyncCommentsResponse> {
765 self.splitable_request(
766 Method::POST,
767 self.endpoints
768 .sync_comments(source_name)
769 .expect("Could not get sync_comments endpoint"),
770 SyncCommentsRequest { comments },
771 Some(NoChargeQuery { no_charge }),
772 Retry::Yes,
773 )
774 }
775
776 pub fn sync_raw_emails(
777 &self,
778 source_name: &SourceFullName,
779 documents: &[Document],
780 transform_tag: &TransformTag,
781 include_comments: bool,
782 no_charge: bool,
783 ) -> Result<SyncRawEmailsResponse> {
784 self.request(
785 &Method::POST,
786 &self.endpoints.sync_comments_raw_emails(source_name)?,
787 &Some(SyncRawEmailsRequest {
788 documents,
789 transform_tag,
790 include_comments,
791 }),
792 &Some(NoChargeQuery { no_charge }),
793 &Retry::Yes,
794 )
795 }
796
797 pub fn put_emails_split_on_failure(
798 &self,
799 bucket_name: &BucketFullName,
800 emails: Vec<NewEmail>,
801 no_charge: bool,
802 ) -> SplitableRequestResponse<PutEmailsResponse> {
803 self.splitable_request(
804 Method::PUT,
805 self.endpoints
806 .put_emails(bucket_name)
807 .expect("Could not get put_emails endpoint"),
808 PutEmailsRequest { emails },
809 Some(NoChargeQuery { no_charge }),
810 Retry::Yes,
811 )
812 }
813
814 pub fn put_emails(
815 &self,
816 bucket_name: &BucketFullName,
817 emails: Vec<NewEmail>,
818 no_charge: bool,
819 ) -> Result<PutEmailsResponse> {
820 self.request(
821 &Method::PUT,
822 &self.endpoints.put_emails(bucket_name)?,
823 &Some(PutEmailsRequest { emails }),
824 &Some(NoChargeQuery { no_charge }),
825 &Retry::Yes,
826 )
827 }
828
829 pub fn post_user(&self, user_id: &UserId, user: UpdateUser) -> Result<PostUserResponse> {
830 self.post(
831 self.endpoints.post_user(user_id)?,
832 PostUserRequest { user: &user },
833 Retry::Yes,
834 )
835 }
836
837 pub fn put_comment_audio(
838 &self,
839 source_id: &SourceId,
840 comment_id: &CommentId,
841 audio_path: impl AsRef<Path>,
842 ) -> Result<()> {
843 let form = Form::new()
844 .file("file", audio_path)
845 .map_err(|source| Error::Unknown {
846 message: "PUT comment audio operation failed".to_owned(),
847 source: source.into(),
848 })?;
849 let http_response = self
850 .http_client
851 .put(self.endpoints.comment_audio(source_id, comment_id)?)
852 .headers(self.headers.clone())
853 .multipart(form)
854 .send()
855 .map_err(|source| Error::ReqwestError {
856 message: "PUT comment audio operation failed".to_owned(),
857 source,
858 })?;
859 let status = http_response.status();
860 http_response
861 .json::<Response<EmptySuccess>>()
862 .map_err(Error::BadJsonResponse)?
863 .into_result(status)?;
864 Ok(())
865 }
866
867 pub fn upload_ixp_document(
868 &self,
869 source_id: &SourceId,
870 filename: String,
871 bytes: Vec<u8>,
872 ) -> Result<CommentId> {
873 let endpoint = self.endpoints.ixp_documents(source_id)?;
874
875 let do_request = || {
876 let form = Form::new().part(
877 "file",
878 Part::bytes(bytes.clone()).file_name(filename.clone()),
879 );
880 let request = self
881 .http_client
882 .request(Method::PUT, endpoint.clone())
883 .multipart(form)
884 .headers(self.headers.clone());
885
886 request.send()
887 };
888
889 let result = self.with_retries(do_request);
890
891 let http_response = result.map_err(|source| Error::ReqwestError {
892 source,
893 message: "Operation failed.".to_string(),
894 })?;
895
896 let status = http_response.status();
897
898 Ok(http_response
899 .json::<Response<UploadIxpDocumentResponse>>()
900 .map_err(Error::BadJsonResponse)?
901 .into_result(status)?
902 .comment_id)
903 }
904
905 pub fn upload_comment_attachment(
906 &self,
907 source_id: &SourceId,
908 comment_id: &CommentId,
909 attachment_index: usize,
910 attachment: &PathBuf,
911 ) -> Result<UploadAttachmentResponse> {
912 let url = self
913 .endpoints
914 .attachment_upload(source_id, comment_id, attachment_index)?;
915
916 if !attachment.is_file() || !attachment.exists() {
917 return Err(Error::FileDoesNotExist {
918 path: attachment.clone(),
919 });
920 }
921
922 let do_request = || {
923 let form = Form::new()
924 .file("file", attachment)
925 .map_err(|source| Error::Unknown {
926 message: "PUT comment attachment operation failed".to_owned(),
927 source: source.into(),
928 })
929 .unwrap();
930 let request = self
931 .http_client
932 .request(Method::PUT, url.clone())
933 .multipart(form)
934 .headers(self.headers.clone());
935
936 request.send()
937 };
938
939 let result = self.with_retries(do_request);
940
941 let http_response = result.map_err(|source| Error::ReqwestError {
942 source,
943 message: "Operation failed.".to_string(),
944 })?;
945
946 let status = http_response.status();
947
948 http_response
949 .json::<Response<UploadAttachmentResponse>>()
950 .map_err(Error::BadJsonResponse)?
951 .into_result(status)
952 }
953
954 pub fn get_ixp_document(
955 &self,
956 source_id: &SourceId,
957 comment_id: &CommentId,
958 ) -> Result<Vec<u8>> {
959 self.get_octet_stream(&self.endpoints.ixp_document(source_id, comment_id)?)
960 }
961
962 fn get_octet_stream(&self, endpoint: &Url) -> Result<Vec<u8>> {
963 let mut response = self.raw_request(
964 &Method::GET,
965 endpoint,
966 &None::<()>,
967 &None::<()>,
968 &Retry::Yes,
969 None,
970 )?;
971
972 let status = response.status();
973
974 if !status.is_success() {
975 return Err(Error::Api {
976 status_code: status,
977 message: "Bad status code when getting octet stream".to_string(),
978 });
979 }
980
981 let mut buffer = Vec::new();
982
983 response
984 .read_to_end(&mut buffer)
985 .map_err(|source| Error::Unknown {
986 message: "Failed to read buffer".to_string(),
987 source: Box::new(source),
988 })?;
989 Ok(buffer)
990 }
991
992 pub fn get_attachment(&self, reference: &AttachmentReference) -> Result<Vec<u8>> {
993 self.get_octet_stream(&self.endpoints.attachment_reference(reference)?)
994 }
995
996 pub fn get_integrations(&self) -> Result<Vec<Integration>> {
997 Ok(self
998 .get::<_, GetIntegrationsResponse>(self.endpoints.integrations()?)?
999 .integrations)
1000 }
1001
1002 pub fn get_integration(&self, name: &IntegrationFullName) -> Result<Integration> {
1003 Ok(self
1004 .get::<_, GetIntegrationResponse>(self.endpoints.integration(name)?)?
1005 .integration)
1006 }
1007
1008 pub fn get_datasets(&self) -> Result<Vec<Dataset>> {
1009 Ok(self
1010 .get::<_, GetAvailableDatasetsResponse>(self.endpoints.datasets.clone())?
1011 .datasets)
1012 }
1013
1014 pub fn get_dataset<IdentifierT>(&self, dataset: IdentifierT) -> Result<Dataset>
1015 where
1016 IdentifierT: Into<DatasetIdentifier>,
1017 {
1018 Ok(match dataset.into() {
1019 DatasetIdentifier::Id(dataset_id) => {
1020 self.get::<_, GetDatasetResponse>(self.endpoints.dataset_by_id(&dataset_id)?)?
1021 .dataset
1022 }
1023 DatasetIdentifier::FullName(dataset_name) => {
1024 self.get::<_, GetDatasetResponse>(self.endpoints.dataset_by_name(&dataset_name)?)?
1025 .dataset
1026 }
1027 })
1028 }
1029
1030 pub fn create_ixp_dataset(&self, dataset: IxpDatasetNew) -> Result<Dataset> {
1032 Ok(self
1033 .put::<_, _, CreateIxpDatasetResponse>(
1034 self.endpoints.ixp_datasets()?,
1035 CreateIxpDatasetRequest { dataset },
1036 )?
1037 .dataset)
1038 }
1039
1040 pub fn create_dataset(
1042 &self,
1043 dataset_name: &DatasetFullName,
1044 options: NewDataset<'_>,
1045 ) -> Result<Dataset> {
1046 Ok(self
1047 .put::<_, _, CreateDatasetResponse>(
1048 self.endpoints.dataset_by_name(dataset_name)?,
1049 CreateDatasetRequest { dataset: options },
1050 )?
1051 .dataset)
1052 }
1053
1054 pub fn update_dataset(
1056 &self,
1057 dataset_name: &DatasetFullName,
1058 options: UpdateDataset<'_>,
1059 ) -> Result<Dataset> {
1060 Ok(self
1061 .post::<_, _, UpdateDatasetResponse>(
1062 self.endpoints.dataset_by_name(dataset_name)?,
1063 UpdateDatasetRequest { dataset: options },
1064 Retry::Yes,
1065 )?
1066 .dataset)
1067 }
1068
1069 pub fn delete_dataset<IdentifierT>(&self, dataset: IdentifierT) -> Result<()>
1070 where
1071 IdentifierT: Into<DatasetIdentifier>,
1072 {
1073 let dataset_id = match dataset.into() {
1074 DatasetIdentifier::Id(dataset_id) => dataset_id,
1075 dataset @ DatasetIdentifier::FullName(_) => self.get_dataset(dataset)?.id,
1076 };
1077 self.delete(self.endpoints.dataset_by_id(&dataset_id)?)
1078 }
1079
1080 pub fn get_labellings<'a>(
1082 &self,
1083 dataset_name: &DatasetFullName,
1084 comment_uids: impl Iterator<Item = &'a CommentUid>,
1085 ) -> Result<Vec<AnnotatedComment>> {
1086 Ok(self
1087 .get_query::<_, _, GetAnnotationsResponse>(
1088 self.endpoints.get_labellings(dataset_name)?,
1089 Some(&id_list_query(comment_uids.into_iter().map(|id| &id.0))),
1090 )?
1091 .results)
1092 }
1093
1094 pub fn get_labellings_iter<'a>(
1096 &'a self,
1097 dataset_name: &'a DatasetFullName,
1098 source_id: &'a SourceId,
1099 return_predictions: bool,
1100 limit: Option<usize>,
1101 ) -> LabellingsIter<'a> {
1102 LabellingsIter::new(self, dataset_name, source_id, return_predictions, limit)
1103 }
1104
1105 pub fn get_labellings_in_bulk(
1107 &self,
1108 dataset_name: &DatasetFullName,
1109 query_parameters: GetLabellingsInBulk<'_>,
1110 ) -> Result<GetAnnotationsResponse> {
1111 self.get_query::<_, _, GetAnnotationsResponse>(
1112 self.endpoints.get_labellings(dataset_name)?,
1113 Some(&query_parameters),
1114 )
1115 }
1116
1117 pub fn update_labelling(
1119 &self,
1120 dataset_name: &DatasetFullName,
1121 comment_uid: &CommentUid,
1122 labelling: Option<&[NewLabelling]>,
1123 entities: Option<&NewEntities>,
1124 moon_forms: Option<&[NewMoonForm]>,
1125 ) -> Result<AnnotatedComment> {
1126 self.post::<_, _, AnnotatedComment>(
1127 self.endpoints.post_labelling(dataset_name, comment_uid)?,
1128 UpdateAnnotationsRequest {
1129 labelling,
1130 entities,
1131 moon_forms,
1132 },
1133 Retry::Yes,
1134 )
1135 }
1136
1137 pub fn get_comment_predictions<'a>(
1139 &self,
1140 dataset_name: &DatasetFullName,
1141 model_version: &ModelVersion,
1142 comment_uids: impl Iterator<Item = &'a CommentUid>,
1143 threshold: Option<CommentPredictionsThreshold>,
1144 labels: Option<Vec<TriggerLabelThreshold>>,
1145 ) -> Result<Vec<Prediction>> {
1146 Ok(self
1147 .post::<_, _, GetPredictionsResponse>(
1148 self.endpoints
1149 .get_comment_predictions(dataset_name, model_version)?,
1150 GetCommentPredictionsRequest {
1151 uids: comment_uids
1152 .into_iter()
1153 .map(|id| id.0.clone())
1154 .collect::<Vec<_>>(),
1155
1156 threshold,
1157 labels,
1158 },
1159 Retry::Yes,
1160 )?
1161 .predictions)
1162 }
1163
1164 pub fn get_streams(&self, dataset_name: &DatasetFullName) -> Result<Vec<Stream>> {
1165 Ok(self
1166 .get::<_, GetStreamsResponse>(self.endpoints.streams(dataset_name)?)?
1167 .streams)
1168 }
1169
1170 pub fn get_recent_comments(
1171 &self,
1172 dataset_name: &DatasetFullName,
1173 filter: &CommentFilter,
1174 limit: usize,
1175 continuation: Option<&Continuation>,
1176 ) -> Result<RecentCommentsPage> {
1177 self.post::<_, _, RecentCommentsPage>(
1178 self.endpoints.recent_comments(dataset_name)?,
1179 GetRecentRequest {
1180 limit,
1181 filter,
1182 continuation,
1183 },
1184 Retry::No,
1185 )
1186 }
1187
1188 pub fn refresh_user_permissions(&self) -> Result<RefreshUserPermissionsResponse> {
1189 self.post::<_, _, RefreshUserPermissionsResponse>(
1190 self.endpoints.refresh_user_permissions()?,
1191 RefreshUserPermissionsRequest {},
1192 Retry::Yes,
1193 )
1194 }
1195
1196 pub fn get_current_user(&self) -> Result<User> {
1197 Ok(self
1198 .get::<_, GetCurrentUserResponse>(self.endpoints.current_user.clone())?
1199 .user)
1200 }
1201
1202 pub fn get_users(&self) -> Result<Vec<User>> {
1203 Ok(self
1204 .get::<_, GetAvailableUsersResponse>(self.endpoints.users.clone())?
1205 .users)
1206 }
1207
1208 pub fn create_user(&self, user: NewUser<'_>) -> Result<User> {
1209 Ok(self
1210 .put::<_, _, CreateUserResponse>(
1211 self.endpoints.users.clone(),
1212 CreateUserRequest { user },
1213 )?
1214 .user)
1215 }
1216
1217 pub fn dataset_summary(
1218 &self,
1219 dataset_name: &DatasetFullName,
1220 params: &SummaryRequestParams,
1221 ) -> Result<SummaryResponse> {
1222 self.post::<_, _, SummaryResponse>(
1223 self.endpoints.dataset_summary(dataset_name)?,
1224 serde_json::to_value(params).expect("summary params serialization error"),
1225 Retry::Yes,
1226 )
1227 }
1228
1229 pub fn query_dataset_csv(
1230 &self,
1231 dataset_name: &DatasetFullName,
1232 params: &QueryRequestParams,
1233 ) -> Result<String> {
1234 let response = self
1235 .raw_request(
1236 &Method::POST,
1237 &self.endpoints.query_dataset(dataset_name)?,
1238 &Some(serde_json::to_value(params).expect("query params serialization error")),
1239 &None::<()>,
1240 &Retry::Yes,
1241 Some(HeaderValue::from_str("text/csv").expect("Could not parse csv header")),
1242 )?
1243 .text()
1244 .expect("Could not get csv text");
1245
1246 Ok(response)
1247 }
1248
1249 pub fn query_dataset(
1250 &self,
1251 dataset_name: &DatasetFullName,
1252 params: &QueryRequestParams,
1253 ) -> Result<QueryResponse> {
1254 self.post::<_, _, QueryResponse>(
1255 self.endpoints.query_dataset(dataset_name)?,
1256 serde_json::to_value(params).expect("query params serialization error"),
1257 Retry::Yes,
1258 )
1259 }
1260
1261 pub fn send_welcome_email(&self, user_id: UserId) -> Result<()> {
1262 self.post::<_, _, WelcomeEmailResponse>(
1263 self.endpoints.welcome_email(&user_id)?,
1264 json!({}),
1265 Retry::No,
1266 )?;
1267 Ok(())
1268 }
1269
1270 pub fn get_bucket_statistics(&self, bucket_name: &BucketFullName) -> Result<BucketStatistics> {
1271 Ok(self
1272 .get::<_, GetBucketStatisticsResponse>(self.endpoints.bucket_statistics(bucket_name)?)?
1273 .statistics)
1274 }
1275
1276 pub fn get_dataset_statistics(
1277 &self,
1278 dataset_name: &DatasetFullName,
1279 params: &DatasetStatisticsRequestParams,
1280 ) -> Result<CommentStatistics> {
1281 Ok(self
1282 .post::<_, _, GetStatisticsResponse>(
1283 self.endpoints.dataset_statistics(dataset_name)?,
1284 serde_json::to_value(params)
1285 .expect("dataset statistics params serialization error"),
1286 Retry::No,
1287 )?
1288 .statistics)
1289 }
1290
1291 pub fn get_source_statistics(
1292 &self,
1293 source_name: &SourceFullName,
1294 params: &SourceStatisticsRequestParams,
1295 ) -> Result<CommentStatistics> {
1296 Ok(self
1297 .post::<_, _, GetStatisticsResponse>(
1298 self.endpoints.source_statistics(source_name)?,
1299 serde_json::to_value(params).expect("source statistics params serialization error"),
1300 Retry::No,
1301 )?
1302 .statistics)
1303 }
1304
1305 pub fn create_bucket(
1307 &self,
1308 bucket_name: &BucketFullName,
1309 options: NewBucket<'_>,
1310 ) -> Result<Bucket> {
1311 Ok(self
1312 .put::<_, _, CreateBucketResponse>(
1313 self.endpoints.bucket_by_name(bucket_name)?,
1314 CreateBucketRequest { bucket: options },
1315 )?
1316 .bucket)
1317 }
1318
1319 pub fn get_buckets(&self) -> Result<Vec<Bucket>> {
1320 Ok(self
1321 .get::<_, GetAvailableBucketsResponse>(self.endpoints.buckets.clone())?
1322 .buckets)
1323 }
1324
1325 pub fn get_bucket<IdentifierT>(&self, bucket: IdentifierT) -> Result<Bucket>
1326 where
1327 IdentifierT: Into<BucketIdentifier>,
1328 {
1329 Ok(match bucket.into() {
1330 BucketIdentifier::Id(bucket_id) => {
1331 self.get::<_, GetBucketResponse>(self.endpoints.bucket_by_id(&bucket_id)?)?
1332 .bucket
1333 }
1334 BucketIdentifier::FullName(bucket_name) => {
1335 self.get::<_, GetBucketResponse>(self.endpoints.bucket_by_name(&bucket_name)?)?
1336 .bucket
1337 }
1338 })
1339 }
1340
1341 pub fn delete_bucket<IdentifierT>(&self, bucket: IdentifierT) -> Result<()>
1342 where
1343 IdentifierT: Into<BucketIdentifier>,
1344 {
1345 let bucket_id = match bucket.into() {
1346 BucketIdentifier::Id(bucket_id) => bucket_id,
1347 bucket @ BucketIdentifier::FullName(_) => self.get_bucket(bucket)?.id,
1348 };
1349 self.delete(self.endpoints.bucket_by_id(&bucket_id)?)
1350 }
1351
1352 pub fn fetch_stream_comments(
1353 &self,
1354 stream_name: &StreamFullName,
1355 size: u32,
1356 ) -> Result<StreamBatch> {
1357 self.post(
1358 self.endpoints.stream_fetch(stream_name)?,
1359 StreamFetchRequest { size },
1360 Retry::No,
1361 )
1362 }
1363
1364 pub fn get_stream(&self, stream_name: &StreamFullName) -> Result<Stream> {
1365 Ok(self
1366 .get::<_, GetStreamResponse>(self.endpoints.stream(stream_name)?)?
1367 .stream)
1368 }
1369
1370 pub fn advance_stream(
1371 &self,
1372 stream_name: &StreamFullName,
1373 sequence_id: StreamSequenceId,
1374 ) -> Result<()> {
1375 self.post::<_, _, serde::de::IgnoredAny>(
1376 self.endpoints.stream_advance(stream_name)?,
1377 StreamAdvanceRequest { sequence_id },
1378 Retry::No,
1379 )?;
1380 Ok(())
1381 }
1382
1383 pub fn reset_stream(
1384 &self,
1385 stream_name: &StreamFullName,
1386 to_comment_created_at: DateTime<Utc>,
1387 ) -> Result<()> {
1388 self.post::<_, _, serde::de::IgnoredAny>(
1389 self.endpoints.stream_reset(stream_name)?,
1390 StreamResetRequest {
1391 to_comment_created_at,
1392 },
1393 Retry::No,
1394 )?;
1395 Ok(())
1396 }
1397
1398 pub fn tag_stream_exceptions(
1399 &self,
1400 stream_name: &StreamFullName,
1401 exceptions: &[StreamException],
1402 ) -> Result<()> {
1403 self.put::<_, _, serde::de::IgnoredAny>(
1404 self.endpoints.stream_exceptions(stream_name)?,
1405 TagStreamExceptionsRequest { exceptions },
1406 )?;
1407 Ok(())
1408 }
1409
1410 pub fn get_project(&self, project_name: &ProjectName) -> Result<Project> {
1412 let response =
1413 self.get::<_, GetProjectResponse>(self.endpoints.project_by_name(project_name)?)?;
1414 Ok(response.project)
1415 }
1416
1417 pub fn get_projects(&self) -> Result<Vec<Project>> {
1419 let response = self.get::<_, GetProjectsResponse>(self.endpoints.projects.clone())?;
1420 Ok(response.projects)
1421 }
1422
1423 pub fn create_project(
1425 &self,
1426 project_name: &ProjectName,
1427 options: NewProject,
1428 user_ids: &[UserId],
1429 ) -> Result<Project> {
1430 Ok(self
1431 .put::<_, _, CreateProjectResponse>(
1432 self.endpoints.project_by_name(project_name)?,
1433 CreateProjectRequest {
1434 project: options,
1435 user_ids,
1436 },
1437 )?
1438 .project)
1439 }
1440
1441 pub fn update_project(
1443 &self,
1444 project_name: &ProjectName,
1445 options: UpdateProject,
1446 ) -> Result<Project> {
1447 Ok(self
1448 .post::<_, _, UpdateProjectResponse>(
1449 self.endpoints.project_by_name(project_name)?,
1450 UpdateProjectRequest { project: options },
1451 Retry::Yes,
1452 )?
1453 .project)
1454 }
1455
1456 pub fn delete_project(
1458 &self,
1459 project_name: &ProjectName,
1460 force_delete: ForceDeleteProject,
1461 ) -> Result<()> {
1462 let endpoint = self.endpoints.project_by_name(project_name)?;
1463 match force_delete {
1464 ForceDeleteProject::No => self.delete(endpoint)?,
1465 ForceDeleteProject::Yes => {
1466 self.delete_query(endpoint, Some(&json!({ "force": true })))?
1467 }
1468 };
1469 Ok(())
1470 }
1471
1472 fn get<LocationT, SuccessT>(&self, url: LocationT) -> Result<SuccessT>
1473 where
1474 LocationT: IntoUrl + Display + Clone,
1475 for<'de> SuccessT: Deserialize<'de>,
1476 {
1477 self.request(&Method::GET, &url, &None::<()>, &None::<()>, &Retry::Yes)
1478 }
1479
1480 fn get_query<LocationT, QueryT, SuccessT>(
1481 &self,
1482 url: LocationT,
1483 query: Option<&QueryT>,
1484 ) -> Result<SuccessT>
1485 where
1486 LocationT: IntoUrl + Display + Clone,
1487 QueryT: Serialize,
1488 for<'de> SuccessT: Deserialize<'de>,
1489 {
1490 self.request(&Method::GET, &url, &None::<()>, &Some(query), &Retry::Yes)
1491 }
1492
1493 fn delete<LocationT>(&self, url: LocationT) -> Result<()>
1494 where
1495 LocationT: IntoUrl + Display + Clone,
1496 {
1497 self.delete_query::<LocationT, ()>(url, None)
1498 }
1499
1500 fn delete_query<LocationT, QueryT>(&self, url: LocationT, query: Option<&QueryT>) -> Result<()>
1501 where
1502 LocationT: IntoUrl + Display + Clone,
1503 QueryT: Serialize,
1504 {
1505 debug!("Attempting DELETE `{url}`");
1506
1507 let attempts = Cell::new(0);
1508 let http_response = self
1509 .with_retries(|| {
1510 attempts.set(attempts.get() + 1);
1511
1512 let mut request = self
1513 .http_client
1514 .delete(url.clone())
1515 .headers(self.headers.clone());
1516 if let Some(query) = query {
1517 request = request.query(query);
1518 }
1519 request.send()
1520 })
1521 .map_err(|source| Error::ReqwestError {
1522 source,
1523 message: "DELETE operation failed.".to_owned(),
1524 })?;
1525 let status = http_response.status();
1526 http_response
1527 .json::<Response<EmptySuccess>>()
1528 .map_err(Error::BadJsonResponse)?
1529 .into_result(status)
1530 .map_or_else(
1531 |error| {
1534 if attempts.get() > 1 && status == reqwest::StatusCode::NOT_FOUND {
1535 Ok(())
1536 } else {
1537 Err(error)
1538 }
1539 },
1540 |_| Ok(()),
1541 )
1542 }
1543
1544 fn post<LocationT, RequestT, SuccessT>(
1545 &self,
1546 url: LocationT,
1547 request: RequestT,
1548 retry: Retry,
1549 ) -> Result<SuccessT>
1550 where
1551 LocationT: IntoUrl + Display + Clone,
1552 RequestT: Serialize,
1553 for<'de> SuccessT: Deserialize<'de>,
1554 {
1555 self.request(&Method::POST, &url, &Some(request), &None::<()>, &retry)
1556 }
1557
1558 fn put<LocationT, RequestT, SuccessT>(
1559 &self,
1560 url: LocationT,
1561 request: RequestT,
1562 ) -> Result<SuccessT>
1563 where
1564 LocationT: IntoUrl + Display + Clone,
1565 RequestT: Serialize,
1566 for<'de> SuccessT: Deserialize<'de>,
1567 {
1568 self.request(&Method::PUT, &url, &Some(request), &None::<()>, &Retry::Yes)
1569 }
1570
1571 fn raw_request<LocationT, RequestT, QueryT>(
1572 &self,
1573 method: &Method,
1574 url: &LocationT,
1575 body: &Option<RequestT>,
1576 query: &Option<QueryT>,
1577 retry: &Retry,
1578 accept_header: Option<HeaderValue>,
1579 ) -> Result<reqwest::blocking::Response>
1580 where
1581 LocationT: IntoUrl + Display + Clone,
1582 RequestT: Serialize,
1583 QueryT: Serialize,
1584 {
1585 let mut headers = self.headers.clone();
1586
1587 if let Some(accept_header) = accept_header {
1588 headers.insert(ACCEPT, accept_header);
1589 }
1590
1591 let do_request = || {
1592 let request = self
1593 .http_client
1594 .request(method.clone(), url.clone())
1595 .headers(headers.clone());
1596
1597 let request = match &query {
1598 Some(query) => request.query(query),
1599 None => request,
1600 };
1601 let request = match &body {
1602 Some(body) => request.json(body),
1603 None => request,
1604 };
1605 request.send()
1606 };
1607
1608 let result = match retry {
1609 Retry::Yes => self.with_retries(do_request),
1610 Retry::No => do_request(),
1611 };
1612 let http_response = result.map_err(|source| Error::ReqwestError {
1613 source,
1614 message: format!("{method} operation failed."),
1615 })?;
1616
1617 Ok(http_response)
1618 }
1619
1620 fn splitable_request<LocationT, RequestT, SuccessT, QueryT>(
1621 &self,
1622 method: Method,
1623 url: LocationT,
1624 body: RequestT,
1625 query: Option<QueryT>,
1626 retry: Retry,
1627 ) -> SplitableRequestResponse<SuccessT>
1628 where
1629 LocationT: IntoUrl + Display + Clone,
1630 RequestT: Serialize + SplittableRequest + Clone,
1631 QueryT: Serialize + Clone,
1632 for<'de> SuccessT: Deserialize<'de> + ReducibleResponse + Clone + Default,
1633 {
1634 debug!("Attempting {method} `{url}`");
1635 let result: Result<SuccessT> =
1636 self.request(&method, &url, &Some(body.clone()), &query, &retry);
1637
1638 match result {
1639 Ok(response) => SplitableRequestResponse {
1640 response,
1641 num_failed: 0,
1642 },
1643 Err(_) => {
1644 let mut num_failed = 0;
1645 let response = body
1646 .split()
1647 .filter_map(|request| {
1648 match self.request(&method, &url, &Some(request), &query, &retry) {
1649 Ok(response) => Some(response),
1650 Err(err) => {
1651 debug!("{err}");
1652 num_failed += 1;
1653 None
1654 }
1655 }
1656 })
1657 .fold(SuccessT::empty(), |merged, next: SuccessT| {
1658 merged.merge(next)
1659 });
1660
1661 SplitableRequestResponse {
1662 num_failed,
1663 response,
1664 }
1665 }
1666 }
1667 }
1668
1669 fn request<LocationT, RequestT, SuccessT, QueryT>(
1670 &self,
1671 method: &Method,
1672 url: &LocationT,
1673 body: &Option<RequestT>,
1674 query: &Option<QueryT>,
1675 retry: &Retry,
1676 ) -> Result<SuccessT>
1677 where
1678 LocationT: IntoUrl + Display + Clone,
1679 RequestT: Serialize,
1680 QueryT: Serialize + Clone,
1681 for<'de> SuccessT: Deserialize<'de>,
1682 {
1683 debug!("Attempting {method} `{url}`");
1684 let http_response = self.raw_request(method, url, body, query, retry, None)?;
1685
1686 let status = http_response.status();
1687
1688 let response_text = http_response.text().map_err(|source| Error::ReqwestError {
1689 message: "Could not get request text".to_string(),
1690 source,
1691 })?;
1692
1693 let mut deserializer = serde_json::Deserializer::from_str(&response_text);
1694 deserializer.disable_recursion_limit();
1695
1696 Response::<SuccessT>::deserialize(&mut deserializer)
1697 .map_err(Error::BadSerdeJsonResponse)?
1698 .into_result(status)
1699 }
1700
1701 fn with_retries(
1702 &self,
1703 send_request: impl Fn() -> ReqwestResult<HttpResponse>,
1704 ) -> ReqwestResult<HttpResponse> {
1705 match &self.retrier {
1706 Some(retrier) => retrier.with_retries(send_request),
1707 None => send_request(),
1708 }
1709 }
1710}
1711
1712#[derive(Copy, Clone)]
1713enum Retry {
1714 Yes,
1715 No,
1716}
1717
1718pub struct DatasetQueryIter<'a> {
1719 client: &'a Client,
1720 dataset_name: &'a DatasetFullName,
1721 done: bool,
1722 params: &'a mut QueryRequestParams,
1723}
1724
1725impl<'a> DatasetQueryIter<'a> {
1726 fn new(
1727 client: &'a Client,
1728 dataset_name: &'a DatasetFullName,
1729 params: &'a mut QueryRequestParams,
1730 ) -> Self {
1731 Self {
1732 client,
1733 dataset_name,
1734 done: false,
1735 params,
1736 }
1737 }
1738}
1739
1740impl Iterator for DatasetQueryIter<'_> {
1741 type Item = Result<Vec<AnnotatedComment>>;
1742
1743 fn next(&mut self) -> Option<Self::Item> {
1744 if self.done {
1745 return None;
1746 }
1747
1748 let response = self.client.query_dataset(self.dataset_name, self.params);
1749 Some(response.map(|page| {
1750 self.params.continuation = page.continuation;
1751 self.done = self.params.continuation.is_none();
1752 page.results
1753 }))
1754 }
1755}
1756
1757pub enum ContinuationKind {
1758 Timestamp(DateTime<Utc>),
1759 Continuation(Continuation),
1760}
1761
1762pub struct EmailsIter<'a> {
1763 client: &'a Client,
1764 bucket_name: &'a BucketFullName,
1765 continuation: Option<EmailContinuation>,
1766 done: bool,
1767 page_size: usize,
1768}
1769
1770impl<'a> EmailsIter<'a> {
1771 pub const DEFAULT_PAGE_SIZE: usize = 64;
1773 pub const MAX_PAGE_SIZE: usize = 256;
1775
1776 fn new(client: &'a Client, bucket_name: &'a BucketFullName, page_size: Option<usize>) -> Self {
1777 Self {
1778 client,
1779 bucket_name,
1780 continuation: None,
1781 done: false,
1782 page_size: page_size.unwrap_or(Self::DEFAULT_PAGE_SIZE),
1783 }
1784 }
1785}
1786
1787impl Iterator for EmailsIter<'_> {
1788 type Item = Result<Vec<Email>>;
1789
1790 fn next(&mut self) -> Option<Self::Item> {
1791 if self.done {
1792 return None;
1793 }
1794 let response = self.client.get_emails_iter_page(
1795 self.bucket_name,
1796 self.continuation.as_ref(),
1797 self.page_size,
1798 );
1799 Some(response.map(|page| {
1800 self.continuation = page.continuation;
1801 self.done = self.continuation.is_none();
1802 page.emails
1803 }))
1804 }
1805}
1806
1807#[derive(Debug, Default)]
1808pub struct EmailsQueryFilter {
1809 pub from_timestamp: Option<DateTime<Utc>>,
1810 pub to_timestamp: Option<DateTime<Utc>>,
1811 pub mailbox_name: Option<String>,
1812}
1813
1814impl EmailsQueryFilter {
1815 pub fn is_empty(&self) -> bool {
1816 self.from_timestamp.is_none() && self.to_timestamp.is_none() && self.mailbox_name.is_none()
1817 }
1818}
1819
1820pub struct EmailsQueryIter<'a> {
1821 client: &'a Client,
1822 bucket_name: &'a BucketFullName,
1823 filter: EmailsQueryFilter,
1824 continuation: Option<EmailContinuation>,
1825 done: bool,
1826 page_size: usize,
1827}
1828
1829impl<'a> EmailsQueryIter<'a> {
1830 pub const DEFAULT_PAGE_SIZE: usize = 64;
1832
1833 fn new(
1834 client: &'a Client,
1835 bucket_name: &'a BucketFullName,
1836 filter: EmailsQueryFilter,
1837 page_size: Option<usize>,
1838 ) -> Self {
1839 Self {
1840 client,
1841 bucket_name,
1842 filter,
1843 continuation: None,
1844 done: false,
1845 page_size: page_size.unwrap_or(Self::DEFAULT_PAGE_SIZE),
1846 }
1847 }
1848}
1849
1850impl Iterator for EmailsQueryIter<'_> {
1851 type Item = Result<Vec<Email>>;
1852
1853 fn next(&mut self) -> Option<Self::Item> {
1854 if self.done {
1855 return None;
1856 }
1857 let response = self.client.query_emails_iter_page(
1858 self.bucket_name,
1859 &self.filter,
1860 self.continuation.as_ref(),
1861 self.page_size,
1862 );
1863 Some(response.map(|page| {
1864 self.continuation = page.continuation;
1865 self.done = !page.more_results;
1869 page.emails
1870 }))
1871 }
1872}
1873
1874pub struct CommentsIter<'a> {
1875 client: &'a Client,
1876 source_name: &'a SourceFullName,
1877 continuation: Option<ContinuationKind>,
1878 done: bool,
1879 page_size: usize,
1880 to_timestamp: Option<DateTime<Utc>>,
1881}
1882
1883#[derive(Debug, Default)]
1884pub struct CommentsIterTimerange {
1885 pub from: Option<DateTime<Utc>>,
1886 pub to: Option<DateTime<Utc>>,
1887}
1888impl<'a> CommentsIter<'a> {
1889 pub const DEFAULT_PAGE_SIZE: usize = 64;
1891 pub const MAX_PAGE_SIZE: usize = 256;
1893
1894 fn new(
1895 client: &'a Client,
1896 source_name: &'a SourceFullName,
1897 page_size: Option<usize>,
1898 timerange: CommentsIterTimerange,
1899 ) -> Self {
1900 let (from_timestamp, to_timestamp) = (timerange.from, timerange.to);
1901 Self {
1902 client,
1903 source_name,
1904 to_timestamp,
1905 continuation: from_timestamp.map(ContinuationKind::Timestamp),
1906 done: false,
1907 page_size: page_size.unwrap_or(Self::DEFAULT_PAGE_SIZE),
1908 }
1909 }
1910}
1911
1912impl Iterator for CommentsIter<'_> {
1913 type Item = Result<Vec<Comment>>;
1914
1915 fn next(&mut self) -> Option<Self::Item> {
1916 if self.done {
1917 return None;
1918 }
1919 let response = self.client.get_comments_iter_page(
1920 self.source_name,
1921 self.continuation.as_ref(),
1922 self.to_timestamp,
1923 self.page_size,
1924 );
1925 Some(response.map(|page| {
1926 self.continuation = page.continuation.map(ContinuationKind::Continuation);
1927 self.done = self.continuation.is_none();
1928 page.comments
1929 }))
1930 }
1931}
1932
1933pub struct LabellingsIter<'a> {
1934 client: &'a Client,
1935 dataset_name: &'a DatasetFullName,
1936 source_id: &'a SourceId,
1937 return_predictions: bool,
1938 after: Option<GetLabellingsAfter>,
1939 limit: Option<usize>,
1940 done: bool,
1941}
1942
1943impl<'a> LabellingsIter<'a> {
1944 fn new(
1945 client: &'a Client,
1946 dataset_name: &'a DatasetFullName,
1947 source_id: &'a SourceId,
1948 return_predictions: bool,
1949 limit: Option<usize>,
1950 ) -> Self {
1951 Self {
1952 client,
1953 dataset_name,
1954 source_id,
1955 return_predictions,
1956 after: None,
1957 limit,
1958 done: false,
1959 }
1960 }
1961}
1962
1963impl Iterator for LabellingsIter<'_> {
1964 type Item = Result<Vec<AnnotatedComment>>;
1965
1966 fn next(&mut self) -> Option<Self::Item> {
1967 if self.done {
1968 return None;
1969 }
1970 let response = self.client.get_labellings_in_bulk(
1971 self.dataset_name,
1972 GetLabellingsInBulk {
1973 source_id: self.source_id,
1974 return_predictions: &self.return_predictions,
1975 after: &self.after,
1976 limit: &self.limit,
1977 },
1978 );
1979 Some(response.map(|page| {
1980 if self.after == page.after && !page.results.is_empty() {
1981 panic!("Labellings API did not increment pagination continuation");
1982 }
1983 self.after = page.after;
1984 if page.results.is_empty() {
1985 self.done = true;
1986 }
1987 page.results
1988 }))
1989 }
1990}
1991
1992#[derive(Debug)]
1993struct Endpoints {
1994 base: Url,
1995 datasets: Url,
1996 sources: Url,
1997 buckets: Url,
1998 users: Url,
1999 current_user: Url,
2000 projects: Url,
2001}
2002
2003#[derive(Debug, Serialize, Clone, Copy)]
2004struct NoChargeQuery {
2005 no_charge: bool,
2006}
2007
2008fn construct_endpoint(base: &Url, segments: &[&str]) -> Result<Url> {
2009 let mut endpoint = base.clone();
2010
2011 let mut endpoint_segments = endpoint
2012 .path_segments_mut()
2013 .map_err(|_| Error::BadEndpoint {
2014 endpoint: base.clone(),
2015 })?;
2016
2017 for segment in segments {
2018 endpoint_segments.push(segment);
2019 }
2020
2021 drop(endpoint_segments);
2022
2023 Ok(endpoint)
2024}
2025
2026impl Endpoints {
2027 pub fn new(base: Url) -> Result<Self> {
2028 let datasets = construct_endpoint(&base, &["api", "v1", "datasets"])?;
2029 let sources = construct_endpoint(&base, &["api", "v1", "sources"])?;
2030 let buckets = construct_endpoint(&base, &["api", "_private", "buckets"])?;
2031 let users = construct_endpoint(&base, &["api", "_private", "users"])?;
2032 let current_user = construct_endpoint(&base, &["auth", "user"])?;
2033 let projects = construct_endpoint(&base, &["api", "_private", "projects"])?;
2034
2035 Ok(Endpoints {
2036 base,
2037 datasets,
2038 sources,
2039 buckets,
2040 users,
2041 current_user,
2042 projects,
2043 })
2044 }
2045
2046 fn refresh_user_permissions(&self) -> Result<Url> {
2047 construct_endpoint(&self.base, &["auth", "refresh-user-permissions"])
2048 }
2049
2050 fn label_group(
2051 &self,
2052 dataset_name: &DatasetFullName,
2053 label_group: LabelGroupName,
2054 ) -> Result<Url> {
2055 construct_endpoint(
2056 &self.base,
2057 &[
2058 "api",
2059 "_private",
2060 "datasets",
2061 &dataset_name.0,
2062 "labels",
2063 &label_group.0,
2064 ],
2065 )
2066 }
2067
2068 fn ixp_datasets(&self) -> Result<Url> {
2069 construct_endpoint(&self.base, &["api", "_private", "ixp", "datasets"])
2070 }
2071
2072 fn ixp_documents(&self, source_id: &SourceId) -> Result<Url> {
2073 construct_endpoint(
2074 &self.base,
2075 &[
2076 "api",
2077 "_private",
2078 "sources",
2079 &format!("id:{0}", source_id.0),
2080 "documents",
2081 ],
2082 )
2083 }
2084
2085 fn ixp_document(&self, source_id: &SourceId, comment_id: &CommentId) -> Result<Url> {
2086 construct_endpoint(
2087 &self.base,
2088 &[
2089 "api",
2090 "_private",
2091 "sources",
2092 &format!("id:{0}", source_id.0),
2093 "documents",
2094 &comment_id.0,
2095 ],
2096 )
2097 }
2098
2099 fn keyed_sync_states(&self, bucket_id: &BucketId) -> Result<Url> {
2100 construct_endpoint(
2101 &self.base,
2102 &[
2103 "api",
2104 "_private",
2105 "buckets",
2106 &format!("id:{}", bucket_id.0),
2107 "keyed-sync-states/",
2108 ],
2109 )
2110 }
2111
2112 fn keyed_sync_state(&self, bucket_id: &BucketId, id: &KeyedSyncStateId) -> Result<Url> {
2113 construct_endpoint(
2114 &self.base,
2115 &[
2116 "api",
2117 "_private",
2118 "buckets",
2119 &format!("id:{}", bucket_id.0),
2120 "keyed-sync-state",
2121 &id.0,
2122 ],
2123 )
2124 }
2125
2126 fn query_keyed_sync_state_ids(&self, bucket_id: &BucketId) -> Result<Url> {
2127 construct_endpoint(
2128 &self.base,
2129 &[
2130 "api",
2131 "_private",
2132 "buckets",
2133 &format!("id:{}", bucket_id.0),
2134 "keyed-sync-state-ids",
2135 ],
2136 )
2137 }
2138
2139 fn audit_events_query(&self) -> Result<Url> {
2140 construct_endpoint(&self.base, &["api", "v1", "audit_events", "query"])
2141 }
2142
2143 fn integrations(&self) -> Result<Url> {
2144 construct_endpoint(&self.base, &["api", "_private", "integrations"])
2145 }
2146
2147 fn integration(&self, name: &IntegrationFullName) -> Result<Url> {
2148 construct_endpoint(&self.base, &["api", "_private", "integrations", &name.0])
2149 }
2150
2151 fn attachment_reference(&self, reference: &AttachmentReference) -> Result<Url> {
2152 construct_endpoint(&self.base, &["api", "v1", "attachments", &reference.0])
2153 }
2154
2155 fn attachment_upload(
2156 &self,
2157 source_id: &SourceId,
2158 comment_id: &CommentId,
2159 attachment_index: usize,
2160 ) -> Result<Url> {
2161 construct_endpoint(
2162 &self.base,
2163 &[
2164 "api",
2165 "_private",
2166 "sources",
2167 &format!("id:{}", source_id.0),
2168 "comments",
2169 &comment_id.0,
2170 "attachments",
2171 &attachment_index.to_string(),
2172 ],
2173 )
2174 }
2175 fn latest_validation(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2176 construct_endpoint(
2177 &self.base,
2178 &[
2179 "api",
2180 "_private",
2181 "datasets",
2182 &dataset_name.0,
2183 "labellers",
2184 "latest",
2185 "validation",
2186 ],
2187 )
2188 }
2189
2190 fn validation(
2191 &self,
2192 dataset_name: &DatasetFullName,
2193 model_version: &ModelVersion,
2194 ) -> Result<Url> {
2195 construct_endpoint(
2196 &self.base,
2197 &[
2198 "api",
2199 "_private",
2200 "datasets",
2201 &dataset_name.0,
2202 "labellers",
2203 &model_version.0.to_string(),
2204 "validation",
2205 ],
2206 )
2207 }
2208
2209 fn label_validation(
2210 &self,
2211 dataset_name: &DatasetFullName,
2212 model_version: &ModelVersion,
2213 ) -> Result<Url> {
2214 construct_endpoint(
2215 &self.base,
2216 &[
2217 "api",
2218 "_private",
2219 "datasets",
2220 &dataset_name.0,
2221 "labellers",
2222 &model_version.0.to_string(),
2223 "label-validation",
2224 ],
2225 )
2226 }
2227 fn bucket_statistics(&self, bucket_name: &BucketFullName) -> Result<Url> {
2228 construct_endpoint(
2229 &self.base,
2230 &["api", "_private", "buckets", &bucket_name.0, "statistics"],
2231 )
2232 }
2233
2234 fn dataset_summary(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2235 construct_endpoint(
2236 &self.base,
2237 &["api", "_private", "datasets", &dataset_name.0, "summary"],
2238 )
2239 }
2240
2241 fn query_dataset(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2242 construct_endpoint(
2243 &self.base,
2244 &["api", "_private", "datasets", &dataset_name.0, "query"],
2245 )
2246 }
2247
2248 fn streams(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2249 construct_endpoint(
2250 &self.base,
2251 &["api", "v1", "datasets", &dataset_name.0, "streams"],
2252 )
2253 }
2254
2255 fn stream(&self, stream_name: &StreamFullName) -> Result<Url> {
2256 construct_endpoint(
2257 &self.base,
2258 &[
2259 "api",
2260 "v1",
2261 "datasets",
2262 &stream_name.dataset.0,
2263 "streams",
2264 &stream_name.stream.0,
2265 ],
2266 )
2267 }
2268
2269 fn stream_fetch(&self, stream_name: &StreamFullName) -> Result<Url> {
2270 construct_endpoint(
2271 &self.base,
2272 &[
2273 "api",
2274 "v1",
2275 "datasets",
2276 &stream_name.dataset.0,
2277 "streams",
2278 &stream_name.stream.0,
2279 "fetch",
2280 ],
2281 )
2282 }
2283
2284 fn stream_advance(&self, stream_name: &StreamFullName) -> Result<Url> {
2285 construct_endpoint(
2286 &self.base,
2287 &[
2288 "api",
2289 "v1",
2290 "datasets",
2291 &stream_name.dataset.0,
2292 "streams",
2293 &stream_name.stream.0,
2294 "advance",
2295 ],
2296 )
2297 }
2298
2299 fn stream_reset(&self, stream_name: &StreamFullName) -> Result<Url> {
2300 construct_endpoint(
2301 &self.base,
2302 &[
2303 "api",
2304 "v1",
2305 "datasets",
2306 &stream_name.dataset.0,
2307 "streams",
2308 &stream_name.stream.0,
2309 "reset",
2310 ],
2311 )
2312 }
2313
2314 fn stream_exceptions(&self, stream_name: &StreamFullName) -> Result<Url> {
2315 construct_endpoint(
2316 &self.base,
2317 &[
2318 "api",
2319 "v1",
2320 "datasets",
2321 &stream_name.dataset.0,
2322 "streams",
2323 &stream_name.stream.0,
2324 "exceptions",
2325 ],
2326 )
2327 }
2328
2329 fn recent_comments(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2330 construct_endpoint(
2331 &self.base,
2332 &["api", "_private", "datasets", &dataset_name.0, "recent"],
2333 )
2334 }
2335
2336 fn dataset_statistics(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2337 construct_endpoint(
2338 &self.base,
2339 &["api", "_private", "datasets", &dataset_name.0, "statistics"],
2340 )
2341 }
2342
2343 fn source_statistics(&self, source_name: &SourceFullName) -> Result<Url> {
2344 construct_endpoint(
2345 &self.base,
2346 &["api", "v1", "sources", &source_name.0, "statistics"],
2347 )
2348 }
2349
2350 fn user_by_id(&self, user_id: &UserId) -> Result<Url> {
2351 construct_endpoint(&self.base, &["api", "_private", "users", &user_id.0])
2352 }
2353
2354 fn source_by_id(&self, source_id: &SourceId) -> Result<Url> {
2355 construct_endpoint(
2356 &self.base,
2357 &["api", "v1", "sources", &format!("id:{}", source_id.0)],
2358 )
2359 }
2360
2361 fn source_by_name(&self, source_name: &SourceFullName) -> Result<Url> {
2362 construct_endpoint(&self.base, &["api", "v1", "sources", &source_name.0])
2363 }
2364
2365 fn quotas(&self, tenant_id: &Option<UiPathTenantId>) -> Result<Url> {
2366 if let Some(tenant_id) = tenant_id {
2367 construct_endpoint(&self.base, &["api", "_private", "quotas", &tenant_id.0])
2368 } else {
2369 construct_endpoint(&self.base, &["api", "_private", "quotas"])
2370 }
2371 }
2372
2373 fn quota(&self, tenant_id: &TenantId, tenant_quota_kind: TenantQuotaKind) -> Result<Url> {
2374 construct_endpoint(
2375 &self.base,
2376 &[
2377 "api",
2378 "_private",
2379 "quotas",
2380 &tenant_id.to_string(),
2381 &tenant_quota_kind.to_string(),
2382 ],
2383 )
2384 }
2385
2386 fn put_comments(&self, source_name: &SourceFullName) -> Result<Url> {
2387 construct_endpoint(
2388 &self.base,
2389 &["api", "_private", "sources", &source_name.0, "comments"],
2390 )
2391 }
2392
2393 fn comments(&self, source_name: &SourceFullName) -> Result<Url> {
2394 construct_endpoint(
2395 &self.base,
2396 &["api", "_private", "sources", &source_name.0, "comments"],
2397 )
2398 }
2399
2400 fn comment_by_id(&self, source_name: &SourceFullName, comment_id: &CommentId) -> Result<Url> {
2401 construct_endpoint(
2402 &self.base,
2403 &[
2404 "api",
2405 "v1",
2406 "sources",
2407 &source_name.0,
2408 "comments",
2409 &comment_id.0,
2410 ],
2411 )
2412 }
2413
2414 fn comments_v1(&self, source_name: &SourceFullName) -> Result<Url> {
2415 construct_endpoint(
2416 &self.base,
2417 &["api", "v1", "sources", &source_name.0, "comments"],
2418 )
2419 }
2420
2421 fn sync_comments(&self, source_name: &SourceFullName) -> Result<Url> {
2422 construct_endpoint(
2423 &self.base,
2424 &["api", "v1", "sources", &source_name.0, "sync"],
2425 )
2426 }
2427
2428 fn sync_comments_raw_emails(&self, source_name: &SourceFullName) -> Result<Url> {
2429 construct_endpoint(
2430 &self.base,
2431 &["api", "v1", "sources", &source_name.0, "sync-raw-emails"],
2432 )
2433 }
2434
2435 fn comment_audio(&self, source_id: &SourceId, comment_id: &CommentId) -> Result<Url> {
2436 construct_endpoint(
2437 &self.base,
2438 &[
2439 "api",
2440 "_private",
2441 "sources",
2442 &format!("id:{}", source_id.0),
2443 "comments",
2444 &comment_id.0,
2445 "audio",
2446 ],
2447 )
2448 }
2449
2450 fn get_emails(&self, bucket_name: &BucketFullName) -> Result<Url> {
2451 construct_endpoint(
2452 &self.base,
2453 &["api", "_private", "buckets", &bucket_name.0, "emails"],
2454 )
2455 }
2456
2457 fn query_emails(&self, bucket_name: &BucketFullName) -> Result<Url> {
2458 construct_endpoint(
2459 &self.base,
2460 &[
2461 "api",
2462 "_private",
2463 "buckets",
2464 &bucket_name.0,
2465 "emails",
2466 "query",
2467 ],
2468 )
2469 }
2470
2471 fn put_emails(&self, bucket_name: &BucketFullName) -> Result<Url> {
2472 construct_endpoint(
2473 &self.base,
2474 &["api", "_private", "buckets", &bucket_name.0, "emails"],
2475 )
2476 }
2477
2478 fn delete_emails(&self, bucket_name: &BucketFullName) -> Result<Url> {
2479 construct_endpoint(
2480 &self.base,
2481 &["api", "_private", "buckets", &bucket_name.0, "emails"],
2482 )
2483 }
2484
2485 fn post_user(&self, user_id: &UserId) -> Result<Url> {
2486 construct_endpoint(&self.base, &["api", "_private", "users", &user_id.0])
2487 }
2488
2489 fn dataset_by_id(&self, dataset_id: &DatasetId) -> Result<Url> {
2490 construct_endpoint(
2491 &self.base,
2492 &["api", "v1", "datasets", &format!("id:{}", dataset_id.0)],
2493 )
2494 }
2495
2496 fn dataset_by_name(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2497 construct_endpoint(&self.base, &["api", "v1", "datasets", &dataset_name.0])
2498 }
2499
2500 fn get_labellings(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2501 construct_endpoint(
2502 &self.base,
2503 &["api", "_private", "datasets", &dataset_name.0, "labellings"],
2504 )
2505 }
2506
2507 fn labellers(&self, dataset_name: &DatasetFullName) -> Result<Url> {
2508 construct_endpoint(
2509 &self.base,
2510 &["api", "_private", "datasets", &dataset_name.0, "labellers"],
2511 )
2512 }
2513
2514 fn get_comment_predictions(
2515 &self,
2516 dataset_name: &DatasetFullName,
2517 model_version: &ModelVersion,
2518 ) -> Result<Url> {
2519 construct_endpoint(
2520 &self.base,
2521 &[
2522 "api",
2523 "v1",
2524 "datasets",
2525 &dataset_name.0,
2526 "labellers",
2527 &model_version.0.to_string(),
2528 "predict-comments",
2529 ],
2530 )
2531 }
2532
2533 fn post_labelling(
2534 &self,
2535 dataset_name: &DatasetFullName,
2536 comment_uid: &CommentUid,
2537 ) -> Result<Url> {
2538 construct_endpoint(
2539 &self.base,
2540 &[
2541 "api",
2542 "_private",
2543 "datasets",
2544 &dataset_name.0,
2545 "labellings",
2546 &comment_uid.0,
2547 ],
2548 )
2549 }
2550
2551 fn bucket_by_id(&self, bucket_id: &BucketId) -> Result<Url> {
2552 construct_endpoint(
2553 &self.base,
2554 &["api", "_private", "buckets", &format!("id:{}", bucket_id.0)],
2555 )
2556 }
2557
2558 fn bucket_by_name(&self, bucket_name: &BucketFullName) -> Result<Url> {
2559 construct_endpoint(&self.base, &["api", "_private", "buckets", &bucket_name.0])
2560 }
2561
2562 fn project_by_name(&self, project_name: &ProjectName) -> Result<Url> {
2563 construct_endpoint(
2564 &self.base,
2565 &["api", "_private", "projects", &project_name.0],
2566 )
2567 }
2568
2569 fn welcome_email(&self, user_id: &UserId) -> Result<Url> {
2570 construct_endpoint(
2571 &self.base,
2572 &["api", "_private", "users", &user_id.0, "welcome-email"],
2573 )
2574 }
2575}
2576
2577const DEFAULT_HTTP_TIMEOUT_SECONDS: u64 = 240;
2578
2579fn build_http_client(config: &Config) -> Result<HttpClient> {
2580 let mut builder = HttpClient::builder()
2581 .gzip(true)
2582 .danger_accept_invalid_certs(config.accept_invalid_certificates)
2583 .timeout(Some(Duration::from_secs(DEFAULT_HTTP_TIMEOUT_SECONDS)));
2584
2585 if let Some(proxy) = config.proxy.clone() {
2586 builder = builder.proxy(Proxy::all(proxy).map_err(Error::BuildHttpClient)?);
2587 }
2588 builder.build().map_err(Error::BuildHttpClient)
2589}
2590
2591fn build_headers(config: &Config) -> Result<HeaderMap> {
2592 let mut headers = HeaderMap::new();
2593 headers.insert(
2594 header::AUTHORIZATION,
2595 HeaderValue::from_str(&format!("Bearer {}", config.token.0)).map_err(|_| {
2596 Error::BadToken {
2597 token: config.token.0.clone(),
2598 }
2599 })?,
2600 );
2601 Ok(headers)
2602}
2603
2604fn id_list_query<'a>(ids: impl Iterator<Item = &'a String>) -> Vec<(&'static str, &'a str)> {
2605 ids.map(|id| ("id", id.as_str())).collect()
2609}
2610
2611pub static DEFAULT_ENDPOINT: Lazy<Url> =
2612 Lazy::new(|| Url::parse("https://reinfer.dev").expect("Default URL is well-formed"));
2613
2614#[cfg(test)]
2615mod tests {
2616 use super::*;
2617
2618 #[test]
2619 fn test_construct_endpoint() {
2620 let url = construct_endpoint(
2621 &Url::parse("https://cloud.uipath.com/org/tenant/reinfer_").unwrap(),
2622 &["api", "v1", "sources", "project", "source", "sync"],
2623 )
2624 .unwrap();
2625
2626 assert_eq!(
2627 url.to_string(),
2628 "https://cloud.uipath.com/org/tenant/reinfer_/api/v1/sources/project/source/sync"
2629 )
2630 }
2631
2632 #[test]
2633 fn test_id_list_query() {
2634 assert_eq!(id_list_query(Vec::new().iter()), Vec::new());
2635 assert_eq!(
2636 id_list_query(["foo".to_owned()].iter()),
2637 vec![("id", "foo")]
2638 );
2639 assert_eq!(
2640 id_list_query(
2641 [
2642 "Stream".to_owned(),
2643 "River".to_owned(),
2644 "Waterfall".to_owned()
2645 ]
2646 .iter()
2647 ),
2648 [("id", "Stream"), ("id", "River"), ("id", "Waterfall"),]
2649 );
2650 }
2651}