risingwave_license/feature.rs
1// Copyright 2024 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use strum::{EnumMessage, VariantArray};
16use thiserror::Error;
17
18use super::{LicenseError, LicenseManager, report_telemetry};
19
20// Define all features that require a license to use.
21//
22// # Define a new feature
23//
24// To add a new feature, add a new entry at the END of `feature.json`, following the same pattern
25// as the existing ones.
26//
27// # Check the availability of a feature
28//
29// To check the availability of a feature during runtime, call the method
30// [`check_available`](Feature::check_available) on the feature. If the feature is not available,
31// an error of type [`FeatureNotAvailable`] will be returned and you should handle it properly,
32// generally by returning an error to the user.
33//
34// # Feature availability in tests
35//
36// In tests with `debug_assertions` enabled, a special license key with all features enabled is set by
37// default. To test the behavior when a feature is not available, you can manually set a license key.
38// Check the e2e test cases under `error_ui` for examples.
39typify::import_types!(
40 schema = "src/feature.json",
41 derives = [
42 strum::VariantArray,
43 strum::IntoStaticStr,
44 strum::EnumMessage,
45 ],
46);
47
48impl Feature {
49 /// Name of the feature.
50 pub fn name(self) -> &'static str {
51 self.into()
52 }
53
54 /// Description of the feature.
55 pub fn description(self) -> &'static str {
56 self.get_documentation().unwrap_or_default()
57 }
58
59 /// Get a slice of all features.
60 pub fn all() -> &'static [Feature] {
61 Feature::VARIANTS
62 }
63
64 /// Get a slice of all features available as of 2.5 (before we introduce custom tier).
65 pub(crate) fn all_as_of_2_5() -> &'static [Feature] {
66 // `IcebergCompaction` was the last feature introduced.
67 &Feature::all()[..=Feature::IcebergCompaction as usize]
68 }
69}
70
71/// The error type for feature not available due to license.
72#[derive(Debug, Error)]
73pub enum FeatureNotAvailable {
74 // TODO(license): refine error message to include tier name & better instructions
75 #[error(
76 "feature {feature:?} is not available based on your license\n\n\
77 Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command. For more information, visit https://go.risingwave.com/premium."
78 )]
79 NotAvailable { feature: Feature },
80
81 #[error("feature {feature:?} is not available due to license error")]
82 LicenseError {
83 feature: Feature,
84 source: LicenseError,
85 },
86}
87
88impl Feature {
89 /// Check whether the feature is available based on the given license manager.
90 pub(crate) fn check_available_with(
91 self,
92 manager: &LicenseManager,
93 ) -> Result<(), FeatureNotAvailable> {
94 let check_res = match manager.license() {
95 Ok(license) => {
96 if license.tier.available_features().any(|x| x == self) {
97 Ok(())
98 } else {
99 Err(FeatureNotAvailable::NotAvailable { feature: self })
100 }
101 }
102 Err(error) => Err(FeatureNotAvailable::LicenseError {
103 feature: self,
104 source: error,
105 }),
106 };
107
108 report_telemetry(&self, self.name(), check_res.is_ok());
109
110 check_res
111 }
112
113 /// Check whether the feature is available based on the current license.
114 pub fn check_available(self) -> Result<(), FeatureNotAvailable> {
115 self.check_available_with(LicenseManager::get())
116 }
117}