astroapi-ruby 1.0.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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/lib/astroapi/categories/analysis.rb +219 -0
  3. data/lib/astroapi/categories/astrocartography.rb +119 -0
  4. data/lib/astroapi/categories/base_category.rb +30 -0
  5. data/lib/astroapi/categories/charts.rb +119 -0
  6. data/lib/astroapi/categories/chinese.rb +72 -0
  7. data/lib/astroapi/categories/data.rb +100 -0
  8. data/lib/astroapi/categories/eclipses.rb +40 -0
  9. data/lib/astroapi/categories/enhanced.rb +53 -0
  10. data/lib/astroapi/categories/fengshui.rb +44 -0
  11. data/lib/astroapi/categories/fixed_stars.rb +47 -0
  12. data/lib/astroapi/categories/glossary.rb +114 -0
  13. data/lib/astroapi/categories/horary.rb +58 -0
  14. data/lib/astroapi/categories/horoscope.rb +144 -0
  15. data/lib/astroapi/categories/human_design.rb +77 -0
  16. data/lib/astroapi/categories/insights/business.rb +62 -0
  17. data/lib/astroapi/categories/insights/financial.rb +62 -0
  18. data/lib/astroapi/categories/insights/pet.rb +53 -0
  19. data/lib/astroapi/categories/insights/relationship.rb +67 -0
  20. data/lib/astroapi/categories/insights/wellness.rb +67 -0
  21. data/lib/astroapi/categories/insights.rb +41 -0
  22. data/lib/astroapi/categories/kabbalah.rb +69 -0
  23. data/lib/astroapi/categories/lunar.rb +51 -0
  24. data/lib/astroapi/categories/numerology.rb +37 -0
  25. data/lib/astroapi/categories/palmistry.rb +44 -0
  26. data/lib/astroapi/categories/pdf.rb +47 -0
  27. data/lib/astroapi/categories/render.rb +47 -0
  28. data/lib/astroapi/categories/svg.rb +48 -0
  29. data/lib/astroapi/categories/tarot.rb +156 -0
  30. data/lib/astroapi/categories/traditional.rb +93 -0
  31. data/lib/astroapi/categories/vedic.rb +190 -0
  32. data/lib/astroapi/categories/ziwei.rb +25 -0
  33. data/lib/astroapi/client.rb +103 -0
  34. data/lib/astroapi/configuration.rb +44 -0
  35. data/lib/astroapi/error.rb +74 -0
  36. data/lib/astroapi/http/client.rb +108 -0
  37. data/lib/astroapi/http/middleware/authentication.rb +22 -0
  38. data/lib/astroapi/http/middleware/logger.rb +43 -0
  39. data/lib/astroapi/http/middleware/response_unwrapper.rb +25 -0
  40. data/lib/astroapi/validators/base_validator.rb +72 -0
  41. data/lib/astroapi/validators/subject_validator.rb +87 -0
  42. data/lib/astroapi/version.rb +5 -0
  43. data/lib/astroapi.rb +18 -0
  44. metadata +218 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+ require_relative 'middleware/authentication'
6
+ require_relative 'middleware/response_unwrapper'
7
+ require_relative 'middleware/logger'
8
+
9
+ module Astroapi
10
+ module HTTP
11
+ # HTTP client wrapper using Faraday
12
+ class Client
13
+ attr_reader :config
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ @connection = build_connection
18
+ end
19
+
20
+ # Make a GET request
21
+ # @param path [String] Request path
22
+ # @param params [Hash] Query parameters
23
+ # @return [Hash] Response body
24
+ def get(path, params: {})
25
+ response = @connection.get(path, params)
26
+ response.body
27
+ rescue Faraday::Error => e
28
+ raise Astroapi::Error.from_faraday_error(e)
29
+ end
30
+
31
+ # Make a POST request
32
+ # @param path [String] Request path
33
+ # @param body [Hash] Request body
34
+ # @return [Hash] Response body
35
+ def post(path, body: {})
36
+ response = @connection.post(path) do |req|
37
+ req.body = body.to_json
38
+ end
39
+ response.body
40
+ rescue Faraday::Error => e
41
+ raise Astroapi::Error.from_faraday_error(e)
42
+ end
43
+
44
+ # Make a PUT request
45
+ # @param path [String] Request path
46
+ # @param body [Hash] Request body
47
+ # @return [Hash] Response body
48
+ def put(path, body: {})
49
+ response = @connection.put(path) do |req|
50
+ req.body = body.to_json
51
+ end
52
+ response.body
53
+ rescue Faraday::Error => e
54
+ raise Astroapi::Error.from_faraday_error(e)
55
+ end
56
+
57
+ # Make a DELETE request
58
+ # @param path [String] Request path
59
+ # @return [Hash] Response body
60
+ def delete(path)
61
+ response = @connection.delete(path)
62
+ response.body
63
+ rescue Faraday::Error => e
64
+ raise Astroapi::Error.from_faraday_error(e)
65
+ end
66
+
67
+ private
68
+
69
+ def build_connection
70
+ Faraday.new(url: @config.base_url) do |faraday|
71
+ # Request middleware
72
+ faraday.request :json
73
+
74
+ # Custom middleware
75
+ faraday.use Middleware::Authentication, @config
76
+ faraday.use Middleware::Logger, @config
77
+
78
+ # Retry middleware (if configured)
79
+ if @config.retry.attempts.positive?
80
+ faraday.request :retry,
81
+ max: @config.retry.attempts,
82
+ interval: (@config.retry.delay_ms / 1000.0),
83
+ retry_statuses: @config.retry.retry_status_codes,
84
+ methods: %i[get post put delete],
85
+ exceptions: [
86
+ Faraday::TimeoutError,
87
+ Faraday::ConnectionFailed,
88
+ Errno::ETIMEDOUT,
89
+ Errno::ECONNREFUSED
90
+ ]
91
+ end
92
+
93
+ # Response middleware (runs in reverse order for responses)
94
+ faraday.use Middleware::ResponseUnwrapper
95
+ faraday.response :raise_error
96
+ faraday.response :json, content_type: /\bjson$/
97
+
98
+ # Adapter
99
+ faraday.adapter Faraday.default_adapter
100
+
101
+ # Timeout
102
+ faraday.options.timeout = @config.timeout
103
+ faraday.options.open_timeout = @config.timeout
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astroapi
4
+ module HTTP
5
+ module Middleware
6
+ # Faraday middleware to add Bearer token authentication
7
+ class Authentication < Faraday::Middleware
8
+ def initialize(app, config)
9
+ super(app)
10
+ @config = config
11
+ end
12
+
13
+ def call(env)
14
+ if @config.api_key && !env.request_headers['Authorization']
15
+ env.request_headers['Authorization'] = "Bearer #{@config.api_key}"
16
+ end
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astroapi
4
+ module HTTP
5
+ module Middleware
6
+ # Faraday middleware for debug logging
7
+ class Logger < Faraday::Middleware
8
+ def initialize(app, config)
9
+ super(app)
10
+ @config = config
11
+ end
12
+
13
+ def call(env)
14
+ log_request(env) if @config.debug
15
+
16
+ @app.call(env).on_complete do |response_env|
17
+ log_response(response_env) if @config.debug
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def log_request(env)
24
+ @config.logger.debug("[Astroapi] Request: #{env.method.upcase} #{env.url}")
25
+ @config.logger.debug("[Astroapi] Request Headers: #{sanitize_headers(env.request_headers)}")
26
+ @config.logger.debug("[Astroapi] Request Body: #{env.body}") if env.body
27
+ end
28
+
29
+ def log_response(env)
30
+ @config.logger.debug("[Astroapi] Response: #{env.status}")
31
+ @config.logger.debug("[Astroapi] Response Headers: #{env.response_headers}")
32
+ @config.logger.debug("[Astroapi] Response Body: #{env.body}")
33
+ end
34
+
35
+ def sanitize_headers(headers)
36
+ sanitized = headers.dup
37
+ sanitized['Authorization'] = '[REDACTED]' if sanitized['Authorization']
38
+ sanitized
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astroapi
4
+ module HTTP
5
+ module Middleware
6
+ # Faraday middleware to unwrap API response envelopes
7
+ # The API returns responses wrapped in { data: {...} } or { result: {...} }
8
+ # This middleware extracts the inner payload transparently
9
+ class ResponseUnwrapper < Faraday::Middleware
10
+ def on_complete(env)
11
+ env[:body] = unwrap(env[:body]) if env[:body].is_a?(Hash)
12
+ end
13
+
14
+ private
15
+
16
+ def unwrap(body)
17
+ return body unless body.is_a?(Hash)
18
+
19
+ # Try to extract 'data' first, then 'result', otherwise return body as-is
20
+ body.fetch('data') { body.fetch('result', body) }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astroapi
4
+ module Validators
5
+ # Base validator class for input validation
6
+ class BaseValidator
7
+ def self.validate!(value, context = nil)
8
+ new(value, context).validate!
9
+ end
10
+
11
+ def initialize(value, context = nil)
12
+ @value = value
13
+ @context = context
14
+ @errors = []
15
+ end
16
+
17
+ def validate!
18
+ perform_validation
19
+ raise ValidationError, error_message unless @errors.empty?
20
+ end
21
+
22
+ protected
23
+
24
+ def perform_validation
25
+ raise NotImplementedError, 'Subclasses must implement perform_validation'
26
+ end
27
+
28
+ def add_error(message)
29
+ @errors << message
30
+ end
31
+
32
+ def error_message
33
+ @errors.join('; ')
34
+ end
35
+
36
+ def validate_presence(hash, key, label = nil)
37
+ label ||= key.to_s
38
+ add_error("#{label} is required") if hash.nil? || !hash.key?(key) || hash[key].nil?
39
+ end
40
+
41
+ def validate_range(value, range, label)
42
+ return if value.nil?
43
+
44
+ return if range.cover?(value)
45
+
46
+ add_error("#{label} must be between #{range.min} and #{range.max}, got #{value}")
47
+ end
48
+
49
+ def validate_type(value, type, label)
50
+ return if value.nil?
51
+
52
+ return if value.is_a?(type)
53
+
54
+ add_error("#{label} must be a #{type}, got #{value.class}")
55
+ end
56
+
57
+ def validate_enum(value, allowed_values, label)
58
+ return if value.nil?
59
+
60
+ return if allowed_values.include?(value)
61
+
62
+ add_error("#{label} must be one of #{allowed_values.join(', ')}, got #{value}")
63
+ end
64
+
65
+ def validate_string_non_empty(value, label)
66
+ return if value.nil?
67
+
68
+ add_error("#{label} must be a non-empty string") unless value.is_a?(String) && !value.strip.empty?
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_validator'
4
+
5
+ module Astroapi
6
+ module Validators
7
+ # Validator for subject/birth data
8
+ class SubjectValidator < BaseValidator
9
+ protected
10
+
11
+ def perform_validation
12
+ return add_error('Subject must be a Hash') unless @value.is_a?(Hash)
13
+
14
+ validate_birth_data
15
+ validate_location
16
+ end
17
+
18
+ private
19
+
20
+ def validate_birth_data
21
+ birth_data = @value[:birth_data] || @value['birth_data']
22
+ return add_error('birth_data is required') if birth_data.nil?
23
+ return add_error('birth_data must be a Hash') unless birth_data.is_a?(Hash)
24
+
25
+ # Validate date components
26
+ validate_presence(birth_data, :year, 'year')
27
+ validate_presence(birth_data, :month, 'month')
28
+ validate_presence(birth_data, :day, 'day')
29
+
30
+ year = birth_data[:year] || birth_data['year']
31
+ month = birth_data[:month] || birth_data['month']
32
+ day = birth_data[:day] || birth_data['day']
33
+
34
+ validate_range(year, 1800..2200, 'year')
35
+ validate_range(month, 1..12, 'month')
36
+ validate_range(day, 1..31, 'day')
37
+
38
+ # Validate time components
39
+ validate_presence(birth_data, :hour, 'hour')
40
+ validate_presence(birth_data, :minute, 'minute')
41
+
42
+ hour = birth_data[:hour] || birth_data['hour']
43
+ minute = birth_data[:minute] || birth_data['minute']
44
+
45
+ validate_range(hour, 0..23, 'hour')
46
+ validate_range(minute, 0..59, 'minute')
47
+ end
48
+
49
+ def validate_location
50
+ birth_data = @value[:birth_data] || @value['birth_data']
51
+ return if birth_data.nil?
52
+
53
+ city = birth_data[:city] || birth_data['city']
54
+ country_code = birth_data[:country_code] || birth_data['country_code']
55
+ latitude = birth_data[:latitude] || birth_data['latitude']
56
+ longitude = birth_data[:longitude] || birth_data['longitude']
57
+
58
+ has_city_location = city && country_code
59
+ has_coordinates = latitude && longitude
60
+
61
+ unless has_city_location || has_coordinates
62
+ add_error('Either city/country_code OR latitude/longitude is required')
63
+ end
64
+
65
+ # Validate coordinates if provided
66
+ if latitude || longitude
67
+ validate_range(latitude, -90..90, 'latitude') if latitude
68
+ validate_range(longitude, -180..180, 'longitude') if longitude
69
+
70
+ if (latitude && !longitude) || (!latitude && longitude)
71
+ add_error('Both latitude and longitude must be provided together')
72
+ end
73
+ end
74
+
75
+ # Validate city/country if provided
76
+ return unless city || country_code
77
+
78
+ validate_string_non_empty(city, 'city') if city
79
+ validate_string_non_empty(country_code, 'country_code') if country_code
80
+
81
+ return unless (city && !country_code) || (!city && country_code)
82
+
83
+ add_error('Both city and country_code must be provided together')
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astroapi
4
+ VERSION = '1.0.0'
5
+ end
data/lib/astroapi.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'astroapi/version'
4
+ require_relative 'astroapi/error'
5
+ require_relative 'astroapi/configuration'
6
+ require_relative 'astroapi/client'
7
+
8
+ # Main module for the Astrology API Ruby client
9
+ module Astroapi
10
+ class << self
11
+ # Create a new client instance
12
+ # @param options [Hash] Configuration options
13
+ # @return [Client] New client instance
14
+ def new(options = {}, &block)
15
+ Client.new(options, &block)
16
+ end
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: astroapi-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Astrology API
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-02-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-retry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.50'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.50'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.20'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.20'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.22'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.22'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.18'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.18'
125
+ - !ruby/object:Gem::Dependency
126
+ name: yard
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
139
+ description: A comprehensive Ruby SDK for the Astrology API (api.astrology-api.io),
140
+ providing access to planetary positions, charts, horoscopes, analysis, and more.
141
+ email:
142
+ - support@astrology-api.io
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - lib/astroapi.rb
148
+ - lib/astroapi/categories/analysis.rb
149
+ - lib/astroapi/categories/astrocartography.rb
150
+ - lib/astroapi/categories/base_category.rb
151
+ - lib/astroapi/categories/charts.rb
152
+ - lib/astroapi/categories/chinese.rb
153
+ - lib/astroapi/categories/data.rb
154
+ - lib/astroapi/categories/eclipses.rb
155
+ - lib/astroapi/categories/enhanced.rb
156
+ - lib/astroapi/categories/fengshui.rb
157
+ - lib/astroapi/categories/fixed_stars.rb
158
+ - lib/astroapi/categories/glossary.rb
159
+ - lib/astroapi/categories/horary.rb
160
+ - lib/astroapi/categories/horoscope.rb
161
+ - lib/astroapi/categories/human_design.rb
162
+ - lib/astroapi/categories/insights.rb
163
+ - lib/astroapi/categories/insights/business.rb
164
+ - lib/astroapi/categories/insights/financial.rb
165
+ - lib/astroapi/categories/insights/pet.rb
166
+ - lib/astroapi/categories/insights/relationship.rb
167
+ - lib/astroapi/categories/insights/wellness.rb
168
+ - lib/astroapi/categories/kabbalah.rb
169
+ - lib/astroapi/categories/lunar.rb
170
+ - lib/astroapi/categories/numerology.rb
171
+ - lib/astroapi/categories/palmistry.rb
172
+ - lib/astroapi/categories/pdf.rb
173
+ - lib/astroapi/categories/render.rb
174
+ - lib/astroapi/categories/svg.rb
175
+ - lib/astroapi/categories/tarot.rb
176
+ - lib/astroapi/categories/traditional.rb
177
+ - lib/astroapi/categories/vedic.rb
178
+ - lib/astroapi/categories/ziwei.rb
179
+ - lib/astroapi/client.rb
180
+ - lib/astroapi/configuration.rb
181
+ - lib/astroapi/error.rb
182
+ - lib/astroapi/http/client.rb
183
+ - lib/astroapi/http/middleware/authentication.rb
184
+ - lib/astroapi/http/middleware/logger.rb
185
+ - lib/astroapi/http/middleware/response_unwrapper.rb
186
+ - lib/astroapi/validators/base_validator.rb
187
+ - lib/astroapi/validators/subject_validator.rb
188
+ - lib/astroapi/version.rb
189
+ homepage: https://github.com/astro-api/astroapi-ruby
190
+ licenses:
191
+ - MIT
192
+ metadata:
193
+ bug_tracker_uri: https://github.com/astro-api/astroapi-ruby/issues
194
+ changelog_uri: https://github.com/astro-api/astroapi-ruby/blob/main/CHANGELOG.md
195
+ documentation_uri: https://www.rubydoc.info/gems/astroapi-ruby
196
+ homepage_uri: https://github.com/astro-api/astroapi-ruby
197
+ source_code_uri: https://github.com/astro-api/astroapi-ruby
198
+ rubygems_mfa_required: 'true'
199
+ post_install_message:
200
+ rdoc_options: []
201
+ require_paths:
202
+ - lib
203
+ required_ruby_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: 2.6.0
208
+ required_rubygems_version: !ruby/object:Gem::Requirement
209
+ requirements:
210
+ - - ">="
211
+ - !ruby/object:Gem::Version
212
+ version: '0'
213
+ requirements: []
214
+ rubygems_version: 3.5.22
215
+ signing_key:
216
+ specification_version: 4
217
+ summary: Ruby client for the Astrology API
218
+ test_files: []