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
15use thiserror::Error;
16
17use super::{LicenseError, LicenseManager, Tier, report_telemetry};
18
19/// Define all features that are available based on the tier of the license.
20///
21/// # Define a new feature
22///
23/// To add a new feature, add a new entry below following the same pattern as the existing ones.
24///
25/// Check the definition of [`Tier`] for all available tiers. Note that normally there's no need to
26/// add a feature with the minimum tier of `Free`, as you can directly write the code without
27/// gating it with a feature check.
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 license key of the paid (maximum) tier is set by
39/// default. As a result, all features are available in tests. To test the behavior when a feature
40/// is not available, you can manually set a license key with a lower tier. Check the e2e test cases
41/// under `error_ui` for examples.
42macro_rules! for_all_features {
43 ($macro:ident) => {
44 $macro! {
45 // name min tier doc
46 { TestPaid, Paid, "A dummy feature that's only available on paid tier for testing purposes." },
47 { TimeTravel, Paid, "Query historical data within the retention period."},
48 { GlueSchemaRegistry, Paid, "Use Schema Registry from AWS Glue rather than Confluent." },
49 { SnowflakeSink, Paid, "Delivering data to SnowFlake." },
50 { DynamoDbSink, Paid, "Delivering data to DynamoDb." },
51 { OpenSearchSink, Paid, "Delivering data to OpenSearch." },
52 { BigQuerySink, Paid, "Delivering data to BigQuery." },
53 { ClickHouseSharedEngine, Paid, "Delivering data to Shared tree on clickhouse cloud"},
54 { SecretManagement, Paid, "Secret management." },
55 { CdcTableSchemaMap, Paid, "Automatically map upstream schema to CDC Table."},
56 { SqlServerSink, Paid, "Sink data from RisingWave to SQL Server." },
57 { SqlServerCdcSource, Paid, "CDC source connector for Sql Server." },
58 { CdcAutoSchemaChange, Paid, "Auto replicate upstream DDL to CDC Table." },
59 { IcebergSinkWithGlue, Paid, "Delivering data to Iceberg with Glue catalog." },
60 { ElasticDiskCache, Paid, "Disk cache and refilling to boost performance and reduce object store access cost." },
61 { ResourceGroup, Paid, "Resource group to isolate workload and failure." },
62 { DatabaseFailureIsolation, Paid, "Failure isolation between databases" },
63 }
64 };
65}
66
67macro_rules! def_feature {
68 ($({ $name:ident, $min_tier:ident, $doc:literal },)*) => {
69 /// A set of features that are available based on the tier of the license.
70 ///
71 /// To define a new feature, add a new entry in the macro [`for_all_features`].
72 #[derive(Clone, Copy, Debug)]
73 pub enum Feature {
74 $(
75 #[doc = concat!($doc, "\n\nAvailable for tier `", stringify!($min_tier), "` and above.")]
76 $name,
77 )*
78 }
79
80 impl Feature {
81 /// Minimum tier required to use this feature.
82 fn min_tier(self) -> Tier {
83 match self {
84 $(
85 Self::$name => Tier::$min_tier,
86 )*
87 }
88 }
89
90 fn get_feature_name(&self) -> &'static str {
91 match &self {
92 $(
93 Self::$name => stringify!($name),
94 )*
95 }
96 }
97 }
98 };
99}
100
101for_all_features!(def_feature);
102
103/// The error type for feature not available due to license.
104#[derive(Debug, Error)]
105pub enum FeatureNotAvailable {
106 #[error(
107 "feature {:?} is only available for tier {:?} and above, while the current tier is {:?}\n\n\
108 Hint: You may want to set a license key with `ALTER SYSTEM SET license_key = '...';` command.",
109 feature, feature.min_tier(), current_tier,
110 )]
111 InsufficientTier {
112 feature: Feature,
113 current_tier: Tier,
114 },
115
116 #[error("feature {feature:?} is not available due to license error")]
117 LicenseError {
118 feature: Feature,
119 source: LicenseError,
120 },
121}
122
123impl Feature {
124 /// Check whether the feature is available based on the given license manager.
125 pub(crate) fn check_available_with(
126 self,
127 manager: &LicenseManager,
128 ) -> Result<(), FeatureNotAvailable> {
129 let check_res = match manager.license() {
130 Ok(license) => {
131 if license.tier >= self.min_tier() {
132 Ok(())
133 } else {
134 Err(FeatureNotAvailable::InsufficientTier {
135 feature: self,
136 current_tier: license.tier,
137 })
138 }
139 }
140 Err(error) => Err(FeatureNotAvailable::LicenseError {
141 feature: self,
142 source: error,
143 }),
144 };
145
146 report_telemetry(&self, self.get_feature_name(), check_res.is_ok());
147
148 check_res
149 }
150
151 /// Check whether the feature is available based on the current license.
152 pub fn check_available(self) -> Result<(), FeatureNotAvailable> {
153 self.check_available_with(LicenseManager::get())
154 }
155}