Skip to content

Commit

Permalink
Add F# Functions server generator (OpenAPITools#3933)
Browse files Browse the repository at this point in the history
* cherry pick F# Functions generator

test fix

fix template paths

replace giraffe sample

* update doc
  • Loading branch information
nmfisher authored and Jesse Michael committed Oct 3, 2019
1 parent 1294b72 commit 8634403
Show file tree
Hide file tree
Showing 52 changed files with 2,825 additions and 122 deletions.
31 changes: 31 additions & 0 deletions bin/fsharp-functions-server-petstore.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh

SCRIPT="$0"

while [ -h "$SCRIPT" ] ; do
ls=$(ls -ld "$SCRIPT")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=$(dirname "$SCRIPT")/"$link"
fi
done

if [ ! -d "${APP_DIR}" ]; then
APP_DIR=$(dirname "$SCRIPT")/..
APP_DIR=$(cd "${APP_DIR}"; pwd)
fi

executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"

if [ ! -f "$executable" ]
then
mvn clean package
fi

# if you've executed sbt assembly previously it will use that instead.
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g fsharp-functions -o samples/server/petstore/fsharp-functions"

java ${JAVA_OPTS} -jar ${executable} ${ags}
10 changes: 10 additions & 0 deletions bin/windows/fsharp-functions-server-petstore.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar

If Not Exist %executable% (
mvn clean package
)

REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M -DloggerPath=conf/log4j.properties
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

java %JAVA_OPTS% -jar %executable% %ags%
1 change: 1 addition & 0 deletions docs/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The following generators are available:
* [cpp-restbed-server](generators/cpp-restbed-server)
* [csharp-nancyfx](generators/csharp-nancyfx)
* [erlang-server](generators/erlang-server)
* [fsharp-functions](generators/fsharp-functions)
* [fsharp-giraffe-server](generators/fsharp-giraffe-server)
* [go-gin-server](generators/go-gin-server)
* [go-server](generators/go-server)
Expand Down
22 changes: 22 additions & 0 deletions docs/generators/fsharp-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

---
id: generator-opts-server-fsharp-functions
title: Config Options for fsharp-functions
sidebar_label: fsharp-functions
---

| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|licenseUrl|The URL of the license| |http://localhost|
|licenseName|The name of the license| |NoLicense|
|packageCopyright|Specifies an AssemblyCopyright for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |No Copyright|
|packageAuthors|Specifies Authors property in the .NET Core project file.| |OpenAPI|
|packageTitle|Specifies an AssemblyTitle for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |OpenAPI Library|
|packageName|F# module name (convention: Title.Case).| |OpenAPI|
|packageVersion|F# package version.| |1.0.0|
|packageGuid|The GUID that will be associated with the C# project| |null|
|sourceFolder|source folder for generated code| |OpenAPI/src|
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.Exception;

import java.io.File;
import java.util.*;

import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;

public abstract class AbstractFSharpCodegen extends DefaultCodegen implements CodegenConfig {

Expand Down Expand Up @@ -246,11 +248,6 @@ public void processOpts() {
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
}

if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
LOGGER.warn(String.format(Locale.ROOT, "%s is not used by F# generators. Please use %s",
CodegenConstants.INVOKER_PACKAGE, CodegenConstants.PACKAGE_NAME));
}

// {{packageTitle}}
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_TITLE)) {
setPackageTitle((String) additionalProperties.get(CodegenConstants.PACKAGE_TITLE));
Expand Down Expand Up @@ -300,32 +297,8 @@ public void processOpts() {
additionalProperties.put(CodegenConstants.USE_DATETIME_OFFSET, useDateTimeOffsetFlag);
}

if (additionalProperties.containsKey(CodegenConstants.USE_COLLECTION)) {
setUseCollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.USE_COLLECTION));
} else {
additionalProperties.put(CodegenConstants.USE_COLLECTION, useCollection);
}

if (additionalProperties.containsKey(CodegenConstants.RETURN_ICOLLECTION)) {
setReturnICollection(convertPropertyToBooleanAndWriteBack(CodegenConstants.RETURN_ICOLLECTION));
} else {
additionalProperties.put(CodegenConstants.RETURN_ICOLLECTION, returnICollection);
}

if (additionalProperties.containsKey(CodegenConstants.NETCORE_PROJECT_FILE)) {
setNetCoreProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.NETCORE_PROJECT_FILE));
} else {
additionalProperties.put(CodegenConstants.NETCORE_PROJECT_FILE, netCoreProjectFileFlag);
}

if (additionalProperties.containsKey(CodegenConstants.INTERFACE_PREFIX)) {
String useInterfacePrefix = additionalProperties.get(CodegenConstants.INTERFACE_PREFIX).toString();
if ("false".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
setInterfacePrefix("");
} else if (!"true".equals(useInterfacePrefix.toLowerCase(Locale.ROOT))) {
// NOTE: if user passes "true" explicitly, we use the default I- prefix. The other supported case here is a custom prefix.
setInterfacePrefix(sanitizeName(useInterfacePrefix));
}
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}

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

/*
* F# does not allow forward declarations, so files must be imported in the correct order.
* Output of CodeGen models must therefore bein dependency order (rather than alphabetical order, which seems to be the default).
* We achieve this by creating a comparator to check whether the first model contains any properties of the comparison model's type
* This could probably be made more efficient if absolutely needed.
*/
* F# does not allow forward declarations, so files must be imported in the correct order.
* Output of CodeGen models must therefore bein dependency order (rather than alphabetical order, which seems to be the default).
* This could probably be made more efficient if absolutely needed.
*/
@SuppressWarnings({"unchecked"})
public Map<String, Object> postProcessDependencyOrders(final Map<String, Object> objs) {
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String key1, String key2) {
// Get the corresponding models
CodegenModel model1 = ModelUtils.getModelByName(key1, objs);
CodegenModel model2 = ModelUtils.getModelByName(key2, objs);

List<String> complexVars1 = new ArrayList<String>();
List<String> complexVars2 = new ArrayList<String>();

for (CodegenProperty prop : model1.vars) {
if (prop.complexType != null)
complexVars1.add(prop.complexType);
}
for (CodegenProperty prop : model2.vars) {
if (prop.complexType != null)
complexVars2.add(prop.complexType);
}

// if first has complex vars and second has none, first is greater
if (complexVars1.size() > 0 && complexVars2.size() == 0)
return 1;

// if second has complex vars and first has none, first is lesser
if (complexVars1.size() == 0 && complexVars2.size() > 0)
return -1;

// if first has complex var that matches the second's key, first is greater
if (complexVars1.contains(key2))
return 1;

// if second has complex var that matches the first's key, first is lesser
if (complexVars2.contains(key1))
return -1;

// if none of the above, don't care
return 0;

}
};
PriorityQueue<String> queue = new PriorityQueue<String>(objs.size(), comparator);
for (Object k : objs.keySet()) {
queue.add(k.toString());
public Map<String,Object> postProcessDependencyOrders(final Map<String, Object> objs) {

Map<String,Set<String>> dependencies = new HashMap<String,Set<String>>();

List<String> classNames = new ArrayList<String>();

for(String k : objs.keySet()) {
CodegenModel model = ModelUtils.getModelByName(k, objs);
if(model == null || model.classname == null) {
throw new RuntimeException("Null model encountered");
}

Map<String, Object> sorted = new LinkedHashMap<String, Object>();

while (queue.size() > 0) {
String key = queue.poll();
sorted.put(key, objs.get(key));
dependencies.put(model.classname, model.imports);

classNames.add(model.classname);
}

Object[] sortedKeys = classNames.toArray();

for(int i1 = 0 ; i1 < sortedKeys.length; i1++) {
String k1 = sortedKeys[i1].toString();
for(int i2 = i1 + 1; i2 < sortedKeys.length; i2++) {
String k2 = sortedKeys[i2].toString();
if(dependencies.get(k2).contains(k1)) {
sortedKeys[i2] = k1;
sortedKeys[i1] = k2;
i1 = -1;
break;
}
}
return sorted;
}

Map<String,Object> sorted = new LinkedHashMap<String,Object>();
for(int i = sortedKeys.length - 1; i >= 0; i--) {
Object k = sortedKeys[i];
sorted.put(k.toString(), objs.get(k));
}

return sorted;
}

/**
Expand Down Expand Up @@ -684,6 +641,39 @@ public String toOperationId(String operationId) {
return camelize(sanitizeName(operationId));
}

public String getModelPropertyNaming() {
return this.modelPropertyNaming;
}

public void setModelPropertyNaming(String naming) {
if ("original".equals(naming) || "camelCase".equals(naming) ||
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
this.modelPropertyNaming = naming;
} else {
throw new IllegalArgumentException("Invalid model property naming '" +
naming + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}


public String getNameUsingModelPropertyNaming(String name) {
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
case original:
return name;
case camelCase:
return camelize(name, true);
case PascalCase:
return camelize(name);
case snake_case:
return underscore(name);
default:
throw new IllegalArgumentException("Invalid model property naming '" +
name + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}

@Override
public String toVarName(String name) {
// sanitize name
Expand All @@ -694,9 +684,8 @@ public String toVarName(String name) {
return name;
}

// camelize the variable name
// pet_id => PetId
name = camelize(name);
name = getNameUsingModelPropertyNaming(name);

// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
Expand Down
Loading

0 comments on commit 8634403

Please sign in to comment.