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 invited_channels: Vec<Channel>,
621
622 pub observed_buffer_versions: Vec<proto::ChannelBufferVersion>,
623 pub observed_channel_messages: Vec<proto::ChannelMessageId>,
624 pub latest_buffer_versions: Vec<proto::ChannelBufferVersion>,
625 pub latest_channel_messages: Vec<proto::ChannelMessageId>,
626}
627
628#[derive(Debug)]
629pub struct RejoinedChannelBuffer {
630 pub buffer: proto::RejoinedChannelBuffer,
631 pub old_connection_id: ConnectionId,
632}
633
634#[derive(Clone)]
635pub struct JoinRoom {
636 pub room: proto::Room,
637 pub channel: Option<channel::Model>,
638}
639
640pub struct RejoinedRoom {
641 pub room: proto::Room,
642 pub rejoined_projects: Vec<RejoinedProject>,
643 pub reshared_projects: Vec<ResharedProject>,
644 pub channel: Option<channel::Model>,
645}
646
647pub struct ResharedProject {
648 pub id: ProjectId,
649 pub old_connection_id: ConnectionId,
650 pub collaborators: Vec<ProjectCollaborator>,
651 pub worktrees: Vec<proto::WorktreeMetadata>,
652}
653
654pub struct RejoinedProject {
655 pub id: ProjectId,
656 pub old_connection_id: ConnectionId,
657 pub collaborators: Vec<ProjectCollaborator>,
658 pub worktrees: Vec<RejoinedWorktree>,
659 pub language_servers: Vec<proto::LanguageServer>,
660}
661
662impl RejoinedProject {
663 pub fn to_proto(&self) -> proto::RejoinedProject {
664 proto::RejoinedProject {
665 id: self.id.to_proto(),
666 worktrees: self
667 .worktrees
668 .iter()
669 .map(|worktree| proto::WorktreeMetadata {
670 id: worktree.id,
671 root_name: worktree.root_name.clone(),
672 visible: worktree.visible,
673 abs_path: worktree.abs_path.clone(),
674 })
675 .collect(),
676 collaborators: self
677 .collaborators
678 .iter()
679 .map(|collaborator| collaborator.to_proto())
680 .collect(),
681 language_servers: self.language_servers.clone(),
682 }
683 }
684}
685
686#[derive(Debug)]
687pub struct RejoinedWorktree {
688 pub id: u64,
689 pub abs_path: String,
690 pub root_name: String,
691 pub visible: bool,
692 pub updated_entries: Vec<proto::Entry>,
693 pub removed_entries: Vec<u64>,
694 pub updated_repositories: Vec<proto::RepositoryEntry>,
695 pub removed_repositories: Vec<u64>,
696 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
697 pub settings_files: Vec<WorktreeSettingsFile>,
698 pub scan_id: u64,
699 pub completed_scan_id: u64,
700}
701
702pub struct LeftRoom {
703 pub room: proto::Room,
704 pub channel: Option<channel::Model>,
705 pub left_projects: HashMap<ProjectId, LeftProject>,
706 pub canceled_calls_to_user_ids: Vec<UserId>,
707 pub deleted: bool,
708}
709
710pub struct RefreshedRoom {
711 pub room: proto::Room,
712 pub channel: Option<channel::Model>,
713 pub stale_participant_user_ids: Vec<UserId>,
714 pub canceled_calls_to_user_ids: Vec<UserId>,
715}
716
717pub struct RefreshedChannelBuffer {
718 pub connection_ids: Vec<ConnectionId>,
719 pub collaborators: Vec<proto::Collaborator>,
720}
721
722pub struct Project {
723 pub id: ProjectId,
724 pub role: ChannelRole,
725 pub collaborators: Vec<ProjectCollaborator>,
726 pub worktrees: BTreeMap<u64, Worktree>,
727 pub language_servers: Vec<proto::LanguageServer>,
728}
729
730pub struct ProjectCollaborator {
731 pub connection_id: ConnectionId,
732 pub user_id: UserId,
733 pub replica_id: ReplicaId,
734 pub is_host: bool,
735}
736
737impl ProjectCollaborator {
738 pub fn to_proto(&self) -> proto::Collaborator {
739 proto::Collaborator {
740 peer_id: Some(self.connection_id.into()),
741 replica_id: self.replica_id.0 as u32,
742 user_id: self.user_id.to_proto(),
743 is_host: self.is_host,
744 }
745 }
746}
747
748#[derive(Debug)]
749pub struct LeftProject {
750 pub id: ProjectId,
751 pub should_unshare: bool,
752 pub connection_ids: Vec<ConnectionId>,
753}
754
755pub struct Worktree {
756 pub id: u64,
757 pub abs_path: String,
758 pub root_name: String,
759 pub visible: bool,
760 pub entries: Vec<proto::Entry>,
761 pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
762 pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
763 pub settings_files: Vec<WorktreeSettingsFile>,
764 pub scan_id: u64,
765 pub completed_scan_id: u64,
766}
767
768#[derive(Debug)]
769pub struct WorktreeSettingsFile {
770 pub path: String,
771 pub content: String,
772 pub kind: LocalSettingsKind,
773}
774
775pub struct NewExtensionVersion {
776 pub name: String,
777 pub version: semver::Version,
778 pub description: String,
779 pub authors: Vec<String>,
780 pub repository: String,
781 pub schema_version: i32,
782 pub wasm_api_version: Option<String>,
783 pub published_at: PrimitiveDateTime,
784}
785
786pub struct ExtensionVersionConstraints {
787 pub schema_versions: RangeInclusive<i32>,
788 pub wasm_api_versions: RangeInclusive<SemanticVersion>,
789}
790
791impl LocalSettingsKind {
792 pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self {
793 match proto_kind {
794 proto::LocalSettingsKind::Settings => Self::Settings,
795 proto::LocalSettingsKind::Tasks => Self::Tasks,
796 proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
797 }
798 }
799
800 pub fn to_proto(&self) -> proto::LocalSettingsKind {
801 match self {
802 Self::Settings => proto::LocalSettingsKind::Settings,
803 Self::Tasks => proto::LocalSettingsKind::Tasks,
804 Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
805 }
806 }
807}