openfga 0.2.0 → 0.3.1

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: 0e25bb28bc13a0c2c3ff7dae1f0122f38bf310d0d3221c474c2bdef57c0582aa
4
- data.tar.gz: 8580e442c77504d629d19811f2059ee7cdb76f2edbc961bd1ce9bf1f1337f256
3
+ metadata.gz: e86ca5c3b828544d91af3d2ef40b045e6df1278c230a4118e4927fd55d3f04fd
4
+ data.tar.gz: 40844f4bce13ec832e5e029036c1c662fdb44c46a2492b4566b7e5cd775c0908
5
5
  SHA512:
6
- metadata.gz: 6019181013768317662a4bce8299b70c4966404bf63b280a3db203331fc1fca14d38448a2dc0d4f73fbc51aebd41a8d48b498b15b5675ff6cd560df7558142d7
7
- data.tar.gz: c1ebfdf0b2df021b497a984c206e9abb0795b12dee79443d73af55673e4e0333a8fbcb4d893bbf764512498862cfa781465c8e0f27a4abba40ffdd809c343170
6
+ metadata.gz: 55460e092ba8ec56bd63cc31ae198b359f97fa310de08882c7600d243aa638787dd3bc9e622442f2ce4068b26d01f9d3f030548abb5d405fcf5a9ad66e983e05
7
+ data.tar.gz: 07e96741790c259696adfd345d64f31a4d4efc4b5ea47cbd1fd13755e36ef0957c2cc3454163525c1ab553bea03ce1d67135500b07f7e923d6dd46cc53c79b99
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/carlastabile/openfga-ruby-sdk/compare/openfga/v0.3.0...openfga/v0.3.1) (2026-06-26)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * empty rake task to fix publishing ([#39](https://github.com/carlastabile/openfga-ruby-sdk/issues/39)) ([f789069](https://github.com/carlastabile/openfga-ruby-sdk/commit/f7890690cef60d4374f9d09276ae42664a65b065))
9
+
10
+ ## [0.3.0](https://github.com/carlastabile/openfga-ruby-sdk/compare/openfga/v0.2.0...openfga/v0.3.0) (2026-06-25)
11
+
12
+
13
+ ### Features
14
+
15
+ * add execute_api_request for calling arbitrary API endpoints ([#34](https://github.com/carlastabile/openfga-ruby-sdk/issues/34)) ([c5a6f8f](https://github.com/carlastabile/openfga-ruby-sdk/commit/c5a6f8f2c9d7a3f234cc3903fadc14f16e8b8ce7))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * custom logger initialization and usage ([#36](https://github.com/carlastabile/openfga-ruby-sdk/issues/36)) ([50b9d34](https://github.com/carlastabile/openfga-ruby-sdk/commit/50b9d34e8a9cbb0a53a1be65867ddbd437757511))
21
+
3
22
  ## [0.2.0](https://github.com/carlastabile/openfga-ruby-sdk/compare/openfga/v0.1.5...openfga/v0.2.0) (2026-04-17)
4
23
 
5
24
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- openfga (0.2.0)
4
+ openfga (0.3.1)
5
5
  concurrent-ruby
6
6
  faraday (>= 1.0.1, < 3.0)
7
7
  faraday-multipart
data/Rakefile CHANGED
@@ -1,5 +1,9 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
+ # release-please already pushes the tag and commit, so skip bundler's git push
4
+ Rake::Task['release:source_control_push'].clear
5
+ task 'release:source_control_push' do; end
6
+
3
7
  begin
4
8
  require 'rspec/core/rake_task'
5
9
 
@@ -0,0 +1,83 @@
1
+ # Example: API Executor
2
+
3
+ This example demonstrates using `SdkClient#execute_api_request` to call arbitrary OpenFGA API endpoints. Rather than using the SDK's typed methods, you supply an HTTP method, path, and optional parameters directly - useful for endpoints not yet covered by the SDK or for advanced use cases.
4
+
5
+ ## 1. Prerequisites
6
+
7
+ - Ruby 3.x (uses `bundler/inline` - no separate `bundle install` step needed)
8
+ - A running OpenFGA server
9
+
10
+ ## 2. Start OpenFGA (Local Dev)
11
+
12
+ ```bash
13
+ docker run -p 8080:8080 --name openfga openfga/openfga:latest run
14
+ ```
15
+
16
+ Or from the repo root:
17
+
18
+ ```bash
19
+ make start-openfga
20
+ ```
21
+
22
+ ## 3. Run the Example
23
+
24
+ From the repo root:
25
+
26
+ ```bash
27
+ ruby example/api-executor/main.rb
28
+ ```
29
+
30
+ Or from this directory:
31
+
32
+ ```bash
33
+ ruby main.rb
34
+ ```
35
+
36
+ ### With a store ID (enables the check example)
37
+
38
+ ```bash
39
+ FGA_STORE_ID=<your-store-id> ruby main.rb
40
+ ```
41
+
42
+ ### With an API token
43
+
44
+ ```bash
45
+ FGA_API_TOKEN=<your-token> FGA_STORE_ID=<your-store-id> ruby main.rb
46
+ ```
47
+
48
+ ## 4. Environment Variables
49
+
50
+ | Variable | Required | Description | Default |
51
+ |-----------------|----------|---------------------------------------------------|--------------------------|
52
+ | `FGA_API_URL` | No | Base URL of the OpenFGA server | `http://localhost:8080` |
53
+ | `FGA_STORE_ID` | No | Store ID for path-param and check examples | *(skips those examples)* |
54
+ | `FGA_API_TOKEN` | No | Bearer token for authenticated requests | *(no auth)* |
55
+
56
+ ## 5. What the Example Covers
57
+
58
+ | Example | Method | Path | Demonstrates |
59
+ |---------|--------|--------------------------------|-------------------------------------|
60
+ | 1 | GET | `/stores` | Query params, basic response access |
61
+ | 2 | GET | `/stores/{store_id}` | Path param substitution |
62
+ | 3 | POST | `/stores/{store_id}/check` | Request body, custom headers |
63
+ | 4 | GET | `/stores/nonexistent-store-id` | Error handling with `ApiError` |
64
+
65
+ ## 6. API Reference
66
+
67
+ ```ruby
68
+ response = client.execute_api_request(
69
+ method: :post, # HTTP verb (symbol or string)
70
+ path: '/stores/{store_id}/check', # Path template
71
+ path_params: { store_id: 'abc123' }, # Substituted into {placeholders}
72
+ query_params: { consistency: 'STRONG' }, # Appended to the URL
73
+ body: { tuple_key: { ... } }, # JSON-serialized automatically
74
+ headers: { 'X-Request-ID' => 'xyz' } # Merged with SDK defaults
75
+ )
76
+
77
+ response.status # Integer HTTP status code
78
+ response.data # Hash with symbolized keys (parsed JSON body)
79
+ response.headers # Response headers
80
+ response.success? # true for 2xx
81
+ ```
82
+
83
+ Auth headers (`Authorization: Bearer ...`) are injected automatically from the client's configured credentials.
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/inline'
5
+
6
+ gemfile do
7
+ source 'https://rubygems.org'
8
+ gem 'json'
9
+ gem 'openfga', path: File.expand_path('../../', __dir__)
10
+ gem 'logger'
11
+ end
12
+
13
+ # OpenFGA API Executor Example
14
+ #
15
+ # Demonstrates how to call arbitrary OpenFGA API endpoints using
16
+ # SdkClient#execute_api_request. Auth headers are injected automatically,
17
+ # and path parameters are substituted and URL-encoded.
18
+ #
19
+ # Usage:
20
+ # FGA_API_URL=http://localhost:8080 ruby example/api-executor/main.rb
21
+ #
22
+ # Optional env vars:
23
+ # FGA_STORE_ID — run the check and get-store examples
24
+ # FGA_API_TOKEN — authenticate with a bearer token
25
+
26
+ logger = Logger.new($stdout)
27
+ logger.level = Logger::INFO
28
+
29
+ api_url = ENV.fetch('FGA_API_URL', 'http://localhost:8080')
30
+ store_id = ENV.fetch('FGA_STORE_ID', nil)
31
+ api_token = ENV.fetch('FGA_API_TOKEN', nil)
32
+
33
+ credentials = if api_token
34
+ { method: :api_token, api_token: }
35
+ else
36
+ { method: :none }
37
+ end
38
+
39
+ client = OpenFga::SdkClient.new(
40
+ api_url:,
41
+ store_id:,
42
+ credentials:,
43
+ logger:
44
+ )
45
+
46
+ # --- Example 1: GET /stores (no path params, with query params) ---
47
+ logger.info '=== Example 1: List stores ==='
48
+
49
+ response = client.execute_api_request(
50
+ method: :get,
51
+ path: '/stores',
52
+ query_params: { page_size: 5 }
53
+ )
54
+ logger.info "Status : #{response.status}"
55
+ logger.info "Success: #{response.success?}"
56
+ logger.info "Stores : #{response.data[:stores]&.length || 0} found"
57
+
58
+ # --- Example 2: GET /stores/{store_id} (path param substitution) ---
59
+ if store_id
60
+ logger.info '=== Example 2: Get store by ID ==='
61
+
62
+ response = client.execute_api_request(
63
+ method: :get,
64
+ path: '/stores/{store_id}',
65
+ path_params: { store_id: }
66
+ )
67
+ logger.info "Status : #{response.status}"
68
+ logger.info "Store name: #{response.data[:name]}"
69
+ else
70
+ logger.warn 'Set FGA_STORE_ID to run example 2'
71
+ end
72
+
73
+ # --- Example 3: POST using the block builder, with custom headers ---
74
+ if store_id
75
+ logger.info '=== Example 3: Check authorization via block builder ==='
76
+
77
+ response = client.execute_api_request(
78
+ method: :post,
79
+ path: '/stores/{store_id}/check',
80
+ path_params: { store_id: },
81
+ body: {
82
+ tuple_key: {
83
+ user: 'user:anne',
84
+ relation: 'reader',
85
+ object: 'document:2021-budget'
86
+ }
87
+ },
88
+ headers: { 'X-Request-ID' => 'api-executor-example-001' }
89
+ )
90
+ logger.info "Status : #{response.status}"
91
+ logger.info "Allowed: #{response.data[:allowed]}"
92
+ else
93
+ logger.warn 'Set FGA_STORE_ID to run example 3'
94
+ end
95
+
96
+ # --- Example 4: Error handling ---
97
+ logger.info '=== Example 4: Error handling ==='
98
+
99
+ begin
100
+ client.execute_api_request(method: :get, path: '/stores/nonexistent-store-id')
101
+ rescue OpenFga::ApiError => e
102
+ logger.info "Caught ApiError — HTTP #{e.code}"
103
+ end
@@ -18,7 +18,7 @@ require 'ulid'
18
18
  # This example demonstrates how to use the OpenFGA Ruby SDK to interact with an OpenFGA server.
19
19
  class OpenFgaExample
20
20
  def initialize
21
- @logger = Logger.new($stdout)
21
+ @logger = Logger.new(STDOUT)
22
22
  @logger.level = Logger::INFO
23
23
 
24
24
  # Initialize the OpenFGA client
@@ -105,7 +105,8 @@ class OpenFgaExample
105
105
  # Update client with store_id
106
106
  @client = OpenFga::SdkClient.new(
107
107
  api_url: ENV.fetch('FGA_API_URL', 'http://localhost:8080'),
108
- store_id: @store_id
108
+ store_id: @store_id,
109
+ logger: @logger
109
110
  )
110
111
  end
111
112
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenFga
4
+ class ApiExecutorRequest
5
+ attr_accessor :method, :path, :path_params, :query_params, :body, :headers
6
+
7
+ def initialize(method:, path:, path_params: {}, query_params: {}, body: nil, headers: {})
8
+ @method = method
9
+ @path = path
10
+ @path_params = path_params || {}
11
+ @query_params = query_params || {}
12
+ @body = body
13
+ @headers = headers || {}
14
+ end
15
+
16
+ def self.build
17
+ req = new(method: nil, path: nil)
18
+ yield req
19
+ req
20
+ end
21
+
22
+ def validate!
23
+ raise ArgumentError, 'ApiExecutorRequest#method is required' if @method.nil?
24
+ raise ArgumentError, 'ApiExecutorRequest#path is required' if @path.nil? || @path.empty?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenFga
4
+ class ApiExecutorResponse
5
+ attr_reader :data, :status, :headers
6
+
7
+ def initialize(data:, status:, headers:)
8
+ @data = data
9
+ @status = status
10
+ @headers = headers
11
+ end
12
+
13
+ def success?
14
+ !@status.nil? && @status >= 200 && @status < 300
15
+ end
16
+ end
17
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
3
4
  require 'concurrent'
4
5
  require 'set'
5
6
 
@@ -17,7 +18,8 @@ module OpenFga
17
18
  method: :none
18
19
  }
19
20
 
20
- @logger = config[:logger] || Logger.new($stdout)
21
+ @logger = new_logger(@config)
22
+ @logger.debug('Using custom logger instance') if @config[:logger]
21
23
 
22
24
  # Later we can support custom token managers.
23
25
  @token_manager = case @config[:credentials][:method]
@@ -47,6 +49,7 @@ module OpenFga
47
49
  c.host = @config[:api_url]
48
50
  c.scheme = URI(@config[:api_url]).scheme
49
51
  c.logger = @logger
52
+ c.debugging = true if @config[:logger]
50
53
  end
51
54
 
52
55
  @api_client = OpenFga::OpenFgaApi.new(ApiClient.new(api_client_config))
@@ -464,6 +467,39 @@ module OpenFga
464
467
  @api_client.write_authorization_model(store_id(opts), request_body, wrap_options(opts))
465
468
  end
466
469
 
470
+ # Executes a raw API request against the FGA server with automatic auth injection.
471
+ # @param method [String, Symbol] HTTP method (e.g. :get, :post)
472
+ # @param path [String] URL path template (e.g. '/stores/{store_id}/check')
473
+ # @param path_params [Hash] Values for {placeholder} substitution
474
+ # @param query_params [Hash] URL query parameters
475
+ # @param body [Hash, nil] Request body (JSON-serialized automatically)
476
+ # @param headers [Hash] Additional request headers
477
+ # @return [ApiExecutorResponse]
478
+ def execute_api_request(method:, path:, path_params: {}, query_params: {}, body: nil, headers: {})
479
+ request = ApiExecutorRequest.new(
480
+ method:,
481
+ path:,
482
+ path_params:,
483
+ query_params:,
484
+ body:,
485
+ headers:
486
+ )
487
+ request.validate!
488
+
489
+ resolved_path = substitute_path_params(request.path, request.path_params)
490
+ merged_headers = build_auth_headers.merge(request.headers)
491
+
492
+ opts = {
493
+ header_params: merged_headers,
494
+ query_params: request.query_params,
495
+ body: request.body,
496
+ return_type: 'Object'
497
+ }
498
+
499
+ data, status, response_headers = @api_client.api_client.call_api(request.method, resolved_path, opts)
500
+ ApiExecutorResponse.new(data:, status:, headers: response_headers)
501
+ end
502
+
467
503
  private
468
504
 
469
505
  # Executes a single batch check (used when no splitting is needed)
@@ -580,5 +616,20 @@ module OpenFga
580
616
  'Authorization' => "Bearer #{token}"
581
617
  }
582
618
  end
619
+
620
+ def substitute_path_params(path, path_params)
621
+ path.gsub(/\{(\w+)\}/) do
622
+ key = $1
623
+ value = path_params[key] || path_params[key.to_sym]
624
+ value ? CGI.escape(value.to_s) : "{#{key}}"
625
+ end
626
+ end
627
+
628
+ def new_logger(config)
629
+ logger = Logger.new($stdout)
630
+ logger.level = Logger::INFO
631
+
632
+ config[:logger] || (defined?(Rails) ? Rails.logger : logger)
633
+ end
583
634
  end
584
635
  end
@@ -12,5 +12,9 @@ require 'openfga/configuration'
12
12
  require 'openfga/client/client_errors'
13
13
  require 'openfga/client/openfga_client'
14
14
 
15
+ # Client models
16
+ require 'openfga/client/models/api_executor_request'
17
+ require 'openfga/client/models/api_executor_response'
18
+
15
19
  # Token management
16
20
  require 'openfga/token_manager/token_manager'
@@ -55,7 +55,7 @@ module OpenFga
55
55
  @config = config
56
56
  @access_token_expires_at = nil
57
57
  @access_token = nil
58
- @logger = config.logger || Logger.new($stdout)
58
+ @logger = config.logger || Logger.new(STDOUT)
59
59
  end
60
60
 
61
61
  def access_token
@@ -63,7 +63,7 @@ module OpenFga
63
63
  return @access_token
64
64
  end
65
65
 
66
- @logger.info "Refreshing access token from #{@config.token_issuer}"
66
+ @logger.debug "Refreshing access token from #{@config.token_issuer}"
67
67
 
68
68
  form_data = {
69
69
  'grant_type' => 'client_credentials',
@@ -92,7 +92,7 @@ module OpenFga
92
92
  @access_token = body['access_token']
93
93
  @access_token_expires_at = Time.now.utc + body['expires_in'].to_i
94
94
 
95
- @logger.info "Obtained new access token, expires at #{@access_token_expires_at}"
95
+ @logger.debug "Obtained new access token, expires at #{@access_token_expires_at}"
96
96
 
97
97
  @access_token
98
98
  else raise TokenRefreshError.new("Failed to obtain access token: #{response.code} #{response.body}")
@@ -12,5 +12,5 @@ Generator version: 6.4.0
12
12
  =end
13
13
 
14
14
  module OpenFga
15
- VERSION = '0.2.0'
15
+ VERSION = '0.3.1'
16
16
  end
@@ -0,0 +1,358 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenFga::ApiExecutorRequest do
4
+ describe '.new' do
5
+ it 'accepts keyword arguments' do
6
+ req = described_class.new(
7
+ method: :get,
8
+ path: '/stores',
9
+ query_params: { page_size: 10 }
10
+ )
11
+ expect(req.method).to eq(:get)
12
+ expect(req.path).to eq('/stores')
13
+ expect(req.query_params).to eq({ page_size: 10 })
14
+ end
15
+
16
+ it 'defaults path_params, query_params, and headers to empty hashes' do
17
+ req = described_class.new(method: :get, path: '/stores')
18
+ expect(req.path_params).to eq({})
19
+ expect(req.query_params).to eq({})
20
+ expect(req.headers).to eq({})
21
+ end
22
+
23
+ it 'defaults body to nil' do
24
+ req = described_class.new(method: :get, path: '/stores')
25
+ expect(req.body).to be_nil
26
+ end
27
+ end
28
+
29
+ describe '#validate!' do
30
+ it 'raises ArgumentError when method is nil' do
31
+ req = described_class.new(method: nil, path: '/stores')
32
+ expect { req.validate! }.to raise_error(ArgumentError, /method is required/)
33
+ end
34
+
35
+ it 'raises ArgumentError when path is empty' do
36
+ req = described_class.new(method: :get, path: '')
37
+ expect { req.validate! }.to raise_error(ArgumentError, /path is required/)
38
+ end
39
+
40
+ it 'does not raise when method and path are present' do
41
+ req = described_class.new(method: :get, path: '/stores')
42
+ expect { req.validate! }.not_to raise_error
43
+ end
44
+ end
45
+ end
46
+
47
+ describe OpenFga::ApiExecutorResponse do
48
+ it 'exposes data, status, and headers' do
49
+ resp = described_class.new(data: { allowed: true }, status: 200, headers: { 'x-foo' => 'bar' })
50
+ expect(resp.data).to eq({ allowed: true })
51
+ expect(resp.status).to eq(200)
52
+ expect(resp.headers).to eq({ 'x-foo' => 'bar' })
53
+ end
54
+
55
+ it '#success? returns true for 2xx' do
56
+ expect(described_class.new(data: nil, status: 200, headers: {}).success?).to be(true)
57
+ expect(described_class.new(data: nil, status: 201, headers: {}).success?).to be(true)
58
+ end
59
+
60
+ it '#success? returns false for non-2xx' do
61
+ expect(described_class.new(data: nil, status: 404, headers: {}).success?).to be(false)
62
+ expect(described_class.new(data: nil, status: 500, headers: {}).success?).to be(false)
63
+ end
64
+ end
65
+
66
+ describe OpenFga::SdkClient, '#execute_api_request' do
67
+ let(:api_url) { 'http://localhost:8090' }
68
+ let(:store_id) { '01JSKYVY76JYW2DG65NG1444T4' }
69
+ let(:subject) { OpenFga::SdkClient.new(api_url:, store_id:) }
70
+
71
+ let(:subject_with_token) do
72
+ OpenFga::SdkClient.new(
73
+ api_url:,
74
+ store_id:,
75
+ credentials: { method: :api_token, api_token: 'my-token' }
76
+ )
77
+ end
78
+
79
+ # --- input validation ---
80
+
81
+ it 'raises ArgumentError when method is missing' do
82
+ expect { subject.execute_api_request(path: '/stores') }
83
+ .to raise_error(ArgumentError)
84
+ end
85
+
86
+ it 'raises ArgumentError when path is empty' do
87
+ expect { subject.execute_api_request(method: :get, path: '') }
88
+ .to raise_error(ArgumentError, /path is required/)
89
+ end
90
+
91
+ # --- return value ---
92
+
93
+ it 'returns an ApiExecutorResponse with data, status, and headers' do
94
+ stub_request(:get, "#{api_url}/stores")
95
+ .to_return(
96
+ status: 200,
97
+ body: { stores: [] }.to_json,
98
+ headers: { 'Content-Type' => 'application/json', 'X-Request-Id' => 'abc' }
99
+ )
100
+
101
+ resp = subject.execute_api_request(method: :get, path: '/stores')
102
+
103
+ expect(resp).to be_a(OpenFga::ApiExecutorResponse)
104
+ expect(resp.status).to eq(200)
105
+ expect(resp.data).to eq({ stores: [] })
106
+ expect(resp.headers).to include('X-Request-Id' => 'abc')
107
+ expect(resp.success?).to be(true)
108
+ end
109
+
110
+ it 'returns nil data for a 204 No Content response' do
111
+ stub_request(:delete, "#{api_url}/stores/#{store_id}")
112
+ .to_return(status: 204, body: '', headers: {})
113
+
114
+ resp = subject.execute_api_request(
115
+ method: :delete,
116
+ path: '/stores/{store_id}',
117
+ path_params: { store_id: }
118
+ )
119
+
120
+ expect(resp.status).to eq(204)
121
+ expect(resp.data).to be_nil
122
+ expect(resp.success?).to be(true)
123
+ end
124
+
125
+ # --- HTTP methods ---
126
+
127
+ it 'sends a PUT request' do
128
+ stub = stub_request(:put, "#{api_url}/stores/#{store_id}")
129
+ .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
130
+
131
+ subject.execute_api_request(
132
+ method: :put,
133
+ path: '/stores/{store_id}',
134
+ path_params: { store_id: },
135
+ body: { name: 'updated' }
136
+ )
137
+
138
+ expect(stub).to have_been_requested
139
+ end
140
+
141
+ it 'sends a PATCH request' do
142
+ stub = stub_request(:patch, "#{api_url}/stores/#{store_id}")
143
+ .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
144
+
145
+ subject.execute_api_request(
146
+ method: :patch,
147
+ path: '/stores/{store_id}',
148
+ path_params: { store_id: },
149
+ body: { name: 'patched' }
150
+ )
151
+
152
+ expect(stub).to have_been_requested
153
+ end
154
+
155
+ it 'sends a DELETE request' do
156
+ stub = stub_request(:delete, "#{api_url}/stores/#{store_id}")
157
+ .to_return(status: 204, body: '', headers: {})
158
+
159
+ subject.execute_api_request(
160
+ method: :delete,
161
+ path: '/stores/{store_id}',
162
+ path_params: { store_id: }
163
+ )
164
+
165
+ expect(stub).to have_been_requested
166
+ end
167
+
168
+ it 'accepts method as a string' do
169
+ stub = stub_request(:get, "#{api_url}/stores")
170
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
171
+
172
+ subject.execute_api_request(method: 'GET', path: '/stores')
173
+
174
+ expect(stub).to have_been_requested
175
+ end
176
+
177
+ # --- path param substitution ---
178
+
179
+ context 'path params' do
180
+ it 'substitutes a single {placeholder} and CGI-encodes the value' do
181
+ stub = stub_request(:post, "#{api_url}/stores/#{store_id}/check")
182
+ .to_return(status: 200, body: { allowed: true }.to_json, headers: { 'Content-Type' => 'application/json' })
183
+
184
+ resp = subject.execute_api_request(
185
+ method: :post,
186
+ path: '/stores/{store_id}/check',
187
+ path_params: { store_id: },
188
+ body: { tuple_key: { user: 'user:anne', relation: 'reader', object: 'doc:1' } }
189
+ )
190
+
191
+ expect(stub).to have_been_requested
192
+ expect(resp.data[:allowed]).to be(true)
193
+ end
194
+
195
+ it 'substitutes multiple placeholders' do
196
+ model_id = '01G50QVV17PECNVAHX1GG4Y5NC'
197
+ stub = stub_request(:get, "#{api_url}/stores/#{store_id}/authorization-models/#{model_id}")
198
+ .to_return(status: 200, body: { authorization_model: {} }.to_json, headers: { 'Content-Type' => 'application/json' })
199
+
200
+ subject.execute_api_request(
201
+ method: :get,
202
+ path: '/stores/{store_id}/authorization-models/{model_id}',
203
+ path_params: { store_id:, model_id: }
204
+ )
205
+
206
+ expect(stub).to have_been_requested
207
+ end
208
+
209
+ it 'accepts symbol keys in path_params' do
210
+ stub = stub_request(:get, "#{api_url}/stores/#{store_id}")
211
+ .to_return(status: 200, body: { id: store_id }.to_json, headers: { 'Content-Type' => 'application/json' })
212
+
213
+ subject.execute_api_request(
214
+ method: :get,
215
+ path: '/stores/{store_id}',
216
+ path_params: { store_id: }
217
+ )
218
+
219
+ expect(stub).to have_been_requested
220
+ end
221
+
222
+ it 'URL-encodes special characters in path param values' do
223
+ encoded = CGI.escape('store/with spaces')
224
+ stub = stub_request(:get, "#{api_url}/stores/#{encoded}")
225
+ .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
226
+
227
+ subject.execute_api_request(
228
+ method: :get,
229
+ path: '/stores/{store_id}',
230
+ path_params: { store_id: 'store/with spaces' }
231
+ )
232
+
233
+ expect(stub).to have_been_requested
234
+ end
235
+
236
+ it 'raises URI::InvalidURIError when a placeholder has no matching path_param' do
237
+ expect {
238
+ subject.execute_api_request(method: :get, path: '/stores/{unknown_key}')
239
+ }.to raise_error(URI::InvalidURIError)
240
+ end
241
+ end
242
+
243
+ # --- auth header injection ---
244
+
245
+ context 'auth headers' do
246
+ it 'sends no Authorization header when credentials method is :none' do
247
+ stub = stub_request(:get, "#{api_url}/stores")
248
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
249
+
250
+ subject.execute_api_request(method: :get, path: '/stores')
251
+
252
+ expect(stub).to have_been_requested
253
+ expect(stub.with(headers: { 'Authorization' => /.*/ })).not_to have_been_requested
254
+ end
255
+
256
+ it 'injects Authorization header when api_token credential is configured' do
257
+ stub = stub_request(:get, "#{api_url}/stores")
258
+ .with(headers: { 'Authorization' => 'Bearer my-token' })
259
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
260
+
261
+ subject_with_token.execute_api_request(method: :get, path: '/stores')
262
+
263
+ expect(stub).to have_been_requested
264
+ end
265
+
266
+ it 'caller-supplied Authorization header overrides the SDK token' do
267
+ stub = stub_request(:get, "#{api_url}/stores")
268
+ .with(headers: { 'Authorization' => 'Bearer caller-override' })
269
+ .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
270
+
271
+ subject_with_token.execute_api_request(
272
+ method: :get,
273
+ path: '/stores',
274
+ headers: { 'Authorization' => 'Bearer caller-override' }
275
+ )
276
+
277
+ expect(stub).to have_been_requested
278
+ end
279
+
280
+ it 'merges caller-supplied headers with SDK auth headers' do
281
+ stub = stub_request(:get, "#{api_url}/stores")
282
+ .with(headers: { 'Authorization' => 'Bearer my-token', 'X-Custom' => 'value' })
283
+ .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
284
+
285
+ subject_with_token.execute_api_request(
286
+ method: :get,
287
+ path: '/stores',
288
+ headers: { 'X-Custom' => 'value' }
289
+ )
290
+
291
+ expect(stub).to have_been_requested
292
+ end
293
+ end
294
+
295
+ # --- query params ---
296
+
297
+ context 'query params' do
298
+ it 'passes a single query param to the HTTP layer' do
299
+ stub = stub_request(:get, "#{api_url}/stores")
300
+ .with(query: { 'page_size' => '25' })
301
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
302
+
303
+ subject.execute_api_request(method: :get, path: '/stores', query_params: { page_size: 25 })
304
+
305
+ expect(stub).to have_been_requested
306
+ end
307
+
308
+ it 'passes multiple query params to the HTTP layer' do
309
+ stub = stub_request(:get, "#{api_url}/stores")
310
+ .with(query: { 'page_size' => '10', 'continuation_token' => 'tok123' })
311
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
312
+
313
+ subject.execute_api_request(
314
+ method: :get,
315
+ path: '/stores',
316
+ query_params: { page_size: 10, continuation_token: 'tok123' }
317
+ )
318
+
319
+ expect(stub).to have_been_requested
320
+ end
321
+ end
322
+
323
+ # --- error responses ---
324
+
325
+ context 'error responses' do
326
+ it 'raises ApiError for a 404 response' do
327
+ stub_request(:get, "#{api_url}/stores/does-not-exist")
328
+ .to_return(status: 404, body: { code: 'store_id_not_found' }.to_json, headers: { 'Content-Type' => 'application/json' })
329
+
330
+ expect {
331
+ subject.execute_api_request(method: :get, path: '/stores/does-not-exist')
332
+ }.to raise_error(OpenFga::ApiError)
333
+ end
334
+
335
+ it 'raises ApiError for a 500 response' do
336
+ stub_request(:post, "#{api_url}/stores/#{store_id}/check")
337
+ .to_return(status: 500, body: { code: 'internal_error' }.to_json, headers: { 'Content-Type' => 'application/json' })
338
+
339
+ expect {
340
+ subject.execute_api_request(
341
+ method: :post,
342
+ path: '/stores/{store_id}/check',
343
+ path_params: { store_id: },
344
+ body: { tuple_key: {} }
345
+ )
346
+ }.to raise_error(OpenFga::ApiError)
347
+ end
348
+
349
+ it 'includes the HTTP status code in the raised ApiError' do
350
+ stub_request(:get, "#{api_url}/stores/does-not-exist")
351
+ .to_return(status: 404, body: { code: 'store_id_not_found' }.to_json, headers: { 'Content-Type' => 'application/json' })
352
+
353
+ expect {
354
+ subject.execute_api_request(method: :get, path: '/stores/does-not-exist')
355
+ }.to raise_error(OpenFga::ApiError) { |e| expect(e.code).to eq(404) }
356
+ end
357
+ end
358
+ end
@@ -2185,4 +2185,79 @@ describe OpenFga::SdkClient do
2185
2185
  end
2186
2186
  end
2187
2187
  end
2188
+
2189
+ describe '#execute_api_request' do
2190
+ it 'raises ArgumentError when method is omitted' do
2191
+ expect { subject.execute_api_request(path: '/stores') }.to raise_error(ArgumentError)
2192
+ end
2193
+
2194
+ it 'raises ArgumentError when path is empty' do
2195
+ expect { subject.execute_api_request(method: :get, path: '') }
2196
+ .to raise_error(ArgumentError, /path is required/)
2197
+ end
2198
+
2199
+ it 'returns an ApiExecutorResponse on success' do
2200
+ stub_request(:get, stores_url)
2201
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
2202
+
2203
+ resp = subject.execute_api_request(method: :get, path: '/stores')
2204
+
2205
+ expect(resp).to be_a(OpenFga::ApiExecutorResponse)
2206
+ expect(resp.status).to eq(200)
2207
+ expect(resp.data).to eq({ stores: [] })
2208
+ expect(resp.success?).to be(true)
2209
+ end
2210
+
2211
+ it 'substitutes path params into the URL' do
2212
+ stub = stub_request(:get, stores_url(store_id))
2213
+ .to_return(status: 200, body: { id: store_id }.to_json, headers: { 'Content-Type' => 'application/json' })
2214
+
2215
+ subject.execute_api_request(
2216
+ method: :get,
2217
+ path: '/stores/{store_id}',
2218
+ path_params: { store_id: }
2219
+ )
2220
+
2221
+ expect(stub).to have_been_requested
2222
+ end
2223
+
2224
+ it 'sends the request body as JSON' do
2225
+ stub = stub_request(:post, "#{stores_url(store_id)}/check")
2226
+ .with(body: { tuple_key: { user: 'user:anne', relation: 'reader', object: 'doc:1' } }.to_json)
2227
+ .to_return(status: 200, body: { allowed: true }.to_json, headers: { 'Content-Type' => 'application/json' })
2228
+
2229
+ resp = subject.execute_api_request(
2230
+ method: :post,
2231
+ path: '/stores/{store_id}/check',
2232
+ path_params: { store_id: },
2233
+ body: { tuple_key: { user: 'user:anne', relation: 'reader', object: 'doc:1' } }
2234
+ )
2235
+
2236
+ expect(stub).to have_been_requested
2237
+ expect(resp.data[:allowed]).to be(true)
2238
+ end
2239
+
2240
+ it 'injects the Authorization header when credentials are configured' do
2241
+ stub = stub_request(:get, stores_url)
2242
+ .with(headers: api_token_authz_header)
2243
+ .to_return(status: 200, body: { stores: [] }.to_json, headers: { 'Content-Type' => 'application/json' })
2244
+
2245
+ subject_with_api_token.execute_api_request(method: :get, path: '/stores')
2246
+
2247
+ expect(stub).to have_been_requested
2248
+ end
2249
+
2250
+ it 'raises ApiError for non-2xx responses' do
2251
+ stub_request(:get, stores_url(store_id))
2252
+ .to_return(status: 404, body: { code: 'store_id_not_found' }.to_json, headers: { 'Content-Type' => 'application/json' })
2253
+
2254
+ expect {
2255
+ subject.execute_api_request(
2256
+ method: :get,
2257
+ path: '/stores/{store_id}',
2258
+ path_params: { store_id: }
2259
+ )
2260
+ }.to raise_error(OpenFga::ApiError)
2261
+ end
2262
+ end
2188
2263
  end
@@ -22,17 +22,17 @@ checking for RB_WARN_CATEGORY_DEPRECATED in ruby.h... yes
22
22
  creating Makefile
23
23
 
24
24
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal
25
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-cz6r9q sitelibdir\=./.gem.20260417-2252-cz6r9q clean
25
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-8ayezx sitelibdir\=./.gem.20260625-2219-8ayezx clean
26
26
 
27
27
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal
28
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-cz6r9q sitelibdir\=./.gem.20260417-2252-cz6r9q
29
- compiling bigdecimal.c
28
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-8ayezx sitelibdir\=./.gem.20260625-2219-8ayezx
30
29
  compiling missing.c
30
+ compiling bigdecimal.c
31
31
  linking shared-object bigdecimal.so
32
32
 
33
33
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal
34
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-cz6r9q sitelibdir\=./.gem.20260417-2252-cz6r9q install
35
- /usr/bin/install -c -m 0755 bigdecimal.so ./.gem.20260417-2252-cz6r9q
34
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-8ayezx sitelibdir\=./.gem.20260625-2219-8ayezx install
35
+ /usr/bin/install -c -m 0755 bigdecimal.so ./.gem.20260625-2219-8ayezx
36
36
 
37
37
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal
38
- make DESTDIR\= sitearchdir\=./.gem.20260417-2252-cz6r9q sitelibdir\=./.gem.20260417-2252-cz6r9q clean
38
+ make DESTDIR\= sitearchdir\=./.gem.20260625-2219-8ayezx sitelibdir\=./.gem.20260625-2219-8ayezx clean
@@ -135,7 +135,7 @@ checked program was:
135
135
  have_func: checking for _lzcnt_u32() in x86intrin.h... -------------------- no
136
136
 
137
137
  LD_LIBRARY_PATH=.:/opt/hostedtoolcache/Ruby/4.0.1/x64/lib LSAN_OPTIONS=detect_leaks=0 "gcc -o conftest -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0/x86_64-linux -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0/ruby/backward -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0 -I. -DENABLE_PATH_CHECK=0 -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wmisleading-indentation -Wundef -fPIC conftest.c -L. -L/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -Wl,-rpath,/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -L. -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -Wl,-rpath,/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -L/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -lruby -lm -lpthread -lc"
138
- /usr/bin/ld: /tmp/ccB6b3my.o: in function `t':
138
+ /usr/bin/ld: /tmp/cchVHUwO.o: in function `t':
139
139
  /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal/conftest.c:16:(.text+0x7): undefined reference to `_lzcnt_u32'
140
140
  collect2: error: ld returned 1 exit status
141
141
  checked program was:
@@ -197,7 +197,7 @@ checked program was:
197
197
  have_func: checking for _lzcnt_u64() in x86intrin.h... -------------------- no
198
198
 
199
199
  LD_LIBRARY_PATH=.:/opt/hostedtoolcache/Ruby/4.0.1/x64/lib LSAN_OPTIONS=detect_leaks=0 "gcc -o conftest -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0/x86_64-linux -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0/ruby/backward -I/opt/hostedtoolcache/Ruby/4.0.1/x64/include/ruby-4.0.0 -I. -DENABLE_PATH_CHECK=0 -fstack-protector-strong -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wdiv-by-zero -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wwrite-strings -Wold-style-definition -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable -Wmisleading-indentation -Wundef -fPIC conftest.c -L. -L/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -Wl,-rpath,/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -L. -fstack-protector-strong -rdynamic -Wl,-export-dynamic -Wl,--no-as-needed -Wl,-rpath,/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -L/opt/hostedtoolcache/Ruby/4.0.1/x64/lib -lruby -lm -lpthread -lc"
200
- /usr/bin/ld: /tmp/ccPvZ3eH.o: in function `t':
200
+ /usr/bin/ld: /tmp/cc0DFTcc.o: in function `t':
201
201
  /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.0.1/ext/bigdecimal/conftest.c:16:(.text+0x7): undefined reference to `_lzcnt_u64'
202
202
  collect2: error: ld returned 1 exit status
203
203
  checked program was:
@@ -3,23 +3,23 @@ current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bu
3
3
  creating Makefile
4
4
 
5
5
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/byebug-13.0.0/ext/byebug
6
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-ou69bz sitelibdir\=./.gem.20260417-2252-ou69bz clean
6
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-efvxys sitelibdir\=./.gem.20260625-2219-efvxys clean
7
7
 
8
8
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/byebug-13.0.0/ext/byebug
9
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-ou69bz sitelibdir\=./.gem.20260417-2252-ou69bz
9
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-efvxys sitelibdir\=./.gem.20260625-2219-efvxys
10
10
  compiling breakpoint.c
11
11
  compiling byebug.c
12
12
  compiling context.c
13
- compiling locker.c
14
13
  compiling threads.c
15
- byebug.c: In function ‘check_started’:
16
- byebug.c:69:1: warning: old-style function definition [-Wold-style-definition]
17
- 69 | check_started()
18
- | ^~~~~~~~~~~~~
14
+ compiling locker.c
19
15
  locker.c: In function ‘byebug_pop_from_locked’:
20
16
  locker.c:53:1: warning: old-style function definition [-Wold-style-definition]
21
17
  53 | byebug_pop_from_locked()
22
18
  | ^~~~~~~~~~~~~~~~~~~~~~
19
+ byebug.c: In function ‘check_started’:
20
+ byebug.c:69:1: warning: old-style function definition [-Wold-style-definition]
21
+ 69 | check_started()
22
+ | ^~~~~~~~~~~~~
23
23
  byebug.c: In function ‘Init_byebug’:
24
24
  byebug.c:871:1: warning: old-style function definition [-Wold-style-definition]
25
25
  871 | Init_byebug()
@@ -35,8 +35,8 @@ cc1: note: unrecognized command-line option ‘-Wno-constant-logical-operand’
35
35
  linking shared-object byebug/byebug.so
36
36
 
37
37
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/byebug-13.0.0/ext/byebug
38
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-ou69bz sitelibdir\=./.gem.20260417-2252-ou69bz install
39
- /usr/bin/install -c -m 0755 byebug.so ./.gem.20260417-2252-ou69bz/byebug
38
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-efvxys sitelibdir\=./.gem.20260625-2219-efvxys install
39
+ /usr/bin/install -c -m 0755 byebug.so ./.gem.20260625-2219-efvxys/byebug
40
40
 
41
41
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/byebug-13.0.0/ext/byebug
42
- make DESTDIR\= sitearchdir\=./.gem.20260417-2252-ou69bz sitelibdir\=./.gem.20260417-2252-ou69bz clean
42
+ make DESTDIR\= sitearchdir\=./.gem.20260625-2219-efvxys sitelibdir\=./.gem.20260625-2219-efvxys clean
@@ -10,16 +10,16 @@ checking for cpuid.h... yes
10
10
  creating Makefile
11
11
 
12
12
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/json-2.18.1/ext/json/ext/parser
13
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-xyozx9 sitelibdir\=./.gem.20260417-2252-xyozx9 clean
13
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-2s49ni sitelibdir\=./.gem.20260625-2219-2s49ni clean
14
14
 
15
15
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/json-2.18.1/ext/json/ext/parser
16
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-xyozx9 sitelibdir\=./.gem.20260417-2252-xyozx9
16
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-2s49ni sitelibdir\=./.gem.20260625-2219-2s49ni
17
17
  compiling parser.c
18
18
  linking shared-object json/ext/parser.so
19
19
 
20
20
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/json-2.18.1/ext/json/ext/parser
21
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-xyozx9 sitelibdir\=./.gem.20260417-2252-xyozx9 install
22
- /usr/bin/install -c -m 0755 parser.so ./.gem.20260417-2252-xyozx9/json/ext
21
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-2s49ni sitelibdir\=./.gem.20260625-2219-2s49ni install
22
+ /usr/bin/install -c -m 0755 parser.so ./.gem.20260625-2219-2s49ni/json/ext
23
23
 
24
24
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/json-2.18.1/ext/json/ext/parser
25
- make DESTDIR\= sitearchdir\=./.gem.20260417-2252-xyozx9 sitelibdir\=./.gem.20260417-2252-xyozx9 clean
25
+ make DESTDIR\= sitearchdir\=./.gem.20260625-2219-2s49ni sitelibdir\=./.gem.20260625-2219-2s49ni clean
@@ -6,15 +6,15 @@ checking for whether -fvisibility=hidden is accepted as CFLAGS... yes
6
6
  creating Makefile
7
7
 
8
8
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
9
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-miuykf sitelibdir\=./.gem.20260417-2252-miuykf clean
9
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-q0aq54 sitelibdir\=./.gem.20260625-2219-q0aq54 clean
10
10
 
11
11
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
12
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-miuykf sitelibdir\=./.gem.20260417-2252-miuykf
12
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-q0aq54 sitelibdir\=./.gem.20260625-2219-q0aq54
13
13
  compiling api_node.c
14
14
  compiling api_pack.c
15
15
  compiling extension.c
16
- compiling ./../../src/diagnostic.c
17
16
  compiling ./../../src/encoding.c
17
+ compiling ./../../src/diagnostic.c
18
18
  compiling ./../../src/node.c
19
19
  compiling ./../../src/options.c
20
20
  compiling ./../../src/pack.c
@@ -37,8 +37,8 @@ compiling ./../../src/util/pm_strpbrk.c
37
37
  linking shared-object prism/prism.so
38
38
 
39
39
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
40
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-miuykf sitelibdir\=./.gem.20260417-2252-miuykf install
41
- /usr/bin/install -c -m 0755 prism.so ./.gem.20260417-2252-miuykf/prism
40
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-q0aq54 sitelibdir\=./.gem.20260625-2219-q0aq54 install
41
+ /usr/bin/install -c -m 0755 prism.so ./.gem.20260625-2219-q0aq54/prism
42
42
 
43
43
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/ext/prism
44
- make DESTDIR\= sitearchdir\=./.gem.20260417-2252-miuykf sitelibdir\=./.gem.20260417-2252-miuykf clean
44
+ make DESTDIR\= sitearchdir\=./.gem.20260625-2219-q0aq54 sitelibdir\=./.gem.20260625-2219-q0aq54 clean
@@ -3,16 +3,16 @@ current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bu
3
3
  creating Makefile
4
4
 
5
5
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
6
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-zbf2hm sitelibdir\=./.gem.20260417-2252-zbf2hm clean
6
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-rl1ihv sitelibdir\=./.gem.20260625-2219-rl1ihv clean
7
7
 
8
8
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
9
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-zbf2hm sitelibdir\=./.gem.20260417-2252-zbf2hm
9
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-rl1ihv sitelibdir\=./.gem.20260625-2219-rl1ihv
10
10
  compiling cparse.c
11
11
  linking shared-object racc/cparse.so
12
12
 
13
13
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
14
- make -j5 DESTDIR\= sitearchdir\=./.gem.20260417-2252-zbf2hm sitelibdir\=./.gem.20260417-2252-zbf2hm install
15
- /usr/bin/install -c -m 0755 cparse.so ./.gem.20260417-2252-zbf2hm/racc
14
+ make -j5 DESTDIR\= sitearchdir\=./.gem.20260625-2219-rl1ihv sitelibdir\=./.gem.20260625-2219-rl1ihv install
15
+ /usr/bin/install -c -m 0755 cparse.so ./.gem.20260625-2219-rl1ihv/racc
16
16
 
17
17
  current directory: /home/runner/work/openfga-ruby-sdk/openfga-ruby-sdk/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ext/racc/cparse
18
- make DESTDIR\= sitearchdir\=./.gem.20260417-2252-zbf2hm sitelibdir\=./.gem.20260417-2252-zbf2hm clean
18
+ make DESTDIR\= sitearchdir\=./.gem.20260625-2219-rl1ihv sitelibdir\=./.gem.20260625-2219-rl1ihv clean
@@ -21,5 +21,5 @@ Gem::Specification.new do |s|
21
21
  s.rubygems_version = "3.5.9".freeze
22
22
  s.summary = "The best way to manage your application's dependencies".freeze
23
23
 
24
- s.installed_by_version = "4.0.10".freeze
24
+ s.installed_by_version = "4.0.15".freeze
25
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openfga
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carla Urrea Stabile, Steven Hobbs
@@ -196,6 +196,8 @@ files:
196
196
  - docs/WriteRequest.md
197
197
  - docs/WriteRequestDeletes.md
198
198
  - docs/WriteRequestWrites.md
199
+ - example/api-executor/README.md
200
+ - example/api-executor/main.rb
199
201
  - example/example1/README.md
200
202
  - example/example1/main.rb
201
203
  - example/example2/.env.example
@@ -209,6 +211,8 @@ files:
209
211
  - lib/openfga/api_model_base.rb
210
212
  - lib/openfga/client.rb
211
213
  - lib/openfga/client/client_errors.rb
214
+ - lib/openfga/client/models/api_executor_request.rb
215
+ - lib/openfga/client/models/api_executor_response.rb
212
216
  - lib/openfga/client/openfga_client.rb
213
217
  - lib/openfga/configuration.rb
214
218
  - lib/openfga/constants.rb
@@ -308,6 +312,7 @@ files:
308
312
  - release-please-config.json
309
313
  - spec/api/open_fga_api_spec.rb
310
314
  - spec/api_client_spec.rb
315
+ - spec/client/api_executor_spec.rb
311
316
  - spec/client/openfga_client_spec.rb
312
317
  - spec/configuration_spec.rb
313
318
  - spec/fixtures/expand_response.json
@@ -4224,7 +4229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
4224
4229
  - !ruby/object:Gem::Version
4225
4230
  version: '0'
4226
4231
  requirements: []
4227
- rubygems_version: 4.0.10
4232
+ rubygems_version: 4.0.15
4228
4233
  specification_version: 4
4229
4234
  summary: This is community-driven Ruby SDK for OpenFGA. It provides a wrapper around
4230
4235
  the [OpenFGA API definition](https://openfga.dev/api).