Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add F# Functions server generator #3933

Merged
merged 2 commits into from
Sep 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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