risingwave_license/
feature.rs

1// Copyright 2025 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
15#![allow(clippy::doc_markdown)]
16
17use strum::VariantArray;
18use thiserror::Error;
19
20use super::{LicenseError, LicenseManager, report_telemetry};
21
22// Define all features that require a license to use.
23//
24// # Define a new feature
25//
26// To add a new feature, add a new entry at the END of `feature.json`, following the same pattern
27// as the existing ones.
28//
29// # Check the availability of a feature
30//
31// To check the availability of a feature during runtime, call the method
32// [`check_available`](Feature::check_available) on the feature. If the feature is not available,
33// an error of type [`FeatureNotAvailable`] will be returned and you should handle it properly,
34// generally by returning an error to the user.
35//
36// # Feature availability in tests
37//
38// In tests with `debug_assertions` enabled, a special license key with all features enabled is set by
39// default. To test the behavior when a feature is not available, you can manually set a license key.
40// Check the e2e test cases under `error_ui` for examples.
41typify::import_types!(
42    schema = "src/feature.json",
43    derives = [strum::VariantArray, strum::IntoStaticStr],
44);
45
46impl Feature {
47    /// Name of the feature.
48    pub(crate) fn name(self) -> &'static str {
49        self.into()
50    }
51
52    /// Get a slice of all features.
53    pub(crate) fn all() -> &'static [Feature] {
54        Feature::VARIANTS
55    }
56
57    /// Get a slice of all features available as of 2.5 (before we introduce custom tier).
58    pub(crate) fn all_as_of_2_5() -> &'static [Feature] {
59        // `IcebergCompaction` was the last feature introduced.
60        &Feature::all()[..=Feature::IcebergCompaction as usize]
61    }
62}
63
64/// The error type for feature not available due to license.
65#[derive(Debug, Error)]
66pub enum FeatureNotAvailable {
67    // TODO(license): refine error message to include tier name & better instructions
68    #[error(
69        "feature {feature:?} is not available based on your license\n\n\
70        Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command."
71    )]
72    NotAvailable { feature: Feature },
73
74    #[error("feature {feature:?} is not available due to license error")]
75    LicenseError {
76        feature: Feature,
77        source: LicenseError,
78    },
79}
80
81impl Feature {
82    /// Check whether the feature is available based on the given license manager.
83    pub(crate) fn check_available_with(
84        self,
85        manager: &LicenseManager,
86    ) -> Result<(), FeatureNotAvailable> {
87        let check_res = match manager.license() {
88            Ok(license) => {
89                if license.tier.available_features().any(|x| x == self) {
90                    Ok(())
91                } else {
92                    Err(FeatureNotAvailable::NotAvailable { feature: self })
93                }
94            }
95            Err(error) => Err(FeatureNotAvailable::LicenseError {
96                feature: self,
97                source: error,
98            }),
99        };
100
101        report_telemetry(&self, self.name(), check_res.is_ok());
102
103        check_res
104    }
105
106    /// Check whether the feature is available based on the current license.
107    pub fn check_available(self) -> Result<(), FeatureNotAvailable> {
108        self.check_available_with(LicenseManager::get())
109    }
110}