api-regulator 0.1.19 → 0.1.20

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: 2222bdbbc060773ad1c512bfdedd9f7b7acee3920249223236e4468317520353
4
- data.tar.gz: 3642bc547ab2ddde180ab34b551d42e496fcc1e49bda947b1f0f9f521a79d498
3
+ metadata.gz: 837c44e67ec38b0e133c4aa4bb42959bd8488497da252a71d2eed57d2c366b3b
4
+ data.tar.gz: ae1383ca7891e8c4e07967f4a52e497375faab6d495d72201403ce73f88d06cd
5
5
  SHA512:
6
- metadata.gz: 22a0cb6db6778f6fdbe0cc795658628120c57907a513c73bdd52f3d138135d8895b1044931e9392e67e265bf5e112b22003837c924d56717f5043494a68401e0
7
- data.tar.gz: e728660a393d6f57028fa26732b160c94bcc7e7567bc6957fa0d55322de4cfac490c4a742136c53f1119df79bd5d6ea088d393fcc363115e91734b7e858adac4
6
+ metadata.gz: 42f8341aa27841393a663ac1fa2ec2b540dbedaf54d3a3d2b6144e2369933514e7cc896e2dc6929363f5c5fac140d378618a1e16304d0c24e72ba3746644d119
7
+ data.tar.gz: 39da0748340946964f64615dc392f742e47ccfce02b9c2c09cb9a80cc85aaf206128ae480f27ab57c44722c56db99e83ed13c7d2846bf6c54c8882abda417748
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- api-regulator (0.1.19)
4
+ api-regulator (0.1.20)
5
5
  activemodel (~> 8.0)
6
6
  activesupport (~> 8.0)
7
7
 
data/README.md CHANGED
@@ -43,7 +43,14 @@ Run `bundle install` to install the gem.
43
43
  config.api_base_url = "/api/v1" # Set a common base path for your API endpoints
44
44
  config.docs_path = Rails.root.join("doc").to_s # Path for folder for docs
45
45
  config.app_name = "My API" # shows in docs
46
- config.rdme_api_id = ENV["RDME_API_ID"] # Optional: ReadMe API ID for schema uploads
46
+
47
+ # Optional: ReadMe versions and API ID for schema uploads
48
+ config.versions = {
49
+ "v1.0" => "abc123",
50
+ "v1.0-internal" => "abc345"
51
+ }
52
+ config.default_version = "v1.0-internal"
53
+
47
54
  config.servers = [
48
55
  { url: "https://stg.example.com", description: "Staging", "x-default": true },
49
56
  { url: "https://example.com", description: "Production" }
@@ -120,7 +127,8 @@ Generate OpenAPI documentation using the provided Rake tasks:
120
127
 
121
128
  1. **Generate the Schema:**
122
129
  ```bash
123
- rake api_docs:generate
130
+ rake api_docs:generate # uses default (or no) version
131
+ VERSION=v1.0-internal rake api_docs:generate # specifies the to generate
124
132
  ```
125
133
 
126
134
  2. **Upload to ReadMe (Optional)**:
@@ -1,14 +1,15 @@
1
1
  module ApiRegulator
2
2
  class Api
3
- attr_reader :controller_class, :controller_path, :controller_name, :action_name, :description, :title, :params, :responses
3
+ attr_reader :controller_class, :controller_path, :controller_name, :action_name, :description, :title, :params, :responses, :versions
4
4
 
5
- def initialize(controller_class, action_name, desc: nil, title: nil, &block)
5
+ def initialize(controller_class, action_name, desc: nil, title: nil, versions: [], &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
10
  @description = desc
11
11
  @title = title
12
+ @versions = Array(versions).map(&:to_sym)
12
13
 
13
14
  @params = []
14
15
  @responses = {}
@@ -16,9 +17,9 @@ module ApiRegulator
16
17
  instance_eval(&block) if block_given?
17
18
  end
18
19
 
19
- def param(name, type = nil, item_type: nil, desc: "", location: :body, allow_arbitrary_keys: false, **options, &block)
20
- param = Param.new(name, type, item_type: item_type, desc: desc, location: location, api: self, allow_arbitrary_keys: allow_arbitrary_keys, **options, &block)
21
- @params << param
20
+ def param(name, type = nil, **options, &block)
21
+ options[:api] ||= self
22
+ @params << Param.new(name, type, **options, &block)
22
23
  end
23
24
 
24
25
  def ref(ref_name, except: [], only: [])
@@ -90,6 +91,12 @@ module ApiRegulator
90
91
  ]
91
92
  end
92
93
 
94
+ def for_version?(version)
95
+ return true unless versions.present? && version.present?
96
+
97
+ versions.include?(version.to_sym)
98
+ end
99
+
93
100
  def allows_body?
94
101
  http_method != "get"
95
102
  end
@@ -1,6 +1,6 @@
1
1
  module ApiRegulator
2
2
  class Configuration
3
- attr_accessor :api_base_url, :app_name, :docs_path, :rdme_api_id, :servers
3
+ attr_accessor :api_base_url, :app_name, :docs_path, :versions, :default_version, :servers
4
4
 
5
5
  def initialize
6
6
  # Set default values
@@ -9,7 +9,7 @@ module ApiRegulator
9
9
  end
10
10
 
11
11
  module ClassMethods
12
- def api(controller_class, action, desc: nil, title: nil, &block)
12
+ def api(controller_class, action, desc: nil, title: nil, versions: [], &block)
13
13
  @api_definitions ||= []
14
14
 
15
15
  api_definition = Api.new(
@@ -17,6 +17,7 @@ module ApiRegulator
17
17
  action.to_s,
18
18
  desc: desc,
19
19
  title: title,
20
+ versions: versions,
20
21
  &block
21
22
  )
22
23
 
@@ -2,35 +2,60 @@ require 'json'
2
2
 
3
3
  module ApiRegulator
4
4
  class OpenApiGenerator
5
- def self.generate(api_definitions)
6
- schema = {
5
+ def self.generate(api_definitions, version: ApiRegulator.configuration.default_version)
6
+ generator = new(api_definitions, version: version)
7
+ generator.generate
8
+ end
9
+
10
+ def self.schema_file_path(version: ApiRegulator.configuration.default_version)
11
+ file_name = version.present? ? "openapi-#{version}.json" : "openapi.json"
12
+ [
13
+ ApiRegulator.configuration.docs_path,
14
+ "/",
15
+ file_name
16
+ ].compact.join
17
+ end
18
+
19
+ attr_reader :api_definitions
20
+ attr_reader :version
21
+ attr_reader :final_schema
22
+
23
+ def initialize(api_definitions, version:)
24
+ @api_definitions = api_definitions
25
+ @version = version
26
+ end
27
+
28
+ def generate
29
+ @final_schema = {
7
30
  openapi: '3.1.0', # Explicitly target OpenAPI 3.1.0
8
31
  info: {
9
32
  title: ApiRegulator.configuration.app_name,
10
- version: '1.0.0',
11
33
  description: 'Generated by ApiRegulator'
12
34
  },
13
35
  servers: ApiRegulator.configuration.servers,
14
36
  paths: {}
15
37
  }
38
+ final_schema[:info][:version] = version if version.present?
16
39
 
17
- add_components(schema)
18
- add_security(schema)
19
- add_webhooks(schema)
40
+ add_components
41
+ add_security
42
+ add_webhooks
20
43
 
21
44
  api_definitions.each do |api|
22
- add_api_to_schema(schema, api)
45
+ add_api_to_schema(api)
23
46
  end
24
47
 
25
- schema_path = "#{ApiRegulator.configuration.docs_path}/openapi.json"
26
- File.write(schema_path, JSON.pretty_generate(schema))
48
+ schema_path = self.class.schema_file_path(version: version)
49
+ File.write(schema_path, JSON.pretty_generate(final_schema))
27
50
  puts "OpenAPI schema generated: #{schema_path}"
28
51
  end
29
52
 
30
53
  private
31
54
 
32
- def self.add_api_to_schema(schema, api)
33
- schema[:paths][api.path] ||= {}
55
+ def add_api_to_schema(api)
56
+ return unless api.for_version?(version)
57
+
58
+ final_schema[:paths][api.path] ||= {}
34
59
  data = {
35
60
  summary: api.title,
36
61
  description: api.description.presence || api.title,
@@ -43,10 +68,10 @@ module ApiRegulator
43
68
  data[:requestBody] = generate_request_body(api) if api.allows_body?
44
69
  data.delete(:requestBody) if data[:requestBody].blank?
45
70
 
46
- schema[:paths][api.path][api.http_method] = data
71
+ final_schema[:paths][api.path][api.http_method] = data
47
72
  end
48
73
 
49
- def self.generate_request_body(api)
74
+ def generate_request_body(api)
50
75
  params = api.params.select(&:body?)
51
76
  return {} if params.empty?
52
77
 
@@ -60,7 +85,7 @@ module ApiRegulator
60
85
  }
61
86
  end
62
87
 
63
- def self.expand_nested_params(params)
88
+ def expand_nested_params(params)
64
89
  schema = {
65
90
  type: 'object',
66
91
  properties: {},
@@ -68,6 +93,8 @@ module ApiRegulator
68
93
  }
69
94
 
70
95
  params.each do |param|
96
+ next unless param.for_version?(version)
97
+
71
98
  schema[:properties][param.name] =
72
99
  if param.type == :array
73
100
  generate_array_schema(param)
@@ -86,7 +113,7 @@ module ApiRegulator
86
113
  schema
87
114
  end
88
115
 
89
- def self.generate_array_schema(param)
116
+ def generate_array_schema(param)
90
117
  item_schema = param.children.any? ? generate_object_schema(param) : { type: param.item_type }
91
118
 
92
119
  {
@@ -96,24 +123,26 @@ module ApiRegulator
96
123
  }.compact
97
124
  end
98
125
 
99
- def self.generate_object_schema(param)
126
+ def generate_object_schema(param)
100
127
  object_schema = expand_nested_params(param.children)
101
128
  object_schema[:description] = param.desc if param.desc.present?
102
129
  object_schema[:type] = param.allow_nil? ? ['object', 'null'] : 'object'
103
130
  object_schema
104
131
  end
105
132
 
106
- def self.generate_parameters(api)
107
- api.params.select(&:parameter?).map do |p|
108
- generate_param_schema(p)
133
+ def generate_parameters(api)
134
+ api.params.select(&:parameter?).filter_map do |param|
135
+ next unless param.for_version?(version)
136
+
137
+ generate_param_schema(param)
109
138
  .merge({
110
- name: p.name,
111
- required: p.location == :path || p.required? || false
139
+ name: param.name,
140
+ required: param.path? || param.required? || false
112
141
  })
113
142
  end
114
143
  end
115
144
 
116
- def self.generate_param_schema(param)
145
+ def generate_param_schema(param)
117
146
  schema = {}
118
147
 
119
148
  schema[:description] = param.desc if param.desc.present?
@@ -136,7 +165,7 @@ module ApiRegulator
136
165
  schema
137
166
  end
138
167
 
139
- def self.generate_param_schema_details(param, schema)
168
+ def generate_param_schema_details(param, schema)
140
169
  # Add length constraints
141
170
  if param.options[:length]
142
171
  schema[:minLength] = param.options[:length][:minimum] if param.options[:length][:minimum]
@@ -178,7 +207,7 @@ module ApiRegulator
178
207
 
179
208
  end
180
209
 
181
- def self.generate_responses(api)
210
+ def generate_responses(api)
182
211
  Hash[api.responses.sort].each_with_object({}) do |(status_code, schema), responses|
183
212
  if schema.options[:ref]
184
213
  shared_schema = ApiRegulator.shared_schema(schema.options[:ref])
@@ -209,17 +238,17 @@ module ApiRegulator
209
238
  end
210
239
  end
211
240
 
212
- def self.add_components(schema)
213
- add_shared_schemas(schema)
214
- add_security_schemes(schema)
241
+ def add_components
242
+ add_shared_schemas
243
+ add_security_schemes
215
244
  end
216
245
 
217
- def self.add_webhooks(schema)
246
+ def add_webhooks
218
247
  return if ApiRegulator.webhook_definitions.empty?
219
248
 
220
- schema[:webhooks] ||= {}
249
+ final_schema[:webhooks] ||= {}
221
250
  ApiRegulator.webhook_definitions.each do |webhook|
222
- schema[:webhooks][webhook.event_name] = {
251
+ final_schema[:webhooks][webhook.event_name] = {
223
252
  post: {
224
253
  summary: webhook.title,
225
254
  description: webhook.description.presence || webhook.title,
@@ -239,30 +268,30 @@ module ApiRegulator
239
268
  end
240
269
  end
241
270
 
242
- def self.add_shared_schemas(schema)
271
+ def add_shared_schemas
243
272
  return unless ApiRegulator.shared_schemas.present?
244
273
 
245
- schema[:components] ||= {}
246
- schema[:components][:schemas] ||= {}
274
+ final_schema[:components] ||= {}
275
+ final_schema[:components][:schemas] ||= {}
247
276
 
248
277
  ApiRegulator.shared_schemas.each do |name, shared_schema|
249
- schema[:components][:schemas][name] = expand_nested_params(shared_schema.params).merge(
278
+ final_schema[:components][:schemas][name] = expand_nested_params(shared_schema.params).merge(
250
279
  description: shared_schema.description
251
280
  ).compact
252
281
  end
253
282
  end
254
283
 
255
- def self.add_security_schemes(schema)
284
+ def add_security_schemes
256
285
  return unless ApiRegulator.security_schemes.present?
257
286
 
258
- schema[:components] ||= {}
259
- schema[:components][:securitySchemes] = ApiRegulator.security_schemes
287
+ final_schema[:components] ||= {}
288
+ final_schema[:components][:securitySchemes] = ApiRegulator.security_schemes
260
289
  end
261
290
 
262
- def self.add_security(schema)
291
+ def add_security
263
292
  return unless ApiRegulator.security.present?
264
293
 
265
- schema[:security] = ApiRegulator.security
294
+ final_schema[:security] = ApiRegulator.security
266
295
  end
267
296
  end
268
297
  end
@@ -1,24 +1,27 @@
1
1
  module ApiRegulator
2
2
  class Param
3
- attr_reader :name, :type, :options, :item_type, :desc, :location, :children, :api, :allow_arbitrary_keys
3
+ attr_reader :name, :type, :options, :item_type, :desc, :location, :children, :api, :allow_arbitrary_keys, :versions
4
4
 
5
- def initialize(name, type = nil, item_type: nil, desc: "", location: :body, api: nil, allow_arbitrary_keys: false, **options, &block)
5
+ def initialize(name, type = nil, **options, &block)
6
6
  @name = name
7
7
  @type = type&.to_sym || (block_given? ? :object : :string)
8
- @item_type = item_type
9
- @location = location.to_sym
10
- @desc = desc
11
- @options = options
8
+
9
+ @item_type = options.delete(:item_type)
10
+ @desc = options.delete(:desc) || ""
11
+ @location = (options.delete(:location) || :body).to_sym
12
+ @api = options.delete(:api)
13
+ @allow_arbitrary_keys = options.delete(:allow_arbitrary_keys) || false
14
+ @versions = Array(options.delete(:versions)).map(&:to_sym)
15
+
12
16
  @children = []
13
- @api = api
14
- @allow_arbitrary_keys = allow_arbitrary_keys
17
+ @options = options
15
18
 
16
19
  instance_eval(&block) if block_given?
17
20
  end
18
21
 
19
- def param(name, type = nil, item_type: nil, desc: "", location: :body, allow_arbitrary_keys: false, **options, &block)
20
- child = Param.new(name, type, item_type: item_type, desc: desc, location: location, api: api, allow_arbitrary_keys: allow_arbitrary_keys, **options, &block)
21
- @children << child
22
+ def param(name, type = nil, **options, &block)
23
+ options[:api] ||= api
24
+ @children << Param.new(name, type, **options, &block)
22
25
  end
23
26
 
24
27
  def ref(ref_name, except: [], only: [])
@@ -111,6 +114,12 @@ module ApiRegulator
111
114
  end
112
115
  end
113
116
 
117
+ def for_version?(version)
118
+ return true unless versions.present? && version.present?
119
+
120
+ versions.include?(version.to_sym)
121
+ end
122
+
114
123
  def body?
115
124
  location == :body
116
125
  end
@@ -10,9 +10,8 @@ module ApiRegulator
10
10
  instance_eval(&block) if block_given?
11
11
  end
12
12
 
13
- def param(name, type = nil, item_type: nil, desc: "", location: :body, allow_arbitrary_keys: false, **options, &block)
14
- param = Param.new(name, type, item_type: item_type, desc: desc, location: location, allow_arbitrary_keys: allow_arbitrary_keys, **options, &block)
15
- @params << param
13
+ def param(name, type = nil, **options, &block)
14
+ @params << Param.new(name, type, **options, &block)
16
15
  end
17
16
 
18
17
  def response(status_code, description_or_options, &block)
@@ -1,3 +1,3 @@
1
1
  module ApiRegulator
2
- VERSION = "0.1.19"
2
+ VERSION = "0.1.20"
3
3
  end
@@ -13,9 +13,8 @@ module ApiRegulator
13
13
  instance_eval(&block) if block_given?
14
14
  end
15
15
 
16
- def param(name, type = nil, item_type: nil, desc: "", location: :body, allow_arbitrary_keys: false, **options, &block)
17
- param = Param.new(name, type, item_type: item_type, desc: desc, location: location, allow_arbitrary_keys: allow_arbitrary_keys, **options, &block)
18
- @params << param
16
+ def param(name, type = nil, **options, &block)
17
+ @params << Param.new(name, type, **options, &block)
19
18
  end
20
19
 
21
20
  def ref(ref_name, except: [], only: [])
@@ -5,22 +5,24 @@ namespace :api_docs do
5
5
  task generate: :environment do
6
6
  # Set an empty api key if none is provided
7
7
  ENV['RDME_API_KEY'] ||= ''
8
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
8
9
 
9
10
  Rails.application.eager_load! # Ensure all controllers and API definitions are loaded
10
11
 
11
- ApiRegulator::OpenApiGenerator.generate(ApiRegulator.api_definitions)
12
+ ApiRegulator::OpenApiGenerator.generate(ApiRegulator.api_definitions, version: version.presence)
12
13
  end
13
14
 
14
15
  desc "Upload OpenAPI schema to ReadMe"
15
16
  task :upload => :environment do
16
17
  # ReadMe API key and version
17
18
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
19
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
18
20
 
19
21
  # ReadMe API endpoint
20
22
  readme_api_endpoint = "https://dash.readme.com/api/v1/api-specification"
21
23
 
22
24
  # Read the OpenAPI schema file
23
- schema_path = "#{ApiRegulator.configuration.docs_path}/openapi.json"
25
+ schema_path = ApiRegulator::OpenApiGenerator.schema_file_path(version: version)
24
26
  unless File.exist?(schema_path)
25
27
  raise "OpenAPI schema file not found at #{schema_path}"
26
28
  end
@@ -31,8 +33,8 @@ namespace :api_docs do
31
33
  require 'uri'
32
34
  require 'json'
33
35
 
34
- if ApiRegulator.configuration.rdme_api_id
35
- uri = URI.parse("#{readme_api_endpoint}/#{ApiRegulator.configuration.rdme_api_id}")
36
+ if version.present?
37
+ uri = URI.parse("#{readme_api_endpoint}/#{ApiRegulator.configuration.versions[version]}")
36
38
  request = Net::HTTP::Put.new(uri)
37
39
  else
38
40
  uri = URI.parse(readme_api_endpoint)
@@ -40,6 +42,7 @@ namespace :api_docs do
40
42
  end
41
43
  request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
42
44
  request["Content-Type"] = "application/json"
45
+ request["x-readme-version"] = version if version.present?
43
46
  request.body = {
44
47
  spec: JSON.parse(openapi_content)
45
48
  }.to_json
@@ -54,7 +57,9 @@ namespace :api_docs do
54
57
  puts "OpenAPI schema successfully created!"
55
58
  puts "To use this for future publishing, add this to your ApiRegulator configs:"
56
59
  puts ""
57
- puts " config.rdme_api_id = \"#{JSON.parse(response.body)["_id"]}\""
60
+ puts " config.versions = {"
61
+ puts " \"<versionNumber>\": \"#{JSON.parse(response.body)["_id"]}\""
62
+ puts " }"
58
63
  puts ""
59
64
  else
60
65
  puts "Failed to upload OpenAPI schema to ReadMe!"
@@ -75,6 +80,7 @@ namespace :api_docs do
75
80
  task :upload_pages => :environment do
76
81
  # Configuration
77
82
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
83
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
78
84
  base_readme_api_endpoint = "https://dash.readme.com/api/v1/docs"
79
85
 
80
86
  # Discover all documentation files
@@ -109,6 +115,7 @@ namespace :api_docs do
109
115
  end
110
116
  request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
111
117
  request["Content-Type"] = "application/json"
118
+ request["x-readme-version"] = version if version.present?
112
119
  request.body = request_body.compact.to_json
113
120
 
114
121
  # Send the request
@@ -134,6 +141,7 @@ namespace :api_docs do
134
141
  task :fetch_categories => :environment do
135
142
  # Configuration
136
143
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
144
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
137
145
  readme_categories_api_endpoint = "https://dash.readme.com/api/v1/categories"
138
146
 
139
147
  # Build the API request
@@ -141,6 +149,7 @@ namespace :api_docs do
141
149
  request = Net::HTTP::Get.new(uri)
142
150
  request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
143
151
  request["Content-Type"] = "application/json"
152
+ request["x-readme-version"] = version if version.present?
144
153
 
145
154
  # Send the request
146
155
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
@@ -177,10 +186,12 @@ namespace :api_docs do
177
186
 
178
187
  def check_if_page_exists(slug)
179
188
  readme_api_key = ENV['RDME_API_KEY'].presence || raise("RDME_API_KEY is not set")
189
+ version = ENV['VERSION'] || ApiRegulator.configuration.default_version
180
190
  uri = URI.parse("https://dash.readme.com/api/v1/docs/#{slug}")
181
191
  request = Net::HTTP::Get.new(uri)
182
192
  request["Authorization"] = "Basic #{Base64.strict_encode64(readme_api_key)}"
183
193
  request["Content-Type"] = "application/json"
194
+ request["x-readme-version"] = version if version.present?
184
195
 
185
196
  # Send the request
186
197
  response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
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.19
4
+ version: 0.1.20
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geoff Massanek
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-13 00:00:00.000000000 Z
11
+ date: 2025-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport