Skip to content

Commit 067bd6b

Browse files
committed
OCaml: Add support for direct recursive types
1 parent 292041d commit 067bd6b

22 files changed

Lines changed: 468 additions & 5 deletions

File tree

.github/workflows/samples-ocaml.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ on:
88
- 'samples/client/petstore/ocaml-oneOf-primitive/**'
99
- 'samples/client/petstore/ocaml-additional-properties/**'
1010
- 'samples/client/petstore/ocaml-enum-in-composed-schema/**'
11+
- 'samples/client/petstore/ocaml-recursion-test/**'
1112
pull_request:
1213
paths:
1314
- 'samples/client/petstore/ocaml/**'
1415
- 'samples/client/petstore/ocaml-fake-petstore/**'
1516
- 'samples/client/petstore/ocaml-oneOf-primitive/**'
1617
- 'samples/client/petstore/ocaml-additional-properties/**'
1718
- 'samples/client/petstore/ocaml-enum-in-composed-schema/**'
19+
- 'samples/client/petstore/ocaml-recursion-test/**'
1820

1921
jobs:
2022
build:
@@ -29,6 +31,7 @@ jobs:
2931
- 'samples/client/petstore/ocaml-oneOf-primitive/'
3032
- 'samples/client/petstore/ocaml-additional-properties/'
3133
- 'samples/client/petstore/ocaml-enum-in-composed-schema/'
34+
- 'samples/client/petstore/ocaml-recursion-test/'
3235
steps:
3336
- uses: actions/checkout@v5
3437
- name: Set-up OCaml

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ samples/client/petstore/ocaml-fake-petstore/_build/
305305
samples/client/petstore/ocaml-oneOf-primitive/_build/
306306
samples/client/petstore/ocaml-additional-properties/_build/
307307
samples/client/petstore/ocaml-enum-in-composed-schema/_build/
308+
samples/client/petstore/ocaml-recursion-test/_build/
308309

309310
# jetbrain http client
310311
samples/client/jetbrains/adyen/checkout71/http/client/Apis/http-client.private.env.json
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
generatorName: ocaml
2+
outputDir: samples/client/petstore/ocaml-recursion-test
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/recursion.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/ocaml
5+
additionalProperties:
6+
packageName: recursion_test

docs/generators/ocaml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ title: Documentation for the ocaml Generator
1111
| generator type | CLIENT | |
1212
| generator language | OCaml | |
1313
| generator default templating engine | mustache | |
14-
| helpTxt | Generates an OCaml client library (beta). | |
14+
| helpTxt | Generates an OCaml client library. | |
1515

1616
## CONFIG OPTIONS
1717
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.

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/OCamlClientCodegen.java

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ public class OCamlClientCodegen extends DefaultCodegen implements CodegenConfig
5151

5252
static final String X_MODEL_MODULE = "x-model-module";
5353

54-
@Setter protected String packageName = "openapi";
55-
@Setter protected String packageVersion = "1.0.0";
54+
@Setter
55+
protected String packageName = "openapi";
56+
@Setter
57+
protected String packageVersion = "1.0.0";
5658
protected String apiDocPath = "docs/";
5759
protected String modelDocPath = "docs/";
5860
protected String apiFolder = "src/apis";
@@ -74,7 +76,7 @@ public String getName() {
7476

7577
@Override
7678
public String getHelp() {
77-
return "Generates an OCaml client library (beta).";
79+
return "Generates an OCaml client library.";
7880
}
7981

8082
public OCamlClientCodegen() {
@@ -233,6 +235,71 @@ public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> supero
233235

234236
}
235237

238+
/**
239+
* Add support for direct recursive types (e.g., A -> A).
240+
* This does *not* support mutually recursive types (e.g., A -> B -> A), as this is a much more complex beast in OCaml (since mutually recursive types must live in the same file).
241+
*/
242+
@Override
243+
public ModelsMap postProcessModels(ModelsMap objs) {
244+
objs = super.postProcessModels(objs);
245+
246+
for (ModelMap mo : objs.getModels()) {
247+
CodegenModel cm = mo.getModel();
248+
249+
// Check if any property is a self-reference
250+
boolean hasSelfRef = cm.allVars.stream()
251+
.anyMatch(prop -> prop.isSelfReference);
252+
253+
if (hasSelfRef) {
254+
// Add vendor extension for template
255+
cm.getVendorExtensions().put("x-ocaml-has-self-reference", true);
256+
257+
// Collect names of self-referencing properties
258+
Set<String> selfRefPropNames = cm.allVars.stream()
259+
.filter(p -> p.isSelfReference)
260+
.map(p -> p.name)
261+
.collect(Collectors.toSet());
262+
263+
// The property lists (vars, allVars, etc.) contain DIFFERENT objects
264+
// Match by name since isSelfReference might only be set in allVars
265+
List<List<CodegenProperty>> allPropertyLists = Arrays.asList(
266+
cm.allVars, cm.vars, cm.requiredVars, cm.optionalVars,
267+
cm.readOnlyVars, cm.readWriteVars, cm.parentVars
268+
);
269+
270+
for (List<CodegenProperty> propList : allPropertyLists) {
271+
for (CodegenProperty prop : propList) {
272+
if (selfRefPropNames.contains(prop.name)) {
273+
// Replace "ModelName.t" with just "t" in all relevant fields
274+
prop.dataType = "t";
275+
prop.datatypeWithEnum = "t";
276+
if (prop.baseType != null) {
277+
prop.baseType = "t";
278+
}
279+
if (prop.complexType != null) {
280+
prop.complexType = "t";
281+
}
282+
283+
// If it's a container type (e.g., array), update items as well
284+
if (prop.isContainer && prop.items != null) {
285+
prop.items.dataType = "t";
286+
prop.items.datatypeWithEnum = "t";
287+
if (prop.items.baseType != null) {
288+
prop.items.baseType = "t";
289+
}
290+
if (prop.items.complexType != null) {
291+
prop.items.complexType = "t";
292+
}
293+
}
294+
}
295+
}
296+
}
297+
}
298+
}
299+
300+
return objs;
301+
}
302+
236303
private void enrichPropertiesWithEnumDefaultValues(List<CodegenProperty> properties) {
237304
for (CodegenProperty property : properties) {
238305
if (property.get_enum() != null && property.get_enum().size() == 1) {

modules/openapi-generator/src/main/resources/ocaml/model-record.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type t = {
1+
type {{#vendorExtensions.x-ocaml-has-self-reference}}rec {{/vendorExtensions.x-ocaml-has-self-reference}}t = {
22
{{#vars}}
33
{{#isEnum}}
44
{{{name}}}: {{^isMap}}Enums.{{/isMap}}{{{datatypeWithEnum}}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
version=0.27.0
2+
ocaml-version=4.14.0
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.ocamlformat
2+
README.md
3+
dune
4+
dune-project
5+
recursion_test.opam
6+
src/apis/default_api.ml
7+
src/apis/default_api.mli
8+
src/models/bar.ml
9+
src/models/baz.ml
10+
src/models/foo.ml
11+
src/support/enums.ml
12+
src/support/jsonSupport.ml
13+
src/support/request.ml
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.21.0-SNAPSHOT

0 commit comments

Comments
 (0)