spec_forge 0.7.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +139 -9
- data/README.md +125 -203
- data/bin/spec_forge +1 -1
- data/flake.lock +76 -4
- data/flake.nix +5 -4
- data/lib/spec_forge/attribute/chainable.rb +6 -6
- data/lib/spec_forge/attribute/environment.rb +45 -0
- data/lib/spec_forge/attribute/factory.rb +26 -17
- data/lib/spec_forge/attribute/faker.rb +6 -1
- data/lib/spec_forge/attribute/generate.rb +114 -0
- data/lib/spec_forge/attribute/literal.rb +1 -14
- data/lib/spec_forge/attribute/matcher.rb +6 -2
- data/lib/spec_forge/attribute/parameterized.rb +20 -22
- data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
- data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
- data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
- data/lib/spec_forge/attribute/template.rb +118 -0
- data/lib/spec_forge/attribute/transform.rb +14 -19
- data/lib/spec_forge/attribute/variable.rb +31 -31
- data/lib/spec_forge/attribute.rb +54 -100
- data/lib/spec_forge/blueprint.rb +27 -0
- data/lib/spec_forge/cli/docs/generate.rb +28 -8
- data/lib/spec_forge/cli/docs.rb +5 -2
- data/lib/spec_forge/cli/init.rb +4 -4
- data/lib/spec_forge/cli/new.rb +78 -27
- data/lib/spec_forge/cli/run.rb +84 -52
- data/lib/spec_forge/cli/serve.rb +6 -0
- data/lib/spec_forge/cli.rb +6 -14
- data/lib/spec_forge/configuration.rb +212 -78
- data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
- data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
- data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
- data/lib/spec_forge/documentation/builder.rb +77 -329
- data/lib/spec_forge/documentation/document/operation.rb +4 -4
- data/lib/spec_forge/documentation/document.rb +0 -6
- data/lib/spec_forge/documentation/generator.rb +88 -0
- data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
- data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +22 -6
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +29 -7
- data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
- data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
- data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
- data/lib/spec_forge/documentation/openapi.rb +40 -12
- data/lib/spec_forge/documentation.rb +1 -7
- data/lib/spec_forge/error.rb +215 -41
- data/lib/spec_forge/factory.rb +38 -18
- data/lib/spec_forge/forge/action.rb +41 -0
- data/lib/spec_forge/forge/actions/call.rb +33 -0
- data/lib/spec_forge/forge/actions/debug.rb +47 -0
- data/lib/spec_forge/forge/actions/expect.rb +44 -0
- data/lib/spec_forge/forge/actions/request.rb +65 -0
- data/lib/spec_forge/forge/actions/store.rb +31 -0
- data/lib/spec_forge/forge/callbacks.rb +80 -0
- data/lib/spec_forge/forge/context.rb +41 -0
- data/lib/spec_forge/forge/display.rb +503 -0
- data/lib/spec_forge/forge/hooks.rb +131 -0
- data/lib/spec_forge/forge/runner/array_io.rb +81 -0
- data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
- data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
- data/lib/spec_forge/forge/runner/reporter.rb +56 -0
- data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
- data/lib/spec_forge/forge/runner.rb +118 -0
- data/lib/spec_forge/forge/timer.rb +94 -0
- data/lib/spec_forge/forge/variables.rb +38 -0
- data/lib/spec_forge/forge.rb +207 -133
- data/lib/spec_forge/http/backend.rb +49 -143
- data/lib/spec_forge/http/client.rb +14 -17
- data/lib/spec_forge/http/request.rb +37 -84
- data/lib/spec_forge/http/verb.rb +4 -0
- data/lib/spec_forge/http.rb +0 -5
- data/lib/spec_forge/loader/filter.rb +85 -0
- data/lib/spec_forge/loader/step_processor.rb +282 -0
- data/lib/spec_forge/loader.rb +105 -220
- data/lib/spec_forge/normalizer/default.rb +1 -1
- data/lib/spec_forge/normalizer/structure.rb +140 -0
- data/lib/spec_forge/normalizer/transformers.rb +168 -0
- data/lib/spec_forge/normalizer/validators.rb +50 -8
- data/lib/spec_forge/normalizer.rb +76 -119
- data/lib/spec_forge/normalizers/callback.yml +38 -0
- data/lib/spec_forge/normalizers/configuration.yml +59 -9
- data/lib/spec_forge/normalizers/factory.yml +53 -2
- data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
- data/lib/spec_forge/normalizers/json_schema.yml +79 -0
- data/lib/spec_forge/normalizers/step.yml +506 -0
- data/lib/spec_forge/step/call.rb +36 -0
- data/lib/spec_forge/step/expect.rb +110 -0
- data/lib/spec_forge/step/source.rb +22 -0
- data/lib/spec_forge/step.rb +129 -0
- data/lib/spec_forge/type.rb +115 -66
- data/lib/spec_forge/version.rb +1 -1
- data/lib/spec_forge.rb +44 -106
- data/lib/templates/forge_helper.rb.tt +43 -22
- data/lib/templates/new_blueprint.yml.tt +54 -0
- metadata +75 -44
- data/lib/spec_forge/attribute/global.rb +0 -96
- data/lib/spec_forge/attribute/store.rb +0 -65
- data/lib/spec_forge/backtrace_formatter.rb +0 -50
- data/lib/spec_forge/callbacks.rb +0 -88
- data/lib/spec_forge/context/callbacks.rb +0 -91
- data/lib/spec_forge/context/global.rb +0 -72
- data/lib/spec_forge/context/store.rb +0 -131
- data/lib/spec_forge/context/variables.rb +0 -91
- data/lib/spec_forge/context.rb +0 -36
- data/lib/spec_forge/core_ext/rspec.rb +0 -55
- data/lib/spec_forge/core_ext.rb +0 -5
- data/lib/spec_forge/documentation/generators/base.rb +0 -81
- data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
- data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
- data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
- data/lib/spec_forge/documentation/generators.rb +0 -17
- data/lib/spec_forge/documentation/loader.rb +0 -159
- data/lib/spec_forge/documentation/openapi/base.rb +0 -33
- data/lib/spec_forge/filter.rb +0 -86
- data/lib/spec_forge/normalizer/definition.rb +0 -248
- data/lib/spec_forge/normalizers/_shared.yml +0 -74
- data/lib/spec_forge/normalizers/constraint.yml +0 -8
- data/lib/spec_forge/normalizers/expectation.yml +0 -47
- data/lib/spec_forge/normalizers/global_context.yml +0 -28
- data/lib/spec_forge/normalizers/spec.yml +0 -50
- data/lib/spec_forge/runner/adapter.rb +0 -183
- data/lib/spec_forge/runner/callbacks.rb +0 -246
- data/lib/spec_forge/runner/debug_proxy.rb +0 -213
- data/lib/spec_forge/runner/listener.rb +0 -54
- data/lib/spec_forge/runner/metadata.rb +0 -58
- data/lib/spec_forge/runner/state.rb +0 -98
- data/lib/spec_forge/runner.rb +0 -75
- data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
- data/lib/spec_forge/spec/expectation.rb +0 -68
- data/lib/spec_forge/spec.rb +0 -68
- data/lib/templates/new_spec.yml.tt +0 -43
|
@@ -2,129 +2,263 @@
|
|
|
2
2
|
|
|
3
3
|
module SpecForge
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
# Defines default values and validation for all configuration options
|
|
5
|
+
# Holds configuration options for SpecForge
|
|
7
6
|
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
# Configuration is typically set in forge_helper.rb using the configure block.
|
|
8
|
+
# It controls base URL, global variables, factory settings, callbacks, and more.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic configuration
|
|
11
|
+
# SpecForge.configure do |config|
|
|
12
|
+
# config.base_url = "http://localhost:3000"
|
|
13
|
+
# config.global_variables = {api_version: "v1"}
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
class Configuration
|
|
12
17
|
#
|
|
13
|
-
#
|
|
14
|
-
# config.factories.auto_discover = false
|
|
15
|
-
# config.factories.paths += ["lib/factories"]
|
|
18
|
+
# Configuration for FactoryBot factory loading
|
|
16
19
|
#
|
|
17
20
|
class Factories < Struct.new(:auto_discover, :paths)
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
attr_predicate :
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
# Initializes a new Factories configuration
|
|
26
|
-
# Sets default values for auto-discovery and paths
|
|
27
|
-
#
|
|
28
|
-
# @param auto_discover [Boolean] Whether to auto-discover factories (default: true)
|
|
29
|
-
# @param paths [Array<String>] Additional paths to look for factories (default: [])
|
|
30
|
-
#
|
|
31
|
-
# @return [Factories] A new factories configuration instance
|
|
32
|
-
#
|
|
21
|
+
# @return [Boolean] Whether auto-discovery is enabled
|
|
22
|
+
attr_predicate :auto_discover
|
|
23
|
+
|
|
24
|
+
# @return [Array<String>] Factory file paths
|
|
25
|
+
attr_predicate :paths
|
|
26
|
+
|
|
33
27
|
def initialize(auto_discover: true, paths: []) = super
|
|
34
28
|
end
|
|
35
29
|
|
|
30
|
+
# @return [String] Base URL for HTTP requests
|
|
31
|
+
attr_accessor :base_url
|
|
32
|
+
|
|
33
|
+
# @return [Hash] Global variables available to all blueprints
|
|
34
|
+
attr_accessor :global_variables
|
|
35
|
+
|
|
36
|
+
# @return [Factories] Factory configuration
|
|
37
|
+
attr_reader :factories
|
|
38
|
+
|
|
39
|
+
# @return [Proc, nil] Debug handler proc
|
|
40
|
+
attr_reader :on_debug_proc
|
|
41
|
+
|
|
42
|
+
# @return [Hash{Symbol => Proc}] Registered callbacks
|
|
43
|
+
attr_reader :callbacks
|
|
44
|
+
|
|
45
|
+
# @return [Hash{Symbol => Array}] Global lifecycle hooks for forge, blueprint, and step events
|
|
46
|
+
attr_reader :hooks
|
|
47
|
+
|
|
36
48
|
#
|
|
37
|
-
#
|
|
38
|
-
# Sets up the configuration structure including factory settings and debug proxy
|
|
49
|
+
# Creates a new Configuration with default values
|
|
39
50
|
#
|
|
40
|
-
# @return [Configuration] A new configuration instance
|
|
51
|
+
# @return [Configuration] A new configuration instance
|
|
41
52
|
#
|
|
42
53
|
def initialize
|
|
43
|
-
|
|
54
|
+
# Validated
|
|
55
|
+
@base_url = "http://localhost:3000"
|
|
56
|
+
@factories = Factories.new
|
|
57
|
+
@global_variables = {}
|
|
44
58
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
# Internal
|
|
60
|
+
@on_debug_proc = nil
|
|
61
|
+
@callbacks = {}
|
|
62
|
+
@hooks = {
|
|
63
|
+
before_forge: [],
|
|
64
|
+
before_blueprint: [],
|
|
65
|
+
before_step: [],
|
|
66
|
+
after_step: [],
|
|
67
|
+
after_blueprint: [],
|
|
68
|
+
after_forge: []
|
|
69
|
+
}
|
|
50
70
|
end
|
|
51
71
|
|
|
52
72
|
#
|
|
53
|
-
# Validates the configuration and
|
|
54
|
-
# Ensures all required fields have values and applies defaults when needed
|
|
73
|
+
# Validates the configuration and normalizes values
|
|
55
74
|
#
|
|
56
|
-
# @return [
|
|
75
|
+
# @return [Configuration] self
|
|
57
76
|
#
|
|
58
|
-
# @
|
|
77
|
+
# @raise [Error::InvalidStructureError] If configuration is invalid
|
|
59
78
|
#
|
|
60
79
|
def validate
|
|
61
|
-
output = Normalizer.normalize!(
|
|
80
|
+
output = Normalizer.normalize!(
|
|
81
|
+
{
|
|
82
|
+
base_url: @base_url,
|
|
83
|
+
factories: @factories.to_h,
|
|
84
|
+
global_variables: @global_variables
|
|
85
|
+
},
|
|
86
|
+
using: :configuration
|
|
87
|
+
)
|
|
62
88
|
|
|
63
89
|
# In case any value was set to `nil`
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.headers = output[:headers] if headers.blank?
|
|
90
|
+
@global_variables = output[:global_variables] if @global_variables.blank?
|
|
91
|
+
@global_variables.symbolize_keys!
|
|
67
92
|
|
|
68
93
|
self
|
|
69
94
|
end
|
|
70
95
|
|
|
71
96
|
#
|
|
72
|
-
#
|
|
97
|
+
# Sets a debug handler block to be called when a step has debug: true
|
|
98
|
+
#
|
|
99
|
+
# @yield [context] Block called when debug is triggered
|
|
100
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
73
101
|
#
|
|
74
|
-
# @
|
|
102
|
+
# @example
|
|
103
|
+
# config.on_debug { binding.pry }
|
|
75
104
|
#
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
hash[:factories] = hash[:factories].to_h
|
|
79
|
-
hash
|
|
105
|
+
def on_debug(&block)
|
|
106
|
+
@on_debug_proc = block
|
|
80
107
|
end
|
|
81
108
|
|
|
82
109
|
#
|
|
83
|
-
# Returns
|
|
84
|
-
# Provides access to RSpec's internal configuration for test customization
|
|
110
|
+
# Returns RSpec's configuration for customization
|
|
85
111
|
#
|
|
86
|
-
# @return [RSpec::Core::Configuration] RSpec
|
|
112
|
+
# @return [RSpec::Core::Configuration] RSpec configuration
|
|
87
113
|
#
|
|
88
|
-
|
|
89
|
-
# SpecForge.configure do |config|
|
|
90
|
-
# config.specs.formatter = :documentation
|
|
91
|
-
# end
|
|
92
|
-
#
|
|
93
|
-
def specs
|
|
114
|
+
def rspec
|
|
94
115
|
RSpec.configuration
|
|
95
116
|
end
|
|
96
117
|
|
|
97
|
-
alias_method :rspec, :specs
|
|
98
|
-
|
|
99
118
|
#
|
|
100
|
-
# Registers a callback
|
|
101
|
-
# Allows custom code execution at specific points during test execution
|
|
119
|
+
# Registers a callback that can be invoked from blueprints using call:
|
|
102
120
|
#
|
|
103
|
-
# @param name [
|
|
104
|
-
# (:before_file, :after_expectation, etc.)
|
|
105
|
-
# @yield A block to execute when the callback is triggered
|
|
106
|
-
# @yieldparam context [Object] An object containing context-specific state data, depending
|
|
107
|
-
# on which hook the callback is triggered from.
|
|
121
|
+
# @param name [String, Symbol] The callback name to register
|
|
108
122
|
#
|
|
109
|
-
# @
|
|
123
|
+
# @yield [context, *args] Block to execute when callback is called
|
|
124
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
110
125
|
#
|
|
111
|
-
# @example
|
|
112
|
-
#
|
|
113
|
-
#
|
|
126
|
+
# @example Simple callback
|
|
127
|
+
# config.register_callback("seed_data") do |context|
|
|
128
|
+
# User.create!(name: "Test")
|
|
114
129
|
# end
|
|
115
130
|
#
|
|
116
|
-
# @example
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# DatabaseCleaner.clean
|
|
120
|
-
# end
|
|
131
|
+
# @example Callback with arguments
|
|
132
|
+
# config.register_callback("create_users") do |context, count:|
|
|
133
|
+
# count.times { User.create! }
|
|
121
134
|
# end
|
|
122
135
|
#
|
|
123
|
-
def register_callback(name, &)
|
|
124
|
-
|
|
136
|
+
def register_callback(name, &block)
|
|
137
|
+
@callbacks[name.to_sym] = block
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
#
|
|
141
|
+
# Removes a registered callback and detaches it from all lifecycle hooks
|
|
142
|
+
#
|
|
143
|
+
# @param name [String, Symbol] The callback name to remove
|
|
144
|
+
#
|
|
145
|
+
# @return [Proc, nil] The removed callback proc, or nil if not found
|
|
146
|
+
#
|
|
147
|
+
# @example Remove a callback
|
|
148
|
+
# config.register_callback(:my_hook) { |context| puts "hook" }
|
|
149
|
+
# config.before(:step, :my_hook)
|
|
150
|
+
# config.deregister_callback(:my_hook) # Removes from callbacks and hooks
|
|
151
|
+
#
|
|
152
|
+
def deregister_callback(name)
|
|
153
|
+
name = name.to_sym
|
|
154
|
+
|
|
155
|
+
callback = @callbacks.delete(name)
|
|
156
|
+
@hooks.each_value { |a| a.delete(name) }
|
|
157
|
+
|
|
158
|
+
callback
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Attaches a callback to a before lifecycle event
|
|
163
|
+
#
|
|
164
|
+
# Global hooks run for all blueprints and execute in registration order.
|
|
165
|
+
# Can either reference a pre-registered callback by name, or accept a block
|
|
166
|
+
# to register and attach a callback in one step (like RSpec's before hooks).
|
|
167
|
+
#
|
|
168
|
+
# @param event [Symbol] The lifecycle event (:forge, :blueprint, or :step)
|
|
169
|
+
# @param callback_name [String, Symbol, nil] The name of a registered callback
|
|
170
|
+
# (optional if block is provided)
|
|
171
|
+
#
|
|
172
|
+
# @yield [context] Block to execute (registers callback automatically)
|
|
173
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
174
|
+
#
|
|
175
|
+
# @return [String, Symbol] The callback name (auto-generated if block provided)
|
|
176
|
+
#
|
|
177
|
+
# @raise [ArgumentError] If the event is invalid
|
|
178
|
+
# @raise [ArgumentError] If the callback is not registered (when using name)
|
|
179
|
+
#
|
|
180
|
+
# @example Attach a pre-registered callback
|
|
181
|
+
# config.register_callback(:setup) { |context| Database.seed }
|
|
182
|
+
# config.before(:forge, :setup)
|
|
183
|
+
#
|
|
184
|
+
# @example Register and attach with a block (like RSpec)
|
|
185
|
+
# config.before(:step) { |context| Logger.info("Starting step") }
|
|
186
|
+
# config.before(:blueprint) { |context| Database.clean }
|
|
187
|
+
#
|
|
188
|
+
# @example Store the callback name for later deregistration
|
|
189
|
+
# callback_name = config.before(:step) { |context| puts "hook" }
|
|
190
|
+
# config.deregister_callback(callback_name)
|
|
191
|
+
#
|
|
192
|
+
def before(event, callback_name = nil, &block)
|
|
193
|
+
if block
|
|
194
|
+
callback_name = "__sf_cb_#{SecureRandom.uuid.tr("-", "")}"
|
|
195
|
+
register_callback(callback_name, &block)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
register_hook("before", event, callback_name)
|
|
199
|
+
|
|
200
|
+
callback_name
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
#
|
|
204
|
+
# Attaches a callback to an after lifecycle event
|
|
205
|
+
#
|
|
206
|
+
# Global hooks run for all blueprints and execute in registration order.
|
|
207
|
+
# Can either reference a pre-registered callback by name, or accept a block
|
|
208
|
+
# to register and attach a callback in one step (like RSpec's after hooks).
|
|
209
|
+
#
|
|
210
|
+
# @param event [Symbol] The lifecycle event (:forge, :blueprint, or :step)
|
|
211
|
+
# @param callback_name [String, Symbol, nil] The name of a registered callback
|
|
212
|
+
# (optional if block is provided)
|
|
213
|
+
#
|
|
214
|
+
# @yield [context] Block to execute (registers callback automatically)
|
|
215
|
+
# @yieldparam context [Forge::Context] The current execution context
|
|
216
|
+
#
|
|
217
|
+
# @return [String, Symbol] The callback name (auto-generated if block provided)
|
|
218
|
+
#
|
|
219
|
+
# @raise [ArgumentError] If the event is invalid
|
|
220
|
+
# @raise [ArgumentError] If the callback is not registered (when using name)
|
|
221
|
+
#
|
|
222
|
+
# @example Attach a pre-registered callback
|
|
223
|
+
# config.register_callback(:cleanup) { |context| Database.clean }
|
|
224
|
+
# config.after(:forge, :cleanup)
|
|
225
|
+
#
|
|
226
|
+
# @example Register and attach with a block (like RSpec)
|
|
227
|
+
# config.after(:step) { |context| Logger.info("Step complete") }
|
|
228
|
+
# config.after(:blueprint) { |context| Database.rollback }
|
|
229
|
+
#
|
|
230
|
+
# @example Store the callback name for later deregistration
|
|
231
|
+
# callback_name = config.after(:step) { |context| puts "done" }
|
|
232
|
+
# config.deregister_callback(callback_name)
|
|
233
|
+
#
|
|
234
|
+
def after(event, callback_name = nil, &block)
|
|
235
|
+
if block
|
|
236
|
+
callback_name = "__sf_cb_#{SecureRandom.uuid.tr("-", "")}"
|
|
237
|
+
register_callback(callback_name, &block)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
register_hook("after", event, callback_name)
|
|
241
|
+
|
|
242
|
+
callback_name
|
|
125
243
|
end
|
|
126
244
|
|
|
127
|
-
|
|
128
|
-
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
def register_hook(timing, event, callback_name)
|
|
248
|
+
hook = :"#{timing}_#{event}"
|
|
249
|
+
callback_name = callback_name.to_sym
|
|
250
|
+
|
|
251
|
+
if !@hooks.key?(hook)
|
|
252
|
+
keys = @hooks.keys.select { |k| k.to_s.start_with?(timing) }.map(&:in_quotes)
|
|
253
|
+
raise ArgumentError, "Invalid event #{hook.in_quotes}. Expected one of #{keys.to_or_sentence}"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
if !@callbacks.key?(callback_name)
|
|
257
|
+
keys = @callbacks.keys.map(&:in_quotes)
|
|
258
|
+
raise ArgumentError, "Invalid callback #{callback_name.in_quotes}. Expected one of #{keys.to_or_sentence}"
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
@hooks[hook] << callback_name
|
|
262
|
+
end
|
|
129
263
|
end
|
|
130
264
|
end
|
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module SpecForge
|
|
4
4
|
module Documentation
|
|
5
|
-
class
|
|
5
|
+
class Builder
|
|
6
6
|
#
|
|
7
|
-
# Manages caching of
|
|
7
|
+
# Manages caching of endpoint data to avoid re-running blueprints
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# The Cache stores extracted endpoint data and tracks blueprint file
|
|
10
|
+
# modification times. When blueprints haven't changed, cached data
|
|
11
|
+
# can be reused to speed up documentation generation.
|
|
12
|
+
#
|
|
13
|
+
# Cache files are stored in the OpenAPI generated directory under
|
|
14
|
+
# a .cache subdirectory.
|
|
12
15
|
#
|
|
13
16
|
# @example Using the cache
|
|
14
17
|
# cache = Cache.new
|
|
15
18
|
# if cache.valid?
|
|
16
19
|
# endpoints = cache.read
|
|
17
20
|
# else
|
|
18
|
-
# endpoints =
|
|
21
|
+
# endpoints = extract_from_blueprints
|
|
19
22
|
# cache.create(endpoints)
|
|
20
23
|
# end
|
|
21
24
|
#
|
|
@@ -23,41 +26,41 @@ module SpecForge
|
|
|
23
26
|
#
|
|
24
27
|
# Creates a new cache manager
|
|
25
28
|
#
|
|
26
|
-
# Sets up file paths for endpoint and
|
|
29
|
+
# Sets up file paths for endpoint and blueprint caches in the OpenAPI
|
|
27
30
|
# generated directory structure.
|
|
28
31
|
#
|
|
29
32
|
# @return [Cache] A new cache instance
|
|
30
33
|
#
|
|
31
34
|
def initialize
|
|
32
35
|
@endpoint_cache = SpecForge.openapi_path.join("generated", ".cache", "endpoints.yml")
|
|
33
|
-
@
|
|
36
|
+
@blueprint_cache = SpecForge.openapi_path.join("generated", ".cache", "blueprints.yml")
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
#
|
|
37
40
|
# Checks if the cache is valid and can be used
|
|
38
41
|
#
|
|
39
42
|
# Determines cache validity by checking if endpoint cache exists
|
|
40
|
-
# and whether any
|
|
43
|
+
# and whether any blueprint files have been modified since the cache
|
|
41
44
|
# was created.
|
|
42
45
|
#
|
|
43
46
|
# @return [Boolean] true if cache is valid and can be used
|
|
44
47
|
#
|
|
45
48
|
def valid?
|
|
46
|
-
endpoint_cache? && !
|
|
49
|
+
endpoint_cache? && !blueprints_updated?
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
#
|
|
50
|
-
# Creates a cache entry with endpoint data and
|
|
53
|
+
# Creates a cache entry with endpoint data and blueprint file metadata
|
|
51
54
|
#
|
|
52
|
-
# Writes both the endpoint data and current
|
|
53
|
-
# to enable cache invalidation when
|
|
55
|
+
# Writes both the endpoint data and current blueprint file modification times
|
|
56
|
+
# to enable cache invalidation when blueprints change.
|
|
54
57
|
#
|
|
55
58
|
# @param endpoints [Array<Hash>] Endpoint data to cache
|
|
56
59
|
#
|
|
57
60
|
# @return [void]
|
|
58
61
|
#
|
|
59
62
|
def create(endpoints)
|
|
60
|
-
|
|
63
|
+
write_blueprint_cache
|
|
61
64
|
write(endpoints)
|
|
62
65
|
end
|
|
63
66
|
|
|
@@ -93,11 +96,11 @@ module SpecForge
|
|
|
93
96
|
YAML.safe_load_file(path, symbolize_names: true, permitted_classes: [Symbol, Time])
|
|
94
97
|
end
|
|
95
98
|
|
|
96
|
-
def
|
|
97
|
-
return true if !File.exist?(@
|
|
99
|
+
def blueprints_updated?
|
|
100
|
+
return true if !File.exist?(@blueprint_cache)
|
|
98
101
|
|
|
99
|
-
cache = read_from_file(@
|
|
100
|
-
new_cache =
|
|
102
|
+
cache = read_from_file(@blueprint_cache)
|
|
103
|
+
new_cache = generate_blueprint_cache
|
|
101
104
|
|
|
102
105
|
different?(cache, new_cache)
|
|
103
106
|
end
|
|
@@ -106,17 +109,17 @@ module SpecForge
|
|
|
106
109
|
File.exist?(@endpoint_cache)
|
|
107
110
|
end
|
|
108
111
|
|
|
109
|
-
def
|
|
110
|
-
paths = SpecForge.forge_path.join("
|
|
112
|
+
def generate_blueprint_cache
|
|
113
|
+
paths = SpecForge.forge_path.join("blueprints", "**", "*.{yml,yaml}")
|
|
111
114
|
|
|
112
115
|
Dir[paths].each_with_object({}) do |path, hash|
|
|
113
116
|
hash[path.to_sym] = File.mtime(path)
|
|
114
117
|
end
|
|
115
118
|
end
|
|
116
119
|
|
|
117
|
-
def
|
|
118
|
-
data =
|
|
119
|
-
write_to_file(data, @
|
|
120
|
+
def write_blueprint_cache
|
|
121
|
+
data = generate_blueprint_cache
|
|
122
|
+
write_to_file(data, @blueprint_cache)
|
|
120
123
|
end
|
|
121
124
|
|
|
122
125
|
def different?(cache_left, cache_right)
|