spec_forge 0.7.1 → 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 (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -1
  3. data/README.md +124 -202
  4. data/bin/spec_forge +1 -1
  5. data/flake.lock +76 -4
  6. data/flake.nix +5 -4
  7. data/lib/spec_forge/attribute/chainable.rb +6 -6
  8. data/lib/spec_forge/attribute/environment.rb +45 -0
  9. data/lib/spec_forge/attribute/factory.rb +26 -17
  10. data/lib/spec_forge/attribute/faker.rb +6 -1
  11. data/lib/spec_forge/attribute/generate.rb +114 -0
  12. data/lib/spec_forge/attribute/literal.rb +1 -14
  13. data/lib/spec_forge/attribute/matcher.rb +6 -2
  14. data/lib/spec_forge/attribute/parameterized.rb +20 -22
  15. data/lib/spec_forge/attribute/resolvable_array.rb +16 -16
  16. data/lib/spec_forge/attribute/resolvable_hash.rb +17 -16
  17. data/lib/spec_forge/attribute/resolvable_struct.rb +67 -0
  18. data/lib/spec_forge/attribute/template.rb +118 -0
  19. data/lib/spec_forge/attribute/transform.rb +14 -19
  20. data/lib/spec_forge/attribute/variable.rb +31 -31
  21. data/lib/spec_forge/attribute.rb +54 -100
  22. data/lib/spec_forge/blueprint.rb +27 -0
  23. data/lib/spec_forge/cli/docs/generate.rb +28 -8
  24. data/lib/spec_forge/cli/docs.rb +5 -2
  25. data/lib/spec_forge/cli/init.rb +4 -4
  26. data/lib/spec_forge/cli/new.rb +78 -27
  27. data/lib/spec_forge/cli/run.rb +84 -52
  28. data/lib/spec_forge/cli/serve.rb +5 -0
  29. data/lib/spec_forge/cli.rb +6 -14
  30. data/lib/spec_forge/configuration.rb +209 -79
  31. data/lib/spec_forge/documentation/{loader → builder}/cache.rb +26 -23
  32. data/lib/spec_forge/documentation/builder/compiler.rb +373 -0
  33. data/lib/spec_forge/documentation/builder/extractor.rb +75 -0
  34. data/lib/spec_forge/documentation/builder.rb +77 -329
  35. data/lib/spec_forge/documentation/document/operation.rb +4 -4
  36. data/lib/spec_forge/documentation/document.rb +0 -6
  37. data/lib/spec_forge/documentation/generator.rb +88 -0
  38. data/lib/spec_forge/documentation/{generators/openapi → openapi/v3_0}/error_formatter.rb +2 -2
  39. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +1 -1
  40. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +1 -1
  41. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +21 -5
  42. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +28 -6
  43. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +20 -2
  44. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +1 -1
  45. data/lib/spec_forge/documentation/openapi/v3_0.rb +116 -0
  46. data/lib/spec_forge/documentation/openapi.rb +40 -12
  47. data/lib/spec_forge/documentation.rb +1 -7
  48. data/lib/spec_forge/error.rb +215 -41
  49. data/lib/spec_forge/factory.rb +38 -18
  50. data/lib/spec_forge/forge/action.rb +41 -0
  51. data/lib/spec_forge/forge/actions/call.rb +33 -0
  52. data/lib/spec_forge/forge/actions/debug.rb +47 -0
  53. data/lib/spec_forge/forge/actions/expect.rb +44 -0
  54. data/lib/spec_forge/forge/actions/request.rb +65 -0
  55. data/lib/spec_forge/forge/actions/store.rb +31 -0
  56. data/lib/spec_forge/forge/callbacks.rb +80 -0
  57. data/lib/spec_forge/forge/context.rb +41 -0
  58. data/lib/spec_forge/forge/display.rb +503 -0
  59. data/lib/spec_forge/forge/hooks.rb +131 -0
  60. data/lib/spec_forge/forge/runner/array_io.rb +81 -0
  61. data/lib/spec_forge/forge/runner/content_validator.rb +92 -0
  62. data/lib/spec_forge/forge/runner/header_validator.rb +66 -0
  63. data/lib/spec_forge/forge/runner/reporter.rb +56 -0
  64. data/lib/spec_forge/forge/runner/schema_validator.rb +113 -0
  65. data/lib/spec_forge/forge/runner.rb +118 -0
  66. data/lib/spec_forge/forge/timer.rb +94 -0
  67. data/lib/spec_forge/forge/variables.rb +38 -0
  68. data/lib/spec_forge/forge.rb +207 -133
  69. data/lib/spec_forge/http/backend.rb +49 -146
  70. data/lib/spec_forge/http/client.rb +14 -17
  71. data/lib/spec_forge/http/request.rb +37 -84
  72. data/lib/spec_forge/http/verb.rb +4 -0
  73. data/lib/spec_forge/http.rb +0 -5
  74. data/lib/spec_forge/loader/filter.rb +85 -0
  75. data/lib/spec_forge/loader/step_processor.rb +282 -0
  76. data/lib/spec_forge/loader.rb +105 -220
  77. data/lib/spec_forge/normalizer/default.rb +1 -1
  78. data/lib/spec_forge/normalizer/structure.rb +140 -0
  79. data/lib/spec_forge/normalizer/transformers.rb +168 -0
  80. data/lib/spec_forge/normalizer/validators.rb +50 -8
  81. data/lib/spec_forge/normalizer.rb +76 -119
  82. data/lib/spec_forge/normalizers/callback.yml +38 -0
  83. data/lib/spec_forge/normalizers/configuration.yml +59 -9
  84. data/lib/spec_forge/normalizers/factory.yml +53 -2
  85. data/lib/spec_forge/normalizers/factory_reference.yml +63 -2
  86. data/lib/spec_forge/normalizers/json_schema.yml +79 -0
  87. data/lib/spec_forge/normalizers/step.yml +506 -0
  88. data/lib/spec_forge/step/call.rb +36 -0
  89. data/lib/spec_forge/step/expect.rb +110 -0
  90. data/lib/spec_forge/step/source.rb +22 -0
  91. data/lib/spec_forge/step.rb +129 -0
  92. data/lib/spec_forge/type.rb +115 -66
  93. data/lib/spec_forge/version.rb +1 -1
  94. data/lib/spec_forge.rb +44 -106
  95. data/lib/templates/forge_helper.rb.tt +43 -22
  96. data/lib/templates/new_blueprint.yml.tt +54 -0
  97. metadata +75 -44
  98. data/lib/spec_forge/attribute/global.rb +0 -96
  99. data/lib/spec_forge/attribute/store.rb +0 -65
  100. data/lib/spec_forge/backtrace_formatter.rb +0 -50
  101. data/lib/spec_forge/callbacks.rb +0 -88
  102. data/lib/spec_forge/context/callbacks.rb +0 -91
  103. data/lib/spec_forge/context/global.rb +0 -72
  104. data/lib/spec_forge/context/store.rb +0 -131
  105. data/lib/spec_forge/context/variables.rb +0 -91
  106. data/lib/spec_forge/context.rb +0 -36
  107. data/lib/spec_forge/core_ext/rspec.rb +0 -55
  108. data/lib/spec_forge/core_ext.rb +0 -5
  109. data/lib/spec_forge/documentation/generators/base.rb +0 -81
  110. data/lib/spec_forge/documentation/generators/openapi/base.rb +0 -100
  111. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +0 -65
  112. data/lib/spec_forge/documentation/generators/openapi.rb +0 -59
  113. data/lib/spec_forge/documentation/generators.rb +0 -17
  114. data/lib/spec_forge/documentation/loader.rb +0 -159
  115. data/lib/spec_forge/documentation/openapi/base.rb +0 -33
  116. data/lib/spec_forge/filter.rb +0 -86
  117. data/lib/spec_forge/normalizer/definition.rb +0 -248
  118. data/lib/spec_forge/normalizers/_shared.yml +0 -76
  119. data/lib/spec_forge/normalizers/constraint.yml +0 -8
  120. data/lib/spec_forge/normalizers/expectation.yml +0 -47
  121. data/lib/spec_forge/normalizers/global_context.yml +0 -28
  122. data/lib/spec_forge/normalizers/spec.yml +0 -50
  123. data/lib/spec_forge/runner/adapter.rb +0 -181
  124. data/lib/spec_forge/runner/callbacks.rb +0 -246
  125. data/lib/spec_forge/runner/debug_proxy.rb +0 -215
  126. data/lib/spec_forge/runner/listener.rb +0 -54
  127. data/lib/spec_forge/runner/metadata.rb +0 -58
  128. data/lib/spec_forge/runner/state.rb +0 -98
  129. data/lib/spec_forge/runner.rb +0 -75
  130. data/lib/spec_forge/spec/expectation/constraint.rb +0 -127
  131. data/lib/spec_forge/spec/expectation.rb +0 -68
  132. data/lib/spec_forge/spec.rb +0 -68
  133. data/lib/templates/new_spec.yml.tt +0 -43
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Context
5
- #
6
- # Manages user-defined callbacks grouped by lifecycle hook
7
- #
8
- # This class collects and organizes callbacks by their hook type
9
- # (before_file, after_each, etc.) to support the test lifecycle.
10
- # It ensures callbacks are properly categorized for execution.
11
- #
12
- # @example Creating callback groups
13
- # callbacks = Context::Callbacks.new([
14
- # {before_file: "setup_environment"},
15
- # {after_each: "log_test_result"}
16
- # ])
17
- #
18
- class Callbacks
19
- #
20
- # Creates a new callbacks collection
21
- #
22
- # @param callback_array [Array] Optional initial callbacks to register
23
- #
24
- # @return [Callbacks] A new callbacks collection
25
- #
26
- def initialize(callback_array = [])
27
- set(callback_array)
28
- end
29
-
30
- #
31
- # Updates the callbacks collection
32
- #
33
- # @param callback_array [Array] New callbacks to register
34
- #
35
- # @return [self]
36
- #
37
- def set(callback_array)
38
- @inner = organize_callbacks_by_hook(callback_array)
39
- self
40
- end
41
-
42
- #
43
- # Returns the hash representation of callbacks
44
- #
45
- # @return [Hash] Callbacks organized by hook type
46
- #
47
- def to_h
48
- @inner
49
- end
50
-
51
- #
52
- # Executes all registered callbacks for a specific lifecycle hook
53
- #
54
- # @param hook_name [String, Symbol] The lifecycle hook (before_file, after_each, etc.)
55
- # @param context [Hash] State data that will be converted to a structured object
56
- # and passed to callbacks
57
- #
58
- def run(hook_name, context = {})
59
- context = context.to_struct
60
-
61
- @inner[hook_name].each do |callback_name|
62
- SpecForge::Callbacks.run(callback_name, context)
63
- end
64
- end
65
-
66
- private
67
-
68
- #
69
- # Organizes callbacks from an array to hash structure by hook type
70
- # Groups callbacks like before_file, after_each, etc. for easier lookup
71
- #
72
- # @param callback_array [Array] The array of callbacks
73
- #
74
- # @return [Hash] Callbacks indexed by hook type
75
- #
76
- # @private
77
- #
78
- def organize_callbacks_by_hook(callback_array)
79
- groups = Hash.new { |h, k| h[k] = Set.new }
80
-
81
- callback_array.each_with_object(groups) do |callbacks, groups|
82
- callbacks.each do |hook, name|
83
- next if name.blank?
84
-
85
- groups[hook].add(name)
86
- end
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Context
5
- #
6
- # Manages global state and variables at the spec file level.
7
- #
8
- # The Global class provides access to variables that are defined at the global level
9
- # in a spec file and are accessible across all specs and expectations in a file.
10
- # Unlike regular variables, global variables do not support overlaying - they maintain
11
- # consistent values throughout test execution.
12
- #
13
- # @example Basic usage
14
- # global = Global.new(variables: {api_version: "v2", environment: "test"})
15
- #
16
- # global.variables[:api_version] #=> "v2"
17
- # global.variables[:environment] #=> "test"
18
- #
19
- # # Update global variables
20
- # global.set(variables: {environment: "staging"})
21
- # global.variables[:environment] #=> "staging"
22
- # global.variables[:api_version] #=> nil
23
- #
24
- class Global
25
- # @return [Context::Variables] The container for global variables
26
- attr_reader :variables
27
-
28
- # @return [Context::Callbacks] The container for callbacks
29
- attr_reader :callbacks
30
-
31
- #
32
- # Creates a new Global context instance
33
- #
34
- # @param variables [Hash<Symbol, Object>] A hash of variable names and values
35
- # @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
36
- #
37
- # @return [Global] The new Global instance
38
- #
39
- def initialize(variables: {}, callbacks: [])
40
- @variables = Variables.new(base: variables)
41
- @callbacks = Callbacks.new(callbacks)
42
- end
43
-
44
- #
45
- # Sets the global variables
46
- #
47
- # @param variables [Hash<Symbol, Object>] A hash of variable names and values
48
- # @param callbacks [Array<Hash<Symbol, String>>] An array of callback hooks
49
- #
50
- # @return [self]
51
- #
52
- def set(variables: {}, callbacks: [])
53
- @variables.set(base: variables)
54
- @callbacks.set(callbacks)
55
-
56
- self
57
- end
58
-
59
- #
60
- # Returns a hash representation of the global context
61
- #
62
- # @return [Hash]
63
- #
64
- def to_h
65
- {
66
- variables: variables.to_h,
67
- callbacks: callbacks.to_h
68
- }
69
- end
70
- end
71
- end
72
- end
@@ -1,131 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Context
5
- #
6
- # Manages storage of API responses for use in subsequent tests
7
- #
8
- # This class provides a mechanism to store HTTP requests and responses
9
- # during test execution, allowing values to be referenced in later tests
10
- # through the `store.id.body.attribute` syntax.
11
- #
12
- # @example Storing and retrieving a response in specs
13
- # # In one expectation:
14
- # store_as: user_creation
15
- #
16
- # # In a later test:
17
- # query:
18
- # id: store.user_creation.body.id
19
- #
20
- class Store
21
- #
22
- # Represents a stored entry containing arbitrary data from test execution
23
- #
24
- # Entries are created during test execution to store custom data that can be
25
- # accessed in subsequent tests. Unlike the original rigid Data structure, this
26
- # OpenStruct-based approach allows storing any key-value pairs, making it perfect
27
- # for complex test scenarios that need custom configuration, metadata, or
28
- # computed values.
29
- #
30
- # @example Storing custom configuration data
31
- # SpecForge.context.store.set(
32
- # "app_config",
33
- # api_version: "v2.1",
34
- # feature_flags: { advanced_search: true }
35
- # )
36
- #
37
- # @example Accessing stored data in tests
38
- # headers:
39
- # X-API-Version: store.app_config.api_version
40
- # query:
41
- # search_enabled: store.app_config.feature_flags.advanced_search
42
- #
43
- class Entry < OpenStruct
44
- #
45
- # Creates a new store entry
46
- #
47
- # @param scope [Symbol] Scope of this entry, either :file or :spec
48
- #
49
- # @return [Entry] A new entry instance
50
- #
51
- def initialize(scope: :file, **)
52
- super
53
- end
54
-
55
- #
56
- # Returns all available methods that can be called
57
- #
58
- # @return [Array] The method names
59
- #
60
- def available_methods
61
- @table.keys
62
- end
63
- end
64
-
65
- #
66
- # Creates a new empty store
67
- #
68
- # @return [Store] A new store instance
69
- #
70
- def initialize
71
- @inner = {}
72
- end
73
-
74
- #
75
- # Retrieves a stored entry by ID
76
- #
77
- # @param id [String, Symbol] The identifier for the stored entry
78
- #
79
- # @return [Entry, nil] The stored entry or nil if not found
80
- #
81
- def [](id)
82
- @inner[id]
83
- end
84
-
85
- #
86
- # Returns the number of entries in the store
87
- #
88
- # @return [Integer] The count of stored entries
89
- #
90
- def size
91
- @inner.size
92
- end
93
-
94
- #
95
- # Stores an entry with the specified ID
96
- #
97
- # @param id [String, Symbol] The identifier to store the entry under
98
- #
99
- # @return [self]
100
- #
101
- def set(id, **)
102
- @inner[id] = Entry.new(**)
103
-
104
- self
105
- end
106
-
107
- #
108
- # Removes all entries from the store
109
- #
110
- def clear
111
- @inner.clear
112
- end
113
-
114
- #
115
- # Removes all spec entries from the store
116
- #
117
- def clear_specs
118
- @inner.delete_if { |_k, v| v.scope == :spec }
119
- end
120
-
121
- #
122
- # Returns a hash representation of store
123
- #
124
- # @return [Hash]
125
- #
126
- def to_h
127
- @inner.transform_values(&:to_h).deep_stringify_keys
128
- end
129
- end
130
- end
131
- end
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- class Context
5
- #
6
- # Manages variable resolution across different expectations in SpecForge tests.
7
- #
8
- # The Variables class handles two layers of variable definitions:
9
- # - Base variables: The core set of variables defined at the spec level
10
- # - Overlay variables: Additional variables defined at the expectation level
11
- # that can override base variables with the same name.
12
- #
13
- # @example Basic usage
14
- # variables = Variables.new(
15
- # base: {user_id: 123, name: "Test User"},
16
- # overlay: {
17
- # "expectation_1": {name: "Override User"}
18
- # }
19
- # )
20
- #
21
- # variables[:user_id] #=> 123
22
- # variables[:name] #=> "Test User"
23
- #
24
- # variables.use_overlay("expectation_1")
25
- # variables[:name] #=> "Override User"
26
- # variables[:user_id] #=> 123 (unchanged)
27
- #
28
- class Variables < Hash
29
- attr_reader :base, :overlay
30
-
31
- #
32
- # Creates a new Variables container with base and overlay definitions
33
- #
34
- # @param base [Hash] The base set of variables (typically defined at spec level)
35
- # @param overlay [Hash<String, Hash>] A hash of overlay variable sets keyed by ID
36
- #
37
- # @return [Variables]
38
- #
39
- def initialize(base: {}, overlay: {})
40
- set(base:, overlay:)
41
- end
42
-
43
- #
44
- # Sets the base and overlay variable hashes
45
- #
46
- # @param base [Hash] The new base variable hash
47
- # @param overlay [Hash<String, Hash>] The new overlay variable hashes
48
- #
49
- # @return [self]
50
- #
51
- def set(base:, overlay: {})
52
- @base = Attribute.from(base)
53
- @overlay = overlay
54
-
55
- resolve_into_self(@base)
56
- self
57
- end
58
-
59
- #
60
- # Applies a specific overlay to the base variables
61
- # If the overlay doesn't exist or is empty, no changes are made.
62
- #
63
- # @param id [String] The ID of the overlay to apply
64
- #
65
- # @return [nil]
66
- #
67
- def use_overlay(id)
68
- active = @base
69
-
70
- if (overlay = @overlay[id]) && overlay.present?
71
- active = active.deep_merge(overlay)
72
- end
73
-
74
- resolve_into_self(active)
75
- self
76
- end
77
-
78
- private
79
-
80
- def resolve_into_self(hash)
81
- # Start fresh
82
- clear
83
-
84
- # Load the resolved values into self
85
- hash.each do |key, value|
86
- self[key] = Attribute.from(value).resolved
87
- end
88
- end
89
- end
90
- end
91
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- #
5
- # Core data structure that maintains context during test execution
6
- #
7
- # Context stores and provides access to global variables, test variables, and
8
- # shared state across specs.
9
- # It acts as a central repository for test data during execution.
10
- #
11
- # @example Accessing the current context
12
- # SpecForge.context.variables[:user_id] #=> 123
13
- #
14
- class Context < Data.define(:global, :store, :variables)
15
- #
16
- # Creates a new context with default values
17
- #
18
- # @param global [Hash] Global variables shared across all specs
19
- # @param variables [Hash] Test variables specific to the current context
20
- #
21
- # @return [Context] A new context instance
22
- #
23
- def initialize(global: {}, variables: {})
24
- super(
25
- global: Global.new(**global),
26
- store: Store.new,
27
- variables: Variables.new(**variables)
28
- )
29
- end
30
- end
31
- end
32
-
33
- require_relative "context/callbacks"
34
- require_relative "context/global"
35
- require_relative "context/store"
36
- require_relative "context/variables"
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- return if defined?(SPEC_FORGE_INTERNAL_TESTING)
4
-
5
- #
6
- # RSpec's core testing framework module
7
- # Provides the fundamental structure and functionality for RSpec tests
8
- #
9
- module RSpec
10
- #
11
- # Core implementation details and extensions for RSpec
12
- # Contains the fundamental building blocks of the RSpec testing framework
13
- #
14
- module Core
15
- #
16
- # Handles notifications and reporting for RSpec test runs
17
- # Manages how test results and metadata are processed and communicated
18
- #
19
- module Notifications
20
- #
21
- # A monkey patch of an internal RSpec class to allow SpecForge to replace parts of
22
- # RSpec's reporting output in order to provide useful feedback to the user.
23
- # This replaces "rspec" in commands with "spec_forge", removes any line numbers, and
24
- # ensures that failures properly report the YAML file that it occurred in.
25
- #
26
- class SummaryNotification
27
- #
28
- # Create an alias to RSpec original colorized_rerun_commands so it can be called at a
29
- # later point.
30
- #
31
- alias_method :og_colorized_rerun_commands, :colorized_rerun_commands
32
-
33
- # Customizes RSpec's failure output to:
34
- # 1. Use 'spec_forge' instead of 'rspec' for rerun commands
35
- # 2. Remove line numbers since SpecForge uses dynamic spec generation
36
- def colorized_rerun_commands(colorizer)
37
- # Updating these at this point fixes the re-run for some failures - it depends
38
- failed_examples.each do |example|
39
- metadata = example.metadata[:example_group]
40
-
41
- # I might've uncovered an inconsistency here
42
- # When multiple specs fail, it appears that the rerun_commands will use
43
- # :rerun_file_path from the example's metadata.
44
- # But when a single spec is ran and fails, it's using :location.
45
- example.metadata[:location] = metadata[:rerun_file_path]
46
- example.metadata[:line_number] = metadata[:line_number]
47
- end
48
-
49
- og_colorized_rerun_commands.gsub(/rspec/i, "spec_forge")
50
- .gsub(/\[[\d:]+\]/, "")
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Dir[File.expand_path("core_ext/*.rb", __dir__)].sort.each do |path|
4
- require path
5
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- module Documentation
5
- module Generators
6
- #
7
- # Base class for all documentation generators
8
- #
9
- # Provides the common interface and shared functionality for generators
10
- # that transform SpecForge documents into various output formats.
11
- # Subclasses implement format-specific generation logic.
12
- #
13
- # @example Creating a custom generator
14
- # class MyGenerator < Base
15
- # def generate
16
- # # Transform input document to custom format
17
- # end
18
- # end
19
- #
20
- class Base
21
- #
22
- # Generates documentation from test data with optional caching
23
- #
24
- # @param use_cache [Boolean] Whether to use cached test data if available
25
- #
26
- # @return [Object] The generated documentation in the target format
27
- #
28
- # @raise [RuntimeError] Must be implemented by subclasses
29
- #
30
- def self.generate(use_cache: false)
31
- raise "not implemented"
32
- end
33
-
34
- #
35
- # Validates the generated output according to format specifications
36
- #
37
- # @param input [Object] The generated documentation to validate
38
- #
39
- # @return [void]
40
- #
41
- # @raise [RuntimeError] Must be implemented by subclasses
42
- #
43
- def self.validate!(input)
44
- raise "not implemented"
45
- end
46
-
47
- #
48
- # The input document containing structured API data
49
- #
50
- # Contains all the endpoint information extracted from tests,
51
- # organized and ready for transformation into the target format.
52
- #
53
- # @return [Document] The document to be processed by the generator
54
- #
55
- attr_reader :input
56
-
57
- #
58
- # Initializes a new generators
59
- #
60
- # @param input [Hash, Document] The document to generate
61
- #
62
- # @return [Base] A new generator instance
63
- #
64
- def initialize(input = {})
65
- @input = input
66
- end
67
-
68
- #
69
- # Generates the document into a specific format
70
- #
71
- # @raise [RuntimeError] Must be implemented by subclasses
72
- #
73
- # @return [Object] The generated document
74
- #
75
- def generate
76
- raise "not implemented"
77
- end
78
- end
79
- end
80
- end
81
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SpecForge
4
- module Documentation
5
- module Generators
6
- module OpenAPI
7
- #
8
- # Base class for OpenAPI generators
9
- #
10
- # Provides common functionality for OpenAPI generators of different versions.
11
- #
12
- class Base < Generators::Base
13
- #
14
- # Converts the generator's version to a semantic version object
15
- #
16
- # @return [SemVersion] The semantic version
17
- #
18
- def self.to_sem_version
19
- SemVersion.new(CURRENT_VERSION)
20
- end
21
-
22
- #
23
- # Generates OpenAPI documentation from test data with optional caching
24
- #
25
- # Loads endpoint data from tests (either fresh or cached), creates a document,
26
- # and generates the OpenAPI specification using the appropriate version generator.
27
- #
28
- # @param use_cache [Boolean] Whether to use cached test data if available
29
- #
30
- # @return [Hash] The generated OpenAPI specification
31
- #
32
- def self.generate(use_cache: false)
33
- document = Documentation::Loader.load_document(use_cache:)
34
- new(document).generate
35
- end
36
-
37
- #
38
- # Validates an OpenAPI specification against the standard
39
- #
40
- # Uses the openapi3_parser gem to validate the generated specification
41
- # and provides detailed error reporting if validation fails.
42
- #
43
- # @param output [Hash] The OpenAPI specification to validate
44
- #
45
- # @return [void]
46
- #
47
- # @raise [Error::InvalidOASDocument] If the specification is invalid
48
- #
49
- def self.validate!(output)
50
- document = Openapi3Parser.load(output)
51
- if document.valid?
52
- puts "✅ No validation errors found!"
53
- return
54
- end
55
-
56
- puts ErrorFormatter.format(document.errors.errors)
57
- raise Error::InvalidOASDocument
58
- end
59
-
60
- protected
61
-
62
- #
63
- # Loads OpenAPI configuration from YAML
64
- #
65
- # @return [Hash] The normalized OpenAPI configuration
66
- #
67
- # @api private
68
- #
69
- def config
70
- @config ||= begin
71
- file_extension_glob = "*.{yml,yaml}"
72
- base_path = SpecForge.openapi_path.join("config")
73
-
74
- root_paths = base_path.join(file_extension_glob)
75
- path_paths = base_path.join("paths", "**", file_extension_glob)
76
- component_paths = base_path.join("components", "**", file_extension_glob)
77
-
78
- config = load_yml_from_paths(root_paths).to_merged_h
79
- paths_config = load_yml_from_paths(path_paths).to_merged_h
80
- component_config = load_yml_from_paths(component_paths).to_merged_h
81
-
82
- (config["paths"] ||= {}).deep_merge!(paths_config)
83
- (config["components"] ||= {}).deep_merge!(component_config)
84
-
85
- config
86
- end
87
- end
88
-
89
- private
90
-
91
- def load_yml_from_paths(paths)
92
- Dir[paths].map do |path|
93
- YAML.safe_load_file(path)
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end
100
- end