devcycle-ruby-server-sdk 3.3.0 → 3.6.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 +4 -4
- data/Gemfile +1 -0
- data/devcycle-ruby-server-sdk.gemspec +3 -3
- data/lib/devcycle-ruby-server-sdk/api/client.rb +2 -0
- data/lib/devcycle-ruby-server-sdk/api/dev_cycle_provider.rb +101 -0
- data/lib/devcycle-ruby-server-sdk/models/user.rb +0 -12
- data/lib/devcycle-ruby-server-sdk/version.rb +1 -13
- data/lib/devcycle-ruby-server-sdk.rb +1 -0
- data/spec/devcycle_provider_spec.rb +132 -0
- metadata +23 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 520c41497b99cda3abbd2225065d138e25e6b97c873fff9ba4d15ddd1e5c98bf
|
4
|
+
data.tar.gz: 83f2db46f2f0fead9a80fe36f45d80f8c07f2e62acf28bd323e2c73d33347fc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74b4643e0af83e4bf59d3675b5c50072c699ea2ee2a85df8ac5b72c9fbc127d0f1e43654fb1ee3af326132ec7ebf5c3c494a5d631eb9f1c9e4a4423e9f59b7cf
|
7
|
+
data.tar.gz: 3b5d8452b13ec2fe10c5ced3ad1684d5f144af8ea3ed9f0b04ac2100a4781543da24f4fce963ddb18549964122f0d62f3e808c6919d264f50a4cdb7286dc428f
|
data/Gemfile
CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.summary = "DevCycle Bucketing API Ruby Gem"
|
22
22
|
s.description = "DevCycle Ruby Server SDK, for interacting with feature flags created with the DevCycle platform."
|
23
23
|
s.license = "MIT"
|
24
|
-
s.required_ruby_version = ">= 3.
|
24
|
+
s.required_ruby_version = ">= 3.2"
|
25
25
|
|
26
26
|
s.add_runtime_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
|
27
27
|
s.add_runtime_dependency 'wasmtime', '20.0.2'
|
@@ -29,8 +29,8 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.add_runtime_dependency 'sorbet-runtime', '>= 0.5.11481'
|
30
30
|
s.add_runtime_dependency 'oj', '~> 3.0'
|
31
31
|
s.add_runtime_dependency 'google-protobuf', '~> 3.22'
|
32
|
-
s.add_runtime_dependency 'ld-eventsource', '~> 2.2.
|
33
|
-
|
32
|
+
s.add_runtime_dependency 'ld-eventsource', '~> 2.2.3'
|
33
|
+
s.add_runtime_dependency 'openfeature-sdk', '~> 0.4.0'
|
34
34
|
|
35
35
|
s.add_development_dependency 'rspec', '~> 3.6', '>= 3.6.0'
|
36
36
|
|
@@ -3,6 +3,7 @@ require 'logger'
|
|
3
3
|
|
4
4
|
module DevCycle
|
5
5
|
class Client
|
6
|
+
attr_reader :open_feature_provider
|
6
7
|
def initialize(sdkKey, dvc_options = Options.new, wait_for_init = false)
|
7
8
|
if sdkKey.nil?
|
8
9
|
raise ArgumentError.new('Missing SDK key!')
|
@@ -23,6 +24,7 @@ module DevCycle
|
|
23
24
|
@local_bucketing = LocalBucketing.new(@sdkKey, dvc_options, wait_for_init)
|
24
25
|
@event_queue = EventQueue.new(@sdkKey, dvc_options.event_queue_options, @local_bucketing)
|
25
26
|
end
|
27
|
+
@open_feature_provider = Provider.new(self)
|
26
28
|
end
|
27
29
|
|
28
30
|
def close
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DevCycle
|
4
|
+
class Provider
|
5
|
+
attr_reader :client
|
6
|
+
def initialize(client)
|
7
|
+
unless client.is_a?(DevCycle::Client)
|
8
|
+
fail ArgumentError('Client must be an instance of DevCycleClient')
|
9
|
+
end
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
def init
|
14
|
+
# We handle all initialization on the DVC Client itself
|
15
|
+
end
|
16
|
+
|
17
|
+
def shutdown
|
18
|
+
@client.close
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_boolean_value(flag_key:, default_value:, evaluation_context: nil)
|
22
|
+
# Retrieve a boolean value from provider source
|
23
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def fetch_string_value(flag_key:, default_value:, evaluation_context: nil)
|
27
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_number_value(flag_key:, default_value:, evaluation_context: nil)
|
31
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_integer_value(flag_key:, default_value:, evaluation_context: nil)
|
35
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_float_value(flag_key:, default_value:, evaluation_context: nil)
|
39
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def fetch_object_value(flag_key:, default_value:, evaluation_context: nil)
|
43
|
+
@client.variable(Provider.user_from_openfeature_context(evaluation_context), flag_key, default_value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.user_from_openfeature_context(context)
|
47
|
+
unless context.is_a?(OpenFeature::SDK::EvaluationContext)
|
48
|
+
raise ArgumentError, "Invalid context type, expected OpenFeature::SDK::EvaluationContext but got #{context.class}"
|
49
|
+
end
|
50
|
+
args = {}
|
51
|
+
if context.field('user_id')
|
52
|
+
args.merge!(user_id: context.field('user_id'))
|
53
|
+
elsif context.field('targeting_key')
|
54
|
+
args.merge!(user_id: context.field('targeting_key'))
|
55
|
+
end
|
56
|
+
customData = {}
|
57
|
+
privateCustomData = {}
|
58
|
+
context.fields.each do |field, value|
|
59
|
+
if field === 'user_id' || field === 'targeting_key'
|
60
|
+
next
|
61
|
+
end
|
62
|
+
if !(field === 'privateCustomData' || field === 'customData') && value.is_a?(Hash)
|
63
|
+
next
|
64
|
+
end
|
65
|
+
case field
|
66
|
+
when 'email'
|
67
|
+
args.merge!(email: value)
|
68
|
+
when 'name'
|
69
|
+
args.merge!(name: value)
|
70
|
+
when 'language'
|
71
|
+
args.merge!(language: value)
|
72
|
+
when 'country'
|
73
|
+
args.merge!(country: value)
|
74
|
+
when 'appVersion'
|
75
|
+
if value.is_a?(String)
|
76
|
+
args.merge!(appVersion: value)
|
77
|
+
end
|
78
|
+
next
|
79
|
+
when 'appBuild'
|
80
|
+
if value.is_a?(Numeric)
|
81
|
+
args.merge!(appBuild: value)
|
82
|
+
end
|
83
|
+
when 'customData'
|
84
|
+
if value.is_a?(Hash)
|
85
|
+
customData.merge!(value)
|
86
|
+
end
|
87
|
+
next
|
88
|
+
when 'privateCustomData'
|
89
|
+
if value.is_a?(Hash)
|
90
|
+
privateCustomData.merge!(value)
|
91
|
+
end
|
92
|
+
else
|
93
|
+
customData.merge!(field => value)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
args.merge!(customData: customData)
|
97
|
+
args.merge!(privateCustomData: privateCustomData)
|
98
|
+
User.new(**args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -1,15 +1,3 @@
|
|
1
|
-
=begin
|
2
|
-
#DevCycle Bucketing API
|
3
|
-
|
4
|
-
#Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs.
|
5
|
-
|
6
|
-
The version of the OpenAPI document: 1.0.0
|
7
|
-
|
8
|
-
Generated by: https://openapi-generator.tech
|
9
|
-
OpenAPI Generator version: 5.3.0
|
10
|
-
|
11
|
-
=end
|
12
|
-
|
13
1
|
require 'date'
|
14
2
|
require 'time'
|
15
3
|
require 'oj'
|
@@ -1,15 +1,3 @@
|
|
1
|
-
=begin
|
2
|
-
#DevCycle Bucketing API
|
3
|
-
|
4
|
-
#Documents the DevCycle Bucketing API which provides and API interface to User Bucketing and for generated SDKs.
|
5
|
-
|
6
|
-
The version of the OpenAPI document: 1.0.0
|
7
|
-
|
8
|
-
Generated by: https://openapi-generator.tech
|
9
|
-
OpenAPI Generator version: 5.3.0
|
10
|
-
|
11
|
-
=end
|
12
|
-
|
13
1
|
module DevCycle
|
14
|
-
VERSION = '3.
|
2
|
+
VERSION = '3.6.0'
|
15
3
|
end
|
@@ -27,6 +27,7 @@ require 'devcycle-ruby-server-sdk/models/variable'
|
|
27
27
|
|
28
28
|
# APIs
|
29
29
|
require 'devcycle-ruby-server-sdk/api/client'
|
30
|
+
require 'devcycle-ruby-server-sdk/api/dev_cycle_provider'
|
30
31
|
|
31
32
|
require 'devcycle-ruby-server-sdk/localbucketing/options'
|
32
33
|
require 'devcycle-ruby-server-sdk/localbucketing/local_bucketing'
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'open_feature/sdk'
|
5
|
+
|
6
|
+
context 'user_from_openfeature_context' do
|
7
|
+
context 'user_id' do
|
8
|
+
|
9
|
+
it 'returns a user with the user_id from the context' do
|
10
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id')
|
11
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
12
|
+
expect(user.user_id).to eq('user_id')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns a user with the targeting_key from the context' do
|
16
|
+
context = OpenFeature::SDK::EvaluationContext.new(targeting_key: 'targeting_key')
|
17
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
18
|
+
expect(user.user_id).to eq('targeting_key')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
context 'email' do
|
22
|
+
it 'returns a user with a valid user_id and email' do
|
23
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', email: 'email')
|
24
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
25
|
+
expect(user.user_id).to eq('user_id')
|
26
|
+
expect(user.email).to eq('email')
|
27
|
+
end
|
28
|
+
it 'returns a user with a valid targeting_key and email' do
|
29
|
+
context = OpenFeature::SDK::EvaluationContext.new(targeting_key: 'targeting_key', email: 'email')
|
30
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
31
|
+
expect(user.user_id).to eq('targeting_key')
|
32
|
+
expect(user.email).to eq('email')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'customData' do
|
37
|
+
it 'returns a user with customData' do
|
38
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', customData: { 'key' => 'value' })
|
39
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
40
|
+
expect(user.user_id).to eq('user_id')
|
41
|
+
expect(user.customData).to eq({ 'key' => 'value' })
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'privateCustomData' do
|
46
|
+
it 'returns a user with privateCustomData' do
|
47
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', privateCustomData: { 'key' => 'value' })
|
48
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
49
|
+
expect(user.user_id).to eq('user_id')
|
50
|
+
expect(user.privateCustomData).to eq({ 'key' => 'value' })
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'appVersion' do
|
55
|
+
it 'returns a user with appVersion' do
|
56
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', appVersion: '1.0.0')
|
57
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
58
|
+
expect(user.user_id).to eq('user_id')
|
59
|
+
expect(user.appVersion).to eq('1.0.0')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns a user with appBuild' do
|
63
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', appBuild: 1)
|
64
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
65
|
+
expect(user.user_id).to eq('user_id')
|
66
|
+
expect(user.appBuild).to eq(1)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'randomFields' do
|
70
|
+
it 'returns a user with customData fields mapped to any non-standard fields' do
|
71
|
+
context = OpenFeature::SDK::EvaluationContext.new(user_id: 'user_id', randomField: 'value')
|
72
|
+
user = DevCycle::Provider.user_from_openfeature_context(context)
|
73
|
+
expect(user.user_id).to eq('user_id')
|
74
|
+
expect(user.customData).to eq({ 'randomField' => 'value' })
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'provider' do
|
79
|
+
before(:all) do
|
80
|
+
@dvc = DevCycle::Client.new('dvc_server_token_hash')
|
81
|
+
OpenFeature::SDK.configure do |config|
|
82
|
+
config.set_provider(@dvc.open_feature_provider)
|
83
|
+
end
|
84
|
+
@client = OpenFeature::SDK.build_client
|
85
|
+
sleep(3)
|
86
|
+
end
|
87
|
+
it 'returns a provider with a valid client' do
|
88
|
+
provider = @dvc.open_feature_provider
|
89
|
+
expect(provider).to be_instance_of(DevCycle::Provider)
|
90
|
+
end
|
91
|
+
it 'responds properly to fetch_boolean_value' do
|
92
|
+
expect(@client.fetch_boolean_value(flag_key: 'flag_key', default_value: false, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to be(false)
|
93
|
+
end
|
94
|
+
it 'responds properly to fetch_string_value' do
|
95
|
+
expect(@client.fetch_string_value(flag_key: 'flag_key', default_value: 'default', evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to eq('default')
|
96
|
+
end
|
97
|
+
it 'responds properly to fetch_number_value' do
|
98
|
+
expect(@client.fetch_number_value(flag_key: 'flag_key', default_value: 1, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to eq(1)
|
99
|
+
end
|
100
|
+
it 'responds properly to fetch_integer_value' do
|
101
|
+
expect(@client.fetch_integer_value(flag_key: 'flag_key', default_value: 1, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to eq(1)
|
102
|
+
end
|
103
|
+
it 'responds properly to fetch_float_value' do
|
104
|
+
expect(@client.fetch_float_value(flag_key: 'flag_key', default_value: 1.0, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to eq(1.0)
|
105
|
+
end
|
106
|
+
it 'responds properly to fetch_object_value' do
|
107
|
+
expect(@client.fetch_object_value(flag_key: 'flag_key', default_value: { 'key' => 'value' }, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'user_id'))).to eq({ 'key' => 'value' })
|
108
|
+
end
|
109
|
+
it 'returns a provider with a valid client' do
|
110
|
+
provider = @dvc.open_feature_provider
|
111
|
+
expect(provider).to be_instance_of(DevCycle::Provider)
|
112
|
+
end
|
113
|
+
it 'responds properly to fetch_boolean_value' do
|
114
|
+
expect(@client.fetch_boolean_value(flag_key: 'test', default_value: false, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).to be(true)
|
115
|
+
end
|
116
|
+
it 'responds properly to fetch_string_value' do
|
117
|
+
expect(@client.fetch_string_value(flag_key: 'test-string-variable', default_value: 'default', evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).not_to eq('default')
|
118
|
+
end
|
119
|
+
it 'responds properly to fetch_number_value' do
|
120
|
+
expect(@client.fetch_number_value(flag_key: 'test-number-variable', default_value: 1, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).not_to eq(1)
|
121
|
+
end
|
122
|
+
it 'responds properly to fetch_integer_value' do
|
123
|
+
expect(@client.fetch_integer_value(flag_key: 'test-number-variable', default_value: 1, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).not_to eq(1)
|
124
|
+
end
|
125
|
+
it 'responds properly to fetch_float_value' do
|
126
|
+
expect(@client.fetch_float_value(flag_key: 'test-float-variable', default_value: 1.0, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).not_to eq(1.0)
|
127
|
+
end
|
128
|
+
it 'responds properly to fetch_object_value' do
|
129
|
+
expect(@client.fetch_object_value(flag_key: 'test-json-variable', default_value: { 'key' => 'value' }, evaluation_context: OpenFeature::SDK::EvaluationContext.new(user_id:'test'))).not_to eq({ 'key' => 'value' })
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devcycle-ruby-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DevCycleHQ
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-
|
10
|
+
date: 2025-03-14 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: typhoeus
|
@@ -106,14 +105,28 @@ dependencies:
|
|
106
105
|
requirements:
|
107
106
|
- - "~>"
|
108
107
|
- !ruby/object:Gem::Version
|
109
|
-
version: 2.2.
|
108
|
+
version: 2.2.3
|
110
109
|
type: :runtime
|
111
110
|
prerelease: false
|
112
111
|
version_requirements: !ruby/object:Gem::Requirement
|
113
112
|
requirements:
|
114
113
|
- - "~>"
|
115
114
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2.2.
|
115
|
+
version: 2.2.3
|
116
|
+
- !ruby/object:Gem::Dependency
|
117
|
+
name: openfeature-sdk
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - "~>"
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 0.4.0
|
123
|
+
type: :runtime
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 0.4.0
|
117
130
|
- !ruby/object:Gem::Dependency
|
118
131
|
name: rspec
|
119
132
|
requirement: !ruby/object:Gem::Requirement
|
@@ -148,6 +161,7 @@ files:
|
|
148
161
|
- devcycle-ruby-server-sdk.gemspec
|
149
162
|
- lib/devcycle-ruby-server-sdk.rb
|
150
163
|
- lib/devcycle-ruby-server-sdk/api/client.rb
|
164
|
+
- lib/devcycle-ruby-server-sdk/api/dev_cycle_provider.rb
|
151
165
|
- lib/devcycle-ruby-server-sdk/api_client.rb
|
152
166
|
- lib/devcycle-ruby-server-sdk/api_error.rb
|
153
167
|
- lib/devcycle-ruby-server-sdk/configuration.rb
|
@@ -175,12 +189,12 @@ files:
|
|
175
189
|
- spec/api/devcycle_api_spec.rb
|
176
190
|
- spec/api_client_spec.rb
|
177
191
|
- spec/configuration_spec.rb
|
192
|
+
- spec/devcycle_provider_spec.rb
|
178
193
|
- spec/spec_helper.rb
|
179
194
|
homepage: https://devcycle.com
|
180
195
|
licenses:
|
181
196
|
- MIT
|
182
197
|
metadata: {}
|
183
|
-
post_install_message:
|
184
198
|
rdoc_options: []
|
185
199
|
require_paths:
|
186
200
|
- lib
|
@@ -188,19 +202,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
188
202
|
requirements:
|
189
203
|
- - ">="
|
190
204
|
- !ruby/object:Gem::Version
|
191
|
-
version: '3.
|
205
|
+
version: '3.2'
|
192
206
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
207
|
requirements:
|
194
208
|
- - ">="
|
195
209
|
- !ruby/object:Gem::Version
|
196
210
|
version: '0'
|
197
211
|
requirements: []
|
198
|
-
rubygems_version: 3.
|
199
|
-
signing_key:
|
212
|
+
rubygems_version: 3.6.2
|
200
213
|
specification_version: 4
|
201
214
|
summary: DevCycle Bucketing API Ruby Gem
|
202
215
|
test_files:
|
203
216
|
- spec/api/devcycle_api_spec.rb
|
204
217
|
- spec/api_client_spec.rb
|
205
218
|
- spec/configuration_spec.rb
|
219
|
+
- spec/devcycle_provider_spec.rb
|
206
220
|
- spec/spec_helper.rb
|