schwab_rb 0.2.0 → 0.3.2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/settings.local.json +9 -0
  3. data/.rspec_status +232 -203
  4. data/CLAUDE.md +137 -0
  5. data/README.md +3 -3
  6. data/examples/fetch_account_numbers.rb +12 -15
  7. data/examples/fetch_user_preferences.rb +16 -19
  8. data/lib/schwab_rb/account.rb +1 -1
  9. data/lib/schwab_rb/auth/auth_context.rb +1 -1
  10. data/lib/schwab_rb/auth/init_client_easy.rb +29 -31
  11. data/lib/schwab_rb/auth/init_client_login.rb +24 -21
  12. data/lib/schwab_rb/auth/login_flow_server.rb +2 -2
  13. data/lib/schwab_rb/auth/token.rb +1 -1
  14. data/lib/schwab_rb/auth/token_manager.rb +5 -7
  15. data/lib/schwab_rb/clients/async_client.rb +25 -27
  16. data/lib/schwab_rb/clients/base_client.rb +22 -16
  17. data/lib/schwab_rb/clients/client.rb +14 -14
  18. data/lib/schwab_rb/configuration.rb +4 -4
  19. data/lib/schwab_rb/data_objects/account.rb +3 -3
  20. data/lib/schwab_rb/data_objects/instrument.rb +1 -1
  21. data/lib/schwab_rb/data_objects/market_hours.rb +43 -33
  22. data/lib/schwab_rb/data_objects/market_movers.rb +98 -0
  23. data/lib/schwab_rb/data_objects/option.rb +2 -2
  24. data/lib/schwab_rb/data_objects/option_chain.rb +7 -7
  25. data/lib/schwab_rb/data_objects/option_expiration_chain.rb +26 -25
  26. data/lib/schwab_rb/data_objects/order.rb +7 -6
  27. data/lib/schwab_rb/data_objects/order_leg.rb +5 -5
  28. data/lib/schwab_rb/data_objects/order_preview.rb +13 -16
  29. data/lib/schwab_rb/data_objects/position.rb +4 -4
  30. data/lib/schwab_rb/data_objects/price_history.rb +27 -19
  31. data/lib/schwab_rb/data_objects/quote.rb +6 -6
  32. data/lib/schwab_rb/data_objects/transaction.rb +6 -6
  33. data/lib/schwab_rb/data_objects/user_preferences.rb +3 -3
  34. data/lib/schwab_rb/market_hours.rb +5 -5
  35. data/lib/schwab_rb/movers.rb +16 -16
  36. data/lib/schwab_rb/orders/builder.rb +5 -5
  37. data/lib/schwab_rb/orders/destination.rb +12 -12
  38. data/lib/schwab_rb/orders/duration.rb +7 -7
  39. data/lib/schwab_rb/orders/equity_instructions.rb +4 -4
  40. data/lib/schwab_rb/orders/instruments.rb +8 -8
  41. data/lib/schwab_rb/orders/price_link_basis.rb +9 -9
  42. data/lib/schwab_rb/orders/price_link_type.rb +3 -3
  43. data/lib/schwab_rb/orders/session.rb +4 -4
  44. data/lib/schwab_rb/orders/special_instruction.rb +3 -3
  45. data/lib/schwab_rb/orders/stop_price_link_basis.rb +9 -9
  46. data/lib/schwab_rb/orders/stop_price_link_type.rb +3 -3
  47. data/lib/schwab_rb/orders/stop_type.rb +5 -5
  48. data/lib/schwab_rb/orders/tax_lot_method.rb +7 -7
  49. data/lib/schwab_rb/price_history.rb +8 -8
  50. data/lib/schwab_rb/quote.rb +5 -5
  51. data/lib/schwab_rb/transaction.rb +15 -15
  52. data/lib/schwab_rb/utils/logger.rb +11 -15
  53. data/lib/schwab_rb/utils/redactor.rb +23 -25
  54. data/lib/schwab_rb/version.rb +1 -1
  55. data/lib/schwab_rb.rb +1 -0
  56. metadata +6 -2
data/CLAUDE.md ADDED
@@ -0,0 +1,137 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ### Setup and Installation
8
+ - `bin/setup` - Install dependencies and set up the development environment
9
+ - `bundle install` - Install gem dependencies
10
+ - `bin/console` - Start an interactive Ruby console with the gem loaded
11
+
12
+ ### Testing
13
+ - `bundle exec rake spec` - Run all RSpec tests
14
+ - `bundle exec rspec` - Run RSpec tests directly
15
+ - `bundle exec rspec spec/path/to/specific_spec.rb` - Run a specific test file
16
+ - `bundle exec rspec spec/path/to/specific_spec.rb:line_number` - Run a specific test
17
+
18
+ ### Linting and Code Quality
19
+ - `bundle exec rubocop` - Run RuboCop linter
20
+ - `bundle exec rubocop -a` - Auto-correct RuboCop violations where possible
21
+ - `bundle exec rake` - Run both tests and linting (default task)
22
+
23
+ ### Gem Management
24
+ - `bundle exec rake build` - Build the gem
25
+ - `bundle exec rake install` - Install the gem locally
26
+ - `bundle exec rake release` - Release a new version (creates git tag and pushes to RubyGems)
27
+
28
+ ## Architecture Overview
29
+
30
+ ### Core Structure
31
+ This is a Ruby gem (`schwab_rb`) that provides a client library for the Charles Schwab API. The architecture follows a modular design:
32
+
33
+ **Authentication Layer** (`lib/schwab_rb/auth/`):
34
+ - `init_client_easy.rb` - Recommended initialization method that handles token management automatically
35
+ - `init_client_login.rb` - Interactive browser-based authentication flow
36
+ - `init_client_token_file.rb` - Initialize from existing token file
37
+ - `token_manager.rb` - Handles token refresh and persistence
38
+ - `login_flow_server.rb` - Temporary server for OAuth callback handling
39
+
40
+ **Client Layer** (`lib/schwab_rb/clients/`):
41
+ - `client.rb` - Synchronous HTTP client for API calls
42
+ - `async_client.rb` - Asynchronous client using the `async` gem
43
+ - `base_client.rb` - Shared client functionality
44
+
45
+ **Data Objects** (`lib/schwab_rb/data_objects/`):
46
+ - Structured Ruby objects for API responses (Account, Quote, Order, etc.)
47
+ - Replaces raw JSON responses with typed objects for better developer experience
48
+ - All API methods support `return_data_objects: false` to get raw JSON if needed
49
+
50
+ **Order Management** (`lib/schwab_rb/orders/`):
51
+ - `builder.rb` - Fluent interface for constructing complex orders
52
+ - Various enum classes for order parameters (duration, session, instructions, etc.)
53
+
54
+ ### Key Design Patterns
55
+
56
+ **Three-Tier Client Initialization**:
57
+ 1. `init_client_easy()` - Handles everything automatically (recommended)
58
+ 2. `init_client_token_file()` - For existing tokens
59
+ 3. `init_client_login()` - For interactive authentication
60
+
61
+ **Data Object Strategy**:
62
+ - All API methods return structured Ruby objects by default
63
+ - Use `return_data_objects: false` for raw JSON responses
64
+ - Data objects are built from JSON using factory patterns
65
+
66
+ **Async Support**:
67
+ - Both sync and async clients available
68
+ - Async operations use the `async` gem with promises
69
+ - Set `asyncio: true` during client initialization
70
+
71
+ ### Configuration
72
+
73
+ Environment variables (loaded via `dotenv`):
74
+ - `SCHWAB_API_KEY` - Your Schwab API key
75
+ - `SCHWAB_APP_SECRET` - Your Schwab application secret
76
+ - `APP_CALLBACK_URL` - OAuth callback URL
77
+ - `TOKEN_PATH` - Path for token storage
78
+ - `SCHWAB_ACCOUNT_NUMBER` - Your account number
79
+ - `SCHWAB_LOGFILE` - Log file path (optional, defaults to STDOUT)
80
+ - `SCHWAB_LOG_LEVEL` - Log level (DEBUG, INFO, WARN, ERROR, FATAL)
81
+ - `SCHWAB_SILENCE_OUTPUT` - Set to 'true' to disable logging
82
+
83
+ Programmatic configuration:
84
+ ```ruby
85
+ SchwabRb.configure do |config|
86
+ config.logger = Logger.new(STDOUT)
87
+ config.log_level = 'INFO'
88
+ config.silence_output = false
89
+ end
90
+ ```
91
+
92
+ ### Testing Infrastructure
93
+
94
+ **Fixtures and Factories**:
95
+ - JSON fixtures in `spec/fixtures/` for realistic API responses
96
+ - Factory classes in `spec/factories/` for creating test objects
97
+ - Extensive mocking of HTTP responses for unit tests
98
+
99
+ **Test Organization**:
100
+ - Unit tests mirror the `lib/` structure
101
+ - Separate specs for data objects, clients, auth, and orders
102
+ - Uses `async-rspec` for testing async functionality
103
+
104
+ ### Key Dependencies
105
+ - `oauth2` - OAuth2 authentication
106
+ - `async` + `async-http` - Asynchronous HTTP operations
107
+ - `sinatra` + `puma` - Temporary server for OAuth callbacks
108
+ - `dotenv` - Environment variable management
109
+ - `rspec` + `async-rspec` - Testing framework
110
+ - `rubocop` - Code linting
111
+
112
+ ## Common Patterns
113
+
114
+ ### Client Initialization
115
+ Always use `init_client_easy()` for new development:
116
+ ```ruby
117
+ client = SchwabRb::Auth.init_client_easy(
118
+ ENV['SCHWAB_API_KEY'],
119
+ ENV['SCHWAB_APP_SECRET'],
120
+ ENV['APP_CALLBACK_URL'],
121
+ ENV['TOKEN_PATH']
122
+ )
123
+ ```
124
+
125
+ ### Order Building
126
+ Use the fluent builder pattern for complex orders:
127
+ ```ruby
128
+ order = SchwabRb::Orders::Builder.new
129
+ .set_session(:normal)
130
+ .set_duration(:day)
131
+ .set_order_type(:market)
132
+ .add_equity_leg(:buy, 'AAPL', 100)
133
+ .build
134
+ ```
135
+
136
+ ### Error Handling
137
+ Token expiration is handled automatically by `init_client_easy()`. Custom error classes are defined in `lib/schwab_rb/orders/errors.rb`.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ****# schwab_rb: Schwab API Ruby Client
1
+ # schwab_rb: Schwab API Ruby Client
2
2
 
3
3
  The `schwab_rb` gem is a Ruby client for interacting with the Schwab API. It provides a simple and flexible interface for accessing Schwab account data, placing orders, retrieving quotes, and more.
4
4
 
@@ -204,7 +204,7 @@ response = client.place_order('account_hash', order)
204
204
  The gem supports multiple authentication approaches:
205
205
 
206
206
  1. **Easy Initialization** (Recommended): Automatically handles token storage and refresh
207
- 2. **Token File**: Initialize from a saved token file
207
+ 2. **Token File**: Initialize from a saved token file
208
208
  3. **Login Flow**: Interactive browser-based authentication
209
209
 
210
210
  ### Easy Authentication Setup
@@ -212,7 +212,7 @@ The gem supports multiple authentication approaches:
212
212
  ```ruby
213
213
  client = SchwabRb::Auth.init_client_easy(
214
214
  ENV['SCHWAB_API_KEY'],
215
- ENV['SCHWAB_APP_SECRET'],
215
+ ENV['SCHWAB_APP_SECRET'],
216
216
  ENV['APP_CALLBACK_URL'],
217
217
  ENV['TOKEN_PATH']
218
218
  )
@@ -4,19 +4,19 @@
4
4
  # Script to fetch account numbers data and save as fixture
5
5
  # Usage: ruby examples/fetch_account_numbers.rb
6
6
 
7
- require_relative '../lib/schwab_rb'
8
- require 'dotenv'
9
- require 'json'
10
- require 'fileutils'
7
+ require_relative "../lib/schwab_rb"
8
+ require "dotenv"
9
+ require "json"
10
+ require "fileutils"
11
11
 
12
12
  Dotenv.load
13
13
 
14
14
  def create_client
15
- token_path = ENV['TOKEN_PATH'] || 'schwab_token.json'
15
+ token_path = ENV["TOKEN_PATH"] || "schwab_token.json"
16
16
  SchwabRb::Auth.init_client_easy(
17
- ENV['SCHWAB_API_KEY'],
18
- ENV['SCHWAB_APP_SECRET'],
19
- ENV['APP_CALLBACK_URL'],
17
+ ENV.fetch("SCHWAB_API_KEY", nil),
18
+ ENV.fetch("SCHWAB_APP_SECRET", nil),
19
+ ENV.fetch("APP_CALLBACK_URL", nil),
20
20
  token_path
21
21
  )
22
22
  end
@@ -29,21 +29,18 @@ def fetch_account_numbers
29
29
  parsed_data = JSON.parse(response.body, symbolize_names: true)
30
30
 
31
31
  # Create fixtures directory if it doesn't exist
32
- fixtures_dir = File.join(__dir__, '..', 'spec', 'fixtures')
32
+ fixtures_dir = File.join(__dir__, "..", "spec", "fixtures")
33
33
  FileUtils.mkdir_p(fixtures_dir)
34
34
 
35
35
  # Save the raw response
36
- fixture_file = File.join(fixtures_dir, 'account_numbers.json')
36
+ fixture_file = File.join(fixtures_dir, "account_numbers.json")
37
37
  File.write(fixture_file, JSON.pretty_generate(parsed_data))
38
38
 
39
39
  puts "Account numbers data saved to: #{fixture_file}"
40
40
  puts "Sample data: #{parsed_data.first(2)}"
41
-
42
- rescue => e
41
+ rescue StandardError => e
43
42
  puts "Error fetching account numbers: #{e.message}"
44
43
  puts e.backtrace.first(3)
45
44
  end
46
45
 
47
- if __FILE__ == $0
48
- fetch_account_numbers
49
- end
46
+ fetch_account_numbers if __FILE__ == $0
@@ -4,19 +4,19 @@
4
4
  # Script to fetch user preferences data and save as fixture
5
5
  # Usage: ruby examples/fetch_user_preferences.rb
6
6
 
7
- require_relative '../lib/schwab_rb'
8
- require 'dotenv'
9
- require 'json'
10
- require 'fileutils'
7
+ require_relative "../lib/schwab_rb"
8
+ require "dotenv"
9
+ require "json"
10
+ require "fileutils"
11
11
 
12
12
  Dotenv.load
13
13
 
14
14
  def create_client
15
- token_path = ENV['TOKEN_PATH'] || 'schwab_token.json'
15
+ token_path = ENV["TOKEN_PATH"] || "schwab_token.json"
16
16
  SchwabRb::Auth.init_client_easy(
17
- ENV['SCHWAB_API_KEY'],
18
- ENV['SCHWAB_APP_SECRET'],
19
- ENV['APP_CALLBACK_URL'],
17
+ ENV.fetch("SCHWAB_API_KEY", nil),
18
+ ENV.fetch("SCHWAB_APP_SECRET", nil),
19
+ ENV.fetch("APP_CALLBACK_URL", nil),
20
20
  token_path
21
21
  )
22
22
  end
@@ -24,26 +24,23 @@ end
24
24
  def fetch_user_preferences
25
25
  client = create_client
26
26
  puts "Fetching user preferences..."
27
-
27
+
28
28
  response = client.get_user_preferences
29
29
  parsed_data = JSON.parse(response.body, symbolize_names: true)
30
-
30
+
31
31
  # Create fixtures directory if it doesn't exist
32
- fixtures_dir = File.join(__dir__, '..', 'spec', 'fixtures')
32
+ fixtures_dir = File.join(__dir__, "..", "spec", "fixtures")
33
33
  FileUtils.mkdir_p(fixtures_dir)
34
-
34
+
35
35
  # Save the raw response
36
- fixture_file = File.join(fixtures_dir, 'user_preferences.json')
36
+ fixture_file = File.join(fixtures_dir, "user_preferences.json")
37
37
  File.write(fixture_file, JSON.pretty_generate(parsed_data))
38
-
38
+
39
39
  puts "User preferences data saved to: #{fixture_file}"
40
40
  puts "Sample data keys: #{parsed_data.keys.first(5)}"
41
-
42
- rescue => e
41
+ rescue StandardError => e
43
42
  puts "Error fetching user preferences: #{e.message}"
44
43
  puts e.backtrace.first(3)
45
44
  end
46
45
 
47
- if __FILE__ == $0
48
- fetch_user_preferences
49
- end
46
+ fetch_user_preferences if __FILE__ == $0
@@ -3,7 +3,7 @@
3
3
  module SchwabRb
4
4
  class Account
5
5
  module Statuses
6
- POSITIONS = 'positions'
6
+ POSITIONS = "positions"
7
7
  end
8
8
  end
9
9
  end
@@ -1,4 +1,4 @@
1
- require 'oauth2'
1
+ require "oauth2"
2
2
 
3
3
  module SchwabRb::Auth
4
4
  class AuthContext
@@ -1,6 +1,6 @@
1
- require 'oauth2'
2
- require_relative 'init_client_token_file'
3
- require_relative 'init_client_login'
1
+ require "oauth2"
2
+ require_relative "init_client_token_file"
3
+ require_relative "init_client_login"
4
4
 
5
5
  module SchwabRb::Auth
6
6
  def self.init_client_easy(
@@ -12,34 +12,32 @@ module SchwabRb::Auth
12
12
  enforce_enums: false,
13
13
  callback_timeout: 300.0,
14
14
  interactive: true,
15
- requested_browser: nil)
15
+ requested_browser: nil
16
+ )
16
17
 
17
- begin
18
- if File.exist?(token_path)
19
- client = SchwabRb::Auth::init_client_token_file(
20
- api_key,
21
- app_secret,
22
- token_path,
23
- enforce_enums: enforce_enums
24
- )
25
- client.refresh! if client.session.expired?
26
- raise OAuth2::Error.new("Token expired") if client.session.expired?
27
- client
28
- else
29
- raise OAuth2::Error.new("No token found")
30
- end
31
- rescue
32
- SchwabRb::Auth::init_client_login(
33
- api_key,
34
- app_secret,
35
- callback_url,
36
- token_path,
37
- asyncio: asyncio,
38
- enforce_enums: enforce_enums,
39
- callback_timeout: callback_timeout,
40
- interactive: interactive,
41
- requested_browser: requested_browser
42
- )
43
- end
18
+ raise OAuth2::Error.new("No token found") unless File.exist?(token_path)
19
+
20
+ client = SchwabRb::Auth.init_client_token_file(
21
+ api_key,
22
+ app_secret,
23
+ token_path,
24
+ enforce_enums: enforce_enums
25
+ )
26
+ client.refresh! if client.session.expired?
27
+ raise OAuth2::Error.new("Token expired") if client.session.expired?
28
+
29
+ client
30
+ rescue StandardError
31
+ SchwabRb::Auth.init_client_login(
32
+ api_key,
33
+ app_secret,
34
+ callback_url,
35
+ token_path,
36
+ asyncio: asyncio,
37
+ enforce_enums: enforce_enums,
38
+ callback_timeout: callback_timeout,
39
+ interactive: interactive,
40
+ requested_browser: requested_browser
41
+ )
44
42
  end
45
43
  end
@@ -1,17 +1,19 @@
1
- require 'openssl'
2
- require 'uri'
3
- require 'net/http'
4
- require 'json'
5
- require 'oauth2'
1
+ require "openssl"
2
+ require "uri"
3
+ require "net/http"
4
+ require "json"
5
+ require "oauth2"
6
6
  # require 'logger'
7
7
 
8
8
  module SchwabRb::Auth
9
9
  class RedirectTimeoutError < StandardError
10
- def initialize(msg="Timed out waiting for a callback")
11
- super(msg)
10
+ def initialize(msg = "Timed out waiting for a callback")
11
+ super
12
12
  end
13
13
  end
14
+
14
15
  class RedirectServerExitedError < StandardError; end
16
+
15
17
  # class TokenExchangeError < StandardError
16
18
  # def initialize(msg)
17
19
  # super(msg)
@@ -33,15 +35,16 @@ module SchwabRb::Auth
33
35
  enforce_enums: false,
34
36
  callback_timeout: 300.0,
35
37
  interactive: true,
36
- requested_browser: nil)
38
+ requested_browser: nil
39
+ )
37
40
 
38
- callback_timeout = if not callback_timeout
39
- callback_timeout = 0
41
+ callback_timeout = if !callback_timeout
42
+ 0
40
43
  elsif callback_timeout < 0
41
44
  raise ArgumentError, "callback_timeout must be non-negative"
42
45
  else
43
46
  callback_timeout
44
- end
47
+ end
45
48
 
46
49
  parsed = URI.parse(callback_url)
47
50
  raise InvalidHostname.new(parsed.host) unless parsed.host == "127.0.0.1"
@@ -49,9 +52,9 @@ module SchwabRb::Auth
49
52
  callback_port = parsed.port || 4567
50
53
  callback_path = parsed.path.empty? ? "/" : parsed.path
51
54
 
52
- cert_file, key_file = self.create_ssl_certificate
55
+ cert_file, key_file = create_ssl_certificate
53
56
 
54
- server_thread = SchwabRb::Auth::LoginFlowServer.run_in_thread(
57
+ SchwabRb::Auth::LoginFlowServer.run_in_thread(
55
58
  callback_port: callback_port,
56
59
  callback_path: callback_path,
57
60
  cert_file: cert_file,
@@ -80,13 +83,13 @@ module SchwabRb::Auth
80
83
  raise RedirectServerExitedError if Time.now - start_time > 5
81
84
  end
82
85
 
83
- auth_context = self.build_auth_context(api_key, callback_url)
86
+ auth_context = build_auth_context(api_key, callback_url)
84
87
 
85
88
  puts <<~MESSAGE
86
- ***********************************************************************
87
- Open this URL in your browser to log in:
88
- #{auth_context.authorization_url}
89
- ***********************************************************************
89
+ ***********************************************************************
90
+ Open this URL in your browser to log in:
91
+ #{auth_context.authorization_url}
92
+ ***********************************************************************
90
93
  MESSAGE
91
94
 
92
95
  if interactive
@@ -109,7 +112,7 @@ module SchwabRb::Auth
109
112
 
110
113
  raise RedirectTimeoutError.new unless received_url
111
114
 
112
- self.client_from_received_url(
115
+ client_from_received_url(
113
116
  api_key,
114
117
  app_secret,
115
118
  auth_context,
@@ -132,7 +135,7 @@ module SchwabRb::Auth
132
135
  cert.not_after = Time.now + (60 * 60 * 24) # 1 day
133
136
  cert.serial = 0x0
134
137
  cert.version = 2
135
- cert.sign(key, OpenSSL::Digest::SHA256.new)
138
+ cert.sign(key, OpenSSL::Digest.new("SHA256"))
136
139
 
137
140
  cert_file = Tempfile.new("cert.pem")
138
141
  cert_file.write(cert.to_pem)
@@ -142,7 +145,7 @@ module SchwabRb::Auth
142
145
  key_file.write(key.to_pem)
143
146
  key_file.close
144
147
 
145
- return cert_file, key_file
148
+ [cert_file, key_file]
146
149
  end
147
150
 
148
151
  def self.build_auth_context(api_key, callback_url, state: nil)
@@ -10,7 +10,7 @@ module SchwabRb::Auth
10
10
 
11
11
  self.queue = Queue.new
12
12
 
13
- self.disable :logging
13
+ disable :logging
14
14
 
15
15
  get "/status" do
16
16
  "running"
@@ -34,7 +34,7 @@ module SchwabRb::Auth
34
34
  ctx = Puma::MiniSSL::Context.new.tap do |ctx|
35
35
  ctx.key = key_file.path
36
36
  ctx.cert = cert_file.path
37
- ctx.verify_mode=Puma::MiniSSL::VERIFY_NONE
37
+ ctx.verify_mode = Puma::MiniSSL::VERIFY_NONE
38
38
  end
39
39
 
40
40
  puts ctx.inspect
@@ -19,6 +19,6 @@ module SchwabRb::Auth
19
19
  end
20
20
 
21
21
  attr_reader :token, :expires_in, :token_type, :scope,
22
- :refresh_token, :id_token, :expires_at
22
+ :refresh_token, :id_token, :expires_at
23
23
  end
24
24
  end
@@ -1,5 +1,5 @@
1
- require 'oauth2'
2
- require 'json'
1
+ require "oauth2"
2
+ require "json"
3
3
 
4
4
  module SchwabRb::Auth
5
5
  class TokenManager
@@ -74,9 +74,7 @@ module SchwabRb::Auth
74
74
  end
75
75
 
76
76
  def to_file
77
- File.open(token_path, "w") do |f|
78
- f.write(to_json)
79
- end
77
+ File.write(token_path, to_json)
80
78
  end
81
79
 
82
80
  def token_age
@@ -84,7 +82,7 @@ module SchwabRb::Auth
84
82
  end
85
83
 
86
84
  def to_h
87
- token_data = {
85
+ {
88
86
  timestamp: timestamp,
89
87
  token: {
90
88
  expires_in: token.expires_in,
@@ -98,7 +96,7 @@ module SchwabRb::Auth
98
96
  }
99
97
  end
100
98
 
101
- def to_json
99
+ def to_json(*_args)
102
100
  to_h.to_json
103
101
  end
104
102
  end
@@ -1,11 +1,11 @@
1
- require 'async'
2
- require 'async/http'
3
- require 'json'
4
- require 'uri'
5
- require_relative 'base_client'
6
- require_relative '../utils/logger'
7
- require_relative '../utils/redactor'
8
- require_relative '../constants'
1
+ require "async"
2
+ require "async/http"
3
+ require "json"
4
+ require "uri"
5
+ require_relative "base_client"
6
+ require_relative "../utils/logger"
7
+ require_relative "../utils/redactor"
8
+ require_relative "../constants"
9
9
 
10
10
  module SchwabRb
11
11
  class AsyncClient < BaseClient
@@ -28,7 +28,7 @@ module SchwabRb
28
28
  dest.query = URI.encode_www_form(params) if params.any?
29
29
 
30
30
  req_num = req_num()
31
- log_request('GET', req_num, dest, params)
31
+ log_request("GET", req_num, dest, params)
32
32
 
33
33
  # Use path only since @endpoint already has the base URL
34
34
  query_string = params.any? ? "?#{URI.encode_www_form(params)}" : ""
@@ -45,7 +45,7 @@ module SchwabRb
45
45
  dest = URI(URI::DEFAULT_PARSER.escape("#{SchwabRb::Constants::SCHWAB_BASE_URL}#{path}"))
46
46
 
47
47
  req_num = req_num()
48
- log_request('POST', req_num, dest, data)
48
+ log_request("POST", req_num, dest, data)
49
49
 
50
50
  response = @client.post(path, build_headers, JSON.dump(data))
51
51
 
@@ -60,7 +60,7 @@ module SchwabRb
60
60
  dest = URI(URI::DEFAULT_PARSER.escape("#{SchwabRb::Constants::SCHWAB_BASE_URL}#{path}"))
61
61
 
62
62
  req_num = req_num()
63
- log_request('PUT', req_num, dest, data)
63
+ log_request("PUT", req_num, dest, data)
64
64
 
65
65
  response = @client.put(path, build_headers, JSON.dump(data))
66
66
 
@@ -75,7 +75,7 @@ module SchwabRb
75
75
  dest = URI(URI::DEFAULT_PARSER.escape("#{SchwabRb::Constants::SCHWAB_BASE_URL}#{path}"))
76
76
 
77
77
  req_num = req_num()
78
- log_request('DELETE', req_num, dest)
78
+ log_request("DELETE", req_num, dest)
79
79
 
80
80
  response = @client.delete(path, build_headers)
81
81
 
@@ -86,32 +86,30 @@ module SchwabRb
86
86
 
87
87
  def build_headers
88
88
  headers = { "Content-Type" => "application/json" }
89
-
89
+
90
90
  # Add authorization header if token is available
91
- if @token_manager&.access_token
92
- headers["Authorization"] = "Bearer #{@token_manager.access_token}"
93
- end
94
-
91
+ headers["Authorization"] = "Bearer #{@token_manager.access_token}" if @token_manager&.access_token
92
+
95
93
  headers
96
94
  end
97
95
 
98
96
  def log_request(method, req_num, dest, data = nil)
99
97
  redacted_dest = SchwabRb::Redactor.redact_url(dest.to_s)
100
98
  SchwabRb::Logger.logger.info("Req #{req_num}: #{method} to #{redacted_dest}")
101
-
102
- if data
103
- redacted_data = SchwabRb::Redactor.redact_data(data)
104
- SchwabRb::Logger.logger.debug("Payload: #{JSON.pretty_generate(redacted_data)}")
105
- end
99
+
100
+ return unless data
101
+
102
+ redacted_data = SchwabRb::Redactor.redact_data(data)
103
+ SchwabRb::Logger.logger.debug("Payload: #{JSON.pretty_generate(redacted_data)}")
106
104
  end
107
105
 
108
106
  def log_response(response, req_num)
109
107
  SchwabRb::Logger.logger.info("Resp #{req_num}: Status #{response.status}")
110
-
111
- if SchwabRb::Logger.logger.level == ::Logger::DEBUG
112
- redacted_body = SchwabRb::Redactor.redact_response_body(response)
113
- SchwabRb::Logger.logger.debug("Response body: #{JSON.pretty_generate(redacted_body)}") if redacted_body
114
- end
108
+
109
+ return unless SchwabRb::Logger.logger.level == ::Logger::DEBUG
110
+
111
+ redacted_body = SchwabRb::Redactor.redact_response_body(response)
112
+ SchwabRb::Logger.logger.debug("Response body: #{JSON.pretty_generate(redacted_body)}") if redacted_body
115
113
  end
116
114
 
117
115
  def req_num