Skip to content

Commit 8408232

Browse files
nmfisherwing328
authored andcommitted
Add F# Functions server generator (#3933)
* cherry pick F# Functions generator test fix fix template paths replace giraffe sample * update doc
1 parent 252c3e5 commit 8408232

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2825
-122
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/sh
2+
3+
SCRIPT="$0"
4+
5+
while [ -h "$SCRIPT" ] ; do
6+
ls=$(ls -ld "$SCRIPT")
7+
link=$(expr "$ls" : '.*-> \(.*\)$')
8+
if expr "$link" : '/.*' > /dev/null; then
9+
SCRIPT="$link"
10+
else
11+
SCRIPT=$(dirname "$SCRIPT")/"$link"
12+
fi
13+
done
14+
15+
if [ ! -d "${APP_DIR}" ]; then
16+
APP_DIR=$(dirname "$SCRIPT")/..
17+
APP_DIR=$(cd "${APP_DIR}"; pwd)
18+
fi
19+
20+
executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
21+
22+
if [ ! -f "$executable" ]
23+
then
24+
mvn clean package
25+
fi
26+
27+
# if you've executed sbt assembly previously it will use that instead.
28+
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
29+
ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g fsharp-functions -o samples/server/petstore/fsharp-functions"
30+
31+
java ${JAVA_OPTS} -jar ${executable} ${ags}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
2+
3+
If Not Exist %executable% (
4+
mvn clean package
5+
)
6+
7+
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
8+
set ags=generate --artifact-id "fsharp-functions-petstore-server" -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g fsharp-functions -o samples\server\petstore\fsharp-functions
9+
10+
java %JAVA_OPTS% -jar %executable% %ags%

docs/generators.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ The following generators are available:
7575
* [cpp-restbed-server](generators/cpp-restbed-server)
7676
* [csharp-nancyfx](generators/csharp-nancyfx)
7777
* [erlang-server](generators/erlang-server)
78+
* [fsharp-functions](generators/fsharp-functions)
7879
* [fsharp-giraffe-server](generators/fsharp-giraffe-server)
7980
* [go-gin-server](generators/go-gin-server)
8081
* [go-server](generators/go-server)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
---
3+
id: generator-opts-server-fsharp-functions
4+
title: Config Options for fsharp-functions
5+
sidebar_label: fsharp-functions
6+
---
7+
8+
| Option | Description | Values | Default |
9+
| ------ | ----------- | ------ | ------- |
10+
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
11+
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
12+
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
13+
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
14+
|licenseUrl|The URL of the license| |http://localhost|
15+
|licenseName|The name of the license| |NoLicense|
16+
|packageCopyright|Specifies an AssemblyCopyright for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |No Copyright|
17+
|packageAuthors|Specifies Authors property in the .NET Core project file.| |OpenAPI|
18+
|packageTitle|Specifies an AssemblyTitle for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |OpenAPI Library|
19+
|packageName|F# module name (convention: Title.Case).| |OpenAPI|
20+
|packageVersion|F# package version.| |1.0.0|
21+
|packageGuid|The GUID that will be associated with the C# project| |null|
22+
|sourceFolder|source folder for generated code| |OpenAPI/src|

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

Lines changed: 79 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@
2929
import org.openapitools.codegen.utils.ModelUtils;
3030
import org.slf4j.Logger;
3131
import org.slf4j.LoggerFactory;
32+
import java.lang.Exception;
3233

3334
import java.io.File;
3435
import java.util.*;
3536

3637
import static org.openapitools.codegen.utils.StringUtils.camelize;
38+
import static org.openapitools.codegen.utils.StringUtils.underscore;
3739

3840
public abstract class AbstractFSharpCodegen extends DefaultCodegen implements CodegenConfig {
3941

@@ -246,11 +248,6 @@ public void processOpts() {
246248
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
247249
}
248250

249-
if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
250-
LOGGER.warn(String.format(Locale.ROOT, "%s is not used by F# generators. Please use %s",
251-
CodegenConstants.INVOKER_PACKAGE, CodegenConstants.PACKAGE_NAME));
252-
}
253-
254251
// {{packageTitle}}
255252
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_TITLE)) {
256253
setPackageTitle((String) additionalProperties.get(CodegenConstants.PACKAGE_TITLE));
@@ -300,32 +297,8 @@ public void processOpts() {
300297
additionalProperties.put(CodegenConstants.USE_DATETIME_OFFSET, useDateTimeOffsetFlag);
301298
}
302299

303-
if (additionalProperties.containsKey(CodegenConstants.USE_COLLECTION)) {
304-
setUseCollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_COLLECTION));
305-
} else {
306-
additionalProperties.put(CodegenConstants.USE_COLLECTION, useCollection);
307-
}
308-
309-
if (additionalProperties.containsKey(CodegenConstants.RETURN_ICOLLECTION)) {
310-
setReturnICollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.RETURN_ICOLLECTION));
311-
} else {
312-
additionalProperties.put(CodegenConstants.RETURN_ICOLLECTION, returnICollection);
313-
}
314-
315-
if (additionalProperties.containsKey(CodegenConstants.NETCORE_PROJECT_FILE)) {
316-
setNetCoreProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.NETCORE_PROJECT_FILE));
317-
} else {
318-
additionalProperties.put(CodegenConstants.NETCORE_PROJECT_FILE, netCoreProjectFileFlag);
319-
}
320-
321-
if (additionalProperties.containsKey(CodegenConstants.INTERFACE_PREFIX)) {
322-
String useInterfacePrefix = additionalProperties.get(CodegenConstants.INTERFACE_PREFIX).toString();
323-
if ("false".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
324-
setInterfacePrefix("");
325-
} else if (!"true".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
326-
// NOTE: if user passes "true" explicitly, we use the default I- prefix. The other supported case here is a custom prefix.
327-
setInterfacePrefix(sanitizeName(useInterfacePrefix));
328-
}
300+
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
301+
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
329302
}
330303

331304
// This either updates additionalProperties with the above fixes, or sets the default if the option was not specified.
@@ -372,65 +345,49 @@ public Map<String, Object> postProcessAllModels(Map<String, Object> objs) {
372345
}
373346

374347
/*
375-
* F# does not allow forward declarations, so files must be imported in the correct order.
376-
* Output of CodeGen models must therefore bein dependency order (rather than alphabetical order, which seems to be the default).
377-
* We achieve this by creating a comparator to check whether the first model contains any properties of the comparison model's type
378-
* This could probably be made more efficient if absolutely needed.
379-
*/
348+
* F# does not allow forward declarations, so files must be imported in the correct order.
349+
* Output of CodeGen models must therefore bein dependency order (rather than alphabetical order, which seems to be the default).
350+
* This could probably be made more efficient if absolutely needed.
351+
*/
380352
@SuppressWarnings({"unchecked"})
381-
public Map<String, Object> postProcessDependencyOrders(final Map<String, Object> objs) {
382-
Comparator<String> comparator = new Comparator<String>() {
383-
@Override
384-
public int compare(String key1, String key2) {
385-
// Get the corresponding models
386-
CodegenModel model1 = ModelUtils.getModelByName(key1, objs);
387-
CodegenModel model2 = ModelUtils.getModelByName(key2, objs);
388-
389-
List<String> complexVars1 = new ArrayList<String>();
390-
List<String> complexVars2 = new ArrayList<String>();
391-
392-
for (CodegenProperty prop : model1.vars) {
393-
if (prop.complexType != null)
394-
complexVars1.add(prop.complexType);
395-
}
396-
for (CodegenProperty prop : model2.vars) {
397-
if (prop.complexType != null)
398-
complexVars2.add(prop.complexType);
399-
}
400-
401-
// if first has complex vars and second has none, first is greater
402-
if (complexVars1.size() > 0 && complexVars2.size() == 0)
403-
return 1;
404-
405-
// if second has complex vars and first has none, first is lesser
406-
if (complexVars1.size() == 0 && complexVars2.size() > 0)
407-
return -1;
408-
409-
// if first has complex var that matches the second's key, first is greater
410-
if (complexVars1.contains(key2))
411-
return 1;
412-
413-
// if second has complex var that matches the first's key, first is lesser
414-
if (complexVars2.contains(key1))
415-
return -1;
416-
417-
// if none of the above, don't care
418-
return 0;
419-
420-
}
421-
};
422-
PriorityQueue<String> queue = new PriorityQueue<String>(objs.size(), comparator);
423-
for (Object k : objs.keySet()) {
424-
queue.add(k.toString());
353+
public Map<String,Object> postProcessDependencyOrders(final Map<String, Object> objs) {
354+
355+
Map<String,Set<String>> dependencies = new HashMap<String,Set<String>>();
356+
357+
List<String> classNames = new ArrayList<String>();
358+
359+
for(String k : objs.keySet()) {
360+
CodegenModel model = ModelUtils.getModelByName(k, objs);
361+
if(model == null || model.classname == null) {
362+
throw new RuntimeException("Null model encountered");
425363
}
426-
427-
Map<String, Object> sorted = new LinkedHashMap<String, Object>();
428-
429-
while (queue.size() > 0) {
430-
String key = queue.poll();
431-
sorted.put(key, objs.get(key));
364+
dependencies.put(model.classname, model.imports);
365+
366+
classNames.add(model.classname);
367+
}
368+
369+
Object[] sortedKeys = classNames.toArray();
370+
371+
for(int i1 = 0 ; i1 < sortedKeys.length; i1++) {
372+
String k1 = sortedKeys[i1].toString();
373+
for(int i2 = i1 + 1; i2 < sortedKeys.length; i2++) {
374+
String k2 = sortedKeys[i2].toString();
375+
if(dependencies.get(k2).contains(k1)) {
376+
sortedKeys[i2] = k1;
377+
sortedKeys[i1] = k2;
378+
i1 = -1;
379+
break;
380+
}
432381
}
433-
return sorted;
382+
}
383+
384+
Map<String,Object> sorted = new LinkedHashMap<String,Object>();
385+
for(int i = sortedKeys.length - 1; i >= 0; i--) {
386+
Object k = sortedKeys[i];
387+
sorted.put(k.toString(), objs.get(k));
388+
}
389+
390+
return sorted;
434391
}
435392

436393
/**
@@ -684,6 +641,39 @@ public String toOperationId(String operationId) {
684641
return camelize(sanitizeName(operationId));
685642
}
686643

644+
public String getModelPropertyNaming() {
645+
return this.modelPropertyNaming;
646+
}
647+
648+
public void setModelPropertyNaming(String naming) {
649+
if ("original".equals(naming) || "camelCase".equals(naming) ||
650+
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
651+
this.modelPropertyNaming = naming;
652+
} else {
653+
throw new IllegalArgumentException("Invalid model property naming '" +
654+
naming + "'. Must be 'original', 'camelCase', " +
655+
"'PascalCase' or 'snake_case'");
656+
}
657+
}
658+
659+
660+
public String getNameUsingModelPropertyNaming(String name) {
661+
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
662+
case original:
663+
return name;
664+
case camelCase:
665+
return camelize(name, true);
666+
case PascalCase:
667+
return camelize(name);
668+
case snake_case:
669+
return underscore(name);
670+
default:
671+
throw new IllegalArgumentException("Invalid model property naming '" +
672+
name + "'. Must be 'original', 'camelCase', " +
673+
"'PascalCase' or 'snake_case'");
674+
}
675+
}
676+
687677
@Override
688678
public String toVarName(String name) {
689679
// sanitize name
@@ -694,9 +684,8 @@ public String toVarName(String name) {
694684
return name;
695685
}
696686

697-
// camelize the variable name
698-
// pet_id => PetId
699-
name = camelize(name);
687+
name = getNameUsingModelPropertyNaming(name);
688+
700689
// for reserved word or word starting with number, append _
701690
if (isReservedWord(name) || name.matches("^\\d.*")) {
702691
name = escapeReservedWord(name);

0 commit comments

Comments
 (0)