db.rs

  1mod ids;
  2mod queries;
  3mod tables;
  4#[cfg(test)]
  5pub mod tests;
  6
  7use crate::{executor::Executor, Error, Result};
  8use anyhow::anyhow;
  9use collections::{BTreeMap, HashMap, HashSet};
 10use dashmap::DashMap;
 11use futures::StreamExt;
 12use rand::{prelude::StdRng, Rng, SeedableRng};
 13use rpc::{
 14    proto::{self},
 15    ConnectionId, ExtensionMetadata,
 16};
 17use sea_orm::{
 18    entity::prelude::*,
 19    sea_query::{Alias, Expr, OnConflict},
 20    ActiveValue, Condition, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbErr,
 21    FromQueryResult, IntoActiveModel, IsolationLevel, JoinType, QueryOrder, QuerySelect, Statement,
 22    TransactionTrait,
 23};
 24use semantic_version::SemanticVersion;
 25use serde::{Deserialize, Serialize};
 26use std::ops::RangeInclusive;
 27use std::{
 28    fmt::Write as _,
 29    future::Future,
 30    marker::PhantomData,
 31    ops::{Deref, DerefMut},
 32    rc::Rc,
 33    sync::Arc,
 34    time::Duration,
 35};
 36use time::PrimitiveDateTime;
 37use tokio::sync::{Mutex, OwnedMutexGuard};
 38use worktree_settings_file::LocalSettingsKind;
 39
 40#[cfg(test)]
 41pub use tests::TestDb;
 42
 43pub use ids::*;
 44pub use queries::billing_customers::{CreateBillingCustomerParams, UpdateBillingCustomerParams};
 45pub use queries::billing_preferences::{
 46    CreateBillingPreferencesParams, UpdateBillingPreferencesParams,
 47};
 48pub use queries::billing_subscriptions::{
 49    CreateBillingSubscriptionParams, UpdateBillingSubscriptionParams,
 50};
 51pub use queries::contributors::ContributorSelector;
 52pub use queries::processed_stripe_events::CreateProcessedStripeEventParams;
 53pub use sea_orm::ConnectOptions;
 54pub use tables::user::Model as User;
 55pub use tables::*;
 56
 57/// Database gives you a handle that lets you access the database.
 58/// It handles pooling internally.
 59pub struct Database {
 60    options: ConnectOptions,
 61    pool: DatabaseConnection,
 62    rooms: DashMap<RoomId, Arc<Mutex<()>>>,
 63    projects: DashMap<ProjectId, Arc<Mutex<()>>>,
 64    rng: Mutex<StdRng>,
 65    executor: Executor,
 66    notification_kinds_by_id: HashMap<NotificationKindId, &'static str>,
 67    notification_kinds_by_name: HashMap<String, NotificationKindId>,
 68    #[cfg(test)]
 69    runtime: Option<tokio::runtime::Runtime>,
 70}
 71
 72// The `Database` type has so many methods that its impl blocks are split into
 73// separate files in the `queries` folder.
 74impl Database {
 75    /// Connects to the database with the given options
 76    pub async fn new(options: ConnectOptions, executor: Executor) -> Result<Self> {
 77        sqlx::any::install_default_drivers();
 78        Ok(Self {
 79            options: options.clone(),
 80            pool: sea_orm::Database::connect(options).await?,
 81            rooms: DashMap::with_capacity(16384),
 82            projects: DashMap::with_capacity(16384),
 83            rng: Mutex::new(StdRng::seed_from_u64(0)),
 84            notification_kinds_by_id: HashMap::default(),
 85            notification_kinds_by_name: HashMap::default(),
 86            executor,
 87            #[cfg(test)]
 88            runtime: None,
 89        })
 90    }
 91
 92    pub fn options(&self) -> &ConnectOptions {
 93        &self.options
 94    }
 95
 96    #[cfg(test)]
 97    pub fn reset(&self) {
 98        self.rooms.clear();
 99        self.projects.clear();
100    }
101
102    /// Transaction runs things in a transaction. If you want to call other methods
103    /// and pass the transaction around you need to reborrow the transaction at each
104    /// call site with: `&*tx`.
105    pub async fn transaction<F, Fut, T>(&self, f: F) -> Result<T>
106    where
107        F: Send + Fn(TransactionHandle) -> Fut,
108        Fut: Send + Future<Output = Result<T>>,
109    {
110        let body = async {
111            let mut i = 0;
112            loop {
113                let (tx, result) = self.with_transaction(&f).await?;
114                match result {
115                    Ok(result) => match tx.commit().await.map_err(Into::into) {
116                        Ok(()) => return Ok(result),
117                        Err(error) => {
118                            if !self.retry_on_serialization_error(&error, i).await {
119                                return Err(error);
120                            }
121                        }
122                    },
123                    Err(error) => {
124                        tx.rollback().await?;
125                        if !self.retry_on_serialization_error(&error, i).await {
126                            return Err(error);
127                        }
128                    }
129                }
130                i += 1;
131            }
132        };
133
134        self.run(body).await
135    }
136
137    pub async fn weak_transaction<F, Fut, T>(&self, f: F) -> Result<T>
138    where
139        F: Send + Fn(TransactionHandle) -> Fut,
140        Fut: Send + Future<Output = Result<T>>,
141    {
142        let body = async {
143            let (tx, result) = self.with_weak_transaction(&f).await?;
144            match result {
145                Ok(result) => match tx.commit().await.map_err(Into::into) {
146                    Ok(()) => Ok(result),
147                    Err(error) => Err(error),
148                },
149                Err(error) => {
150                    tx.rollback().await?;
151                    Err(error)
152                }
153            }
154        };
155
156        self.run(body).await
157    }
158
159    /// The same as room_transaction, but if you need to only optionally return a Room.
160    async fn optional_room_transaction<F, Fut, T>(
161        &self,
162        f: F,
163    ) -> Result<Option<TransactionGuard<T>>>
164    where
165        F: Send + Fn(TransactionHandle) -> Fut,
166        Fut: Send + Future<Output = Result<Option<(RoomId, T)>>>,
167    {
168        let body = async {
169            let mut i = 0;
170            loop {
171                let (tx, result) = self.with_transaction(&f).await?;
172                match result {
173                    Ok(Some((room_id, data))) => {
174                        let lock = self.rooms.entry(room_id).or_default().clone();
175                        let _guard = lock.lock_owned().await;
176                        match tx.commit().await.map_err(Into::into) {
177                            Ok(()) => {
178                                return Ok(Some(TransactionGuard {
179                                    data,
180                                    _guard,
181                                    _not_send: PhantomData,
182                                }));
183                            }
184                            Err(error) => {
185                                if !self.retry_on_serialization_error(&error, i).await {
186                                    return Err(error);
187                                }
188                            }
189                        }
190                    }
191                    Ok(None) => match tx.commit().await.map_err(Into::into) {
192                        Ok(()) => return Ok(None),
193                        Err(error) => {
194                            if !self.retry_on_serialization_error(&error, i).await {
195                                return Err(error);
196                            }
197                        }
198                    },
199                    Err(error) => {
200                        tx.rollback().await?;
201                        if !self.retry_on_serialization_error(&error, i).await {
202                            return Err(error);
203                        }
204                    }
205                }
206                i += 1;
207            }
208        };
209
210        self.run(body).await
211    }
212
213    async fn project_transaction<F, Fut, T>(
214        &self,
215        project_id: ProjectId,
216        f: F,
217    ) -> Result<TransactionGuard<T>>
218    where
219        F: Send + Fn(TransactionHandle) -> Fut,
220        Fut: Send + Future<Output = Result<T>>,
221    {
222        let room_id = Database::room_id_for_project(self, project_id).await?;
223        let body = async {
224            let mut i = 0;
225            loop {
226                let lock = if let Some(room_id) = room_id {
227                    self.rooms.entry(room_id).or_default().clone()
228                } else {
229                    self.projects.entry(project_id).or_default().clone()
230                };
231                let _guard = lock.lock_owned().await;
232                let (tx, result) = self.with_transaction(&f).await?;
233                match result {
234                    Ok(data) => match tx.commit().await.map_err(Into::into) {
235                        Ok(()) => {
236                            return Ok(TransactionGuard {
237                                data,
238                                _guard,
239                                _not_send: PhantomData,
240                            });
241                        }
242                        Err(error) => {
243                            if !self.retry_on_serialization_error(&error, i).await {
244                                return Err(error);
245                            }
246                        }
247                    },
248                    Err(error) => {
249                        tx.rollback().await?;
250                        if !self.retry_on_serialization_error(&error, i).await {
251                            return Err(error);
252                        }
253                    }
254                }
255                i += 1;
256            }
257        };
258
259        self.run(body).await
260    }
261
262    /// room_transaction runs the block in a transaction. It returns a RoomGuard, that keeps
263    /// the database locked until it is dropped. This ensures that updates sent to clients are
264    /// properly serialized with respect to database changes.
265    async fn room_transaction<F, Fut, T>(
266        &self,
267        room_id: RoomId,
268        f: F,
269    ) -> Result<TransactionGuard<T>>
270    where
271        F: Send + Fn(TransactionHandle) -> Fut,
272        Fut: Send + Future<Output = Result<T>>,
273    {
274        let body = async {
275            let mut i = 0;
276            loop {
277                let lock = self.rooms.entry(room_id).or_default().clone();
278                let _guard = lock.lock_owned().await;
279                let (tx, result) = self.with_transaction(&f).await?;
280                match result {
281                    Ok(data) => match tx.commit().await.map_err(Into::into) {
282                        Ok(()) => {
283                            return Ok(TransactionGuard {
284                                data,
285                                _guard,
286                                _not_send: PhantomData,
287                            });
288                        }
289                        Err(error) => {
290                            if !self.retry_on_serialization_error(&error, i).await {
291                                return Err(error);
292                            }
293                        }
294                    },
295                    Err(error) => {
296                        tx.rollback().await?;
297                        if !self.retry_on_serialization_error(&error, i).await {
298                            return Err(error);
299                        }
300                    }
301                }
302                i += 1;
303            }
304        };
305
306        self.run(body).await
307    }
308
309    async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
310    where
311        F: Send + Fn(TransactionHandle) -> Fut,
312        Fut: Send + Future<Output = Result<T>>,
313    {
314        let tx = self
315            .pool
316            .begin_with_config(Some(IsolationLevel::Serializable), None)
317            .await?;
318
319        let mut tx = Arc::new(Some(tx));
320        let result = f(TransactionHandle(tx.clone())).await;
321        let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
322            return Err(anyhow!(
323                "couldn't complete transaction because it's still in use"
324            ))?;
325        };
326
327        Ok((tx, result))
328    }
329
330    async fn with_weak_transaction<F, Fut, T>(
331        &self,
332        f: &F,
333    ) -> Result<(DatabaseTransaction, Result<T>)>
334    where
335        F: Send + Fn(TransactionHandle) -> Fut,
336        Fut: Send + Future<Output = Result<T>>,
337    {
338        let tx = self
339            .pool
340            .begin_with_config(Some(IsolationLevel::ReadCommitted), None)
341            .await?;
342
343        let mut tx = Arc::new(Some(tx));
344        let result = f(TransactionHandle(tx.clone())).await;
345        let Some(tx) = Arc::get_mut(&mut tx).and_then(|tx| tx.take()) else {
346            return Err(anyhow!(
347                "couldn't complete transaction because it's still in use"
348            ))?;
349        };
350
351        Ok((tx, result))
352    }
353
354    async fn run<F, T>(&self, future: F) -> Result<T>
355    where
356        F: Future<Output = Result<T>>,
357    {
358        #[cfg(test)]
359        {
360            if let Executor::Deterministic(executor) = &self.executor {
361                executor.simulate_random_delay().await;
362            }
363
364            self.runtime.as_ref().unwrap().block_on(future)
365        }
366
367        #[cfg(not(test))]
368        {
369            future.await
370        }
371    }
372
373    async fn retry_on_serialization_error(&self, error: &Error, prev_attempt_count: usize) -> bool {
374        // If the error is due to a failure to serialize concurrent transactions, then retry
375        // this transaction after a delay. With each subsequent retry, double the delay duration.
376        // Also vary the delay randomly in order to ensure different database connections retry
377        // at different times.
378        const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.];
379        if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() {
380            let base_delay = SLEEPS[prev_attempt_count];
381            let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0);
382            log::warn!(
383                "retrying transaction after serialization error. delay: {} ms.",
384                randomized_delay
385            );
386            self.executor
387                .sleep(Duration::from_millis(randomized_delay as u64))
388                .await;
389            true
390        } else {
391            false
392        }
393    }
394}
395
396fn is_serialization_error(error: &Error) -> bool {
397    const SERIALIZATION_FAILURE_CODE: &str = "40001";
398    match error {
399        Error::Database(
400            DbErr::Exec(sea_orm::RuntimeErr::SqlxError(error))
401            | DbErr::Query(sea_orm::RuntimeErr::SqlxError(error)),
402        ) if error
403            .as_database_error()
404            .and_then(|error| error.code())
405            .as_deref()
406            == Some(SERIALIZATION_FAILURE_CODE) =>
407        {
408            true
409        }
410        _ => false,
411    }
412}
413
414/// A handle to a [`DatabaseTransaction`].
415pub struct TransactionHandle(pub(crate) Arc<Option<DatabaseTransaction>>);
416
417impl Deref for TransactionHandle {
418    type Target = DatabaseTransaction;
419
420    fn deref(&self) -> &Self::Target {
421        self.0.as_ref().as_ref().unwrap()
422    }
423}
424
425/// [`TransactionGuard`] keeps a database transaction alive until it is dropped.
426/// It wraps data that depends on the state of the database and prevents an additional
427/// transaction from starting that would invalidate that data.
428pub struct TransactionGuard<T> {
429    data: T,
430    _guard: OwnedMutexGuard<()>,
431    _not_send: PhantomData<Rc<()>>,
432}
433
434impl<T> Deref for TransactionGuard<T> {
435    type Target = T;
436
437    fn deref(&self) -> &T {
438        &self.data
439    }
440}
441
442impl<T> DerefMut for TransactionGuard<T> {
443    fn deref_mut(&mut self) -> &mut T {
444        &mut self.data
445    }
446}
447
448impl<T> TransactionGuard<T> {
449    /// Returns the inner value of the guard.
450    pub fn into_inner(self) -> T {
451        self.data
452    }
453}
454
455#[derive(Clone, Debug, PartialEq, Eq)]
456pub enum Contact {
457    Accepted { user_id: UserId, busy: bool },
458    Outgoing { user_id: UserId },
459    Incoming { user_id: UserId },
460}
461
462impl Contact {
463    pub fn user_id(&self) -> UserId {
464        match self {
465            Contact::Accepted { user_id, .. } => *user_id,
466            Contact::Outgoing { user_id } => *user_id,
467            Contact::Incoming { user_id, .. } => *user_id,
468        }
469    }
470}
471
472pub type NotificationBatch = Vec<(UserId, proto::Notification)>;
473
474pub struct CreatedChannelMessage {
475    pub message_id: MessageId,
476    pub participant_connection_ids: HashSet<ConnectionId>,
477    pub notifications: NotificationBatch,
478}
479
480pub struct UpdatedChannelMessage {
481    pub message_id: MessageId,
482    pub participant_connection_ids: Vec<ConnectionId>,
483    pub notifications: NotificationBatch,
484    pub reply_to_message_id: Option<MessageId>,
485    pub timestamp: PrimitiveDateTime,
486    pub deleted_mention_notification_ids: Vec<NotificationId>,
487    pub updated_mention_notifications: Vec<rpc::proto::Notification>,
488}
489
490#[derive(Clone, Debug, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
491pub struct Invite {
492    pub email_address: String,
493    pub email_confirmation_code: String,
494}
495
496#[derive(Clone, Debug, Deserialize)]
497pub struct NewSignup {
498    pub email_address: String,
499    pub platform_mac: bool,
500    pub platform_windows: bool,
501    pub platform_linux: bool,
502    pub editor_features: Vec<String>,
503    pub programming_languages: Vec<String>,
504    pub device_id: Option<String>,
505    pub added_to_mailing_list: bool,
506    pub created_at: Option<DateTime>,
507}
508
509#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, FromQueryResult)]
510pub struct WaitlistSummary {
511    pub count: i64,
512    pub linux_count: i64,
513    pub mac_count: i64,
514    pub windows_count: i64,
515    pub unknown_count: i64,
516}
517
518/// The parameters to create a new user.
519#[derive(Debug, Serialize, Deserialize)]
520pub struct NewUserParams {
521    pub github_login: String,
522    pub github_user_id: i32,
523}
524
525/// The result of creating a new user.
526#[derive(Debug)]
527pub struct NewUserResult {
528    pub user_id: UserId,
529    pub metrics_id: String,
530    pub inviting_user_id: Option<UserId>,
531    pub signup_device_id: Option<String>,
532}
533
534/// The result of updating a channel membership.
535#[derive(Debug)]
536pub struct MembershipUpdated {
537    pub channel_id: ChannelId,
538    pub new_channels: ChannelsForUser,
539    pub removed_channels: Vec<ChannelId>,
540}
541
542/// The result of setting a member's role.
543#[derive(Debug)]
544#[allow(clippy::large_enum_variant)]
545pub enum SetMemberRoleResult {
546    InviteUpdated(Channel),
547    MembershipUpdated(MembershipUpdated),
548}
549
550/// The result of inviting a member to a channel.
551#[derive(Debug)]
552pub struct InviteMemberResult {
553    pub channel: Channel,
554    pub notifications: NotificationBatch,
555}
556
557#[derive(Debug)]
558pub struct RespondToChannelInvite {
559    pub membership_update: Option<MembershipUpdated>,
560    pub notifications: NotificationBatch,
561}
562
563#[derive(Debug)]
564pub struct RemoveChannelMemberResult {
565    pub membership_update: MembershipUpdated,
566    pub notification_id: Option<NotificationId>,
567}
568
569#[derive(Debug, PartialEq, Eq, Hash)]
570pub struct Channel {
571    pub id: ChannelId,
572    pub name: String,
573    pub visibility: ChannelVisibility,
574    /// parent_path is the channel ids from the root to this one (not including this one)
575    pub parent_path: Vec<ChannelId>,
576}
577
578impl Channel {
579    pub fn from_model(value: channel::Model) -> Self {
580        Channel {
581            id: value.id,
582            visibility: value.visibility,
583            name: value.clone().name,
584            parent_path: value.ancestors().collect(),
585        }
586    }
587
588    pub fn to_proto(&self) -> proto::Channel {
589        proto::Channel {
590            id: self.id.to_proto(),
591            name: self.name.clone(),
592            visibility: self.visibility.into(),
593            parent_path: self.parent_path.iter().map(|c| c.to_proto()).collect(),
594        }
595    }
596}
597
598#[derive(Debug, PartialEq, Eq, Hash)]
599pub struct ChannelMember {
600    pub role: ChannelRole,
601    pub user_id: UserId,
602    pub kind: proto::channel_member::Kind,
603}
604
605impl ChannelMember {
606    pub fn to_proto(&self) -> proto::ChannelMember {
607        proto::ChannelMember {
608            role: self.role.into(),
609            user_id: self.user_id.to_proto(),
610            kind: self.kind.into(),
611        }
612    }
613}
614
615#[derive(Debug, PartialEq)]
616pub struct ChannelsForUser {
617    pub channels: Vec<Channel>,
618    pub channel_memberships: Vec<channel_member::Model>,
619    pub channel_participants: HashMap<ChannelId, Vec<UserId>>,
620    pub hosted_projects: Vec<proto::HostedProject>,
621    pub invited_channels: Vec<Channel>,
622
623    pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
624    pub observed_channel_messages: Vec<proto::ChannelMessageId>,
625    pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
626    pub latest_channel_messages: Vec<proto::ChannelMessageId>,
627}
628
629#[derive(Debug)]
630pub struct RejoinedChannelBuffer {
631    pub buffer: proto::RejoinedChannelBuffer,
632    pub old_connection_id: ConnectionId,
633}
634
635#[derive(Clone)]
636pub struct JoinRoom {
637    pub room: proto::Room,
638    pub channel: Option<channel::Model>,
639}
640
641pub struct RejoinedRoom {
642    pub room: proto::Room,
643    pub rejoined_projects: Vec<RejoinedProject>,
644    pub reshared_projects: Vec<ResharedProject>,
645    pub channel: Option<channel::Model>,
646}
647
648pub struct ResharedProject {
649    pub id: ProjectId,
650    pub old_connection_id: ConnectionId,
651    pub collaborators: Vec<ProjectCollaborator>,
652    pub worktrees: Vec<proto::WorktreeMetadata>,
653}
654
655pub struct RejoinedProject {
656    pub id: ProjectId,
657    pub old_connection_id: ConnectionId,
658    pub collaborators: Vec<ProjectCollaborator>,
659    pub worktrees: Vec<RejoinedWorktree>,
660    pub language_servers: Vec<proto::LanguageServer>,
661}
662
663impl RejoinedProject {
664    pub fn to_proto(&self) -> proto::RejoinedProject {
665        proto::RejoinedProject {
666            id: self.id.to_proto(),
667            worktrees: self
668                .worktrees
669                .iter()
670                .map(|worktree| proto::WorktreeMetadata {
671                    id: worktree.id,
672                    root_name: worktree.root_name.clone(),
673                    visible: worktree.visible,
674                    abs_path: worktree.abs_path.clone(),
675                })
676                .collect(),
677            collaborators: self
678                .collaborators
679                .iter()
680                .map(|collaborator| collaborator.to_proto())
681                .collect(),
682            language_servers: self.language_servers.clone(),
683        }
684    }
685}
686
687#[derive(Debug)]
688pub struct RejoinedWorktree {
689    pub id: u64,
690    pub abs_path: String,
691    pub root_name: String,
692    pub visible: bool,
693    pub updated_entries: Vec<proto::Entry>,
694    pub removed_entries: Vec<u64>,
695    pub updated_repositories: Vec<proto::RepositoryEntry>,
696    pub removed_repositories: Vec<u64>,
697    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
698    pub settings_files: Vec<WorktreeSettingsFile>,
699    pub scan_id: u64,
700    pub completed_scan_id: u64,
701}
702
703pub struct LeftRoom {
704    pub room: proto::Room,
705    pub channel: Option<channel::Model>,
706    pub left_projects: HashMap<ProjectId, LeftProject>,
707    pub canceled_calls_to_user_ids: Vec<UserId>,
708    pub deleted: bool,
709}
710
711pub struct RefreshedRoom {
712    pub room: proto::Room,
713    pub channel: Option<channel::Model>,
714    pub stale_participant_user_ids: Vec<UserId>,
715    pub canceled_calls_to_user_ids: Vec<UserId>,
716}
717
718pub struct RefreshedChannelBuffer {
719    pub connection_ids: Vec<ConnectionId>,
720    pub collaborators: Vec<proto::Collaborator>,
721}
722
723pub struct Project {
724    pub id: ProjectId,
725    pub role: ChannelRole,
726    pub collaborators: Vec<ProjectCollaborator>,
727    pub worktrees: BTreeMap<u64, Worktree>,
728    pub language_servers: Vec<proto::LanguageServer>,
729    pub dev_server_project_id: Option<DevServerProjectId>,
730}
731
732pub struct ProjectCollaborator {
733    pub connection_id: ConnectionId,
734    pub user_id: UserId,
735    pub replica_id: ReplicaId,
736    pub is_host: bool,
737}
738
739impl ProjectCollaborator {
740    pub fn to_proto(&self) -> proto::Collaborator {
741        proto::Collaborator {
742            peer_id: Some(self.connection_id.into()),
743            replica_id: self.replica_id.0 as u32,
744            user_id: self.user_id.to_proto(),
745        }
746    }
747}
748
749#[derive(Debug)]
750pub struct LeftProject {
751    pub id: ProjectId,
752    pub should_unshare: bool,
753    pub connection_ids: Vec<ConnectionId>,
754}
755
756pub struct Worktree {
757    pub id: u64,
758    pub abs_path: String,
759    pub root_name: String,
760    pub visible: bool,
761    pub entries: Vec<proto::Entry>,
762    pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
763    pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
764    pub settings_files: Vec<WorktreeSettingsFile>,
765    pub scan_id: u64,
766    pub completed_scan_id: u64,
767}
768
769#[derive(Debug)]
770pub struct WorktreeSettingsFile {
771    pub path: String,
772    pub content: String,
773    pub kind: LocalSettingsKind,
774}
775
776pub struct NewExtensionVersion {
777    pub name: String,
778    pub version: semver::Version,
779    pub description: String,
780    pub authors: Vec<String>,
781    pub repository: String,
782    pub schema_version: i32,
783    pub wasm_api_version: Option<String>,
784    pub published_at: PrimitiveDateTime,
785}
786
787pub struct ExtensionVersionConstraints {
788    pub schema_versions: RangeInclusive<i32>,
789    pub wasm_api_versions: RangeInclusive<SemanticVersion>,
790}
791
792impl LocalSettingsKind {
793    pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self {
794        match proto_kind {
795            proto::LocalSettingsKind::Settings => Self::Settings,
796            proto::LocalSettingsKind::Tasks => Self::Tasks,
797            proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
798        }
799    }
800
801    pub fn to_proto(&self) -> proto::LocalSettingsKind {
802        match self {
803            Self::Settings => proto::LocalSettingsKind::Settings,
804            Self::Tasks => proto::LocalSettingsKind::Tasks,
805            Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
806        }
807    }
808}