action_spec 0.2.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf40844c78d8e8281f129c65e12bf07d9fc8ebabc18bd47c717d779cbc90fa99
4
- data.tar.gz: 1552ea6a7da3f13b02febfd4eea39b05b437db5efaa93bb99320662b9f96e98c
3
+ metadata.gz: 0adf599aac952bde5c1969385d71ea5a023fe20814abd835a8ab55dca32ca319
4
+ data.tar.gz: 6761792a92ae44fd93b7fad87d350d952494ff6c3b7bc17023ae5fd7020d373f
5
5
  SHA512:
6
- metadata.gz: 02e508bf9545f6848c351d96e7b95d4b7f22219322d365bba632f793722899922ee9ef347c147d4c98987cc9bf9b1ef18f70436ba105a7e695d0bb96f92acb47
7
- data.tar.gz: cb9023dc36b73d1c155854200d726eb3fe3cb1dea03734da8fde6f64cf93cf58a2376eba90e77ccaff66c29ecbda220c6376037608a5d9fb2c0be702192c51f4
6
+ metadata.gz: 5490efab1cd187ab21a97c10401dc3471c81dccc161e40955b5e53cb2f9b5cacf375cb55cbed382bc19f47e7bd0b9f69aa870c4ca0bf9f78663b6f89e1973d74
7
+ data.tar.gz: ee1d9eb0eaacbbc8df53b5db72568006dde268ecf400f0de4f60087ee60511a51fde327716c5a23612398dd01a0f6e37665123569f28e9e95bba32bd74a03107
data/README.md CHANGED
@@ -6,7 +6,7 @@ Concise and Powerful API Documentation Solution for Rails.
6
6
 
7
7
  - OpenAPI version: `v3.2.0`
8
8
  - Requires: Ruby 3.1+ and Rails 7.0+
9
- - Note: this project was implemented with Codex in about one hour, has not yet been manually reviewed, and has not been validated in production. It does, however, come with fairly detailed RSpec tests generated with Codex.
9
+ - Note: this project was implemented with Codex in about 2 hours, has not yet been manually reviewed, and has not been validated in production. It does, however, come with fairly detailed RSpec tests generated with Codex.
10
10
 
11
11
  ## Table Of Contents
12
12
 
@@ -16,10 +16,11 @@ Concise and Powerful API Documentation Solution for Rails.
16
16
  - [`doc_dry`](#doc_dry)
17
17
  - [DSL Reference](#dsl-reference)
18
18
  - [Schemas](#schemas)
19
- - [Required Fields](#required-fields)
20
- - [Supported Runtime Types](#supported-runtime-types)
19
+ - [Declare A Required Field](#declare-a-required-field)
20
+ - [Field Types](#field-types)
21
+ - [Field Options](#field-options)
22
+ - [Schemas From ActiveRecord](#schemas-from-activerecord)
21
23
  - [Type And Boundary Matrix](#type-and-boundary-matrix)
22
- - [Supported Runtime Options](#supported-runtime-options)
23
24
  - [Parameter Validation And Type Coercion](#parameter-validation-and-type-coercion)
24
25
  - [Validation Flow](#validation-flow)
25
26
  - [Reading Validated Values With `px`](#reading-validated-values-with-px)
@@ -250,7 +251,7 @@ Response declarations are stored as metadata now. They are not yet used to rende
250
251
 
251
252
  ## Schemas
252
253
 
253
- #### Required Fields
254
+ #### Declare A Required Field
254
255
 
255
256
  Use `!` in either place:
256
257
 
@@ -271,7 +272,7 @@ Meaning of `!`:
271
272
  - keys such as `name!:` or `nickname!:` mark nested object fields as required
272
273
  - `body!`, `json!`, and `form!` are currently accepted for DSL consistency, but today they behave the same as the non-bang form at runtime
273
274
 
274
- #### Supported Runtime Types
275
+ #### Field Types
275
276
 
276
277
  Scalar types currently supported by validation/coercion:
277
278
 
@@ -294,28 +295,12 @@ json data: {
294
295
  profile: {
295
296
  nickname!: String
296
297
  },
297
- settings: { type: Object }
298
+ settings: { type: Object },
299
+ users: [{ id: Integer }]
298
300
  }
299
301
  ```
300
302
 
301
- #### Type And Boundary Matrix
302
-
303
- | Type | Accepted examples | Rejected examples / notes |
304
- | --- | --- | --- |
305
- | `String` | `12`, `true`, `""` | Follows `ActiveModel::Type::String`, so `true` becomes `"t"` |
306
- | `Integer` | `"0"`, `"-12"`, `"+7"`, `12` | Rejects `"12.3"`, `"abc"`, `""` |
307
- | `Float` | `"0"`, `"-12.5"`, `12`, `12.5` | Rejects `"12.3.4"`, `"abc"` |
308
- | `BigDecimal` | `"0"`, `"-12.50"`, `12`, `12.5` | Rejects `"abc"` |
309
- | `:boolean` / host-defined `Boolean` | `true`, `false`, `"1"`, `"0"`, `"true"`, `"false"`, `"yes"`, `"no"`, `"on"`, `"off"` | Rejects ambiguous values such as `""`, `"2"`, `"TRUE "`, `"maybe"` |
310
- | `Date` | `"2025-10-17"` | Rejects invalid dates such as `"2025-02-30"` |
311
- | `DateTime` | `"2025-10-17T12:30:00Z"` | Rejects invalid datetimes such as `"2025-10-17 25:00:00"` |
312
- | `Time` | `"2025-10-17T12:30:00Z"` | Follows `ActiveModel::Type::Time`, so the date part becomes `2000-01-01` |
313
- | `File` | `ActionDispatch::Http::UploadedFile`, `Tempfile`, file-like IO objects | Keeps the object as-is and does not read file contents into memory |
314
- | `Object` | `Hash`, `ActionController::Parameters`, arbitrary Ruby objects | Passed through for scalar `Object`; nested hashes use object schema resolution |
315
- | `[Type]` | arrays such as `%w[1 2 3]` for `[Integer]` | Rejects non-array values, and reports item errors like `tags.1` |
316
- | nested object | `{ profile: { nickname: "neo" } }` | Rejects non-hash values, and reports nested paths like `profile.nickname` |
317
-
318
- #### Supported Runtime Options
303
+ #### Field Options
319
304
 
320
305
  These options are currently used by the validator:
321
306
 
@@ -327,14 +312,85 @@ query :score, Integer, range: { ge: 1, le: 5 }
327
312
  query :slug, String, pattern: /\A[a-z\-]+\z/
328
313
  ```
329
314
 
330
- These options are currently accepted as metadata, mainly for future OpenAPI work, but are not yet used by the runtime validator:
315
+ These options are currently used by OpenAPI generation, but are not yet used by the runtime validator:
331
316
 
332
317
  - `desc`
333
318
  - `example`
334
319
  - `examples`
320
+
321
+ These options are not yet used by either the runtime validator or OpenAPI generation:
322
+
335
323
  - `allow_nil`
336
324
  - `allow_blank`
337
325
 
326
+ #### Schemas From ActiveRecord
327
+
328
+ If your model is an `ActiveRecord::Base`, you can derive an ActionSpec-friendly schema hash directly from the model:
329
+
330
+ ```ruby
331
+ class UsersController < ApplicationController
332
+ doc {
333
+ form data: User.schemas
334
+ }
335
+ def create
336
+ end
337
+ end
338
+ ```
339
+
340
+ `User.schemas` returns a hash that can be passed directly into `form data:`, `json data:`, or `body`.
341
+
342
+ By default it includes all model fields:
343
+
344
+ ```ruby
345
+ User.schemas
346
+ ```
347
+
348
+ You can also limit the exported fields:
349
+
350
+ ```ruby
351
+ User.schemas(only: %i[name phone role])
352
+ ```
353
+
354
+ ActionSpec extracts schema-relevant information from ActiveRecord / ActiveModel when available, including:
355
+
356
+ - field type
357
+ - requiredness, rendered as bang keys such as `"name!"`
358
+ - enum values from `enum`
359
+ - `default`
360
+ - `desc` from column comments
361
+ - `pattern` from format validators
362
+ - `range` from numericality validators
363
+ - `length` from length validators and string column limits
364
+
365
+ Example output:
366
+
367
+ ```ruby
368
+ User.schemas
369
+ # {
370
+ # "name!" => { type: String, desc: "user name", length: { maximum: 20 } },
371
+ # "phone!" => { type: String, length: { maximum: 13 }, pattern: /\A1\d{10}\z/ },
372
+ # "role" => { type: String, enum: %w[admin member visitor] }
373
+ # }
374
+ ```
375
+
376
+ #### Type And Boundary Matrix
377
+
378
+ | Type | Accepted examples | Rejected examples / notes |
379
+ | --- | --- | --- |
380
+ | `String` | `12`, `true`, `""` | Follows `ActiveModel::Type::String`, so `true` becomes `"t"` |
381
+ | `Integer` | `"0"`, `"-12"`, `"+7"`, `12` | Rejects `"12.3"`, `"abc"`, `""` |
382
+ | `Float` | `"0"`, `"-12.5"`, `12`, `12.5` | Rejects `"12.3.4"`, `"abc"` |
383
+ | `BigDecimal` | `"0"`, `"-12.50"`, `12`, `12.5` | Rejects `"abc"` |
384
+ | `:boolean` / `Boolean` | `true`, `false`, `"1"`, `"0"`, `"true"`, `"false"`, `"yes"`, `"no"`, `"on"`, `"off"` | Rejects ambiguous values such as `""`, `"2"`, `"TRUE "`, `"maybe"` |
385
+ | `Date` | `"2025-10-17"` | Rejects invalid dates such as `"2025-02-30"` |
386
+ | `DateTime` | `"2025-10-17T12:30:00Z"` | Rejects invalid datetimes such as `"2025-10-17 25:00:00"` |
387
+ | `Time` | `"2025-10-17T12:30:00Z"` | Follows `ActiveModel::Type::Time`, so the date part becomes `2000-01-01` |
388
+ | `File` | `ActionDispatch::Http::UploadedFile`, `Tempfile`, file-like IO objects | Keeps the object as-is and does not read file contents into memory |
389
+ | `Object` | `Hash`, `ActionController::Parameters`, arbitrary Ruby objects | Passed through for scalar `Object`; nested hashes use object schema resolution |
390
+ | `[Type]` | arrays such as `%w[1 2 3]` for `[Integer]` | Rejects non-array values, and reports item errors like `tags.1` |
391
+ | nested object | `{ profile: { nickname: "neo" } }` | Rejects non-hash values, and reports nested paths like `profile.nickname` |
392
+
393
+
338
394
  ## Parameter Validation And Type Coercion
339
395
 
340
396
  ### Validation Flow
@@ -501,7 +557,7 @@ Available config keys:
501
557
 
502
558
  ### I18n
503
559
 
504
- ActionSpec loads its own locale files and uses `ActiveModel::Errors`, so you can override both messages and attribute names:
560
+ ActionSpec uses `ActiveModel::Errors`, so you can override both messages and attribute names:
505
561
 
506
562
  ```yml
507
563
  en:
@@ -529,10 +585,24 @@ end
529
585
 
530
586
  ## What Is Not Implemented Yet
531
587
 
532
- - OpenAPI document generation
533
- - automatic response rendering from `response`
534
- - reusable schema/components system from `zero-rails_openapi`
535
- - runtime behavior for `allow_nil` / `allow_blank`
588
+ - reusable `components` generation
589
+ - `$ref` generation and deduplication
590
+ - `description`, `operationId`, `tags`, `externalDocs`, `deprecated`, and `security` on operations
591
+ - parameter-level `style`, `explode`, `allowReserved`, `examples`, and richer header/cookie serialization controls
592
+ - request body `encoding`
593
+ - multiple request/response media types beyond the current direct DSL mapping
594
+ - response body schema generation; current `response` / `resp` / `error` declarations only generate response descriptions
595
+ - response headers
596
+ - response links
597
+ - callbacks
598
+ - webhooks
599
+ - path-level shared parameters
600
+ - top-level `components.parameters`, `components.requestBodies`, `components.responses`, `components.headers`, `components.examples`, `components.links`, `components.callbacks`, `components.schemas`, `components.securitySchemes`, and `components.pathItems`
601
+ - top-level `security`
602
+ - top-level `tags`
603
+ - top-level `externalDocs`
604
+ - `jsonSchemaDialect`
605
+ - richer schema keywords beyond the current subset, including nullable/blank semantics, object-level constraints, and composition keywords such as `oneOf`, `anyOf`, `allOf`, and `not`
536
606
 
537
607
  ## Contributing
538
608
  .
@@ -53,7 +53,9 @@ module ActionSpec
53
53
  next if path.blank?
54
54
 
55
55
  hash[path] ||= ActiveSupport::OrderedHash.new
56
- hash[path][route_verb(route)] = Operation.new(endpoint).build
56
+ route_verbs(route).each do |verb|
57
+ hash[path][verb] = Operation.new(endpoint).build
58
+ end
57
59
  end
58
60
  end
59
61
 
@@ -87,7 +89,7 @@ module ActionSpec
87
89
  defaults.to_h.symbolize_keys
88
90
  end
89
91
 
90
- def route_verb(route)
92
+ def route_verbs(route)
91
93
  raw_verb =
92
94
  if route.respond_to?(:verb) && route.verb.respond_to?(:source)
93
95
  route.verb.source
@@ -97,7 +99,7 @@ module ActionSpec
97
99
  route[:verb].to_s
98
100
  end
99
101
 
100
- raw_verb.gsub(/[$^]/, "").split("|").find(&:present?).to_s.downcase
102
+ raw_verb.gsub(/[$^]/, "").split("|").filter_map { |verb| verb.presence&.downcase }
101
103
  end
102
104
 
103
105
  def normalized_path(route)
@@ -108,6 +108,7 @@ module ActionSpec
108
108
  definition["default"] = schema.default unless schema.default.respond_to?(:call) || schema.default.nil?
109
109
  definition["enum"] = schema.enum if schema.enum.present?
110
110
  definition["pattern"] = regex_source(schema.pattern) if schema.pattern.present?
111
+ apply_length(definition, schema.length, definition["type"])
111
112
  definition["example"] = schema.example if schema.example.present?
112
113
  definition["examples"] = schema.examples if schema.examples.present?
113
114
  apply_range(definition, schema.range)
@@ -124,6 +125,16 @@ module ActionSpec
124
125
  definition["exclusiveMaximum"] = rules[:lt] if rules.key?(:lt)
125
126
  end
126
127
 
128
+ def apply_length(definition, length, type)
129
+ return if length.blank?
130
+
131
+ rules = length.symbolize_keys
132
+ return unless type == "string"
133
+
134
+ definition["minLength"] = rules[:minimum] if rules.key?(:minimum)
135
+ definition["maxLength"] = rules[:maximum] if rules.key?(:maximum)
136
+ end
137
+
127
138
  def scalar_type(type)
128
139
  case ActionSpec::Schema::TypeCaster.normalize(type)
129
140
  when :string then "string"
@@ -1,14 +1,16 @@
1
1
  module ActionSpec
2
2
  class Railtie < ::Rails::Railtie
3
- initializer "action_spec.i18n" do |app|
4
- app.config.i18n.load_path += Dir[root.join("config/locales/*.yml")]
5
- end
6
-
7
3
  initializer "action_spec.controller" do
8
4
  ActiveSupport.on_load(:action_controller_base) do
9
5
  include ActionSpec::Doc
10
6
  include ActionSpec::Validator
11
7
  end
12
8
  end
9
+
10
+ initializer "action_spec.active_record" do
11
+ ActiveSupport.on_load(:active_record) do
12
+ include ActionSpec::Schema::ActiveRecord
13
+ end
14
+ end
13
15
  end
14
16
  end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionSpec
4
+ module Schema
5
+ module ActiveRecord
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def schemas(only: nil)
10
+ names = selected_column_names(only)
11
+ @action_spec_validator_index = build_validator_index
12
+
13
+ names.each_with_object(ActiveSupport::OrderedHash.new) do |name, hash|
14
+ hash[output_name(name)] = schema_definition_for(name)
15
+ end
16
+ ensure
17
+ remove_instance_variable(:@action_spec_validator_index) if instance_variable_defined?(:@action_spec_validator_index)
18
+ end
19
+
20
+ private
21
+
22
+ def selected_column_names(only)
23
+ selected = Array(only).presence&.map { |name| normalize_name(name) } || column_names
24
+
25
+ column_names.select { |name| selected.include?(name) }
26
+ end
27
+
28
+ def output_name(name)
29
+ required_attribute?(name) ? "#{name}!" : name
30
+ end
31
+
32
+ def schema_definition_for(name)
33
+ definition = { type: schema_type_for(name) }
34
+ definition[:default] = column_default_for(name) unless column_default_for(name).nil?
35
+ definition[:desc] = column_comment_for(name) if column_comment_for(name).present?
36
+ definition[:enum] = resolved_enum_for(name) if resolved_enum_for(name).present?
37
+ definition[:pattern] = pattern_for(name) if pattern_for(name)
38
+ definition[:range] = range_for(name) if range_for(name).present?
39
+ definition[:length] = length_for(name) if length_for(name).present?
40
+ definition
41
+ end
42
+
43
+ def schema_type_for(name)
44
+ return String if enum_values_for(name).present?
45
+
46
+ case columns_hash.fetch(name).type
47
+ when :string, :text, :binary then String
48
+ when :integer, :bigint then Integer
49
+ when :float then Float
50
+ when :decimal then BigDecimal
51
+ when :boolean then :boolean
52
+ when :date then Date
53
+ when :datetime, :timestamp then DateTime
54
+ when :time then Time
55
+ when :json, :jsonb then Object
56
+ else String
57
+ end
58
+ end
59
+
60
+ def required_attribute?(name)
61
+ (!column_nullable?(name) && column_default_for(name).nil?) || presence_validated?(name)
62
+ end
63
+
64
+ def column_nullable?(name)
65
+ columns_hash.fetch(name).null
66
+ end
67
+
68
+ def column_default_for(name)
69
+ columns_hash.fetch(name).default
70
+ end
71
+
72
+ def column_comment_for(name)
73
+ columns_hash.fetch(name).comment
74
+ end
75
+
76
+ def enum_values_for(name)
77
+ defined_enums.fetch(name.to_s, nil)&.keys
78
+ end
79
+
80
+ def inclusion_values_for(name)
81
+ validator_for(name, ActiveModel::Validations::InclusionValidator)&.options&.fetch(:in, nil)&.to_a
82
+ end
83
+
84
+ def resolved_enum_for(name)
85
+ enum_values_for(name).presence || inclusion_values_for(name).presence
86
+ end
87
+
88
+ def pattern_for(name)
89
+ validator_for(name, ActiveModel::Validations::FormatValidator)&.options&.fetch(:with, nil)
90
+ end
91
+
92
+ def range_for(name)
93
+ options = validator_for(name, ActiveModel::Validations::NumericalityValidator)&.options
94
+ return if options.blank?
95
+
96
+ {}.tap do |range|
97
+ range[:gt] = options[:greater_than] if options.key?(:greater_than)
98
+ range[:ge] = options[:greater_than_or_equal_to] if options.key?(:greater_than_or_equal_to)
99
+ range[:lt] = options[:less_than] if options.key?(:less_than)
100
+ range[:le] = options[:less_than_or_equal_to] if options.key?(:less_than_or_equal_to)
101
+ end.presence
102
+ end
103
+
104
+ def length_for(name)
105
+ definition = {}.tap do |length|
106
+ limit = string_limit_for(name)
107
+ length[:maximum] = limit if limit
108
+
109
+ options = validator_for(name, ActiveModel::Validations::LengthValidator)&.options || {}
110
+ length[:minimum] = options[:minimum] if options.key?(:minimum)
111
+ length[:maximum] = options[:maximum] if options.key?(:maximum)
112
+
113
+ if options.key?(:is)
114
+ length[:minimum] = options[:is]
115
+ length[:maximum] = options[:is]
116
+ end
117
+ end
118
+
119
+ definition.presence
120
+ end
121
+
122
+ def string_limit_for(name)
123
+ column = columns_hash.fetch(name)
124
+ return unless column.type.in?([:string, :text])
125
+
126
+ column.limit
127
+ end
128
+
129
+ def presence_validated?(name)
130
+ validator_for(name, ActiveModel::Validations::PresenceValidator).present?
131
+ end
132
+
133
+ def validator_for(name, klass)
134
+ validator_index.fetch(name.to_s, []).find { |validator| validator.is_a?(klass) }
135
+ end
136
+
137
+ def normalize_name(name)
138
+ name.to_s.delete_suffix("!")
139
+ end
140
+
141
+ def build_validator_index
142
+ validators.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |validator, index|
143
+ validator.attributes.each do |attribute|
144
+ index[attribute.to_s] << validator
145
+ end
146
+ end
147
+ end
148
+
149
+ def validator_index
150
+ @action_spec_validator_index ||= build_validator_index
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -24,7 +24,7 @@ module ActionSpec
24
24
  end
25
25
 
26
26
  def copy
27
- self.class.new(item.copy, default:, enum:, range:, pattern:, allow_nil:, allow_blank:, desc: description, example:, examples:)
27
+ self.class.new(item.copy, default:, enum:, range:, pattern:, length:, allow_nil:, allow_blank:, desc: description, example:, examples:)
28
28
  end
29
29
  end
30
30
  end
@@ -3,7 +3,7 @@
3
3
  module ActionSpec
4
4
  module Schema
5
5
  class Base
6
- attr_reader :default, :enum, :range, :pattern, :allow_nil, :allow_blank, :description, :example, :examples
6
+ attr_reader :default, :enum, :range, :pattern, :length, :allow_nil, :allow_blank, :description, :example, :examples
7
7
 
8
8
  def initialize(options = {})
9
9
  options = options.symbolize_keys
@@ -11,6 +11,7 @@ module ActionSpec
11
11
  @enum = options[:enum]
12
12
  @range = options[:range]
13
13
  @pattern = options[:pattern]
14
+ @length = options[:length]
14
15
  @allow_nil = options[:allow_nil]
15
16
  @allow_blank = options[:allow_blank]
16
17
  @description = options[:desc] || options[:description]
@@ -34,7 +34,7 @@ module ActionSpec
34
34
  end
35
35
 
36
36
  def copy
37
- self.class.new(fields.transform_values(&:copy), default:, enum:, range:, pattern:, allow_nil:, allow_blank:, desc: description, example:, examples:)
37
+ self.class.new(fields.transform_values(&:copy), default:, enum:, range:, pattern:, length:, allow_nil:, allow_blank:, desc: description, example:, examples:)
38
38
  end
39
39
 
40
40
  private
@@ -23,7 +23,7 @@ module ActionSpec
23
23
  end
24
24
 
25
25
  def copy
26
- self.class.new(type, default:, enum:, range:, pattern:, allow_nil:, allow_blank:, desc: description, example:, examples:)
26
+ self.class.new(type, default:, enum:, range:, pattern:, length:, allow_nil:, allow_blank:, desc: description, example:, examples:)
27
27
  end
28
28
  end
29
29
  end
@@ -5,13 +5,14 @@ require "action_spec/schema/field"
5
5
  require "action_spec/schema/scalar"
6
6
  require "action_spec/schema/object_of"
7
7
  require "action_spec/schema/array_of"
8
+ require "action_spec/schema/active_record"
8
9
  require "action_spec/schema/resolver"
9
10
  require "action_spec/schema/type_caster"
10
11
 
11
12
  module ActionSpec
12
13
  module Schema
13
14
  Missing = Object.new.freeze
14
- OPTION_KEYS = %i[default desc enum range pattern allow_nil allow_blank example examples].freeze
15
+ OPTION_KEYS = %i[default desc enum range pattern length allow_nil allow_blank example examples].freeze
15
16
 
16
17
  class << self
17
18
  def build(type = nil, **options)
@@ -1,3 +1,3 @@
1
1
  module ActionSpec
2
- VERSION = "0.2.0"
2
+ VERSION = "1.1.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_spec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - zhandao
@@ -43,7 +43,7 @@ dependencies:
43
43
  - - "<"
44
44
  - !ruby/object:Gem::Version
45
45
  version: '9.0'
46
- description: Concise and Powerful API Documentation Solution for Rails.
46
+ description: A concise Rails DSL for declaring API request and response schemas.
47
47
  email:
48
48
  - a@skipping.cat
49
49
  executables: []
@@ -69,6 +69,7 @@ files:
69
69
  - lib/action_spec/open_api/schema.rb
70
70
  - lib/action_spec/railtie.rb
71
71
  - lib/action_spec/schema.rb
72
+ - lib/action_spec/schema/active_record.rb
72
73
  - lib/action_spec/schema/array_of.rb
73
74
  - lib/action_spec/schema/base.rb
74
75
  - lib/action_spec/schema/field.rb
@@ -86,8 +87,8 @@ licenses:
86
87
  - MIT
87
88
  metadata:
88
89
  homepage_uri: https://github.com/action-spec/action_spec
89
- source_code_uri: https://github.com/action-spec/action_spec
90
- changelog_uri: https://github.com/action-spec/action_spec/CHANils.
90
+ source_code_uri: https://github.com/action-spec/action_spec/tree/main
91
+ changelog_uri: https://github.com/action-spec/action_spec/releases
91
92
  rdoc_options: []
92
93
  require_paths:
93
94
  - lib