Skip to content

Commit eae9b36

Browse files
n-rodriguezclaude
andauthored
[Ruby] Add new ruby-idiomatic client generator (opt-in idiomatic redesign) (#24220)
* [ruby-idiomatic] Add new idiomatic Ruby client generator Add `ruby-idiomatic`, a new opt-in Ruby client generator that emits modern, DRY, multi-instance Ruby. It mirrors the Crystal idiomatic redesign as a SEPARATE generator: the existing `ruby` generator, its templates, and its samples are left byte-for-byte unchanged, so users opt in and migrate when they choose. Highlights: - Namespaced sub-clients (`client.dcim.cable_terminations.list`) via a path->route helper (RubyApiRouting) and a hybrid module nesting (moduleName + configurable `apiNamespace` wrapper, resource leaf compact). - A single `Connection#call` choke-point (Faraday) — keyword args everywhere, no `_with_http_info` twins; a delegating `Response` and one rich `ApiError` (all Faraday errors wrapped); per-request auth (header/query/cookie apiKey, basic, bearer) so token refresh/rotation works; a `Configuration#use` Faraday-middleware seam for request signing. - Native multi-instance: a `Client` facade owns a per-instance Configuration/Connection — no global singleton. - Models on a declarative `attribute` DSL + shared `Serializable`/`Validations` mixins; recursive `from_hash` deserializes nested models/arrays/maps/unions/ enums; `additionalProperties` preserved; anyOf/oneOf via a `Polymorphism` helper; array minItems/maxItems validated; numeric enums bare. - Names are always legal Ruby: enum constants and method names derived from digit-leading values/paths get an `N`/`call_` prefix, purely-symbolic enum values (telephony IVR keys `#`/`*`, ...) map to word names, and deeply-nested inline model names are shortened with a deterministic hash suffix to stay under tar's 100-byte path-component limit so `gem build` succeeds. - Class loading via Zeitwerk. A nested `moduleName` (e.g. `Ovh::Api`) is supported: the parent modules are predefined, the loader is driven explicitly, the gemspec reads VERSION by regex, and version.rb is required rather than autoloaded. Acronym names in both model and API resource classes (HTTPConfig, IPRestriction, DedicatedCloud::TwoFAWhitelist, ...) register explicit Zeitwerk inflections from the gem entrypoint so autoloading resolves the real constant instead of raising Zeitwerk::NameError. - Faraday only; form/multipart (incl. file upload); Ruby 3.0 floor; frozen_string_literal on every generated Ruby file; single-quoted strings; path params percent-encoded; FeatureSet declared honestly (JSON only, no XML, allOf/anyOf/oneOf/Union, apiKey/basic/bearer). - Generated gem passes RuboCop clean out of the box: a real `.rubocop.yml` (rubocop-performance/rake/rspec), a modern gemspec with configurable gem* options (author/homepage/summary/license, MIT-aware LICENSE), LICENSE, dev deps + binstubs, `.rspec`, spec_helper (SimpleCov), a commented `.gitignore`, a GitHub Actions CI workflow, and meaningful RSpec. Generated runtime is strict where a caller can err and lenient where the server can drift: model constructors reject unknown keyword args and validate required attributes, Configuration rejects unknown options, and required API params are nil-guarded, while from_hash deserialization bypasses that validation to tolerate server omissions/drift. Authentication is scoped per operation (only the schemes an operation declares are applied, so credentials never leak to `security: []` endpoints); oneOf/anyOf resolve strictly (ambiguous or unmatched payloads raise); JSON parse errors are wrapped in ApiError; deeply-nested request paths keep any base_url path prefix; and a multi-tagged operation yields a single method per path+verb instead of duplicates. Samples: petstore and a real-world Qdrant client, both green and rubocop-clean. Codegen unit tests (RubyIdiomaticClientCodegenTest, RubyApiRoutingTest) pass. A samples CI workflow and the generated generator docs are included. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Do not materialize schema defaults in ruby-idiomatic model constructors - RubyIdiomaticClientCodegen: stop emitting x-rb-default; a schema default is now documentation-only. from_hash bypasses initialize (allocate), so materializing only ever fired on client-built request objects: on optional fields it force-sent the default and overrode the server default (to_hash omits nil optionals precisely to let the server decide), on required fields it was inert since the caller must always supply the value. - partial_model_generic.mustache: drop the constructor default-assignment block. - Regenerate qdrant + petstore samples: optional fields with a schema default (e.g. HnswConfig#max_indexing_threads) now stay nil and are omitted from the request payload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5cba09f commit eae9b36

795 files changed

Lines changed: 27376 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Samples Ruby Idiomatic
2+
3+
on:
4+
push:
5+
paths:
6+
- 'samples/client/petstore/ruby-idiomatic/**'
7+
- 'samples/client/others/ruby-idiomatic-qdrant/**'
8+
pull_request:
9+
paths:
10+
- 'samples/client/petstore/ruby-idiomatic/**'
11+
- 'samples/client/others/ruby-idiomatic-qdrant/**'
12+
13+
jobs:
14+
build:
15+
name: Build Ruby Idiomatic client
16+
runs-on: ubuntu-latest
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
sample:
21+
- samples/client/petstore/ruby-idiomatic
22+
ruby-version:
23+
- '3.0'
24+
- '3.3'
25+
steps:
26+
- uses: actions/checkout@v7
27+
- uses: ruby/setup-ruby@v1
28+
with:
29+
ruby-version: ${{ matrix.ruby-version }}
30+
- name: Install and test
31+
working-directory: ${{ matrix.sample }}
32+
run: |
33+
bundle install
34+
bundle exec rspec
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
generatorName: ruby-idiomatic
2+
outputDir: samples/client/others/ruby-idiomatic-qdrant
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/crystal/qdrant.json
4+
templateDir: modules/openapi-generator/src/main/resources/ruby-idiomatic
5+
globalProperties:
6+
apiTests: "true"
7+
modelTests: "true"
8+
apiDocs: "false"
9+
modelDocs: "false"
10+
additionalProperties:
11+
gemName: qdrant
12+
moduleName: Qdrant
13+
gemVersion: 1.0.0

bin/configs/ruby-idiomatic.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
generatorName: ruby-idiomatic
2+
outputDir: samples/client/petstore/ruby-idiomatic
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/ruby-idiomatic
5+
globalProperties:
6+
apiTests: "true"
7+
modelTests: "true"
8+
apiDocs: "false"
9+
modelDocs: "false"
10+
additionalProperties:
11+
gemName: petstore
12+
moduleName: Petstore
13+
gemVersion: 1.0.0

docs/generators.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ The following generators are available:
5858
* [python-pydantic-v1](generators/python-pydantic-v1.md)
5959
* [r](generators/r.md)
6060
* [ruby](generators/ruby.md)
61+
* [ruby-idiomatic (beta)](generators/ruby-idiomatic.md)
6162
* [rust](generators/rust.md)
6263
* [scala-akka](generators/scala-akka.md)
6364
* [scala-gatling](generators/scala-gatling.md)

docs/generators/ruby-idiomatic.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
title: Documentation for the ruby-idiomatic Generator
3+
---
4+
5+
## METADATA
6+
7+
| Property | Value | Notes |
8+
| -------- | ----- | ----- |
9+
| generator name | ruby-idiomatic | pass this to the generate command after -g |
10+
| generator stability | BETA | |
11+
| generator type | CLIENT | |
12+
| generator language | Ruby | |
13+
| generator default templating engine | mustache | |
14+
| helpTxt | Generates an idiomatic Ruby client library (Faraday, Zeitwerk, namespaced sub-clients). | |
15+
16+
## CONFIG OPTIONS
17+
These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
18+
19+
| Option | Description | Values | Default |
20+
| ------ | ----------- | ------ | ------- |
21+
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
22+
|apiNamespace|Module wrapper nesting the API resource classes (default 'Api'); set empty to disable the wrapper.| |Api|
23+
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
24+
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
25+
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response. With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enums are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
26+
|gemAuthor|gem author (only one is supported).| |OpenAPI-Generator|
27+
|gemAuthorEmail|gem author email (only one is supported).| |null|
28+
|gemDescription|gem description.| |A Ruby client generated by openapi-generator|
29+
|gemHomepage|gem homepage.| |https://openapitools.org|
30+
|gemLicense|gem license.| |unlicense|
31+
|gemName|gem name (convention: underscore_case).| |null|
32+
|gemSummary|gem summary.| |A Ruby client generated by openapi-generator|
33+
|gemVersion|gem version.| |null|
34+
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
35+
|moduleName|top module name (convention: CamelCase, usually corresponding to gem name).| |null|
36+
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
37+
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
38+
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
39+
40+
## IMPORT MAPPING
41+
42+
| Type/Alias | Imports |
43+
| ---------- | ------- |
44+
45+
46+
## INSTANTIATION TYPES
47+
48+
| Type/Alias | Instantiated By |
49+
| ---------- | --------------- |
50+
|array|Array|
51+
|map|Hash|
52+
|set|Set|
53+
54+
55+
## LANGUAGE PRIMITIVES
56+
57+
<ul class="column-ul">
58+
<li>Array</li>
59+
<li>Boolean</li>
60+
<li>Date</li>
61+
<li>File</li>
62+
<li>Float</li>
63+
<li>Hash</li>
64+
<li>Integer</li>
65+
<li>Object</li>
66+
<li>String</li>
67+
<li>Time</li>
68+
</ul>
69+
70+
## RESERVED WORDS
71+
72+
<ul class="column-ul">
73+
<li>__file__</li>
74+
<li>__line__</li>
75+
<li>alias</li>
76+
<li>and</li>
77+
<li>begin</li>
78+
<li>break</li>
79+
<li>case</li>
80+
<li>class</li>
81+
<li>def</li>
82+
<li>defined?</li>
83+
<li>do</li>
84+
<li>else</li>
85+
<li>elsif</li>
86+
<li>end</li>
87+
<li>ensure</li>
88+
<li>false</li>
89+
<li>for</li>
90+
<li>if</li>
91+
<li>in</li>
92+
<li>module</li>
93+
<li>next</li>
94+
<li>nil</li>
95+
<li>not</li>
96+
<li>or</li>
97+
<li>redo</li>
98+
<li>rescue</li>
99+
<li>retry</li>
100+
<li>return</li>
101+
<li>self</li>
102+
<li>super</li>
103+
<li>then</li>
104+
<li>true</li>
105+
<li>undef</li>
106+
<li>unless</li>
107+
<li>until</li>
108+
<li>when</li>
109+
<li>while</li>
110+
<li>yield</li>
111+
</ul>
112+
113+
## FEATURE SET
114+
115+
116+
### Client Modification Feature
117+
| Name | Supported | Defined By |
118+
| ---- | --------- | ---------- |
119+
|BasePath|✗|ToolingExtension
120+
|Authorizations|✗|ToolingExtension
121+
|UserAgent|✗|ToolingExtension
122+
|MockServer|✗|ToolingExtension
123+
124+
### Data Type Feature
125+
| Name | Supported | Defined By |
126+
| ---- | --------- | ---------- |
127+
|Custom|✗|OAS2,OAS3
128+
|Int32|✓|OAS2,OAS3
129+
|Int64|✓|OAS2,OAS3
130+
|Float|✓|OAS2,OAS3
131+
|Double|✓|OAS2,OAS3
132+
|Decimal|✓|ToolingExtension
133+
|String|✓|OAS2,OAS3
134+
|Byte|✓|OAS2,OAS3
135+
|Binary|✓|OAS2,OAS3
136+
|Boolean|✓|OAS2,OAS3
137+
|Date|✓|OAS2,OAS3
138+
|DateTime|✓|OAS2,OAS3
139+
|Password|✓|OAS2,OAS3
140+
|File|✓|OAS2
141+
|Uuid||
142+
|Array|✓|OAS2,OAS3
143+
|Null|✗|OAS3
144+
|AnyType|✗|OAS2,OAS3
145+
|Object|✓|OAS2,OAS3
146+
|Maps|✓|ToolingExtension
147+
|CollectionFormat|✓|OAS2
148+
|CollectionFormatMulti|✓|OAS2
149+
|Enum|✓|OAS2,OAS3
150+
|ArrayOfEnum|✓|ToolingExtension
151+
|ArrayOfModel|✓|ToolingExtension
152+
|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
153+
|ArrayOfCollectionOfModel|✓|ToolingExtension
154+
|ArrayOfCollectionOfEnum|✓|ToolingExtension
155+
|MapOfEnum|✓|ToolingExtension
156+
|MapOfModel|✓|ToolingExtension
157+
|MapOfCollectionOfPrimitives|✓|ToolingExtension
158+
|MapOfCollectionOfModel|✓|ToolingExtension
159+
|MapOfCollectionOfEnum|✓|ToolingExtension
160+
161+
### Documentation Feature
162+
| Name | Supported | Defined By |
163+
| ---- | --------- | ---------- |
164+
|Readme|✗|ToolingExtension
165+
|Model|✓|ToolingExtension
166+
|Api|✓|ToolingExtension
167+
168+
### Global Feature
169+
| Name | Supported | Defined By |
170+
| ---- | --------- | ---------- |
171+
|Host|✓|OAS2,OAS3
172+
|BasePath|✓|OAS2,OAS3
173+
|Info|✓|OAS2,OAS3
174+
|Schemes|✗|OAS2,OAS3
175+
|PartialSchemes|✓|OAS2,OAS3
176+
|Consumes|✓|OAS2
177+
|Produces|✓|OAS2
178+
|ExternalDocumentation|✓|OAS2,OAS3
179+
|Examples|✓|OAS2,OAS3
180+
|XMLStructureDefinitions|✗|OAS2,OAS3
181+
|MultiServer|✗|OAS3
182+
|ParameterizedServer|✗|OAS3
183+
|ParameterStyling|✗|OAS3
184+
|Callbacks|✓|OAS3
185+
|LinkObjects|✗|OAS3
186+
187+
### Parameter Feature
188+
| Name | Supported | Defined By |
189+
| ---- | --------- | ---------- |
190+
|Path|✓|OAS2,OAS3
191+
|Query|✓|OAS2,OAS3
192+
|Header|✓|OAS2,OAS3
193+
|Body|✓|OAS2
194+
|FormUnencoded|✓|OAS2
195+
|FormMultipart|✓|OAS2
196+
|Cookie|✗|OAS3
197+
198+
### Schema Support Feature
199+
| Name | Supported | Defined By |
200+
| ---- | --------- | ---------- |
201+
|Simple|✓|OAS2,OAS3
202+
|Composite|✓|OAS2,OAS3
203+
|Polymorphism|✓|OAS2,OAS3
204+
|Union|✓|OAS3
205+
|allOf|✓|OAS2,OAS3
206+
|anyOf|✓|OAS3
207+
|oneOf|✓|OAS3
208+
|not|✗|OAS3
209+
210+
### Security Feature
211+
| Name | Supported | Defined By |
212+
| ---- | --------- | ---------- |
213+
|BasicAuth|✓|OAS2,OAS3
214+
|ApiKey|✓|OAS2,OAS3
215+
|OpenIDConnect|✗|OAS3
216+
|BearerToken|✓|OAS3
217+
|OAuth2_Implicit|✗|OAS2,OAS3
218+
|OAuth2_Password|✗|OAS2,OAS3
219+
|OAuth2_ClientCredentials|✗|OAS2,OAS3
220+
|OAuth2_AuthorizationCode|✗|OAS2,OAS3
221+
|SignatureAuth|✗|OAS3
222+
|AWSV4Signature|✗|ToolingExtension
223+
224+
### Wire Format Feature
225+
| Name | Supported | Defined By |
226+
| ---- | --------- | ---------- |
227+
|JSON|✓|OAS2,OAS3
228+
|XML|✗|OAS2,OAS3
229+
|PROTOBUF|✗|ToolingExtension
230+
|Custom|✗|OAS2,OAS3

0 commit comments

Comments
 (0)