spec_forge 0.6.0 → 0.7.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 +112 -2
- data/README.md +133 -8
- data/flake.lock +3 -3
- data/flake.nix +3 -3
- data/lib/spec_forge/attribute/factory.rb +1 -1
- data/lib/spec_forge/callbacks.rb +9 -0
- data/lib/spec_forge/cli/docs/generate.rb +72 -0
- data/lib/spec_forge/cli/docs.rb +92 -0
- data/lib/spec_forge/cli/init.rb +39 -7
- data/lib/spec_forge/cli/new.rb +13 -3
- data/lib/spec_forge/cli/run.rb +12 -4
- data/lib/spec_forge/cli/serve.rb +155 -0
- data/lib/spec_forge/cli.rb +14 -6
- data/lib/spec_forge/configuration.rb +2 -2
- data/lib/spec_forge/context/store.rb +23 -40
- data/lib/spec_forge/core_ext/array.rb +27 -0
- data/lib/spec_forge/documentation/builder.rb +383 -0
- data/lib/spec_forge/documentation/document/operation.rb +47 -0
- data/lib/spec_forge/documentation/document/parameter.rb +22 -0
- data/lib/spec_forge/documentation/document/request_body.rb +24 -0
- data/lib/spec_forge/documentation/document/response.rb +39 -0
- data/lib/spec_forge/documentation/document/response_body.rb +27 -0
- data/lib/spec_forge/documentation/document.rb +48 -0
- data/lib/spec_forge/documentation/generators/base.rb +81 -0
- data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
- data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
- data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
- data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
- data/lib/spec_forge/documentation/generators.rb +17 -0
- data/lib/spec_forge/documentation/loader/cache.rb +138 -0
- data/lib/spec_forge/documentation/loader.rb +159 -0
- data/lib/spec_forge/documentation/openapi/base.rb +33 -0
- data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
- data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
- data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
- data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
- data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
- data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
- data/lib/spec_forge/documentation/openapi.rb +23 -0
- data/lib/spec_forge/documentation.rb +27 -0
- data/lib/spec_forge/error.rb +17 -0
- data/lib/spec_forge/factory.rb +2 -2
- data/lib/spec_forge/filter.rb +3 -4
- data/lib/spec_forge/forge.rb +5 -4
- data/lib/spec_forge/http/backend.rb +2 -0
- data/lib/spec_forge/http/request.rb +14 -3
- data/lib/spec_forge/loader.rb +14 -24
- data/lib/spec_forge/normalizer/default.rb +51 -0
- data/lib/spec_forge/normalizer/definition.rb +248 -0
- data/lib/spec_forge/normalizer/validators.rb +99 -0
- data/lib/spec_forge/normalizer.rb +356 -199
- data/lib/spec_forge/normalizers/_shared.yml +74 -0
- data/lib/spec_forge/normalizers/configuration.yml +23 -0
- data/lib/spec_forge/normalizers/constraint.yml +8 -0
- data/lib/spec_forge/normalizers/expectation.yml +47 -0
- data/lib/spec_forge/normalizers/factory.yml +12 -0
- data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
- data/lib/spec_forge/normalizers/global_context.yml +28 -0
- data/lib/spec_forge/normalizers/spec.yml +50 -0
- data/lib/spec_forge/runner/adapter.rb +183 -0
- data/lib/spec_forge/runner/debug_proxy.rb +3 -3
- data/lib/spec_forge/runner/state.rb +4 -5
- data/lib/spec_forge/runner.rb +40 -124
- data/lib/spec_forge/spec/expectation/constraint.rb +13 -5
- data/lib/spec_forge/spec/expectation.rb +7 -3
- data/lib/spec_forge/spec.rb +13 -58
- data/lib/spec_forge/version.rb +1 -1
- data/lib/spec_forge.rb +30 -23
- data/lib/templates/openapi.yml.tt +22 -0
- data/lib/templates/redoc.html.tt +28 -0
- data/lib/templates/swagger.html.tt +59 -0
- metadata +92 -14
- data/lib/spec_forge/normalizer/configuration.rb +0 -90
- data/lib/spec_forge/normalizer/constraint.rb +0 -60
- data/lib/spec_forge/normalizer/expectation.rb +0 -105
- data/lib/spec_forge/normalizer/factory.rb +0 -78
- data/lib/spec_forge/normalizer/factory_reference.rb +0 -85
- data/lib/spec_forge/normalizer/global_context.rb +0 -88
- data/lib/spec_forge/normalizer/spec.rb +0 -97
- /data/lib/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
- /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
- /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
#
|
7
|
+
# Base class for OpenAPI documentation objects
|
8
|
+
#
|
9
|
+
# Provides common functionality for OpenAPI specification objects
|
10
|
+
# like operations, responses, and schemas.
|
11
|
+
#
|
12
|
+
class Base
|
13
|
+
#
|
14
|
+
# The document object containing structured API data
|
15
|
+
#
|
16
|
+
# @return [Object] The document with endpoint information
|
17
|
+
#
|
18
|
+
attr_reader :document
|
19
|
+
|
20
|
+
#
|
21
|
+
# Creates a new OpenAPI base object
|
22
|
+
#
|
23
|
+
# @param document [Object] The document object containing API data
|
24
|
+
#
|
25
|
+
# @return [Base] A new base instance
|
26
|
+
#
|
27
|
+
def initialize(document)
|
28
|
+
@document = document
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Example object
|
9
|
+
#
|
10
|
+
# Creates example objects for request/response documentation with
|
11
|
+
# optional summary, description, and external reference support.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#example-object
|
14
|
+
#
|
15
|
+
class Example < Data.define(:summary, :description, :value, :external_value)
|
16
|
+
#
|
17
|
+
# Creates a new OpenAPI example object
|
18
|
+
#
|
19
|
+
# @param summary [String, nil] Brief summary of the example's purpose
|
20
|
+
# @param description [String, nil] Detailed description of the example
|
21
|
+
# @param value [Object, nil] The actual example value
|
22
|
+
# @param external_value [String, nil] URL pointing to the example value
|
23
|
+
#
|
24
|
+
# @return [Example] A new example instance
|
25
|
+
#
|
26
|
+
def initialize(summary: nil, description: nil, value: nil, external_value: nil)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Converts the example to an OpenAPI-compliant hash
|
32
|
+
#
|
33
|
+
# @return [Hash] OpenAPI-formatted example object
|
34
|
+
#
|
35
|
+
def to_h
|
36
|
+
super
|
37
|
+
.rename_key_unordered!(:external_value, :externalValue)
|
38
|
+
.compact_blank!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Media Type object
|
9
|
+
#
|
10
|
+
# Handles media type definitions for request and response bodies,
|
11
|
+
# including schema definitions, examples, and encoding information.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#media-type-object
|
14
|
+
#
|
15
|
+
class MediaType < Data.define(:schema, :example, :examples, :encoding)
|
16
|
+
#
|
17
|
+
# Creates a new OpenAPI media type object
|
18
|
+
#
|
19
|
+
# @param schema [Hash, nil] Schema definition for the media type
|
20
|
+
# @param example [Object, nil] Single example value
|
21
|
+
# @param examples [Hash, nil] Multiple named examples
|
22
|
+
# @param encoding [Hash, nil] Encoding information for the media type
|
23
|
+
#
|
24
|
+
# @return [MediaType] A new media type instance
|
25
|
+
#
|
26
|
+
def initialize(schema: nil, example: nil, examples: nil, encoding: nil)
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Converts the media type to an OpenAPI-compliant hash
|
32
|
+
#
|
33
|
+
# @return [Hash] OpenAPI-formatted media type object
|
34
|
+
#
|
35
|
+
def to_h
|
36
|
+
super.compact_blank!
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Operation object
|
9
|
+
#
|
10
|
+
# Handles the complete definition of API operations including parameters,
|
11
|
+
# request bodies, responses, and security requirements for OpenAPI specs.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#operation-object
|
14
|
+
#
|
15
|
+
class Operation < OpenAPI::Base
|
16
|
+
#
|
17
|
+
# Converts the operation to an OpenAPI-compliant hash
|
18
|
+
#
|
19
|
+
# Builds the complete operation object with all required and optional
|
20
|
+
# fields properly formatted for OpenAPI specification.
|
21
|
+
#
|
22
|
+
# @return [Hash] OpenAPI-formatted operation object
|
23
|
+
#
|
24
|
+
def to_h
|
25
|
+
{
|
26
|
+
# Required
|
27
|
+
responses:,
|
28
|
+
security:
|
29
|
+
}.merge_compact(
|
30
|
+
# All optional
|
31
|
+
tags:,
|
32
|
+
summary:,
|
33
|
+
description:,
|
34
|
+
operationId:,
|
35
|
+
parameters:,
|
36
|
+
requestBody:
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Returns the operation's unique identifier
|
42
|
+
#
|
43
|
+
# @return [String] The operation ID
|
44
|
+
#
|
45
|
+
def id
|
46
|
+
# The object ID is added to make every ID unique
|
47
|
+
document.id.to_camelcase(:lower) + object_id.to_s
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :operationId, :id
|
51
|
+
|
52
|
+
#
|
53
|
+
# Returns a human-readable summary of the operation
|
54
|
+
#
|
55
|
+
# @return [String, nil] Brief operation summary
|
56
|
+
#
|
57
|
+
def summary
|
58
|
+
document.id.humanize
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Returns detailed description of the operation
|
63
|
+
#
|
64
|
+
# @return [String] Detailed operation description
|
65
|
+
#
|
66
|
+
def description
|
67
|
+
document.description
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Returns security requirements for the operation
|
72
|
+
#
|
73
|
+
# @return [Array] Array of security requirement objects
|
74
|
+
#
|
75
|
+
def security
|
76
|
+
# User defined
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Returns tags for categorizing the operation
|
82
|
+
#
|
83
|
+
# @return [Array] Array of tag names
|
84
|
+
#
|
85
|
+
def tags
|
86
|
+
# User defined
|
87
|
+
[]
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Returns parameter definitions for the operation
|
92
|
+
#
|
93
|
+
# Transforms document parameters into OpenAPI parameter objects
|
94
|
+
# with proper schema types and location information.
|
95
|
+
#
|
96
|
+
# @return [Array] Array of parameter objects
|
97
|
+
#
|
98
|
+
def parameters
|
99
|
+
document.parameters.values.map do |parameter|
|
100
|
+
schema = Schema.new(type: parameter.type).to_h
|
101
|
+
|
102
|
+
{
|
103
|
+
schema:,
|
104
|
+
name: parameter.name,
|
105
|
+
in: parameter.location,
|
106
|
+
required: parameter.location == "path" || false
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Returns request body definition for the operation
|
113
|
+
#
|
114
|
+
# Groups requests by content type and creates proper OpenAPI
|
115
|
+
# request body object with examples and schemas.
|
116
|
+
#
|
117
|
+
# @return [Hash, nil] Request body object
|
118
|
+
#
|
119
|
+
def request_body
|
120
|
+
requests = document.requests
|
121
|
+
return if requests.blank?
|
122
|
+
|
123
|
+
requests = requests.group_by(&:content_type)
|
124
|
+
|
125
|
+
content =
|
126
|
+
requests.transform_values do |grouped_requests|
|
127
|
+
media_type_from_requests(grouped_requests)
|
128
|
+
end
|
129
|
+
|
130
|
+
{
|
131
|
+
description: "",
|
132
|
+
content:
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
alias_method :requestBody, :request_body
|
137
|
+
|
138
|
+
#
|
139
|
+
# Returns response definitions for the operation
|
140
|
+
#
|
141
|
+
# Groups responses by status code and transforms them into
|
142
|
+
# OpenAPI response objects with proper formatting.
|
143
|
+
#
|
144
|
+
# @return [Hash] Hash mapping status codes to response objects
|
145
|
+
#
|
146
|
+
def responses
|
147
|
+
document.responses
|
148
|
+
.group_by(&:status)
|
149
|
+
.transform_values! do |responses|
|
150
|
+
response = responses.first
|
151
|
+
Response.new(response).to_h
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def media_type_from_requests(requests)
|
158
|
+
request = requests.first
|
159
|
+
schema = Schema.new(type: request.type, content: request.content).to_h
|
160
|
+
|
161
|
+
examples =
|
162
|
+
requests.to_h do |request|
|
163
|
+
example_name = request.name.to_camelcase(:lower)
|
164
|
+
example = Example.new(summary: request.name, value: request.content).to_h
|
165
|
+
|
166
|
+
[example_name, example]
|
167
|
+
end
|
168
|
+
|
169
|
+
MediaType.new(schema:, examples:).to_h
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Response object
|
9
|
+
#
|
10
|
+
# Handles response definitions including status descriptions, content types,
|
11
|
+
# headers, and links for OpenAPI specifications.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#response-object
|
14
|
+
#
|
15
|
+
class Response < OpenAPI::Base
|
16
|
+
#
|
17
|
+
# Converts the response to an OpenAPI-compliant hash
|
18
|
+
#
|
19
|
+
# Builds the complete response object with required description and
|
20
|
+
# optional content, headers, and links.
|
21
|
+
#
|
22
|
+
# @return [Hash] OpenAPI-formatted response object
|
23
|
+
#
|
24
|
+
def to_h
|
25
|
+
{
|
26
|
+
# Required
|
27
|
+
description: "",
|
28
|
+
content:
|
29
|
+
}.merge_compact(
|
30
|
+
# Optional
|
31
|
+
headers:
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns content definitions for the response
|
37
|
+
#
|
38
|
+
# Creates media type objects with schemas and merges with any
|
39
|
+
# documentation-provided content definitions.
|
40
|
+
#
|
41
|
+
# @return [Hash] Content definitions by media type
|
42
|
+
#
|
43
|
+
def content
|
44
|
+
schema = Schema.new(type: document.body.type).to_h
|
45
|
+
|
46
|
+
{
|
47
|
+
document.content_type => MediaType.new(schema:).to_h
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Returns header definitions for the response
|
53
|
+
#
|
54
|
+
# Merges document headers with documentation-provided headers.
|
55
|
+
#
|
56
|
+
# @return [Hash, nil] Header definitions
|
57
|
+
#
|
58
|
+
def headers
|
59
|
+
document.headers.presence
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Schema object
|
9
|
+
#
|
10
|
+
# Handles schema definitions for data types, converting internal type
|
11
|
+
# representations to OpenAPI-compliant schema objects.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#schema-object
|
14
|
+
#
|
15
|
+
class Schema
|
16
|
+
#
|
17
|
+
# The schema type (string, integer, object, etc.)
|
18
|
+
#
|
19
|
+
# @return [String, nil] The OpenAPI schema type
|
20
|
+
#
|
21
|
+
attr_reader :type
|
22
|
+
|
23
|
+
#
|
24
|
+
# The schema format (date-time, int64, etc.)
|
25
|
+
#
|
26
|
+
# @return [String, nil] The OpenAPI schema format
|
27
|
+
#
|
28
|
+
attr_reader :format
|
29
|
+
|
30
|
+
#
|
31
|
+
# Creates a new OpenAPI schema object
|
32
|
+
#
|
33
|
+
# @param options [Hash] Schema configuration options
|
34
|
+
# @option options [String] :type The data type to convert to OpenAPI format
|
35
|
+
#
|
36
|
+
# @return [Schema] A new schema instance
|
37
|
+
#
|
38
|
+
def initialize(options = {})
|
39
|
+
@type, @format = transform_type(options[:type])
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Converts the schema to an OpenAPI-compliant hash
|
44
|
+
#
|
45
|
+
# @return [Hash] OpenAPI-formatted schema object
|
46
|
+
#
|
47
|
+
def to_h
|
48
|
+
{
|
49
|
+
type:,
|
50
|
+
format:
|
51
|
+
}.compact_blank!
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def transform_type(format)
|
57
|
+
case format
|
58
|
+
when "datetime", "time"
|
59
|
+
["string", "date-time"]
|
60
|
+
when "int64", "i64"
|
61
|
+
["integer", "int64"]
|
62
|
+
when "int32", "i32"
|
63
|
+
["integer", "int32"]
|
64
|
+
when "double", "float"
|
65
|
+
["number", format]
|
66
|
+
when "object"
|
67
|
+
["object"]
|
68
|
+
when "array"
|
69
|
+
["array"]
|
70
|
+
when "boolean", "number", "integer", "string"
|
71
|
+
[format]
|
72
|
+
else
|
73
|
+
["string", format]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
module OpenAPI
|
6
|
+
module V3_0 # standard:disable Naming/ClassAndModuleCamelCase
|
7
|
+
#
|
8
|
+
# Represents an OpenAPI 3.0 Tag object
|
9
|
+
#
|
10
|
+
# Handles tag definitions for categorizing and organizing API operations
|
11
|
+
# with optional descriptions and external documentation links.
|
12
|
+
#
|
13
|
+
# @see https://spec.openapis.org/oas/v3.0.4.html#tag-object
|
14
|
+
#
|
15
|
+
class Tag < Data.define(:name, :description, :external_docs)
|
16
|
+
#
|
17
|
+
# Creates a tag object from name and data
|
18
|
+
#
|
19
|
+
# Handles both string descriptions and hash configurations with
|
20
|
+
# external documentation references.
|
21
|
+
#
|
22
|
+
# @param name [String, Symbol] The tag name
|
23
|
+
# @param data [String, Hash] Either a description string or full config hash
|
24
|
+
#
|
25
|
+
# @return [Tag] A new tag instance
|
26
|
+
#
|
27
|
+
def self.parse(name, data)
|
28
|
+
name = name.to_s
|
29
|
+
|
30
|
+
case data
|
31
|
+
when String
|
32
|
+
description = data
|
33
|
+
when Hash
|
34
|
+
description = data[:description]
|
35
|
+
external_docs = data[:external_docs]
|
36
|
+
end
|
37
|
+
|
38
|
+
new(name:, description:, external_docs:)
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Creates a new OpenAPI tag object
|
43
|
+
#
|
44
|
+
# @param name [String] The tag name
|
45
|
+
# @param description [String, nil] Optional tag description
|
46
|
+
# @param external_docs [Hash, nil] Optional external documentation reference
|
47
|
+
#
|
48
|
+
# @return [Tag] A new tag instance
|
49
|
+
#
|
50
|
+
def initialize(name:, description: nil, external_docs: nil)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Converts the tag to an OpenAPI-compliant hash
|
56
|
+
#
|
57
|
+
# Transforms internal attribute names to match OpenAPI specification
|
58
|
+
# and removes any blank values for clean output.
|
59
|
+
#
|
60
|
+
# @return [Hash] OpenAPI-formatted tag object
|
61
|
+
#
|
62
|
+
def to_h
|
63
|
+
super
|
64
|
+
.rename_key_unordered!(:external_docs, :externalDocs)
|
65
|
+
.compact_blank!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
module Documentation
|
5
|
+
#
|
6
|
+
# OpenAPI documentation generation functionality
|
7
|
+
#
|
8
|
+
# Contains classes and modules for generating OpenAPI specifications
|
9
|
+
# from SpecForge test data. Supports multiple OpenAPI versions.
|
10
|
+
#
|
11
|
+
module OpenAPI
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require_relative "openapi/base"
|
17
|
+
|
18
|
+
require_relative "openapi/v3_0/example"
|
19
|
+
require_relative "openapi/v3_0/media_type"
|
20
|
+
require_relative "openapi/v3_0/operation"
|
21
|
+
require_relative "openapi/v3_0/response"
|
22
|
+
require_relative "openapi/v3_0/schema"
|
23
|
+
require_relative "openapi/v3_0/tag"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SpecForge
|
4
|
+
#
|
5
|
+
# API documentation generation functionality
|
6
|
+
#
|
7
|
+
# This module provides tools for extracting API documentation from SpecForge
|
8
|
+
# test files and generating various output formats like OpenAPI specifications.
|
9
|
+
# It handles the complete pipeline from test execution to documentation rendering.
|
10
|
+
#
|
11
|
+
# @example Generating OpenAPI documentation
|
12
|
+
# # From CLI
|
13
|
+
# spec_forge docs generate
|
14
|
+
#
|
15
|
+
# # Programmatically
|
16
|
+
# document = Documentation::Loader.load_document
|
17
|
+
# spec = Documentation::Generators::OpenAPI["3.0"].new(document).generate
|
18
|
+
#
|
19
|
+
module Documentation
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require_relative "documentation/builder"
|
24
|
+
require_relative "documentation/document"
|
25
|
+
require_relative "documentation/loader"
|
26
|
+
require_relative "documentation/openapi"
|
27
|
+
require_relative "documentation/generators"
|
data/lib/spec_forge/error.rb
CHANGED
@@ -300,5 +300,22 @@ module SpecForge
|
|
300
300
|
super(message)
|
301
301
|
end
|
302
302
|
end
|
303
|
+
|
304
|
+
#
|
305
|
+
# Raised when an OpenAPI specification fails validation
|
306
|
+
#
|
307
|
+
# This error indicates that the generated OpenAPI document contains
|
308
|
+
# validation errors according to the OpenAPI specification standards.
|
309
|
+
# It's typically raised after attempting to validate a generated specification.
|
310
|
+
#
|
311
|
+
# @example
|
312
|
+
# begin
|
313
|
+
# generator.validate!(openapi_spec)
|
314
|
+
# rescue SpecForge::Error::InvalidOASDocument
|
315
|
+
# puts "Generated specification has validation errors"
|
316
|
+
# end
|
317
|
+
#
|
318
|
+
class InvalidOASDocument < Error
|
319
|
+
end
|
303
320
|
end
|
304
321
|
end
|
data/lib/spec_forge/factory.rb
CHANGED
@@ -33,7 +33,7 @@ module SpecForge
|
|
33
33
|
factories = []
|
34
34
|
|
35
35
|
Dir[path].each do |file_path|
|
36
|
-
hash = YAML.load_file(file_path)
|
36
|
+
hash = YAML.load_file(file_path, symbolize_names: true)
|
37
37
|
|
38
38
|
hash.each do |factory_name, factory_hash|
|
39
39
|
factory_hash[:name] = factory_name
|
@@ -72,7 +72,7 @@ module SpecForge
|
|
72
72
|
#
|
73
73
|
def initialize(name:, **input)
|
74
74
|
@name = name
|
75
|
-
input = Normalizer.
|
75
|
+
input = Normalizer.normalize!(input, using: :factory)
|
76
76
|
|
77
77
|
@input = input
|
78
78
|
@model_class = input[:model_class]
|
data/lib/spec_forge/filter.rb
CHANGED
@@ -22,7 +22,7 @@ module SpecForge
|
|
22
22
|
# @param expectation_name [String, nil] Optional expectation name that was used by the filter
|
23
23
|
#
|
24
24
|
def announce(forges, file_name:, spec_name:, expectation_name:)
|
25
|
-
filters = {file_name:, spec_name:, expectation_name:}.
|
25
|
+
filters = {file_name:, spec_name:, expectation_name:}.compact_blank
|
26
26
|
return if filters.size == 0
|
27
27
|
|
28
28
|
filters_display = filters.join_map(", ") { |k, v| "#{k.in_quotes} => #{v.in_quotes}" }
|
@@ -69,10 +69,9 @@ module SpecForge
|
|
69
69
|
# Expectation filter
|
70
70
|
next spec unless expectation_name
|
71
71
|
|
72
|
-
|
73
|
-
next if expectations.empty?
|
72
|
+
spec.expectations.select! { |e| e.name == expectation_name }
|
73
|
+
next if spec.expectations.empty?
|
74
74
|
|
75
|
-
spec.expectations = expectations
|
76
75
|
spec
|
77
76
|
end
|
78
77
|
|
data/lib/spec_forge/forge.rb
CHANGED
@@ -117,8 +117,7 @@ module SpecForge
|
|
117
117
|
# }
|
118
118
|
#
|
119
119
|
specs.each_with_object({}) do |spec, hash|
|
120
|
-
overlay = spec[:expectations].to_h { |e| [e[:id], e.delete(:variables)] }
|
121
|
-
.reject { |_k, v| v.blank? }
|
120
|
+
overlay = spec[:expectations].to_h { |e| [e[:id], e.delete(:variables)] }.compact_blank
|
122
121
|
|
123
122
|
hash[spec[:id]] = {base: spec.delete(:variables), overlay:}
|
124
123
|
end
|
@@ -153,13 +152,15 @@ module SpecForge
|
|
153
152
|
overlay = spec[:expectations].to_h do |expectation|
|
154
153
|
[
|
155
154
|
expectation[:id],
|
156
|
-
expectation.extract!(*HTTP::REQUEST_ATTRIBUTES).
|
155
|
+
expectation.extract!(*HTTP::REQUEST_ATTRIBUTES).compact_blank
|
157
156
|
]
|
158
157
|
end
|
159
158
|
|
160
|
-
overlay.
|
159
|
+
overlay.compact_blank!
|
161
160
|
|
162
161
|
base = spec.extract!(*HTTP::REQUEST_ATTRIBUTES)
|
162
|
+
base.compact_blank!
|
163
|
+
|
163
164
|
base = config.deep_merge(base)
|
164
165
|
base[:http_verb] ||= "GET"
|
165
166
|
|