contentstack 0.9.2 → 0.10.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: 99ec685f575a322168f56c63337e0ae98ad397a55e6f04a46dd6000785be90ca
4
- data.tar.gz: 5dd2243bea0da8ef6ab411eec56c2128b3c56e000c32180634082d7c2501dd45
3
+ metadata.gz: 7e776d17a2fda85c9e2451aac3c798418b63a0866e0dbff1f6a45ecf35a881af
4
+ data.tar.gz: 55df7efc6ec43bfb3a1e500a2c28ecda534bbe7e9211f1cad62e419f509c35cf
5
5
  SHA512:
6
- metadata.gz: fc0a03ecdd6d58de96e6e98d1e45be6b64086be3f4455aa221cc3d3055ac0691fdecfc0dd2e7bc0628dcef274498cddad34992ff8b26f458c839a7933ae91013
7
- data.tar.gz: 22139d830825ecfab0ff6e662f05ae946f2a87f5b929d51fae13302900a0a08215201c2a08fc023b2b23282a0d9951812003c5c2f2fe6365f395783fe3c0e379
6
+ metadata.gz: 0b19d4521e9d5451bf87f2c6ac385c477a3dcdab171b25c5f1047896f324e64c4bc60085c6cdf8e6cc006d46b4f752351aaf1b42518ae8285b2ed0725987a49a
7
+ data.tar.gz: 057fa0d575a8ae98de2d28f5ce382c1913e0099e3adc128ba2c36cb895d88c9651932f1df06cea8a3bddf158f31d6fc243656d2cbac3fbe4ab36bba2156a55c8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## CHANGELOG
2
2
 
3
+ ## Version 0.10.0
4
+ ### Date: 27th-July-2026
5
+ ### Features
6
+ - Added `variants(variant_uids, branch_name)` on `Contentstack::Entry` and `Contentstack::Query` to fetch entry variants with optional per-request branch scoping. Requests send the `x-cs-variant-uid` header (comma-separated UIDs) and respect stack-level or per-call branch.
7
+
8
+ ------------------------------------------------
9
+
3
10
  ## Version 0.9.2
4
11
  ### Date: 6th-July-2026
5
12
  ### Fix
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contentstack (0.9.2)
4
+ contentstack (0.10.0)
5
5
  activesupport (>= 3.2)
6
6
  contentstack_utils (~> 1.2)
7
7
 
@@ -119,7 +119,7 @@ CHECKSUMS
119
119
  bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
120
120
  concurrent-ruby (1.3.7) sha256=4412caec3a5ea2e5fdc52076724c071a81f2c0593d83b2ac8cbb8ca63b3151b0
121
121
  connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
122
- contentstack (0.9.2)
122
+ contentstack (0.10.0)
123
123
  contentstack_utils (1.2.3) sha256=cf2f5f996eb487559fd2d7d48a99262710f53dec62c84c6e325b9a598cd31ba7
124
124
  crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
125
125
  diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
@@ -73,7 +73,51 @@ module Contentstack
73
73
  fetch_retry(path, query)
74
74
  end
75
75
 
76
+ def self.validate_variant_uids!(variant_uids)
77
+ if variant_uids.nil? || (variant_uids.respond_to?(:empty?) && variant_uids.empty?)
78
+ raise Contentstack::Error.new("Variant UID(s) are required. Provide a variant UID or an array of variant UIDs.")
79
+ end
80
+ unless variant_uids.is_a?(String) || variant_uids.is_a?(Array)
81
+ raise Contentstack::Error.new("Variant UID(s) must be a String or Array of Strings.")
82
+ end
83
+ if variant_uids.is_a?(Array) && variant_uids.any? { |uid| !uid.is_a?(String) || uid.empty? }
84
+ raise Contentstack::Error.new("Variant UID(s) must be a String or Array of Strings.")
85
+ end
86
+ end
87
+
76
88
  private
89
+ def self.prepare_query(q)
90
+ q = (q || {}).dup
91
+ variant_uids = q.delete(:variant_uids)
92
+ branch_override = q.delete(:branch)
93
+ [q, variant_uids, branch_override]
94
+ end
95
+
96
+ def self.format_variant_uids(variant_uids)
97
+ return nil if variant_uids.nil?
98
+
99
+ case variant_uids
100
+ when String
101
+ variant_uids.strip
102
+ when Array
103
+ variant_uids.map(&:to_s).reject(&:empty?).join(', ')
104
+ end
105
+ end
106
+
107
+ def self.resolve_branch(branch_override)
108
+ branch = branch_override
109
+ branch = @branch if branch.nil? || branch.to_s.empty?
110
+ branch
111
+ end
112
+
113
+ def self.apply_variant_headers(params, variant_uids, branch_override)
114
+ formatted = format_variant_uids(variant_uids)
115
+ params["x-cs-variant-uid"] = formatted if formatted && !formatted.empty?
116
+
117
+ branch = resolve_branch(branch_override)
118
+ params["branch"] = branch if !branch.nil? && !branch.empty?
119
+ params
120
+ end
77
121
  def self.fetch_retry(path, query=nil, count=0)
78
122
  response = send_request(path, query)
79
123
  if @errorRetry.include?(response["status_code"].to_i)
@@ -90,7 +134,7 @@ module Contentstack
90
134
  end
91
135
 
92
136
  def self.send_request(path, q=nil)
93
- q ||= {}
137
+ q, variant_uids, branch_override = prepare_query(q)
94
138
 
95
139
  q.merge!(@headers)
96
140
 
@@ -103,9 +147,7 @@ module Contentstack
103
147
  "x-user-agent" => "ruby-sdk/#{Contentstack::VERSION}",
104
148
  "read_timeout" => @timeout
105
149
  }
106
- if !@branch.nil? && !@branch.empty?
107
- params["branch"] = @branch
108
- end
150
+ apply_variant_headers(params, variant_uids, branch_override)
109
151
 
110
152
  if @proxy_details.present? && @proxy_details[:url].present? && @proxy_details[:port].present? && @proxy_details[:username].empty? && @proxy_details[:password].empty?
111
153
  params["proxy"] = URI.parse("http://#{@proxy_details[:url]}:#{@proxy_details[:port]}/").to_s
@@ -130,7 +172,7 @@ module Contentstack
130
172
  end
131
173
 
132
174
  def self.send_preview_request(path, q=nil)
133
- q ||= {}
175
+ q, variant_uids, branch_override = prepare_query(q)
134
176
 
135
177
  q.merge!({live_preview: (!@live_preview.key?(:live_preview) ? 'init' : @live_preview[:live_preview]),})
136
178
 
@@ -143,9 +185,7 @@ module Contentstack
143
185
  "x-user-agent" => "ruby-sdk/#{Contentstack::VERSION}",
144
186
  "read_timeout" => @timeout
145
187
  }
146
- if !@branch.nil? && !@branch.empty?
147
- params["branch"] = @branch
148
- end
188
+ apply_variant_headers(params, variant_uids, branch_override)
149
189
 
150
190
  if @proxy_details.present? && @proxy_details[:url].present? && @proxy_details[:port].present? && @proxy_details[:username].empty? && @proxy_details[:password].empty?
151
191
  params["proxy"] = URI.parse("http://#{@proxy_details[:url]}:#{@proxy_details[:port]}/").to_s
@@ -180,6 +180,26 @@ module Contentstack
180
180
  self
181
181
  end
182
182
 
183
+ # Scope the entry request to one or more entry variants, optionally on a branch.
184
+ #
185
+ # @param [String, Array<String>] variant_uids A variant UID, or an array of variant UIDs
186
+ # @param [String] branch_name Branch name to scope the request (overrides stack-level branch)
187
+ #
188
+ # Example
189
+ #
190
+ # @entry = @stack.content_type('home_page').entry(entry_uid)
191
+ # @entry.variants('xyz', 'branch_name').fetch
192
+ #
193
+ # @entry = @stack.content_type('home_page').entry(entry_uid)
194
+ # @entry.variants(['variant1', 'variant2'], 'branch_name').fetch
195
+ #
196
+ # @return [Contentstack::Entry]
197
+ def variants(variant_uids, branch_name = nil)
198
+ API.validate_variant_uids!(variant_uids)
199
+ @query[:variant_uids] = variant_uids
200
+ @query[:branch] = branch_name if branch_name.is_a?(String) && !branch_name.empty?
201
+ self
202
+ end
183
203
 
184
204
  # Include Embedded Objects (Entries and Assets) along with entry/entries details.
185
205
  #
@@ -552,6 +552,27 @@ module Contentstack
552
552
  self
553
553
  end
554
554
 
555
+ # Scope the entries request to one or more entry variants, optionally on a branch.
556
+ #
557
+ # @param [String, Array<String>] variant_uids A variant UID, or an array of variant UIDs
558
+ # @param [String] branch_name Branch name to scope the request (overrides stack-level branch)
559
+ #
560
+ # Example
561
+ #
562
+ # @query = @stack.content_type('home_page').query
563
+ # @query.variants('xyz', 'branch_name').fetch
564
+ #
565
+ # @query = @stack.content_type('home_page').query
566
+ # @query.variants(['variant1', 'variant2'], 'branch_name').fetch
567
+ #
568
+ # @return [Contentstack::Query]
569
+ def variants(variant_uids, branch_name = nil)
570
+ API.validate_variant_uids!(variant_uids)
571
+ @query[:variant_uids] = variant_uids
572
+ @query[:branch] = branch_name if branch_name.is_a?(String) && !branch_name.empty?
573
+ self
574
+ end
575
+
555
576
  # Include Embedded Objects (Entries and Assets) along with entry/entries details.
556
577
  #
557
578
  # Example
@@ -1,3 +1,3 @@
1
1
  module Contentstack
2
- VERSION = "0.9.2"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'entry variants' do
4
+ let(:client) { create_client }
5
+ let(:branch_client) { create_client(ENV['DELIVERY_TOKEN'], ENV['API_KEY'], ENV['ENVIRONMENT'], { branch: 'stack_branch' }) }
6
+ let(:entry_uid) { 'uid' }
7
+ let(:category_entry) { client.content_type('category').entry(entry_uid) }
8
+ let(:category_query) { client.content_type('category').query }
9
+
10
+ describe Contentstack::Entry do
11
+ it 'stores variant UID and branch on the query' do
12
+ entry = category_entry.variants('variant1', 'branch_name')
13
+ expect(entry.query[:variant_uids]).to eq 'variant1'
14
+ expect(entry.query[:branch]).to eq 'branch_name'
15
+ end
16
+
17
+ it 'stores multiple variant UIDs' do
18
+ entry = category_entry.variants(['variant1', 'variant2'], 'branch_name')
19
+ expect(entry.query[:variant_uids]).to eq ['variant1', 'variant2']
20
+ end
21
+
22
+ it 'raises when variant UIDs are missing' do
23
+ expect { category_entry.variants(nil) }.to raise_error(Contentstack::Error, /Variant UID/)
24
+ expect { category_entry.variants([]) }.to raise_error(Contentstack::Error, /Variant UID/)
25
+ end
26
+
27
+ it 'raises when variant UIDs are invalid' do
28
+ expect { category_entry.variants(123) }.to raise_error(Contentstack::Error, /String or Array/)
29
+ expect { category_entry.variants(['']) }.to raise_error(Contentstack::Error, /String or Array/)
30
+ end
31
+ end
32
+
33
+ describe Contentstack::Query do
34
+ it 'stores variant UID and branch on the query' do
35
+ query = category_query.variants('variant1', 'branch_name')
36
+ expect(query.query[:variant_uids]).to eq 'variant1'
37
+ expect(query.query[:branch]).to eq 'branch_name'
38
+ end
39
+ end
40
+
41
+ describe 'HTTP headers' do
42
+ it 'sends x-cs-variant-uid and branch for a single entry fetch' do
43
+ stub = stub_request(:get, /cdn\.contentstack\.io\/v3\/content_types\/category\/entries\/uid/).
44
+ with { |req|
45
+ req.headers['X-Cs-Variant-Uid'] == 'variant1' &&
46
+ req.headers['Branch'] == 'branch_name'
47
+ }.
48
+ to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/fixtures/category_entry.json'), headers: {})
49
+
50
+ category_entry.variants('variant1', 'branch_name').fetch
51
+ expect(stub).to have_been_requested
52
+ end
53
+
54
+ it 'sends comma-separated variant UIDs for multiple variants' do
55
+ stub = stub_request(:get, /cdn\.contentstack\.io\/v3\/content_types\/category\/entries\/uid/).
56
+ with { |req| req.headers['X-Cs-Variant-Uid'] == 'variant1, variant2' }.
57
+ to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/fixtures/category_entry.json'), headers: {})
58
+
59
+ category_entry.variants(['variant1', 'variant2'], 'branch_name').fetch
60
+ expect(stub).to have_been_requested
61
+ end
62
+
63
+ it 'sends x-cs-variant-uid and branch for an entries query' do
64
+ stub = stub_request(:get, /cdn\.contentstack\.io\/v3\/content_types\/category\/entries/).
65
+ with { |req|
66
+ !req.uri.path.include?('/entries/uid') &&
67
+ req.headers['X-Cs-Variant-Uid'] == 'variant1' &&
68
+ req.headers['Branch'] == 'branch_name'
69
+ }.
70
+ to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/fixtures/category_entry_collection_without_count.json'), headers: {})
71
+
72
+ category_query.variants('variant1', 'branch_name').fetch
73
+ expect(stub).to have_been_requested
74
+ end
75
+
76
+ it 'uses stack-level branch when variants branch is omitted' do
77
+ stub = stub_request(:get, /cdn\.contentstack\.io\/v3\/content_types\/category\/entries\/uid/).
78
+ with { |req|
79
+ req.headers['X-Cs-Variant-Uid'] == 'variant1' &&
80
+ req.headers['Branch'] == 'stack_branch'
81
+ }.
82
+ to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/fixtures/category_entry.json'), headers: {})
83
+
84
+ branch_client.content_type('category').entry(entry_uid).variants('variant1').fetch
85
+ expect(stub).to have_been_requested
86
+ end
87
+
88
+ it 'does not add variant headers when variants is not chained' do
89
+ stub = stub_request(:get, /cdn\.contentstack\.io\/v3\/content_types\/category\/entries\/uid/).
90
+ with { |req| req.headers['X-Cs-Variant-Uid'].nil? }.
91
+ to_return(status: 200, body: File.read(File.dirname(__FILE__) + '/fixtures/category_entry.json'), headers: {})
92
+
93
+ category_entry.fetch
94
+ expect(stub).to have_been_requested
95
+ end
96
+ end
97
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contentstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Contentstack
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-06 00:00:00.000000000 Z
11
+ date: 2026-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -167,6 +167,7 @@ files:
167
167
  - spec/spec_helper.rb
168
168
  - spec/support/load_test_env.rb
169
169
  - spec/sync_spec.rb
170
+ - spec/variants_spec.rb
170
171
  homepage: https://github.com/contentstack/contentstack-ruby
171
172
  licenses:
172
173
  - MIT