api-regulator 0.1.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8474dc3c3898732b6e48a0b2c1722ecc13735e95474b01d9c4223be2cc461653
4
- data.tar.gz: 541a39193368ff06b3932f869e75d27a50375ae5c2acd772e0276aa2c892bc8a
3
+ metadata.gz: 805a7ee5772352ec9d9f1e8bea5f6f79ef5dd67c170bfb3ca62898a2685a0067
4
+ data.tar.gz: 75d36c102ed4f9494cfb8f97be7cca09de8206006fb1ca28352734f15ecc29d1
5
5
  SHA512:
6
- metadata.gz: 67f74a784f20dc965e53a838c6ea8d068242cf77fd7aa7508c6e7e427efd4fef9f29a8242ab737f375b7accc84192551acc40036cfeb9778c1899731510cc102
7
- data.tar.gz: 732b45b1592042bc7259f82d84b396583a74acb92daac22636d1edddc9f2b41771eea4226459b6709ac109b8ed430a6838ef76a592d2c2742d6ae5dcae3e2b36
6
+ metadata.gz: 45f235550878457b9af59dc1f3d4d2b70ac8afe581dfc35d9f5a13a37da67eb17ef9f6e4da1d877352eca27229847367909645f66d20cdaa807d9c52f133c894
7
+ data.tar.gz: a67c9b219a20a6e9dffd99fa33f285222452621b99c7304ffd3e7366f86fb8046505857c27047a86161bf3232cf07b81e35a75a7375fe7bace8a0708982ba9c1
@@ -0,0 +1,35 @@
1
+ name: Run Specs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ test:
13
+ name: Run Tests
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ # Checkout the code
18
+ - name: Checkout code
19
+ uses: actions/checkout@v3
20
+
21
+ # Set up Ruby
22
+ - name: Set up Ruby
23
+ uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: '3.2' # Specify your gem's Ruby version
26
+ bundler-cache: true # Cache gems to speed up the workflow
27
+
28
+ # Install dependencies
29
+ - name: Install dependencies
30
+ run: bundle install
31
+
32
+ # Run RSpec tests
33
+ - name: Run specs
34
+ run: bundle exec rspec
35
+
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ spec/support/log/**
14
14
  spec/tmp
15
15
 
16
16
  api-regulator-*.gem
17
+ .byebug_history
data/Gemfile.lock CHANGED
@@ -1,36 +1,36 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- api-regulator (0.1.0)
4
+ api-regulator (0.1.2)
5
5
  activemodel
6
6
  activesupport
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actioncable (7.2.2)
12
- actionpack (= 7.2.2)
13
- activesupport (= 7.2.2)
11
+ actioncable (7.2.2.1)
12
+ actionpack (= 7.2.2.1)
13
+ activesupport (= 7.2.2.1)
14
14
  nio4r (~> 2.0)
15
15
  websocket-driver (>= 0.6.1)
16
16
  zeitwerk (~> 2.6)
17
- actionmailbox (7.2.2)
18
- actionpack (= 7.2.2)
19
- activejob (= 7.2.2)
20
- activerecord (= 7.2.2)
21
- activestorage (= 7.2.2)
22
- activesupport (= 7.2.2)
17
+ actionmailbox (7.2.2.1)
18
+ actionpack (= 7.2.2.1)
19
+ activejob (= 7.2.2.1)
20
+ activerecord (= 7.2.2.1)
21
+ activestorage (= 7.2.2.1)
22
+ activesupport (= 7.2.2.1)
23
23
  mail (>= 2.8.0)
24
- actionmailer (7.2.2)
25
- actionpack (= 7.2.2)
26
- actionview (= 7.2.2)
27
- activejob (= 7.2.2)
28
- activesupport (= 7.2.2)
24
+ actionmailer (7.2.2.1)
25
+ actionpack (= 7.2.2.1)
26
+ actionview (= 7.2.2.1)
27
+ activejob (= 7.2.2.1)
28
+ activesupport (= 7.2.2.1)
29
29
  mail (>= 2.8.0)
30
30
  rails-dom-testing (~> 2.2)
31
- actionpack (7.2.2)
32
- actionview (= 7.2.2)
33
- activesupport (= 7.2.2)
31
+ actionpack (7.2.2.1)
32
+ actionview (= 7.2.2.1)
33
+ activesupport (= 7.2.2.1)
34
34
  nokogiri (>= 1.8.5)
35
35
  racc
36
36
  rack (>= 2.2.4, < 3.2)
@@ -39,35 +39,35 @@ GEM
39
39
  rails-dom-testing (~> 2.2)
40
40
  rails-html-sanitizer (~> 1.6)
41
41
  useragent (~> 0.16)
42
- actiontext (7.2.2)
43
- actionpack (= 7.2.2)
44
- activerecord (= 7.2.2)
45
- activestorage (= 7.2.2)
46
- activesupport (= 7.2.2)
42
+ actiontext (7.2.2.1)
43
+ actionpack (= 7.2.2.1)
44
+ activerecord (= 7.2.2.1)
45
+ activestorage (= 7.2.2.1)
46
+ activesupport (= 7.2.2.1)
47
47
  globalid (>= 0.6.0)
48
48
  nokogiri (>= 1.8.5)
49
- actionview (7.2.2)
50
- activesupport (= 7.2.2)
49
+ actionview (7.2.2.1)
50
+ activesupport (= 7.2.2.1)
51
51
  builder (~> 3.1)
52
52
  erubi (~> 1.11)
53
53
  rails-dom-testing (~> 2.2)
54
54
  rails-html-sanitizer (~> 1.6)
55
- activejob (7.2.2)
56
- activesupport (= 7.2.2)
55
+ activejob (7.2.2.1)
56
+ activesupport (= 7.2.2.1)
57
57
  globalid (>= 0.3.6)
58
- activemodel (7.2.2)
59
- activesupport (= 7.2.2)
60
- activerecord (7.2.2)
61
- activemodel (= 7.2.2)
62
- activesupport (= 7.2.2)
58
+ activemodel (7.2.2.1)
59
+ activesupport (= 7.2.2.1)
60
+ activerecord (7.2.2.1)
61
+ activemodel (= 7.2.2.1)
62
+ activesupport (= 7.2.2.1)
63
63
  timeout (>= 0.4.0)
64
- activestorage (7.2.2)
65
- actionpack (= 7.2.2)
66
- activejob (= 7.2.2)
67
- activerecord (= 7.2.2)
68
- activesupport (= 7.2.2)
64
+ activestorage (7.2.2.1)
65
+ actionpack (= 7.2.2.1)
66
+ activejob (= 7.2.2.1)
67
+ activerecord (= 7.2.2.1)
68
+ activesupport (= 7.2.2.1)
69
69
  marcel (~> 1.0)
70
- activesupport (7.2.2)
70
+ activesupport (7.2.2.1)
71
71
  base64
72
72
  benchmark (>= 0.3)
73
73
  bigdecimal
@@ -99,7 +99,7 @@ GEM
99
99
  irb (1.14.1)
100
100
  rdoc (>= 4.0.0)
101
101
  reline (>= 0.4.2)
102
- logger (1.6.1)
102
+ logger (1.6.3)
103
103
  loofah (2.23.1)
104
104
  crass (~> 1.0.2)
105
105
  nokogiri (>= 1.12.0)
@@ -111,7 +111,7 @@ GEM
111
111
  marcel (1.0.4)
112
112
  mini_mime (1.1.5)
113
113
  mini_portile2 (2.8.8)
114
- minitest (5.25.2)
114
+ minitest (5.25.4)
115
115
  net-imap (0.5.1)
116
116
  date
117
117
  net-protocol
@@ -122,10 +122,10 @@ GEM
122
122
  net-smtp (0.5.0)
123
123
  net-protocol
124
124
  nio4r (2.7.4)
125
- nokogiri (1.16.8)
125
+ nokogiri (1.17.2)
126
126
  mini_portile2 (~> 2.8.2)
127
127
  racc (~> 1.4)
128
- nokogiri (1.16.8-arm64-darwin)
128
+ nokogiri (1.17.2-arm64-darwin)
129
129
  racc (~> 1.4)
130
130
  psych (5.2.0)
131
131
  stringio
@@ -137,30 +137,30 @@ GEM
137
137
  rack (>= 1.3)
138
138
  rackup (2.2.1)
139
139
  rack (>= 3)
140
- rails (7.2.2)
141
- actioncable (= 7.2.2)
142
- actionmailbox (= 7.2.2)
143
- actionmailer (= 7.2.2)
144
- actionpack (= 7.2.2)
145
- actiontext (= 7.2.2)
146
- actionview (= 7.2.2)
147
- activejob (= 7.2.2)
148
- activemodel (= 7.2.2)
149
- activerecord (= 7.2.2)
150
- activestorage (= 7.2.2)
151
- activesupport (= 7.2.2)
140
+ rails (7.2.2.1)
141
+ actioncable (= 7.2.2.1)
142
+ actionmailbox (= 7.2.2.1)
143
+ actionmailer (= 7.2.2.1)
144
+ actionpack (= 7.2.2.1)
145
+ actiontext (= 7.2.2.1)
146
+ actionview (= 7.2.2.1)
147
+ activejob (= 7.2.2.1)
148
+ activemodel (= 7.2.2.1)
149
+ activerecord (= 7.2.2.1)
150
+ activestorage (= 7.2.2.1)
151
+ activesupport (= 7.2.2.1)
152
152
  bundler (>= 1.15.0)
153
- railties (= 7.2.2)
153
+ railties (= 7.2.2.1)
154
154
  rails-dom-testing (2.2.0)
155
155
  activesupport (>= 5.0.0)
156
156
  minitest
157
157
  nokogiri (>= 1.6)
158
- rails-html-sanitizer (1.6.1)
158
+ rails-html-sanitizer (1.6.2)
159
159
  loofah (~> 2.21)
160
160
  nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
161
- railties (7.2.2)
162
- actionpack (= 7.2.2)
163
- activesupport (= 7.2.2)
161
+ railties (7.2.2.1)
162
+ actionpack (= 7.2.2.1)
163
+ activesupport (= 7.2.2.1)
164
164
  irb (~> 1.13)
165
165
  rackup (>= 1.0.0)
166
166
  rake (>= 12.2)
@@ -192,13 +192,13 @@ GEM
192
192
  rspec-mocks (~> 3.10)
193
193
  rspec-support (~> 3.10)
194
194
  rspec-support (3.13.1)
195
- securerandom (0.3.2)
195
+ securerandom (0.4.0)
196
196
  stringio (3.1.2)
197
197
  thor (1.3.2)
198
198
  timeout (0.4.2)
199
199
  tzinfo (2.0.6)
200
200
  concurrent-ruby (~> 1.0)
201
- useragent (0.16.10)
201
+ useragent (0.16.11)
202
202
  websocket-driver (0.7.6)
203
203
  websocket-extensions (>= 0.1.0)
204
204
  websocket-extensions (0.1.5)
data/README.md CHANGED
@@ -48,9 +48,8 @@ Run `bundle install` to install the gem.
48
48
 
49
49
  ```ruby
50
50
  ApiRegulator.configure do |config|
51
- config.base_controller = "Api::ApplicationController" # Set your base API controller
52
51
  config.api_base_url = "/api/v1" # Set a common base path for your API endpoints
53
- config.docs_path = Rails.root.join("doc", "openapi.json").to_s # Path for OpenAPI JSON file
52
+ config.docs_path = Rails.root.join("doc").to_s # Path for folder for docs
54
53
  config.app_name = "My API" # shows in docs
55
54
  config.rdme_api_id = ENV["RDME_API_ID"] # Optional: ReadMe API ID for schema uploads
56
55
  config.servers = [
@@ -111,7 +110,7 @@ end
111
110
  Define reusable schemas for common responses in your initializer:
112
111
 
113
112
  ```ruby
114
- ApiRegulator.shared_schema :validation_errors, "Validation error response" do
113
+ ApiRegulator.register_shared_schema :validation_errors, "Validation error response" do
115
114
  param :errors, :array, desc: "Array of validation errors", items_type: :string
116
115
  end
117
116
  ```
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "bundler/setup"
4
- require "api/regulator"
4
+ require "api-regulator"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
data/lib/api-regulator.rb CHANGED
@@ -5,10 +5,12 @@ require_relative 'api_regulator/dsl'
5
5
  require_relative 'api_regulator/formats'
6
6
  require_relative 'api_regulator/open_api_generator'
7
7
  require_relative 'api_regulator/param'
8
+ require_relative 'api_regulator/security'
8
9
  require_relative 'api_regulator/shared_schema'
9
10
  require_relative 'api_regulator/validation_error'
10
11
  require_relative 'api_regulator/validator'
11
12
  require_relative 'api_regulator/version'
13
+ require_relative 'api_regulator/webhook'
12
14
 
13
15
  # Load tasks if Rails is present
14
16
  if defined?(Rake)
@@ -28,8 +30,8 @@ module ApiRegulator
28
30
 
29
31
  def prepare_validators
30
32
  Rails.application.eager_load! # Ensure all controllers and API definitions are loaded
31
- api_definitions = ApiRegulator.configuration.base_controller_klass.descendants.flat_map(&:api_definitions)
32
- ApiRegulator::Validator.build_all(api_definitions)
33
+
34
+ ApiRegulator::Validator.build_all(ApiRegulator.api_definitions)
33
35
  end
34
36
  end
35
37
  end
@@ -1,13 +1,14 @@
1
1
  module ApiRegulator
2
2
  class Api
3
- attr_reader :controller_class, :controller_path, :controller_name, :action_name, :description, :params, :responses
3
+ attr_reader :controller_class, :controller_path, :controller_name, :action_name, :description, :title, :params, :responses
4
4
 
5
- def initialize(controller_class, action_name, description, &block)
5
+ def initialize(controller_class, action_name, desc: nil, title: nil, &block)
6
6
  @controller_class = controller_class
7
7
  @controller_name = controller_class.name
8
8
  @controller_path = controller_class.controller_path
9
9
  @action_name = action_name.to_s
10
- @description = description
10
+ @description = desc
11
+ @title = title
11
12
 
12
13
  @params = []
13
14
  @responses = {}
@@ -20,11 +21,20 @@ module ApiRegulator
20
21
  @params << param
21
22
  end
22
23
 
23
- def ref(ref_name)
24
- shared_schema = ApiRegulator.shared_schemas[ref_name]
24
+ def ref(ref_name, except: [], only: [])
25
+ shared_schema = ApiRegulator.shared_schema(ref_name)
25
26
  raise "Shared schema #{ref_name} not found" unless shared_schema
26
27
 
27
- shared_schema.params.each do |shared_param|
28
+ # Filter parameters based on `only` or `except` options
29
+ filtered_params = shared_schema.params
30
+
31
+ if only.any?
32
+ filtered_params = filtered_params.select { |param| only.include?(param.name) }
33
+ elsif except.any?
34
+ filtered_params = filtered_params.reject { |param| except.include?(param.name) }
35
+ end
36
+
37
+ filtered_params.each do |shared_param|
28
38
  @params << shared_param
29
39
  end
30
40
  end
@@ -79,4 +89,14 @@ module ApiRegulator
79
89
  http_method != "get"
80
90
  end
81
91
  end
92
+
93
+ class << self
94
+ def api_definitions
95
+ @api_definitions ||= []
96
+ end
97
+
98
+ def reset_api_definitions
99
+ @api_definitions = []
100
+ end
101
+ end
82
102
  end
@@ -1,18 +1,13 @@
1
1
  module ApiRegulator
2
2
  class Configuration
3
- attr_accessor :base_controller, :api_base_url, :app_name, :docs_path, :rdme_api_id, :servers
3
+ attr_accessor :api_base_url, :app_name, :docs_path, :rdme_api_id, :servers
4
4
 
5
5
  def initialize
6
6
  # Set default values
7
- @base_controller = "ApplicationController"
8
7
  @api_base_url = "api/v1"
9
8
  @app_name = "API Documentation"
10
- @docs_path = "openapi.json"
9
+ @docs_path = "doc"
11
10
  @servers = []
12
11
  end
13
-
14
- def base_controller_klass
15
- ApiRegulator.configuration.base_controller.constantize
16
- end
17
12
  end
18
13
  end
@@ -9,17 +9,24 @@ module ApiRegulator
9
9
  end
10
10
 
11
11
  module ClassMethods
12
- def api(controller_class, action, description, &block)
12
+ def api(controller_class, action, desc: nil, title: nil, &block)
13
13
  @api_definitions ||= []
14
14
 
15
15
  api_definition = Api.new(
16
16
  controller_class,
17
17
  action.to_s,
18
- description,
18
+ desc: desc,
19
+ title: title,
19
20
  &block
20
21
  )
21
22
 
22
23
  @api_definitions << api_definition
24
+ ApiRegulator.api_definitions << api_definition
25
+ end
26
+
27
+ def webhook(event_name, desc: nil, title: nil, tags: [], &block)
28
+ webhook = Webhook.new(event_name, desc: desc, title: title, tags: tags, &block)
29
+ ApiRegulator.webhook_definitions << webhook
23
30
  end
24
31
 
25
32
  def api_definitions
@@ -16,13 +16,15 @@ module ApiRegulator
16
16
 
17
17
  add_components(schema)
18
18
  add_security(schema)
19
+ add_webhooks(schema)
19
20
 
20
21
  api_definitions.each do |api|
21
22
  add_api_to_schema(schema, api)
22
23
  end
23
24
 
24
- File.write(ApiRegulator.configuration.docs_path, JSON.pretty_generate(schema))
25
- puts "OpenAPI schema generated: #{ApiRegulator.configuration.docs_path}"
25
+ schema_path = "#{ApiRegulator.configuration.docs_path}/openapi.json"
26
+ File.write(schema_path, JSON.pretty_generate(schema))
27
+ puts "OpenAPI schema generated: #{schema_path}"
26
28
  end
27
29
 
28
30
  private
@@ -30,13 +32,13 @@ module ApiRegulator
30
32
  def self.add_api_to_schema(schema, api)
31
33
  schema[:paths][api.path] ||= {}
32
34
  data = {
33
- summary: api.description,
34
- description: api.description,
35
+ summary: api.title,
36
+ description: api.description.presence || api.title,
35
37
  operationId: api.operation_id,
36
38
  tags: api.tags,
37
39
  parameters: generate_parameters(api),
38
40
  responses: generate_responses(api)
39
- }
41
+ }.compact
40
42
 
41
43
  data[:requestBody] = generate_request_body(api) if api.allows_body?
42
44
  data.delete(:requestBody) if data[:requestBody].blank?
@@ -113,16 +115,20 @@ module ApiRegulator
113
115
  def self.generate_param_schema(param)
114
116
  schema = {}
115
117
 
118
+ schema[:description] = param.desc if param.desc.present?
119
+
116
120
  if param.parameter?
117
121
  schema[:in] = param.location
118
122
  schema[:schema] = { type: param.type.to_s.downcase }
123
+ generate_param_schema_details(param, schema[:schema])
119
124
  else
120
125
  schema[:type] = param.type.to_s.downcase
126
+ generate_param_schema_details(param, schema)
121
127
  end
128
+ schema
129
+ end
122
130
 
123
- schema[:description] = param.desc if param.desc.present?
124
-
125
-
131
+ def self.generate_param_schema_details(param, schema)
126
132
  # Add length constraints
127
133
  if param.options[:length]
128
134
  schema[:minLength] = param.options[:length][:minimum] if param.options[:length][:minimum]
@@ -142,12 +148,12 @@ module ApiRegulator
142
148
  end
143
149
  end
144
150
 
145
- if param.options[:numericality]
146
- schema[:type] = param.options[:only_integer] ? 'integer' : 'number'
147
- schema[:minimum] = param.options[:greater_than_or_equal_to] if param.options[:greater_than_or_equal_to]
148
- schema[:maximum] = param.options[:less_than_or_equal_to] if param.options[:less_than_or_equal_to]
149
- schema[:exclusiveMinimum] = param.options[:greater_than] if param.options[:greater_than]
150
- schema[:exclusiveMaximum] = param.options[:less_than] if param.options[:less_than]
151
+ if numericality = param.options[:numericality]
152
+ schema[:type] = numericality[:only_integer] || schema[:type] == "integer" ? 'integer' : 'number'
153
+ schema[:minimum] = numericality[:greater_than_or_equal_to] if numericality[:greater_than_or_equal_to]
154
+ schema[:maximum] = numericality[:less_than_or_equal_to] if numericality[:less_than_or_equal_to]
155
+ schema[:exclusiveMinimum] = numericality[:greater_than] if numericality[:greater_than]
156
+ schema[:exclusiveMaximum] = numericality[:less_than] if numericality[:less_than]
151
157
  end
152
158
 
153
159
  if param.options[:inclusion]
@@ -160,13 +166,12 @@ module ApiRegulator
160
166
 
161
167
  schema[:nullable] = true if param.options[:allow_nil]
162
168
 
163
- schema
164
169
  end
165
170
 
166
171
  def self.generate_responses(api)
167
172
  api.responses.each_with_object({}) do |(status_code, schema), responses|
168
173
  if schema.options[:ref]
169
- shared_schema = ApiRegulator.shared_schemas[schema.options[:ref]]
174
+ shared_schema = ApiRegulator.shared_schema(schema.options[:ref])
170
175
  raise "Shared schema not found for ref: #{schema.options[:ref]}" unless shared_schema
171
176
 
172
177
  responses[status_code.to_s] = {
@@ -177,6 +182,10 @@ module ApiRegulator
177
182
  }
178
183
  }
179
184
  }.compact
185
+ elsif schema.children.empty?
186
+ responses[status_code.to_s] = {
187
+ description: schema.desc.presence
188
+ }
180
189
  else
181
190
  responses[status_code.to_s] = {
182
191
  description: schema.desc.presence,
@@ -195,6 +204,31 @@ module ApiRegulator
195
204
  add_security_schemes(schema)
196
205
  end
197
206
 
207
+ def self.add_webhooks(schema)
208
+ return if ApiRegulator.webhook_definitions.empty?
209
+
210
+ schema[:webhooks] ||= {}
211
+ ApiRegulator.webhook_definitions.each do |webhook|
212
+ schema[:webhooks][webhook.event_name] = {
213
+ post: {
214
+ summary: webhook.title,
215
+ description: webhook.description.presence || webhook.title,
216
+ tags: webhook.tags,
217
+ requestBody: {
218
+ required: true,
219
+ content: {
220
+ 'application/json' => {
221
+ schema: expand_nested_params(webhook.params),
222
+ examples: webhook.examples || {}
223
+ }
224
+ }
225
+ },
226
+ responses: generate_responses(webhook)
227
+ }.compact
228
+ }
229
+ end
230
+ end
231
+
198
232
  def self.add_shared_schemas(schema)
199
233
  return unless ApiRegulator.shared_schemas.present?
200
234
 
@@ -19,11 +19,20 @@ module ApiRegulator
19
19
  @children << child
20
20
  end
21
21
 
22
- def ref(ref_name)
23
- shared_schema = ApiRegulator.shared_schemas[ref_name]
22
+ def ref(ref_name, except: [], only: [])
23
+ shared_schema = ApiRegulator.shared_schema(ref_name)
24
24
  raise "Shared schema #{ref_name} not found" unless shared_schema
25
25
 
26
- shared_schema.params.each do |shared_param|
26
+ # Filter parameters based on `only` or `except` options
27
+ filtered_params = shared_schema.params
28
+
29
+ if only.any?
30
+ filtered_params = filtered_params.select { |param| only.include?(param.name) }
31
+ elsif except.any?
32
+ filtered_params = filtered_params.reject { |param| except.include?(param.name) }
33
+ end
34
+
35
+ filtered_params.each do |shared_param|
27
36
  @children << shared_param
28
37
  end
29
38
  end
@@ -0,0 +1,13 @@
1
+ module ApiRegulator
2
+ class << self
3
+ attr_accessor :security
4
+
5
+ def security_schemes
6
+ @security_schemes ||= {}
7
+ end
8
+
9
+ def security_schemes=(scheme)
10
+ @security_schemes = scheme
11
+ end
12
+ end
13
+ end
@@ -15,23 +15,32 @@ module ApiRegulator
15
15
  end
16
16
  end
17
17
 
18
+ @shared_schemas = {}
19
+ @shared_schema_registry = {}
20
+
18
21
  class << self
19
- attr_accessor :security
22
+ attr_accessor :shared_schema_registry
20
23
 
21
24
  def shared_schemas
22
- @shared_schemas ||= {}
23
- end
25
+ shared_schema_registry.each do |name, (description, block)|
26
+ @shared_schemas[name] = SharedSchema.new(name, description, &block)
27
+ end
24
28
 
25
- def security_schemes
26
- @security_schemes ||= {}
29
+ @shared_schemas
27
30
  end
28
31
 
29
- def shared_schema(name, description, &block)
30
- shared_schemas[name] = SharedSchema.new(name, description, &block)
32
+ def shared_schema(name)
33
+ if shared_schema_registry[name]
34
+ description, block = shared_schema_registry.delete(name)
35
+
36
+ @shared_schemas[name] = SharedSchema.new(name, description, &block)
37
+ end
38
+
39
+ @shared_schemas[name]
31
40
  end
32
41
 
33
- def security_schemes=(scheme)
34
- @security_schemes = scheme
42
+ def register_shared_schema(name, description, &block)
43
+ shared_schema_registry[name] = [description, block]
35
44
  end
36
45
  end
37
46
  end
@@ -110,7 +110,7 @@ module ApiRegulator
110
110
  end
111
111
 
112
112
  raw_value.each_with_index do |value, index|
113
- unless value.is_a?(Hash)
113
+ unless value.is_a?(Hash) || raw_value.is_a?(ActionController::Parameters)
114
114
  errors.add("#{attribute}[#{index}]", "must be a hash")
115
115
  next
116
116
  end
@@ -164,7 +164,8 @@ module ApiRegulator
164
164
  errors.add(attribute, "can't be blank") if param.options[:presence]
165
165
  return
166
166
  end
167
- unless raw_value.is_a?(Hash)
167
+
168
+ unless raw_value.is_a?(Hash) || raw_value.is_a?(ActionController::Parameters)
168
169
  errors.add(attribute, "must be a hash")
169
170
  return
170
171
  end
@@ -231,6 +232,10 @@ module ApiRegulator
231
232
  end
232
233
  end
233
234
 
235
+ def self.reset_validators
236
+ @validators = {}
237
+ end
238
+
234
239
  def initialize(attributes = {})
235
240
  @raw_attributes = attributes.deep_symbolize_keys
236
241
  allowed_attributes = attributes.slice(*self.class.defined_attributes.map(&:to_sym))
@@ -238,8 +243,3 @@ module ApiRegulator
238
243
  end
239
244
  end
240
245
  end
241
-
242
- class FooValidator
243
- include ActiveModel::Model
244
- include ActiveModel::Attributes
245
- end
@@ -1,3 +1,3 @@
1
1
  module ApiRegulator
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -0,0 +1,59 @@
1
+ module ApiRegulator
2
+ class Webhook
3
+ attr_reader :event_name, :description, :params, :responses, :examples, :tags, :title
4
+
5
+ def initialize(event_name, desc: nil, title: nil, tags: [], &block)
6
+ @event_name = event_name
7
+ @description = desc
8
+ @title = title
9
+ @tags = tags
10
+ @params = []
11
+ @responses = {}
12
+
13
+ instance_eval(&block) if block_given?
14
+ end
15
+
16
+ def param(name, type = nil, item_type: nil, desc: "", location: :body, **options, &block)
17
+ param = Param.new(name, type, item_type: item_type, desc: desc, location: location, **options, &block)
18
+ @params << param
19
+ end
20
+
21
+ def ref(ref_name, except: [], only: [])
22
+ shared_schema = ApiRegulator.shared_schema(ref_name)
23
+ raise "Shared schema #{ref_name} not found" unless shared_schema
24
+
25
+ # Filter parameters based on `only` or `except` options
26
+ filtered_params = shared_schema.params
27
+
28
+ if only.any?
29
+ filtered_params = filtered_params.select { |param| only.include?(param.name) }
30
+ elsif except.any?
31
+ filtered_params = filtered_params.reject { |param| except.include?(param.name) }
32
+ end
33
+
34
+ filtered_params.each do |shared_param|
35
+ @params << shared_param
36
+ end
37
+ end
38
+
39
+ def response(status_code, description, &block)
40
+ @responses[status_code] = Param.new(:root, :object, desc: description, &block)
41
+ end
42
+
43
+ def example(name, value, default: false)
44
+ @examples ||= {}
45
+ @examples[name] = { summary: "#{name} Example", value: value }
46
+ @default_example = value if default
47
+ end
48
+ end
49
+
50
+ class << self
51
+ def webhook_definitions
52
+ @webhook_definitions ||= []
53
+ end
54
+
55
+ def reset_webhook_definitions
56
+ @webhook_definitions = []
57
+ end
58
+ end
59
+ end
@@ -1,10 +1,11 @@
1
+ require 'yaml'
2
+
1
3
  namespace :api_docs do
2
4
  desc 'Generate OpenAPI schema'
3
5
  task generate: :environment do
4
- Rails.application.eager_load!
6
+ Rails.application.eager_load! # Ensure all controllers and API definitions are loaded
5
7
 
6
- api_definitions = ApiRegulator.configuration.base_controller_klass.descendants.flat_map(&:api_definitions)
7
- ApiRegulator::OpenApiGenerator.generate(api_definitions)
8
+ ApiRegulator::OpenApiGenerator.generate(ApiRegulator.api_definitions)
8
9
  end
9
10
 
10
11
  desc "Upload OpenAPI schema to ReadMe"
@@ -16,10 +17,11 @@ namespace :api_docs do
16
17
  readme_api_endpoint = "https://dash.readme.com/api/v1/api-specification"
17
18
 
18
19
  # Read the OpenAPI schema file
19
- unless File.exist?(ApiRegulator.configuration.docs_path)
20
- raise "OpenAPI schema file not found at #{ApiRegulator.configuration.docs_path}"
20
+ schema_path = "#{ApiRegulator.configuration.docs_path}/openapi.json"
21
+ unless File.exist?(schema_path)
22
+ raise "OpenAPI schema file not found at #{schema_path}"
21
23
  end
22
- openapi_content = File.read(ApiRegulator.configuration.docs_path)
24
+ openapi_content = File.read(schema_path)
23
25
 
24
26
  # Upload to ReadMe
25
27
  require 'net/http'
@@ -63,5 +65,134 @@ namespace :api_docs do
63
65
  task publish: :environment do
64
66
  Rake::Task["api_docs:generate"].invoke
65
67
  Rake::Task["api_docs:upload"].invoke
68
+ Rake::Task["api_docs:upload_pages"].invoke
69
+ end
70
+
71
+ desc "Upload custom pages to ReadMe"
72
+ task :upload_pages => :environment do
73
+ # Configuration
74
+ readme_api_key = ENV['RDME_API_KEY'] || raise("RDME_API_KEY is not set")
75
+ base_readme_api_endpoint = "https://dash.readme.com/api/v1/docs"
76
+
77
+ # Discover all documentation files
78
+ pages_directory = "#{ApiRegulator.configuration.docs_path}/**/*.md"
79
+ page_files = Dir.glob(pages_directory)
80
+
81
+ # Iterate through each file
82
+ page_files.each do |file_path|
83
+ # Extract metadata and body
84
+ metadata, body = parse_markdown_file(file_path)
85
+ raise "No metadata found in #{file_path}" unless metadata
86
+
87
+ # Use metadata to build the API request
88
+ slug = metadata["slug"] || File.basename(file_path, ".md").gsub("_", "-")
89
+ body = {
90
+ type: "basic",
91
+ categorySlug: "documentation",
92
+ hidden: false
93
+ }.merge(metadata)
94
+ body["slug"] ||= slug
95
+
96
+ raise("Title missing in #{file_path}") unless body["title"].present?
97
+
98
+ # Build the API request
99
+ if check_if_page_exists(slug)
100
+ uri = URI.parse("#{base_readme_api_endpoint}/#{slug}")
101
+ request = Net::HTTP::Put.new(uri)
102
+ else
103
+ uri = URI.parse("#{base_readme_api_endpoint}")
104
+ request = Net::HTTP::Post.new(uri)
105
+ end
106
+ request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
107
+ request["Content-Type"] = "application/json"
108
+ request.body = body.compact.to_json
109
+
110
+ # Send the request
111
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
112
+ http.request(request)
113
+ end
114
+
115
+ # Handle the response
116
+ case response.code.to_i
117
+ when 200
118
+ puts "Page '#{body["title"]}' successfully updated!"
119
+ when 201
120
+ puts "Page '#{body["title"]}' successfully created!"
121
+ else
122
+ puts "Failed to upload page '#{body["title"]}'!"
123
+ puts "Response Code: #{response.code}"
124
+ puts "Response Body: #{response.body}"
125
+ end
126
+ end
127
+ end
128
+
129
+ desc "View all categories on Readme.com"
130
+ task :fetch_categories => :environment do
131
+ # Configuration
132
+ readme_api_key = ENV['RDME_API_KEY'] || raise("RDME_API_KEY is not set")
133
+ readme_categories_api_endpoint = "https://dash.readme.com/api/v1/categories"
134
+
135
+ # Build the API request
136
+ uri = URI.parse(readme_categories_api_endpoint)
137
+ request = Net::HTTP::Get.new(uri)
138
+ request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
139
+ request["Content-Type"] = "application/json"
140
+
141
+ # Send the request
142
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
143
+ http.request(request)
144
+ end
145
+
146
+ # Handle the response
147
+ case response.code.to_i
148
+ when 200
149
+ puts "Categories"
150
+ pp JSON.parse(response.body)
151
+ else
152
+ puts "Failed to fetch categories"
153
+ puts "Response Code: #{response.code}"
154
+ puts "Response Body: #{response.body}"
155
+ end
156
+ end
157
+
158
+ def parse_markdown_file(file_path)
159
+ content = File.read(file_path)
160
+ metadata = nil
161
+ body = ""
162
+
163
+ # Split metadata and body
164
+ if content =~ /\A---\s*\n(.*?)\n---\s*\n(.*)/m
165
+ metadata = YAML.safe_load($1)
166
+ body = $2
167
+ else
168
+ raise "YAML front matter missing in #{file_path}"
169
+ end
170
+
171
+ [metadata, body]
172
+ end
173
+
174
+ def check_if_page_exists(slug)
175
+ readme_api_key = ENV['RDME_API_KEY'] || raise("RDME_API_KEY is not set")
176
+ uri = URI.parse("https://dash.readme.com/api/v1/docs/#{slug}")
177
+ request = Net::HTTP::Get.new(uri)
178
+ request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
179
+ request["Content-Type"] = "application/json"
180
+
181
+ # Send the request
182
+ response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
183
+ http.request(request)
184
+ end
185
+
186
+ # Handle the response
187
+ case response.code.to_i
188
+ when 200
189
+ true
190
+ when 404
191
+ false
192
+ else
193
+ puts "Failed to serach for page"
194
+ puts "Response Code: #{response.code}"
195
+ puts "Response Body: #{response.body}"
196
+ end
66
197
  end
67
198
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-regulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geoff Massanek
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-06 00:00:00.000000000 Z
11
+ date: 2024-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -130,6 +130,7 @@ executables: []
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
+ - ".github/workflows/spec.yml"
133
134
  - ".gitignore"
134
135
  - ".rspec"
135
136
  - ".ruby-version"
@@ -151,10 +152,12 @@ files:
151
152
  - lib/api_regulator/formats.rb
152
153
  - lib/api_regulator/open_api_generator.rb
153
154
  - lib/api_regulator/param.rb
155
+ - lib/api_regulator/security.rb
154
156
  - lib/api_regulator/shared_schema.rb
155
157
  - lib/api_regulator/validation_error.rb
156
158
  - lib/api_regulator/validator.rb
157
159
  - lib/api_regulator/version.rb
160
+ - lib/api_regulator/webhook.rb
158
161
  - lib/tasks/api_regulator_tasks.rake
159
162
  homepage: https://github.com/Stellarcred/stellar-gears
160
163
  licenses: