1use serde::{Deserialize, Serialize};
27use std::fmt;
28
29#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41#[non_exhaustive]
42pub enum PluginCapability {
43 Middleware,
48
49 Models,
57
58 Commands,
63
64 ViewSets,
69
70 Signals,
75
76 Services,
81
82 Auth,
87
88 Templates,
93
94 StaticFiles,
99
100 Routing,
105
106 SignalReceivers,
111
112 Handlers,
117
118 NetworkAccess,
123
124 DatabaseAccess,
129
130 StaticSiteGeneration,
139
140 FrontendSsr,
150
151 FrontendHydration,
156
157 TypeScriptRuntime,
164
165 BuildToolIntegration,
170
171 HotModuleReplacement,
176}
177
178impl PluginCapability {
179 pub fn all() -> &'static [Self] {
181 &[
182 Self::Middleware,
183 Self::Models,
184 Self::Commands,
185 Self::ViewSets,
186 Self::Signals,
187 Self::Services,
188 Self::Auth,
189 Self::Templates,
190 Self::StaticFiles,
191 Self::Routing,
192 Self::SignalReceivers,
193 Self::Handlers,
194 Self::NetworkAccess,
195 Self::DatabaseAccess,
196 Self::StaticSiteGeneration,
197 Self::FrontendSsr,
198 Self::FrontendHydration,
199 Self::TypeScriptRuntime,
200 Self::BuildToolIntegration,
201 Self::HotModuleReplacement,
202 ]
203 }
204
205 pub fn as_str(&self) -> &'static str {
207 match self {
208 Self::Middleware => "middleware",
209 Self::Models => "models",
210 Self::Commands => "commands",
211 Self::ViewSets => "viewsets",
212 Self::Signals => "signals",
213 Self::Services => "services",
214 Self::Auth => "auth",
215 Self::Templates => "templates",
216 Self::StaticFiles => "static_files",
217 Self::Routing => "routing",
218 Self::SignalReceivers => "signal_receivers",
219 Self::Handlers => "handlers",
220 Self::NetworkAccess => "network_access",
221 Self::DatabaseAccess => "database_access",
222 Self::StaticSiteGeneration => "static_site_generation",
223 Self::FrontendSsr => "frontend_ssr",
224 Self::FrontendHydration => "frontend_hydration",
225 Self::TypeScriptRuntime => "typescript_runtime",
226 Self::BuildToolIntegration => "build_tool_integration",
227 Self::HotModuleReplacement => "hot_module_replacement",
228 }
229 }
230
231 pub fn is_wasm_compatible(&self) -> bool {
236 match self {
237 Self::Models => false,
239 Self::StaticSiteGeneration => false,
241 Self::FrontendSsr | Self::FrontendHydration => false,
243 Self::TypeScriptRuntime => false,
245 Self::HotModuleReplacement => false,
247 _ => true,
249 }
250 }
251
252 pub fn requires_ts_runtime(&self) -> bool {
254 matches!(
255 self,
256 Self::FrontendSsr
257 | Self::FrontendHydration
258 | Self::TypeScriptRuntime
259 | Self::HotModuleReplacement
260 )
261 }
262}
263
264impl fmt::Display for PluginCapability {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 write!(f, "{}", self.as_str())
267 }
268}
269
270impl std::str::FromStr for PluginCapability {
271 type Err = String;
272
273 fn from_str(s: &str) -> Result<Self, Self::Err> {
274 match s.to_lowercase().as_str() {
275 "middleware" => Ok(Self::Middleware),
276 "models" => Ok(Self::Models),
277 "commands" => Ok(Self::Commands),
278 "viewsets" => Ok(Self::ViewSets),
279 "signals" => Ok(Self::Signals),
280 "services" => Ok(Self::Services),
281 "auth" => Ok(Self::Auth),
282 "templates" => Ok(Self::Templates),
283 "static_files" | "staticfiles" => Ok(Self::StaticFiles),
284 "routing" => Ok(Self::Routing),
285 "signal_receivers" | "signalreceivers" => Ok(Self::SignalReceivers),
286 "handlers" => Ok(Self::Handlers),
287 "network_access" | "networkaccess" => Ok(Self::NetworkAccess),
288 "database_access" | "databaseaccess" => Ok(Self::DatabaseAccess),
289 "static_site_generation" | "staticsitegeneration" | "ssg" => {
290 Ok(Self::StaticSiteGeneration)
291 }
292 "frontend_ssr" | "frontendssr" | "ssr" => Ok(Self::FrontendSsr),
293 "frontend_hydration" | "frontendhydration" | "hydration" => Ok(Self::FrontendHydration),
294 "typescript_runtime" | "typescriptruntime" | "ts_runtime" | "tsruntime" => {
295 Ok(Self::TypeScriptRuntime)
296 }
297 "build_tool_integration" | "buildtoolintegration" => Ok(Self::BuildToolIntegration),
298 "hot_module_replacement" | "hotmodulereplacment" | "hmr" => {
299 Ok(Self::HotModuleReplacement)
300 }
301 _ => Err(format!("unknown capability: {s}")),
302 }
303 }
304}
305
306#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
311#[serde(untagged)]
312pub enum Capability {
313 Core(PluginCapability),
315 Custom(String),
317}
318
319impl Capability {
320 pub fn core(capability: PluginCapability) -> Self {
322 Self::Core(capability)
323 }
324
325 pub fn custom(name: impl Into<String>) -> Self {
327 Self::Custom(name.into())
328 }
329
330 pub fn as_str(&self) -> &str {
332 match self {
333 Self::Core(cap) => cap.as_str(),
334 Self::Custom(name) => name.as_str(),
335 }
336 }
337
338 pub fn is_core(&self) -> bool {
340 matches!(self, Self::Core(_))
341 }
342
343 pub fn is_wasm_compatible(&self) -> bool {
345 match self {
346 Self::Core(cap) => cap.is_wasm_compatible(),
347 Self::Custom(_) => true,
350 }
351 }
352}
353
354impl fmt::Display for Capability {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 write!(f, "{}", self.as_str())
357 }
358}
359
360impl From<PluginCapability> for Capability {
361 fn from(cap: PluginCapability) -> Self {
362 Self::Core(cap)
363 }
364}
365
366impl std::str::FromStr for Capability {
367 type Err = std::convert::Infallible;
368
369 fn from_str(s: &str) -> Result<Self, Self::Err> {
370 if let Ok(core) = s.parse::<PluginCapability>() {
372 Ok(Self::Core(core))
373 } else {
374 Ok(Self::Custom(s.to_string()))
376 }
377 }
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
400#[serde(rename_all = "snake_case")]
401pub enum PluginTier {
402 #[default]
408 Standard,
409
410 Premium,
416
417 Enterprise,
423}
424
425impl PluginTier {
426 pub fn all() -> &'static [Self] {
428 &[Self::Standard, Self::Premium, Self::Enterprise]
429 }
430
431 pub fn as_str(&self) -> &'static str {
433 match self {
434 Self::Standard => "standard",
435 Self::Premium => "premium",
436 Self::Enterprise => "enterprise",
437 }
438 }
439
440 pub fn memory_limit_bytes(&self) -> usize {
442 match self {
443 Self::Standard => 128 * 1024 * 1024, Self::Premium => 512 * 1024 * 1024, Self::Enterprise => 1024 * 1024 * 1024, }
447 }
448
449 pub fn timeout_secs(&self) -> u64 {
451 match self {
452 Self::Standard => 30,
453 Self::Premium => 60,
454 Self::Enterprise => 120,
455 }
456 }
457
458 pub fn fuel_limit(&self) -> u64 {
460 match self {
461 Self::Standard => 100_000_000, Self::Premium => 500_000_000, Self::Enterprise => 1_000_000_000, }
465 }
466}
467
468impl fmt::Display for PluginTier {
469 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
470 write!(f, "{}", self.as_str())
471 }
472}
473
474impl std::str::FromStr for PluginTier {
475 type Err = String;
476
477 fn from_str(s: &str) -> Result<Self, Self::Err> {
478 match s.to_lowercase().as_str() {
479 "standard" | "std" => Ok(Self::Standard),
480 "premium" | "pro" => Ok(Self::Premium),
481 "enterprise" | "ent" => Ok(Self::Enterprise),
482 _ => Err(format!("unknown plugin tier: {s}")),
483 }
484 }
485}
486
487#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
510#[serde(rename_all = "snake_case")]
511pub enum TrustLevel {
512 #[default]
519 Untrusted,
520
521 Verified,
528
529 Trusted,
538}
539
540impl TrustLevel {
541 pub fn all() -> &'static [Self] {
543 &[Self::Untrusted, Self::Verified, Self::Trusted]
544 }
545
546 pub fn as_str(&self) -> &'static str {
548 match self {
549 Self::Untrusted => "untrusted",
550 Self::Verified => "verified",
551 Self::Trusted => "trusted",
552 }
553 }
554
555 pub fn allows_network(&self) -> bool {
560 !matches!(self, Self::Untrusted)
561 }
562
563 pub fn allows_database(&self) -> bool {
568 !matches!(self, Self::Untrusted)
569 }
570
571 pub fn allows_filesystem(&self) -> bool {
575 matches!(self, Self::Trusted)
576 }
577
578 pub fn requires_verification(&self) -> bool {
580 matches!(self, Self::Verified)
581 }
582
583 pub fn is_safe_for_third_party(&self) -> bool {
585 matches!(self, Self::Untrusted | Self::Verified)
586 }
587
588 pub fn allows_js_execution(&self) -> bool {
593 matches!(self, Self::Trusted)
594 }
595
596 pub fn allows_ssr(&self) -> bool {
601 !matches!(self, Self::Untrusted)
602 }
603}
604
605impl fmt::Display for TrustLevel {
606 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
607 write!(f, "{}", self.as_str())
608 }
609}
610
611impl std::str::FromStr for TrustLevel {
612 type Err = String;
613
614 fn from_str(s: &str) -> Result<Self, Self::Err> {
615 match s.to_lowercase().as_str() {
616 "untrusted" | "sandbox" | "sandboxed" => Ok(Self::Untrusted),
617 "verified" | "signed" => Ok(Self::Verified),
618 "trusted" | "full" => Ok(Self::Trusted),
619 _ => Err(format!("unknown trust level: {s}")),
620 }
621 }
622}
623
624#[cfg(test)]
625mod tests {
626 use super::*;
627
628 #[test]
629 fn test_plugin_capability_display() {
630 assert_eq!(PluginCapability::Middleware.to_string(), "middleware");
631 assert_eq!(PluginCapability::Models.to_string(), "models");
632 assert_eq!(PluginCapability::StaticFiles.to_string(), "static_files");
633 }
634
635 #[test]
636 fn test_plugin_capability_from_str() {
637 assert_eq!(
638 "middleware".parse::<PluginCapability>().unwrap(),
639 PluginCapability::Middleware
640 );
641 assert_eq!(
642 "MODELS".parse::<PluginCapability>().unwrap(),
643 PluginCapability::Models
644 );
645 assert!("unknown".parse::<PluginCapability>().is_err());
646 }
647
648 #[test]
649 fn test_capability_from_str() {
650 assert_eq!(
651 "middleware".parse::<Capability>().unwrap(),
652 Capability::Core(PluginCapability::Middleware)
653 );
654 assert_eq!(
655 "custom-feature".parse::<Capability>().unwrap(),
656 Capability::Custom("custom-feature".to_string())
657 );
658 }
659
660 #[test]
661 fn test_wasm_compatibility() {
662 assert!(PluginCapability::Middleware.is_wasm_compatible());
663 assert!(PluginCapability::Commands.is_wasm_compatible());
664 assert!(!PluginCapability::Models.is_wasm_compatible());
665 assert!(!PluginCapability::StaticSiteGeneration.is_wasm_compatible());
666 }
667
668 #[test]
669 fn test_static_site_generation_capability() {
670 assert_eq!(
671 PluginCapability::StaticSiteGeneration.to_string(),
672 "static_site_generation"
673 );
674 assert_eq!(
675 "static_site_generation"
676 .parse::<PluginCapability>()
677 .unwrap(),
678 PluginCapability::StaticSiteGeneration
679 );
680 assert_eq!(
681 "ssg".parse::<PluginCapability>().unwrap(),
682 PluginCapability::StaticSiteGeneration
683 );
684 }
685
686 #[test]
687 fn test_frontend_capabilities() {
688 assert_eq!(PluginCapability::FrontendSsr.to_string(), "frontend_ssr");
690 assert_eq!(
691 "frontend_ssr".parse::<PluginCapability>().unwrap(),
692 PluginCapability::FrontendSsr
693 );
694 assert_eq!(
695 "ssr".parse::<PluginCapability>().unwrap(),
696 PluginCapability::FrontendSsr
697 );
698
699 assert_eq!(
701 PluginCapability::FrontendHydration.to_string(),
702 "frontend_hydration"
703 );
704 assert_eq!(
705 "hydration".parse::<PluginCapability>().unwrap(),
706 PluginCapability::FrontendHydration
707 );
708
709 assert_eq!(
711 PluginCapability::TypeScriptRuntime.to_string(),
712 "typescript_runtime"
713 );
714 assert_eq!(
715 "ts_runtime".parse::<PluginCapability>().unwrap(),
716 PluginCapability::TypeScriptRuntime
717 );
718
719 assert_eq!(
721 PluginCapability::BuildToolIntegration.to_string(),
722 "build_tool_integration"
723 );
724
725 assert_eq!(
727 PluginCapability::HotModuleReplacement.to_string(),
728 "hot_module_replacement"
729 );
730 assert_eq!(
731 "hmr".parse::<PluginCapability>().unwrap(),
732 PluginCapability::HotModuleReplacement
733 );
734 }
735
736 #[test]
737 fn test_frontend_capabilities_wasm_compatibility() {
738 assert!(!PluginCapability::FrontendSsr.is_wasm_compatible());
740 assert!(!PluginCapability::FrontendHydration.is_wasm_compatible());
741 assert!(!PluginCapability::TypeScriptRuntime.is_wasm_compatible());
742 assert!(!PluginCapability::HotModuleReplacement.is_wasm_compatible());
743
744 assert!(PluginCapability::BuildToolIntegration.is_wasm_compatible());
746 }
747
748 #[test]
749 fn test_requires_ts_runtime() {
750 assert!(PluginCapability::FrontendSsr.requires_ts_runtime());
751 assert!(PluginCapability::FrontendHydration.requires_ts_runtime());
752 assert!(PluginCapability::TypeScriptRuntime.requires_ts_runtime());
753 assert!(PluginCapability::HotModuleReplacement.requires_ts_runtime());
754
755 assert!(!PluginCapability::Middleware.requires_ts_runtime());
757 assert!(!PluginCapability::BuildToolIntegration.requires_ts_runtime());
758 }
759
760 #[test]
765 fn test_plugin_tier_default() {
766 assert_eq!(PluginTier::default(), PluginTier::Standard);
767 }
768
769 #[test]
770 fn test_plugin_tier_display() {
771 assert_eq!(PluginTier::Standard.to_string(), "standard");
772 assert_eq!(PluginTier::Premium.to_string(), "premium");
773 assert_eq!(PluginTier::Enterprise.to_string(), "enterprise");
774 }
775
776 #[test]
777 fn test_plugin_tier_from_str() {
778 assert_eq!(
779 "standard".parse::<PluginTier>().unwrap(),
780 PluginTier::Standard
781 );
782 assert_eq!("std".parse::<PluginTier>().unwrap(), PluginTier::Standard);
783 assert_eq!(
784 "premium".parse::<PluginTier>().unwrap(),
785 PluginTier::Premium
786 );
787 assert_eq!("pro".parse::<PluginTier>().unwrap(), PluginTier::Premium);
788 assert_eq!(
789 "enterprise".parse::<PluginTier>().unwrap(),
790 PluginTier::Enterprise
791 );
792 assert_eq!("ent".parse::<PluginTier>().unwrap(), PluginTier::Enterprise);
793 assert!("unknown".parse::<PluginTier>().is_err());
794 }
795
796 #[test]
797 fn test_plugin_tier_memory_limits() {
798 assert_eq!(PluginTier::Standard.memory_limit_bytes(), 128 * 1024 * 1024);
799 assert_eq!(PluginTier::Premium.memory_limit_bytes(), 512 * 1024 * 1024);
800 assert_eq!(
801 PluginTier::Enterprise.memory_limit_bytes(),
802 1024 * 1024 * 1024
803 );
804 }
805
806 #[test]
807 fn test_plugin_tier_timeout() {
808 assert_eq!(PluginTier::Standard.timeout_secs(), 30);
809 assert_eq!(PluginTier::Premium.timeout_secs(), 60);
810 assert_eq!(PluginTier::Enterprise.timeout_secs(), 120);
811 }
812
813 #[test]
814 fn test_plugin_tier_fuel_limits() {
815 assert_eq!(PluginTier::Standard.fuel_limit(), 100_000_000);
816 assert_eq!(PluginTier::Premium.fuel_limit(), 500_000_000);
817 assert_eq!(PluginTier::Enterprise.fuel_limit(), 1_000_000_000);
818 }
819
820 #[test]
821 fn test_plugin_tier_all() {
822 let all = PluginTier::all();
823 assert_eq!(all.len(), 3);
824 assert!(all.contains(&PluginTier::Standard));
825 assert!(all.contains(&PluginTier::Premium));
826 assert!(all.contains(&PluginTier::Enterprise));
827 }
828
829 #[test]
834 fn test_trust_level_default() {
835 assert_eq!(TrustLevel::default(), TrustLevel::Untrusted);
836 }
837
838 #[test]
839 fn test_trust_level_display() {
840 assert_eq!(TrustLevel::Untrusted.to_string(), "untrusted");
841 assert_eq!(TrustLevel::Verified.to_string(), "verified");
842 assert_eq!(TrustLevel::Trusted.to_string(), "trusted");
843 }
844
845 #[test]
846 fn test_trust_level_from_str() {
847 assert_eq!(
848 "untrusted".parse::<TrustLevel>().unwrap(),
849 TrustLevel::Untrusted
850 );
851 assert_eq!(
852 "sandbox".parse::<TrustLevel>().unwrap(),
853 TrustLevel::Untrusted
854 );
855 assert_eq!(
856 "verified".parse::<TrustLevel>().unwrap(),
857 TrustLevel::Verified
858 );
859 assert_eq!(
860 "signed".parse::<TrustLevel>().unwrap(),
861 TrustLevel::Verified
862 );
863 assert_eq!(
864 "trusted".parse::<TrustLevel>().unwrap(),
865 TrustLevel::Trusted
866 );
867 assert_eq!("full".parse::<TrustLevel>().unwrap(), TrustLevel::Trusted);
868 assert!("unknown".parse::<TrustLevel>().is_err());
869 }
870
871 #[test]
872 fn test_trust_level_network_access() {
873 assert!(!TrustLevel::Untrusted.allows_network());
874 assert!(TrustLevel::Verified.allows_network());
875 assert!(TrustLevel::Trusted.allows_network());
876 }
877
878 #[test]
879 fn test_trust_level_database_access() {
880 assert!(!TrustLevel::Untrusted.allows_database());
881 assert!(TrustLevel::Verified.allows_database());
882 assert!(TrustLevel::Trusted.allows_database());
883 }
884
885 #[test]
886 fn test_trust_level_filesystem_access() {
887 assert!(!TrustLevel::Untrusted.allows_filesystem());
888 assert!(!TrustLevel::Verified.allows_filesystem());
889 assert!(TrustLevel::Trusted.allows_filesystem());
890 }
891
892 #[test]
893 fn test_trust_level_verification_requirement() {
894 assert!(!TrustLevel::Untrusted.requires_verification());
895 assert!(TrustLevel::Verified.requires_verification());
896 assert!(!TrustLevel::Trusted.requires_verification());
897 }
898
899 #[test]
900 fn test_trust_level_third_party_safety() {
901 assert!(TrustLevel::Untrusted.is_safe_for_third_party());
902 assert!(TrustLevel::Verified.is_safe_for_third_party());
903 assert!(!TrustLevel::Trusted.is_safe_for_third_party());
904 }
905
906 #[test]
907 fn test_trust_level_all() {
908 let all = TrustLevel::all();
909 assert_eq!(all.len(), 3);
910 assert!(all.contains(&TrustLevel::Untrusted));
911 assert!(all.contains(&TrustLevel::Verified));
912 assert!(all.contains(&TrustLevel::Trusted));
913 }
914
915 #[test]
916 fn test_trust_level_js_execution() {
917 assert!(!TrustLevel::Untrusted.allows_js_execution());
918 assert!(!TrustLevel::Verified.allows_js_execution());
919 assert!(TrustLevel::Trusted.allows_js_execution());
920 }
921
922 #[test]
923 fn test_trust_level_ssr() {
924 assert!(!TrustLevel::Untrusted.allows_ssr());
925 assert!(TrustLevel::Verified.allows_ssr());
926 assert!(TrustLevel::Trusted.allows_ssr());
927 }
928}