optify-config 1.23.2 → 1.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fadc349edb550af2e368cedcc7f780a2161d5792d3ade921330152d07f929d06
4
- data.tar.gz: dc6102db73935e39aa616d3b343ca26cd25aca80747e593febc9b105c2131ed3
3
+ metadata.gz: f295ac7d00dec19d6aac47bdd92452ec0768181e482a24fdcc5de2d85900df31
4
+ data.tar.gz: 9429b9927048c2fb85c7343a839b78947212ba6192091852d9a30bcbd8078420
5
5
  SHA512:
6
- metadata.gz: 8ee7afa6b34837298f6e799535a30072a21a3576a6e6034ba02585973876d0fb0149df4f8b5075a6f38e781af4f18d11077b126d22e547ca4430c77a2e9fa9aa
7
- data.tar.gz: ac4f7dc243e5e9ef479ae781d52c9d7e8af6549b5e974b672cf5b7925202b5236face81216b9a089561f0129aaffe7f96cf5b7b51cc8fd0ac4825e1f5b541bab
6
+ metadata.gz: 3225dcc9006955335b31f0cfa934c97c9460fad48b2adfc5521a2a21597047f9057fc057c6f9699660d4bdee4346b0d827e73e7bd7a1af338cb9f974cde0d42c
7
+ data.tar.gz: 80247e4b63f23e9625365c39e725dbfe4976135a08ec7ec8c497c0be3bbf197ea5437448971b181c1b6255c13198341790f0f4dc003bb0056423f76592cf7881
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "optify_ruby"
3
- version = "0.24.3"
3
+ version = "0.25.0"
4
4
  edition = "2021"
5
5
 
6
6
  description = "optify bindings for Ruby"
@@ -22,6 +22,6 @@ crate-type = ["cdylib"]
22
22
  [dependencies]
23
23
  magnus = "0.8.2"
24
24
  # Can't put a relative path here because then others can't install the source gem: https://github.com/juharris/optify/pull/176
25
- optify = "1.3.3"
25
+ optify = "1.4.0"
26
26
  rb-sys = { version = "0.9.124", default-features = false, features = ["ruby-static"] }
27
27
  serde_json = "1.0.149"
@@ -17,18 +17,29 @@ use crate::preferences::MutGetOptionsPreferences;
17
17
  mod preferences;
18
18
 
19
19
  const UNKNOWN_FEATURE_PATTERN: &str = "is not a known feature.";
20
+ const POLICY_DENIED_PATTERN: &str = "is not permitted to use feature";
20
21
 
21
22
  fn get_unknown_feature_error(ruby: &Ruby) -> Result<ExceptionClass, magnus::Error> {
22
23
  let module: RModule = ruby.class_object().const_get("Optify")?;
23
24
  module.const_get("UnknownFeatureError")
24
25
  }
25
26
 
27
+ fn get_policy_denied_error(ruby: &Ruby) -> Result<ExceptionClass, magnus::Error> {
28
+ let module: RModule = ruby.class_object().const_get("Optify")?;
29
+ module.const_get("PolicyDeniedError")
30
+ }
31
+
26
32
  fn map_feature_error(ruby: &Ruby, error: String) -> magnus::Error {
27
33
  if error.contains(UNKNOWN_FEATURE_PATTERN) {
28
34
  match get_unknown_feature_error(ruby) {
29
35
  Ok(exception_class) => magnus::Error::new(exception_class, error),
30
36
  Err(_) => magnus::Error::new(ruby.exception_runtime_error(), error),
31
37
  }
38
+ } else if error.contains(POLICY_DENIED_PATTERN) {
39
+ match get_policy_denied_error(ruby) {
40
+ Ok(exception_class) => magnus::Error::new(exception_class, error),
41
+ Err(_) => magnus::Error::new(ruby.exception_runtime_error(), error),
42
+ }
32
43
  } else {
33
44
  magnus::Error::new(ruby.exception_runtime_error(), error)
34
45
  }
@@ -115,6 +126,19 @@ impl WrappedOptionsProvider {
115
126
  }
116
127
  }
117
128
 
129
+ fn check_policies(
130
+ ruby: &Ruby,
131
+ rb_self: &Self,
132
+ requester: String,
133
+ feature_names: Vec<String>,
134
+ ) -> Result<(), magnus::Error> {
135
+ rb_self
136
+ .0
137
+ .borrow()
138
+ .check_policies(&requester, &feature_names, None)
139
+ .map_err(|e| map_feature_error(ruby, e))
140
+ }
141
+
118
142
  fn get_aliases(&self) -> Vec<String> {
119
143
  self.0.borrow().get_aliases()
120
144
  }
@@ -291,6 +315,13 @@ impl WrappedOptionsProvider {
291
315
  self.0.borrow().has_conditions(&canonical_feature_name)
292
316
  }
293
317
 
318
+ fn get_policies_json(&self, canonical_feature_name: String) -> Option<String> {
319
+ self.0
320
+ .borrow()
321
+ .get_policies(&canonical_feature_name)
322
+ .map(|p| serde_json::to_string(&p).unwrap())
323
+ }
324
+
294
325
  fn map_feature_names(
295
326
  ruby: &Ruby,
296
327
  rb_self: &Self,
@@ -380,6 +411,19 @@ impl WrappedOptionsWatcher {
380
411
  }
381
412
  }
382
413
 
414
+ fn check_policies(
415
+ ruby: &Ruby,
416
+ rb_self: &Self,
417
+ requester: String,
418
+ feature_names: Vec<String>,
419
+ ) -> Result<(), magnus::Error> {
420
+ rb_self
421
+ .0
422
+ .borrow()
423
+ .check_policies(&requester, &feature_names, None)
424
+ .map_err(|e| map_feature_error(ruby, e))
425
+ }
426
+
383
427
  fn get_aliases(&self) -> Vec<String> {
384
428
  self.0.borrow().get_aliases()
385
429
  }
@@ -552,6 +596,13 @@ impl WrappedOptionsWatcher {
552
596
  self.0.borrow().has_conditions(&canonical_feature_name)
553
597
  }
554
598
 
599
+ fn get_policies_json(&self, canonical_feature_name: String) -> Option<String> {
600
+ self.0
601
+ .borrow()
602
+ .get_policies(&canonical_feature_name)
603
+ .map(|p| serde_json::to_string(&p).unwrap())
604
+ }
605
+
555
606
  fn last_modified(&self) -> std::time::SystemTime {
556
607
  self.0.borrow().last_modified()
557
608
  }
@@ -607,6 +658,7 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
607
658
  let module = ruby.define_module("Optify")?;
608
659
 
609
660
  module.define_error("UnknownFeatureError", ruby.exception_standard_error())?;
661
+ module.define_error("PolicyDeniedError", ruby.exception_standard_error())?;
610
662
 
611
663
  let builder_class = module.define_class("OptionsProviderBuilder", ruby.class_object())?;
612
664
 
@@ -636,6 +688,10 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
636
688
  ),
637
689
  )?;
638
690
  provider_class.define_method("aliases", method!(WrappedOptionsProvider::get_aliases, 0))?;
691
+ provider_class.define_private_method(
692
+ "_check_policies",
693
+ method!(WrappedOptionsProvider::check_policies, 2),
694
+ )?;
639
695
  provider_class.define_method("features", method!(WrappedOptionsProvider::get_features, 0))?;
640
696
  provider_class.define_method(
641
697
  "features_and_aliases",
@@ -695,6 +751,10 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
695
751
  "get_feature_metadata_json",
696
752
  method!(WrappedOptionsProvider::get_feature_metadata_json, 1),
697
753
  )?;
754
+ provider_class.define_private_method(
755
+ "get_policies_json",
756
+ method!(WrappedOptionsProvider::get_policies_json, 1),
757
+ )?;
698
758
 
699
759
  let get_options_preferences_class =
700
760
  module.define_class("GetOptionsPreferences", ruby.class_object())?;
@@ -757,6 +817,22 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
757
817
  "skip_feature_name_conversion",
758
818
  method!(MutGetOptionsPreferences::skip_feature_name_conversion, 0),
759
819
  )?;
820
+ get_options_preferences_class.define_method(
821
+ "requester=",
822
+ method!(MutGetOptionsPreferences::set_requester, 1),
823
+ )?;
824
+ get_options_preferences_class.define_method(
825
+ "requester",
826
+ method!(MutGetOptionsPreferences::get_requester, 0),
827
+ )?;
828
+ get_options_preferences_class.define_method(
829
+ "raise_if_policy_denied=",
830
+ method!(MutGetOptionsPreferences::set_raise_if_policy_denied, 1),
831
+ )?;
832
+ get_options_preferences_class.define_method(
833
+ "raise_if_policy_denied",
834
+ method!(MutGetOptionsPreferences::get_raise_if_policy_denied, 0),
835
+ )?;
760
836
 
761
837
  let watcher_builder_class =
762
838
  module.define_class("OptionsWatcherBuilder", ruby.class_object())?;
@@ -784,6 +860,10 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
784
860
  function!(WrappedOptionsWatcher::build_from_directories_with_schema, 2),
785
861
  )?;
786
862
  watcher_class.define_method("aliases", method!(WrappedOptionsWatcher::get_aliases, 0))?;
863
+ watcher_class.define_private_method(
864
+ "_check_policies",
865
+ method!(WrappedOptionsWatcher::check_policies, 2),
866
+ )?;
787
867
  watcher_class.define_method("features", method!(WrappedOptionsWatcher::get_features, 0))?;
788
868
  watcher_class.define_method(
789
869
  "features_and_aliases",
@@ -847,6 +927,10 @@ fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
847
927
  "get_feature_metadata_json",
848
928
  method!(WrappedOptionsWatcher::get_feature_metadata_json, 1),
849
929
  )?;
930
+ watcher_class.define_private_method(
931
+ "get_policies_json",
932
+ method!(WrappedOptionsWatcher::get_policies_json, 1),
933
+ )?;
850
934
 
851
935
  Ok(())
852
936
  }
@@ -71,4 +71,22 @@ impl MutGetOptionsPreferences {
71
71
  pub fn skip_feature_name_conversion(&self) -> bool {
72
72
  self.0.borrow().skip_feature_name_conversion
73
73
  }
74
+
75
+ // Requester Section
76
+ pub fn set_requester(&self, requester: Option<String>) {
77
+ self.0.borrow_mut().requester = requester;
78
+ }
79
+
80
+ pub fn get_requester(&self) -> Option<String> {
81
+ self.0.borrow().requester.clone()
82
+ }
83
+
84
+ // Policy Section
85
+ pub fn set_raise_if_policy_denied(&self, value: bool) {
86
+ self.0.borrow_mut().raise_if_policy_denied = value;
87
+ }
88
+
89
+ pub fn get_raise_if_policy_denied(&self) -> bool {
90
+ self.0.borrow().raise_if_policy_denied
91
+ }
74
92
  }
@@ -0,0 +1,33 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ require 'optify-from_hash'
7
+
8
+ module Optify
9
+ # The policy for the requester identifier passed via preferences.
10
+ #
11
+ # Either `allow` or `block` will be set, not both.
12
+ # - `allow`: only the listed requesters may use the feature; can be empty.
13
+ # - `block`: the listed requesters may not use the feature; all others are allowed.
14
+ class RequesterPolicy < FromHashable
15
+ extend T::Sig
16
+
17
+ sig { returns(T.nilable(T::Set[String])) }
18
+ attr_reader :allow
19
+
20
+ sig { returns(T.nilable(T::Set[String])) }
21
+ attr_reader :block
22
+ end
23
+
24
+ # Policies that restrict access to a feature based on values in the request's preferences.
25
+ #
26
+ # See https://github.com/juharris/optify#policies for details.
27
+ class Policies < FromHashable
28
+ extend T::Sig
29
+
30
+ sig { returns(T.nilable(RequesterPolicy)) }
31
+ attr_reader :requester
32
+ end
33
+ end
@@ -5,9 +5,27 @@ require 'json'
5
5
  require 'lru_redux'
6
6
  require 'sorbet-runtime'
7
7
 
8
+ require_relative './policies'
9
+
8
10
  module Optify
9
11
  # @!visibility private
10
- module ProviderModule
12
+ module ProviderModule # rubocop:disable Metrics/ModuleLength
13
+ #: (String, Array[String], ?CacheOptions?) -> void
14
+ def check_policies(requester, feature_names, cache_options = nil)
15
+ if cache_options
16
+ init unless @cache
17
+ ProviderModule._cache_getset(
18
+ @cache, #: as !nil
19
+ [:check_policies, feature_names, requester],
20
+ cache_options.on_cache_event,
21
+ ) do
22
+ _check_policies(requester, feature_names)
23
+ end
24
+ else
25
+ _check_policies(requester, feature_names)
26
+ end
27
+ end
28
+
11
29
  #: [T] (LruRedux::Cache | Hash[Array[untyped], T], Array[untyped], (^(Array[untyped] key, T value, bool is_cache_hit) -> void)?) { -> T } -> T
12
30
  def self._cache_getset(cache, cache_key, on_cache_event, &block)
13
31
  is_cache_hit = true #: bool
@@ -69,6 +87,14 @@ module Optify
69
87
  OptionsMetadata.from_hash(JSON.parse(metadata_json))
70
88
  end
71
89
 
90
+ #: (String canonical_feature_name) -> Optify::Policies?
91
+ def get_policies(canonical_feature_name)
92
+ policies_json = get_policies_json(canonical_feature_name)
93
+ return nil if policies_json.nil?
94
+
95
+ Policies.from_hash(JSON.parse(policies_json))
96
+ end
97
+
72
98
  private
73
99
 
74
100
  #: -> Hash[String, OptionsMetadata]
data/rbi/optify.rbi CHANGED
@@ -7,6 +7,31 @@ module Optify
7
7
  class UnknownFeatureError < StandardError
8
8
  end
9
9
 
10
+ # The policy for the requester identifier passed via preferences.
11
+ #
12
+ # Either `allow` or `block` will be set, not both.
13
+ # - `allow`: only the listed requesters may use the feature; can be empty.
14
+ # - `block`: the listed requesters may not use the feature; all others are allowed.
15
+ class RequesterPolicy < FromHashable
16
+ sig { returns(T.nilable(T::Set[String])) }
17
+ attr_reader :allow
18
+
19
+ sig { returns(T.nilable(T::Set[String])) }
20
+ attr_reader :block
21
+ end
22
+
23
+ # Policies that restrict access to a feature based on values in the request's preferences.
24
+ #
25
+ # See https://github.com/juharris/optify#policies for details.
26
+ class Policies < FromHashable
27
+ sig { returns(T.nilable(RequesterPolicy)) }
28
+ attr_reader :requester
29
+ end
30
+
31
+ # Raised when a requester is not permitted to use a feature due to its policies.
32
+ class PolicyDeniedError < StandardError
33
+ end
34
+
10
35
  # DEPRECATED: Use `Optify::FromHashable` instead.
11
36
  # A base class for classes from configuration files.
12
37
  # Classes that derive from this can easily be used with `Optify::OptionsProvider.get_options`
@@ -162,6 +187,22 @@ module Optify
162
187
 
163
188
  sig { returns(T::Boolean) }
164
189
  def skip_feature_name_conversion; end
190
+
191
+ # The requester identifier to use for policy enforcement.
192
+ # If not set, policy checks are bypassed.
193
+ sig { params(value: T.nilable(String)).void }
194
+ def requester=(value); end
195
+
196
+ sig { returns(T.nilable(String)) }
197
+ def requester; end
198
+
199
+ # When `true`, raises `PolicyDeniedError` if a feature is not permitted for the requester.
200
+ # When `false` (default), denied features are silently filtered out.
201
+ sig { params(value: T::Boolean).void }
202
+ def raise_if_policy_denied=(value); end
203
+
204
+ sig { returns(T::Boolean) }
205
+ def raise_if_policy_denied; end
165
206
  end
166
207
 
167
208
  # A registry of features that provides configurations.
@@ -233,6 +274,19 @@ module Optify
233
274
  # Some of the methods shown within this module are implemented in Rust
234
275
  # and are declared in this common module to avoid duplicate declarations in different classes.
235
276
  module ProviderModule
277
+ # Checks policies for a list of features for the given requester.
278
+ # Returns `nil` if there are no policy issues (or no policies defined).
279
+ # Raises `Optify::PolicyDeniedError` if any of the features are not allowed for the requester.
280
+ sig do
281
+ params(
282
+ requester: String,
283
+ feature_names: T::Array[String],
284
+ cache_options: T.nilable(CacheOptions),
285
+ )
286
+ .returns(T.nilable(String))
287
+ end
288
+ def check_policies(requester, feature_names, cache_options = nil); end
289
+
236
290
  # @param canonical_feature_name [String] A canonical feature name
237
291
  # @return Whether the feature has conditions.
238
292
  sig { params(canonical_feature_name: String).returns(T::Boolean) }
@@ -358,8 +412,22 @@ module Optify
358
412
  end
359
413
  def map_feature_names(feature_names, preferences); end
360
414
 
415
+ # @return The policies for the feature, or `nil` if the feature has no policies.
416
+ sig { params(canonical_feature_name: String).returns(T.nilable(Optify::Policies)) }
417
+ def get_policies(canonical_feature_name); end
418
+
361
419
  private
362
420
 
421
+ # Rust implementation.
422
+ sig do
423
+ params(
424
+ requester: String,
425
+ feature_names: T::Array[String],
426
+ )
427
+ .returns(T.nilable(String))
428
+ end
429
+ def _check_policies(requester, feature_names); end
430
+
363
431
  # Map aliases or canonical feature names (perhaps derived from a file names) to the canonical feature names.
364
432
  # Canonical feature names map to themselves.
365
433
  # This implementation calls the Rust implementation directly.
@@ -372,6 +440,10 @@ module Optify
372
440
  # @return The metadata for the feature.
373
441
  sig { params(canonical_feature_name: String).returns(T.nilable(String)) }
374
442
  def get_feature_metadata_json(canonical_feature_name); end
443
+
444
+ # @return The policies for the feature as JSON, or `nil` if the feature has no policies.
445
+ sig { params(canonical_feature_name: String).returns(T.nilable(String)) }
446
+ def get_policies_json(canonical_feature_name); end
375
447
  end
376
448
 
377
449
  # Provides configurations based on keys and enabled feature names.
data/sig/optify.rbs CHANGED
@@ -6,6 +6,24 @@ end
6
6
  class Optify::UnknownFeatureError < StandardError
7
7
  end
8
8
 
9
+ # The policy for the requester identifier passed via preferences.
10
+ #
11
+ # Either `allow` or `block` will be set, not both.
12
+ # - `allow`: only the listed requesters may use the feature; can be empty.
13
+ # - `block`: the listed requesters may not use the feature; all others are allowed.
14
+ class Optify::RequesterPolicy < FromHashable
15
+ end
16
+
17
+ # Policies that restrict access to a feature based on values in the request's preferences.
18
+ #
19
+ # See https://github.com/juharris/optify#policies for details.
20
+ class Optify::Policies < FromHashable
21
+ end
22
+
23
+ # Raised when a requester is not permitted to use a feature due to its policies.
24
+ class Optify::PolicyDeniedError < StandardError
25
+ end
26
+
9
27
  # DEPRECATED: Use `Optify::FromHashable` instead.
10
28
  # A base class for classes from configuration files.
11
29
  # Classes that derive from this can easily be used with `Optify::OptionsProvider.get_options`
@@ -18,7 +36,10 @@ end
18
36
 
19
37
  # Options for caching.
20
38
  class Optify::CacheOptions
21
- def initialize: () -> (^(::Array[untyped] key, untyped value, bool is_cache_hit) -> void)?
39
+ def initialize: () -> ::Set[String]?
40
+ | () -> ::Set[String]?
41
+ | () -> RequesterPolicy?
42
+ | () -> (^(::Array[untyped] key, untyped value, bool is_cache_hit) -> void)?
22
43
  | (?on_cache_event: (^(::Array[untyped] key, untyped value, bool is_cache_hit) -> void)? on_cache_event) -> void
23
44
  end
24
45
 
@@ -100,6 +121,18 @@ class Optify::GetOptionsPreferences
100
121
  def skip_feature_name_conversion=: (bool value) -> void
101
122
 
102
123
  def skip_feature_name_conversion: () -> bool
124
+
125
+ # The requester identifier to use for policy enforcement.
126
+ # If not set, policy checks are bypassed.
127
+ def requester=: (String? value) -> void
128
+
129
+ def requester: () -> String?
130
+
131
+ # When `true`, raises `PolicyDeniedError` if a feature is not permitted for the requester.
132
+ # When `false` (default), denied features are silently filtered out.
133
+ def raise_if_policy_denied=: (bool value) -> void
134
+
135
+ def raise_if_policy_denied: () -> bool
103
136
  end
104
137
 
105
138
  # A registry of features that provides configurations.
@@ -149,6 +182,8 @@ end
149
182
  # Some of the methods shown within this module are implemented in Rust
150
183
  # and are declared in this common module to avoid duplicate declarations in different classes.
151
184
  module Optify::ProviderModule
185
+ def check_policies: (String requester, ::Array[String] feature_names, ?CacheOptions? cache_options) -> String?
186
+
152
187
  # @param canonical_feature_name [String] A canonical feature name
153
188
  # @return Whether the feature has conditions.
154
189
  def conditions?: (String canonical_feature_name) -> bool
@@ -200,6 +235,11 @@ module Optify::ProviderModule
200
235
 
201
236
  def map_feature_names: (::Array[String] feature_names, GetOptionsPreferences preferences) -> ::Array[String?]
202
237
 
238
+ # @return The policies for the feature, or `nil` if the feature has no policies.
239
+ def get_policies: (String canonical_feature_name) -> Optify::Policies?
240
+
241
+ def _check_policies: (String requester, ::Array[String] feature_names) -> String?
242
+
203
243
  # Map aliases or canonical feature names (perhaps derived from a file names) to the canonical feature names.
204
244
  # Canonical feature names map to themselves.
205
245
  # This implementation calls the Rust implementation directly.
@@ -210,6 +250,9 @@ module Optify::ProviderModule
210
250
 
211
251
  # @return The metadata for the feature.
212
252
  def get_feature_metadata_json: (String canonical_feature_name) -> String?
253
+
254
+ # @return The policies for the feature as JSON, or `nil` if the feature has no policies.
255
+ def get_policies_json: (String canonical_feature_name) -> String?
213
256
  end
214
257
 
215
258
  # Provides configurations based on keys and enabled feature names.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: optify-config
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.23.2
4
+ version: 1.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin D. Harris
@@ -193,6 +193,7 @@ files:
193
193
  - lib/optify_ruby/get_options_preferences.rb
194
194
  - lib/optify_ruby/implementation.rb
195
195
  - lib/optify_ruby/options_metadata.rb
196
+ - lib/optify_ruby/policies.rb
196
197
  - lib/optify_ruby/provider_module.rb
197
198
  - lib/optify_ruby/watcher_implementation.rb
198
199
  - rbi/optify.rbi