mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-04 14:40:53 +00:00
Generator erlang-proper (#1102)
* Generator erlang-proper Used to generate PropEr generators for property-based testing * Fix binary/2 implementation. Add behaviour attribute. * Remove line from copyright notice * Avoid escpaing HTML and remove suffix from variable name * Update samples * Include querystring parameters * We use export_all, don't consider warnings as errors * List command sequence on failure * Use hasConsumes instead * Add nowarn_export_all, re-add warning_as_errors
This commit is contained in:
parent
a6b0a8b4b7
commit
fc0a0d2cda
32
bin/erlang-petstore-proper.sh
Executable file
32
bin/erlang-petstore-proper.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
SCRIPT="$0"
|
||||||
|
echo "# START SCRIPT: $SCRIPT"
|
||||||
|
|
||||||
|
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 -B 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 -t modules/openapi-generator/src/main/resources/erlang-proper -DpackageName=petstore -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -g erlang-proper -o samples/client/petstore/erlang-proper $@"
|
||||||
|
|
||||||
|
java $JAVA_OPTS -jar $executable $ags
|
@ -0,0 +1,542 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openapitools.codegen.languages;
|
||||||
|
|
||||||
|
import com.samskivert.mustache.Mustache;
|
||||||
|
import com.samskivert.mustache.Template;
|
||||||
|
import io.swagger.v3.oas.models.media.ArraySchema;
|
||||||
|
import io.swagger.v3.oas.models.media.Schema;
|
||||||
|
import org.openapitools.codegen.*;
|
||||||
|
import org.openapitools.codegen.utils.ModelUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class ErlangProperCodegen extends DefaultCodegen implements CodegenConfig {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ErlangProperCodegen.class);
|
||||||
|
|
||||||
|
protected String packageName = "openapi";
|
||||||
|
protected String packageVersion = "1.0.0";
|
||||||
|
protected String sourceFolder = "src";
|
||||||
|
protected String modelFolder = "model";
|
||||||
|
|
||||||
|
public CodegenType getTag() {
|
||||||
|
return CodegenType.CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return "erlang-proper";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHelp() {
|
||||||
|
return "Generates an Erlang library with PropEr generators (beta).";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ErlangProperCodegen() {
|
||||||
|
super();
|
||||||
|
outputFolder = "generated-code/erlang";
|
||||||
|
modelTemplateFiles.put("model.mustache", ".erl");
|
||||||
|
apiTemplateFiles.put("api.mustache", "_api.erl");
|
||||||
|
apiTemplateFiles.put("statem.mustache", "_statem.erl");
|
||||||
|
|
||||||
|
embeddedTemplateDir = templateDir = "erlang-proper";
|
||||||
|
|
||||||
|
setReservedWordsLowerCase(
|
||||||
|
Arrays.asList(
|
||||||
|
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
|
||||||
|
"catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive",
|
||||||
|
"rem", "try", "when", "xor"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
instantiationTypes.clear();
|
||||||
|
|
||||||
|
typeMapping.clear();
|
||||||
|
typeMapping.put("enum", "binary()");
|
||||||
|
typeMapping.put("date", "date()");
|
||||||
|
typeMapping.put("datetime", "datetime()");
|
||||||
|
typeMapping.put("DateTime", "datetime()");
|
||||||
|
typeMapping.put("boolean", "boolean()");
|
||||||
|
typeMapping.put("string", "binary()");
|
||||||
|
typeMapping.put("integer", "integer()");
|
||||||
|
typeMapping.put("int", "integer()");
|
||||||
|
typeMapping.put("float", "integer()");
|
||||||
|
typeMapping.put("long", "integer()");
|
||||||
|
typeMapping.put("double", "float()");
|
||||||
|
typeMapping.put("array", "list()");
|
||||||
|
typeMapping.put("map", "map()");
|
||||||
|
typeMapping.put("number", "integer()");
|
||||||
|
typeMapping.put("bigdecimal", "float()");
|
||||||
|
typeMapping.put("List", "list()");
|
||||||
|
typeMapping.put("object", "map()");
|
||||||
|
typeMapping.put("file", "binary()");
|
||||||
|
typeMapping.put("binary", "binary()");
|
||||||
|
typeMapping.put("bytearray", "binary()");
|
||||||
|
typeMapping.put("byte", "binary()");
|
||||||
|
typeMapping.put("uuid", "binary()");
|
||||||
|
typeMapping.put("password", "binary()");
|
||||||
|
|
||||||
|
cliOptions.clear();
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang application name (convention: lowercase).")
|
||||||
|
.defaultValue(this.packageName));
|
||||||
|
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang application version")
|
||||||
|
.defaultValue(this.packageVersion));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CodegenModel fromModel(String name, Schema model, Map<String, Schema> allDefinitions) {
|
||||||
|
CodegenModel cm = super.fromModel(name, model, allDefinitions);
|
||||||
|
if(ModelUtils.isArraySchema(model)) {
|
||||||
|
return new CodegenArrayModel(cm, (ArraySchema) model);
|
||||||
|
} else {
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeDeclaration(String name) {
|
||||||
|
return name + ":" + name + "()";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeDeclaration(Schema schema) {
|
||||||
|
String typeDeclaration = super.getSchemaType(schema);
|
||||||
|
if(ModelUtils.isArraySchema(schema)) {
|
||||||
|
ArraySchema arraySchema = (ArraySchema) schema;
|
||||||
|
String complexType = getSchemaType(arraySchema.getItems());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("list(");
|
||||||
|
sb.append(complexType);
|
||||||
|
|
||||||
|
return sb.append(")").toString();
|
||||||
|
} else if (typeMapping.containsKey(typeDeclaration)) {
|
||||||
|
return typeMapping.get(typeDeclaration);
|
||||||
|
} else {
|
||||||
|
return getTypeDeclaration(toModelName(snakeCase(typeDeclaration)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSchemaType(Schema schema) {
|
||||||
|
String schemaType = super.getSchemaType(schema);
|
||||||
|
if(ModelUtils.isArraySchema(schema)) {
|
||||||
|
ArraySchema arraySchema = (ArraySchema) schema;
|
||||||
|
String complexType = getSchemaType(arraySchema.getItems());
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("list(");
|
||||||
|
sb.append(complexType);
|
||||||
|
|
||||||
|
Integer minItems = schema.getMinItems();
|
||||||
|
Integer maxItems = schema.getMaxItems();
|
||||||
|
if(minItems != null) sb.append(", ").append(minItems);
|
||||||
|
if(minItems != null && maxItems != null) sb.append(", ").append(maxItems);
|
||||||
|
|
||||||
|
return sb.append(")").toString();
|
||||||
|
} else if(ModelUtils.isIntegerSchema(schema)) {
|
||||||
|
StringBuilder sb = new StringBuilder("integer(");
|
||||||
|
|
||||||
|
BigDecimal min = schema.getMinimum();
|
||||||
|
BigDecimal max = schema.getMaximum();
|
||||||
|
if(min != null) sb.append(min);
|
||||||
|
if(min != null && max != null) sb.append(", ").append(max);
|
||||||
|
|
||||||
|
return sb.append(")").toString();
|
||||||
|
} else if(ModelUtils.isDateSchema(schema) || ModelUtils.isDateTimeSchema(schema)) {
|
||||||
|
return typeMapping.get(schemaType);
|
||||||
|
} else if(ModelUtils.isStringSchema(schema)) {
|
||||||
|
StringBuilder sb = new StringBuilder("binary(");
|
||||||
|
Integer min = schema.getMinLength();
|
||||||
|
Integer max = schema.getMaxLength();
|
||||||
|
if(min != null) sb.append(min);
|
||||||
|
if(min != null && max != null) sb.append(", ").append(max);
|
||||||
|
|
||||||
|
return sb.append(")").toString();
|
||||||
|
} else if (typeMapping.containsKey(schemaType)) {
|
||||||
|
return typeMapping.get(schemaType);
|
||||||
|
} else {
|
||||||
|
return getTypeDeclaration(toModelName(snakeCase(schemaType)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processOpts() {
|
||||||
|
super.processOpts();
|
||||||
|
|
||||||
|
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
|
||||||
|
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
|
||||||
|
} else {
|
||||||
|
setPackageName("openapi");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
|
||||||
|
setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
|
||||||
|
} else {
|
||||||
|
setPackageVersion("1.0.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
|
||||||
|
additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
|
||||||
|
|
||||||
|
additionalProperties.put("length", new Mustache.Lambda() {
|
||||||
|
@Override
|
||||||
|
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||||
|
writer.write(length(fragment.context()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
additionalProperties.put("qsEncode", new Mustache.Lambda() {
|
||||||
|
@Override
|
||||||
|
public void execute(Template.Fragment fragment, Writer writer) throws IOException {
|
||||||
|
writer.write(qsEncode(fragment.context()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modelPackage = packageName;
|
||||||
|
apiPackage = packageName;
|
||||||
|
|
||||||
|
supportingFiles.add(new SupportingFile("rebar.config.mustache", "", "rebar.config"));
|
||||||
|
supportingFiles.add(new SupportingFile("app.src.mustache", "", "src" + File.separator +
|
||||||
|
this.packageName + ".app.src"));
|
||||||
|
supportingFiles.add(new SupportingFile("utils.mustache", "", "src" + File.separator +
|
||||||
|
this.packageName + "_utils.erl"));
|
||||||
|
supportingFiles.add(new SupportingFile("gen.mustache", "", "src" + File.separator + this
|
||||||
|
.packageName + "_gen.erl"));
|
||||||
|
supportingFiles.add(new SupportingFile("include.mustache", "", "src" + File.separator +
|
||||||
|
this.packageName + ".hrl"));
|
||||||
|
supportingFiles.add(new SupportingFile("statem.hrl.mustache", "", "src" + File.separator +
|
||||||
|
this.packageName + "_statem.hrl"));
|
||||||
|
supportingFiles.add(new SupportingFile("test.mustache", "", "test" + File.separator +
|
||||||
|
"prop_" + this.packageName + ".erl"));
|
||||||
|
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String qsEncode(Object o) {
|
||||||
|
String r = "";
|
||||||
|
CodegenParameter q = (CodegenParameter) o;
|
||||||
|
if (q.required) {
|
||||||
|
if (q.isListContainer) {
|
||||||
|
r += "[{<<\"" + q.baseName + "\">>, X} || X <- " + q.paramName + "]";
|
||||||
|
} else {
|
||||||
|
r += "{<<\"" + q.baseName + "\">>, " + q.paramName + "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeReservedWord(String name) {
|
||||||
|
// Can't start with an underscore, as our fields need to start with an
|
||||||
|
// UppercaseLetter so that Go treats them as public/visible.
|
||||||
|
|
||||||
|
// Options?
|
||||||
|
// - MyName
|
||||||
|
// - AName
|
||||||
|
// - TheName
|
||||||
|
// - XName
|
||||||
|
// - X_Name
|
||||||
|
// ... or maybe a suffix?
|
||||||
|
// - Name_ ... think this will work.
|
||||||
|
if (this.reservedWordsMappings().containsKey(name)) {
|
||||||
|
return this.reservedWordsMappings().get(name);
|
||||||
|
}
|
||||||
|
return camelize(name) + '_';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String apiFileFolder() {
|
||||||
|
return outputFolder + File.separator + sourceFolder + File.separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String modelFileFolder() {
|
||||||
|
return outputFolder + File.separator
|
||||||
|
+ sourceFolder + File.separator
|
||||||
|
+ modelFolder + File.separator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toVarName(String name) {
|
||||||
|
// replace - with _ e.g. created-at => created_at
|
||||||
|
name = sanitizeName(name.replaceAll("-", "_"));
|
||||||
|
// for reserved word or word starting with number, append _
|
||||||
|
if (isReservedWord(name))
|
||||||
|
name = escapeReservedWord(name);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toParamName(String name) {
|
||||||
|
return camelize(toVarName(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toArrayModelParamName(String name) {
|
||||||
|
if (name == null) {
|
||||||
|
LOGGER.warn("parameter name for array model is null. Default to 'array_model'.");
|
||||||
|
name = "array_model";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.indexOf(":") > 0) {
|
||||||
|
name = name.substring(0, name.indexOf(":")) + "_array";
|
||||||
|
}
|
||||||
|
|
||||||
|
return toParamName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toModelName(String name) {
|
||||||
|
return this.packageName + "_" + underscore(name.replaceAll("-", "_").replaceAll("\\.", "_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toApiName(String name) {
|
||||||
|
return this.packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toModelFilename(String name) {
|
||||||
|
return this.packageName + "_" + underscore(name.replaceAll("\\.", "_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toApiFilename(String name) {
|
||||||
|
return toApiName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toOperationId(String operationId) {
|
||||||
|
// method name cannot use reserved keyword, e.g. return
|
||||||
|
if (isReservedWord(operationId)) {
|
||||||
|
LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId)).replaceAll("\\.", "_"));
|
||||||
|
operationId = "call_" + operationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return underscore(operationId.replaceAll("\\.", "_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
|
||||||
|
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
|
||||||
|
List<CodegenOperation> os = (List<CodegenOperation>) operations.get("operation");
|
||||||
|
List<ExtendedCodegenOperation> newOs = new ArrayList<>();
|
||||||
|
Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}");
|
||||||
|
for (CodegenOperation o : os) {
|
||||||
|
// force http method to lower case
|
||||||
|
o.httpMethod = o.httpMethod.toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
|
if (o.isListContainer) {
|
||||||
|
o.returnType = "[" + o.returnBaseType + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
Matcher matcher = pattern.matcher(o.path);
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String pathTemplateName = matcher.group(1);
|
||||||
|
matcher.appendReplacement(buffer, "\", " + camelize(pathTemplateName) + ", \"");
|
||||||
|
}
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
|
||||||
|
ExtendedCodegenOperation eco = new ExtendedCodegenOperation(o);
|
||||||
|
if (buffer.length() == 0) {
|
||||||
|
eco.setReplacedPathName(o.path);
|
||||||
|
} else {
|
||||||
|
eco.setReplacedPathName(buffer.toString());
|
||||||
|
}
|
||||||
|
newOs.add(eco);
|
||||||
|
}
|
||||||
|
operations.put("operation", newOs);
|
||||||
|
return objs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageName(String packageName) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPackageVersion(String packageVersion) {
|
||||||
|
this.packageVersion = packageVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
String length(Object os) {
|
||||||
|
int l = 1;
|
||||||
|
for (CodegenParameter o : ((ExtendedCodegenOperation) os).allParams) {
|
||||||
|
if (o.required)
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.toString(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lengthRequired(List<CodegenParameter> allParams) {
|
||||||
|
int l = 0;
|
||||||
|
for (CodegenParameter o : allParams) {
|
||||||
|
if (o.required || o.isBodyParam)
|
||||||
|
l++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeQuotationMark(String input) {
|
||||||
|
// remove " to avoid code injection
|
||||||
|
return input.replace("\"", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String escapeUnsafeCharacters(String input) {
|
||||||
|
return input.replace("*/", "*_/").replace("/*", "/_*");
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodegenArrayModel extends CodegenModel {
|
||||||
|
Integer minItems;
|
||||||
|
Integer maxItems;
|
||||||
|
|
||||||
|
public CodegenArrayModel(CodegenModel cm, ArraySchema schema) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Copy all fields of CodegenModel
|
||||||
|
this.parent = cm.parent;
|
||||||
|
this.parentSchema = cm.parentSchema;
|
||||||
|
this.parentModel = cm.parentModel;
|
||||||
|
this.interfaceModels = cm.interfaceModels;
|
||||||
|
this.children = cm.children;
|
||||||
|
this.name = cm.name;
|
||||||
|
this.classname = cm.classname;
|
||||||
|
this.title = cm.title;
|
||||||
|
this.description = cm.description;
|
||||||
|
this.classVarName = cm.classVarName;
|
||||||
|
this.modelJson = cm.modelJson;
|
||||||
|
this.dataType = cm.dataType;
|
||||||
|
this.xmlPrefix = cm.xmlPrefix;
|
||||||
|
this.xmlNamespace = cm.xmlNamespace;
|
||||||
|
this.xmlName = cm.xmlName;
|
||||||
|
this.classFilename = cm.classFilename;
|
||||||
|
this.unescapedDescription = cm.unescapedDescription;
|
||||||
|
this.discriminator = cm.discriminator;
|
||||||
|
this.defaultValue = cm.defaultValue;
|
||||||
|
this.arrayModelType = cm.arrayModelType;
|
||||||
|
this.isAlias = cm.isAlias;
|
||||||
|
this.vars = cm.vars;
|
||||||
|
this.requiredVars = cm.requiredVars;
|
||||||
|
this.optionalVars = cm.optionalVars;
|
||||||
|
this.readOnlyVars = cm.readOnlyVars;
|
||||||
|
this.readWriteVars = cm.readWriteVars;
|
||||||
|
this.allVars = cm.allVars;
|
||||||
|
this.parentVars = cm.parentVars;
|
||||||
|
this.allowableValues = cm.allowableValues;
|
||||||
|
this.mandatory = cm.mandatory;
|
||||||
|
this.allMandatory = cm.allMandatory;
|
||||||
|
this.imports = cm.imports;
|
||||||
|
this.hasVars = cm.hasVars;
|
||||||
|
this.emptyVars = cm.emptyVars;
|
||||||
|
this.hasMoreModels = cm.hasMoreModels;
|
||||||
|
this.hasEnums = cm.hasEnums;
|
||||||
|
this.isEnum = cm.isEnum;
|
||||||
|
this.hasRequired = cm.hasRequired;
|
||||||
|
this.hasOptional = cm.hasOptional;
|
||||||
|
this.isArrayModel = cm.isArrayModel;
|
||||||
|
this.hasChildren = cm.hasChildren;
|
||||||
|
this.hasOnlyReadOnly = cm.hasOnlyReadOnly;
|
||||||
|
this.externalDocumentation = cm.externalDocumentation;
|
||||||
|
this.vendorExtensions = cm.vendorExtensions;
|
||||||
|
this.additionalPropertiesType = cm.additionalPropertiesType;
|
||||||
|
|
||||||
|
this.minItems = schema.getMinItems();
|
||||||
|
this.maxItems = schema.getMaxItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtendedCodegenOperation extends CodegenOperation {
|
||||||
|
private String replacedPathName;
|
||||||
|
String arity;
|
||||||
|
|
||||||
|
ExtendedCodegenOperation(CodegenOperation o) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// Copy all fields of CodegenOperation
|
||||||
|
this.responseHeaders.addAll(o.responseHeaders);
|
||||||
|
this.hasAuthMethods = o.hasAuthMethods;
|
||||||
|
this.hasConsumes = o.hasConsumes;
|
||||||
|
this.hasProduces = o.hasProduces;
|
||||||
|
this.hasParams = o.hasParams;
|
||||||
|
this.hasOptionalParams = o.hasOptionalParams;
|
||||||
|
this.returnTypeIsPrimitive = o.returnTypeIsPrimitive;
|
||||||
|
this.returnSimpleType = o.returnSimpleType;
|
||||||
|
this.subresourceOperation = o.subresourceOperation;
|
||||||
|
this.isMapContainer = o.isMapContainer;
|
||||||
|
this.isListContainer = o.isListContainer;
|
||||||
|
this.isMultipart = o.isMultipart;
|
||||||
|
this.hasMore = o.hasMore;
|
||||||
|
this.isResponseBinary = o.isResponseBinary;
|
||||||
|
this.hasReference = o.hasReference;
|
||||||
|
this.isRestfulIndex = o.isRestfulIndex;
|
||||||
|
this.isRestfulShow = o.isRestfulShow;
|
||||||
|
this.isRestfulCreate = o.isRestfulCreate;
|
||||||
|
this.isRestfulUpdate = o.isRestfulUpdate;
|
||||||
|
this.isRestfulDestroy = o.isRestfulDestroy;
|
||||||
|
this.isRestful = o.isRestful;
|
||||||
|
this.path = o.path;
|
||||||
|
this.operationId = o.operationId;
|
||||||
|
this.returnType = o.returnType;
|
||||||
|
this.httpMethod = o.httpMethod;
|
||||||
|
this.returnBaseType = o.returnBaseType;
|
||||||
|
this.returnContainer = o.returnContainer;
|
||||||
|
this.summary = o.summary;
|
||||||
|
this.unescapedNotes = o.unescapedNotes;
|
||||||
|
this.notes = o.notes;
|
||||||
|
this.baseName = o.baseName;
|
||||||
|
this.defaultResponse = o.defaultResponse;
|
||||||
|
this.discriminator = o.discriminator;
|
||||||
|
this.consumes = o.consumes;
|
||||||
|
this.produces = o.produces;
|
||||||
|
this.bodyParam = o.bodyParam;
|
||||||
|
this.allParams = o.allParams;
|
||||||
|
this.arity = Integer.toString(lengthRequired(o.allParams));
|
||||||
|
this.bodyParams = o.bodyParams;
|
||||||
|
this.pathParams = o.pathParams;
|
||||||
|
this.queryParams = o.queryParams;
|
||||||
|
this.headerParams = o.headerParams;
|
||||||
|
this.formParams = o.formParams;
|
||||||
|
this.authMethods = o.authMethods;
|
||||||
|
this.tags = o.tags;
|
||||||
|
this.responses = o.responses;
|
||||||
|
this.imports = o.imports;
|
||||||
|
this.examples = o.examples;
|
||||||
|
this.externalDocs = o.externalDocs;
|
||||||
|
this.vendorExtensions = o.vendorExtensions;
|
||||||
|
this.nickname = o.nickname;
|
||||||
|
this.operationIdLowerCase = o.operationIdLowerCase;
|
||||||
|
this.operationIdCamelCase = o.operationIdCamelCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReplacedPathName() {
|
||||||
|
return replacedPathName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReplacedPathName(String replacedPathName) {
|
||||||
|
this.replacedPathName = replacedPathName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ org.openapitools.codegen.languages.EiffelClientCodegen
|
|||||||
org.openapitools.codegen.languages.ElixirClientCodegen
|
org.openapitools.codegen.languages.ElixirClientCodegen
|
||||||
org.openapitools.codegen.languages.ElmClientCodegen
|
org.openapitools.codegen.languages.ElmClientCodegen
|
||||||
org.openapitools.codegen.languages.ErlangClientCodegen
|
org.openapitools.codegen.languages.ErlangClientCodegen
|
||||||
|
org.openapitools.codegen.languages.ErlangProperCodegen
|
||||||
org.openapitools.codegen.languages.ErlangServerCodegen
|
org.openapitools.codegen.languages.ErlangServerCodegen
|
||||||
org.openapitools.codegen.languages.FlashClientCodegen
|
org.openapitools.codegen.languages.FlashClientCodegen
|
||||||
org.openapitools.codegen.languages.FinchServerCodegen
|
org.openapitools.codegen.languages.FinchServerCodegen
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
# OpenAPI client library for Erlang with Erlang QuickCheck generators
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An Erlang client stub and Erlang QuickCheck generators, generated by
|
||||||
|
[OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
|
@ -0,0 +1,30 @@
|
|||||||
|
-module({{classname}}_api).
|
||||||
|
|
||||||
|
-export([ {{#operations}}{{#operation}}{{^-first}}
|
||||||
|
, {{/-first}}{{operationId}}/{{arity}}{{/operation}}{{/operations}}
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(BASE_URL, "{{{basePathWithoutHost}}}").
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
{{#operation}}
|
||||||
|
%% @doc {{{summary}}}
|
||||||
|
{{^notes.isEmpty}}
|
||||||
|
%% {{{notes}}}
|
||||||
|
{{/notes.isEmpty}}
|
||||||
|
-spec {{operationId}}({{#allParams}}{{#required}}{{^-first}}, {{/-first}}{{{dataType}}}{{/required}}{{/allParams}}) ->
|
||||||
|
{{packageName}}_utils:response().
|
||||||
|
{{operationId}}({{#allParams}}{{#required}}{{^-first}}, {{/-first}}{{paramName}}{{/required}}{{/allParams}}) ->
|
||||||
|
Method = {{httpMethod}},
|
||||||
|
Host = application:get_env({{packageName}}, host, "http://localhost:8080"),
|
||||||
|
Path = ["{{{replacedPathName}}}"],
|
||||||
|
Body = {{^formParams.isEmpty}}{form, [{{#formParams}}{{#required}}{{^-first}}, {{/-first}}{<<"{{{baseName}}}">>, {{paramName}}{{/required}}{{/formParams}}]++{{packageName}}_utils:optional_params([{{#formParams}}{{^required}}{{^-first}}, {{/-first}}'{{{baseName}}}'{{/required}}{{/formParams}}], _OptionalParams)}{{/formParams.isEmpty}}{{#formParams.isEmpty}}{{#bodyParams.isEmpty}}[]{{/bodyParams.isEmpty}}{{^bodyParams.isEmpty}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/bodyParams.isEmpty}}{{/formParams.isEmpty}},
|
||||||
|
ContentType = {{#hasConsumes}}hd([{{#consumes}}{{^-first}}, {{/-first}}"{{mediaType}}"{{/consumes}}]){{/hasConsumes}}{{^hasConsumes}}<<"text/plain">>{{/hasConsumes}},
|
||||||
|
{{^queryParams.isEmpty}}
|
||||||
|
QueryString = [{{#queryParams}}{{^-first}}, {{/-first}}<<"{{{baseName}}}=">>, {{{paramName}}}, <<"&">>{{/queryParams}}],
|
||||||
|
{{/queryParams.isEmpty}}
|
||||||
|
|
||||||
|
{{packageName}}_utils:request(Method, [Host, ?BASE_URL, Path{{^queryParams.isEmpty}}, <<"?">>, QueryString{{/queryParams.isEmpty}}], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
{{/operations}}
|
@ -0,0 +1,21 @@
|
|||||||
|
{ application, {{packageName}}
|
||||||
|
, [ {description, {{#appDescription}}"{{appDescription}}"{{/appDescription}}{{^appDescription}}"OpenAPI client library for EQC testing"{{/appDescription}}}
|
||||||
|
, {vsn, "{{#apiVersion}}{{apiVersion}}{{/apiVersion}}{{^apiVersion}}0.1.0{{/apiVersion}}"}
|
||||||
|
, {registered, []}
|
||||||
|
, { applications
|
||||||
|
, [ kernel
|
||||||
|
, stdlib
|
||||||
|
, ssl
|
||||||
|
, jsx
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, { env
|
||||||
|
, [ {host, "http://{{#host}}{{{host}}}{{/host}}{{^host}}localhost:8080{{/host}}"}
|
||||||
|
, {basic_auth, {"admin", "admin"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, {modules, []}
|
||||||
|
, {maintainers, []}
|
||||||
|
, {licenses, [{{#licenseInfo}}"{{licenseInfo}}"{{/licenseInfo}}]}
|
||||||
|
, {links, [{{#infoUrl}}"{{infoUrl}}"{{/infoUrl}}]}
|
||||||
|
]}.
|
@ -0,0 +1,157 @@
|
|||||||
|
-module({{packageName}}_gen).
|
||||||
|
|
||||||
|
-compile({no_auto_import,[date/0]}).
|
||||||
|
|
||||||
|
-include_lib("proper/include/proper_common.hrl").
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Exports
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
-export([ binary/0
|
||||||
|
, binary/1
|
||||||
|
, binary/2
|
||||||
|
, integer/0
|
||||||
|
, integer/1
|
||||||
|
, integer/2
|
||||||
|
, boolean/0
|
||||||
|
, list/0
|
||||||
|
, list/1
|
||||||
|
, list/2
|
||||||
|
, list/3
|
||||||
|
, map/0
|
||||||
|
, date/0
|
||||||
|
, datetime/0
|
||||||
|
, any/0
|
||||||
|
, elements/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(CHARS, [$a, $b, $c]).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Generators
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
binary() -> binary(10).
|
||||||
|
|
||||||
|
binary(Min, Max) ->
|
||||||
|
?LET( {X, N}
|
||||||
|
, { proper_types:elements(?CHARS)
|
||||||
|
, proper_types:choose(Min, Max)
|
||||||
|
}
|
||||||
|
, iolist_to_binary(lists:duplicate(N, X))
|
||||||
|
).
|
||||||
|
|
||||||
|
binary(N) ->
|
||||||
|
?LET( X
|
||||||
|
, proper_types:elements(?CHARS)
|
||||||
|
, iolist_to_binary(lists:duplicate(N, X))
|
||||||
|
).
|
||||||
|
|
||||||
|
integer() -> proper_types:int().
|
||||||
|
|
||||||
|
integer(0) -> proper_types:nat();
|
||||||
|
integer(Min) ->
|
||||||
|
?LET( N
|
||||||
|
, proper_types:nat()
|
||||||
|
, proper_types:choose(Min, Min + N)
|
||||||
|
).
|
||||||
|
|
||||||
|
integer(Min, Max) -> proper_types:choose(Min, Max).
|
||||||
|
|
||||||
|
boolean() -> proper_types:bool().
|
||||||
|
|
||||||
|
list() -> list(any()).
|
||||||
|
|
||||||
|
list(Type) -> proper_types:list(Type).
|
||||||
|
|
||||||
|
list(Type, Min) ->
|
||||||
|
?LET( N
|
||||||
|
, integer(0)
|
||||||
|
, ?LET(X, list(Type, Min, Min + N), X)
|
||||||
|
).
|
||||||
|
|
||||||
|
list(Type, Min, Max) when Min =< Max ->
|
||||||
|
?LET( {X, Y}
|
||||||
|
, { proper_types:vector(Min, Type)
|
||||||
|
, proper_types:resize(Max - Min, proper_types:list(Type))
|
||||||
|
}
|
||||||
|
, X ++ Y
|
||||||
|
).
|
||||||
|
|
||||||
|
map() -> proper_types:map(any(), any()).
|
||||||
|
|
||||||
|
date() ->
|
||||||
|
?LET( X
|
||||||
|
, ?SUCHTHAT( X
|
||||||
|
, { year()
|
||||||
|
, proper_types:choose(1, 12)
|
||||||
|
, proper_types:choose(1, 31)
|
||||||
|
}
|
||||||
|
, calendar:valid_date(X)
|
||||||
|
)
|
||||||
|
, begin
|
||||||
|
{Year, Month, Day} = X,
|
||||||
|
YearBin = num_binary_format(Year, "4"),
|
||||||
|
MonthBin = num_binary_format(Month, "2"),
|
||||||
|
DayBin = num_binary_format(Day, "2"),
|
||||||
|
<<YearBin/binary, "-", MonthBin/binary, "-", DayBin/binary>>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
datetime() ->
|
||||||
|
Date = date(),
|
||||||
|
Hour = hour(),
|
||||||
|
?LET( X
|
||||||
|
, {Date, Hour}
|
||||||
|
, begin
|
||||||
|
{D, H} = X,
|
||||||
|
<<D/binary, "T", H/binary, "+0000">>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
any() ->
|
||||||
|
Any = [ binary()
|
||||||
|
, integer()
|
||||||
|
, boolean()
|
||||||
|
%% We don't include lists and maps to avoid huge values
|
||||||
|
%% , list()
|
||||||
|
%% , map()
|
||||||
|
, date()
|
||||||
|
, datetime()
|
||||||
|
],
|
||||||
|
proper_types:oneof(Any).
|
||||||
|
|
||||||
|
elements(Items) ->
|
||||||
|
proper_types:elements(Items).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Internal
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
year() ->
|
||||||
|
?LET( X
|
||||||
|
, proper_types:nat()
|
||||||
|
, 1970 + X
|
||||||
|
).
|
||||||
|
|
||||||
|
hour() ->
|
||||||
|
?LET( X
|
||||||
|
, { proper_types:choose(0, 23)
|
||||||
|
, proper_types:choose(0, 59)
|
||||||
|
, proper_types:choose(0, 59)
|
||||||
|
, proper_types:choose(0, 999)
|
||||||
|
}
|
||||||
|
, begin
|
||||||
|
{Hours, Mins, Secs, Millis} = X,
|
||||||
|
HoursBin = num_binary_format(Hours, "2"),
|
||||||
|
MinsBin = num_binary_format(Mins, "2"),
|
||||||
|
SecsBin = num_binary_format(Secs, "2"),
|
||||||
|
MillisBin = num_binary_format(Millis, "3"),
|
||||||
|
<<HoursBin/binary, ":", MinsBin/binary, ":",
|
||||||
|
SecsBin/binary, ".", MillisBin/binary>>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
num_binary_format(X, N) ->
|
||||||
|
list_to_binary(io_lib:format("~" ++ N ++ "..0B", [X])).
|
@ -0,0 +1,24 @@
|
|||||||
|
-compile({no_auto_import,[date/0]}).
|
||||||
|
|
||||||
|
-import( {{packageName}}_gen
|
||||||
|
, [ binary/0
|
||||||
|
, binary/1
|
||||||
|
, binary/2
|
||||||
|
, integer/0
|
||||||
|
, integer/1
|
||||||
|
, integer/2
|
||||||
|
, boolean/0
|
||||||
|
, list/0
|
||||||
|
, list/1
|
||||||
|
, list/2
|
||||||
|
, list/3
|
||||||
|
, map/0
|
||||||
|
, date/0
|
||||||
|
, datetime/0
|
||||||
|
, any/0
|
||||||
|
, elements/1
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
-type date() :: calendar:date().
|
||||||
|
-type datetime() :: calendar:datetime().
|
@ -0,0 +1,26 @@
|
|||||||
|
{{#models}}
|
||||||
|
{{#model}}
|
||||||
|
-module({{classname}}).
|
||||||
|
|
||||||
|
-include("{{packageName}}.hrl").
|
||||||
|
|
||||||
|
-export([{{classname}}/0]).
|
||||||
|
|
||||||
|
-export_type([{{classname}}/0]).
|
||||||
|
|
||||||
|
-type {{classname}}() ::{{#isEnum}}
|
||||||
|
binary().{{/isEnum}}{{^isEnum}}{{#isArrayModel}}
|
||||||
|
list({{arrayModelType}}).{{/isArrayModel}}{{^isArrayModel}}
|
||||||
|
[ {{#vars}}{{^-first}}
|
||||||
|
| {{/-first}}{'{{name}}', {{dataType}} }{{/vars}}
|
||||||
|
].{{/isArrayModel}}{{/isEnum}}
|
||||||
|
|
||||||
|
{{classname}}() ->{{#isEnum}}
|
||||||
|
elements([{{#allowableValues.values}}{{^-first}}, {{/-first}}<<"{{.}}">>{{/allowableValues.values}}]).
|
||||||
|
{{/isEnum}}{{#isArrayModel}}
|
||||||
|
list({{arrayModelType}}{{#minItems}}, {{minItems}}{{#maxItems}}, {{maxItems}}{{/maxItems}}{{/minItems}}).{{/isArrayModel}}{{^isEnum}}{{^isArrayModel}}
|
||||||
|
[ {{#vars}}{{^-first}}
|
||||||
|
, {{/-first}}{'{{baseName}}', {{#isString}}{{#isEnum}}elements([{{#allowableValues.values}}{{^-first}}, {{/-first}}<<"{{.}}">>{{/allowableValues.values}}]){{/isEnum}}{{^isEnum}}binary({{#minLength}}{{minLength}}{{#maxLength}}, {{maxLength}}{{/maxLength}}{{/minLength}}){{/isEnum}}{{/isString}}{{^isString}}{{baseType}}{{/isString}} }{{/vars}}
|
||||||
|
].{{/isArrayModel}}{{/isEnum}}
|
||||||
|
{{/model}}
|
||||||
|
{{/models}}
|
@ -0,0 +1,7 @@
|
|||||||
|
{erl_opts, [debug_info, warnings_as_errors]}.
|
||||||
|
|
||||||
|
{deps, [{jsx, "2.9.0"}, {proper, "1.3.0"}]}.
|
||||||
|
|
||||||
|
{shell, [{apps, [{{packageName}}]}]}.
|
||||||
|
|
||||||
|
{plugins, [rebar3_proper]}.
|
@ -0,0 +1,25 @@
|
|||||||
|
%%==============================================================================
|
||||||
|
%% Setup
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
setup() -> ok.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Cleanup
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
cleanup() -> ok.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Initial State
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
initial_state() -> #{}.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% State transitions callbacks
|
||||||
|
%%
|
||||||
|
%% operation_pre(State) -> true.
|
||||||
|
%% operation_next(State, Result, Args) -> State.
|
||||||
|
%% operation_post(State, Args, Result) -> true.
|
||||||
|
%%==============================================================================
|
@ -0,0 +1,105 @@
|
|||||||
|
-module({{classname}}_statem).
|
||||||
|
|
||||||
|
-behaviour(proper_statem).
|
||||||
|
|
||||||
|
-include("{{packageName}}.hrl").
|
||||||
|
-include_lib("proper/include/proper_common.hrl").
|
||||||
|
-include_lib("stdlib/include/assert.hrl").
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% PropEr callbacks
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
command(State) ->
|
||||||
|
Funs0 = [ {F, list_to_atom(atom_to_list(F) ++ "_args")}
|
||||||
|
|| {F, _} <- ?MODULE:module_info(exports)
|
||||||
|
],
|
||||||
|
|
||||||
|
Funs1 = [ X || {_, FArgs} = X <- Funs0,
|
||||||
|
erlang:function_exported(?MODULE, FArgs, 1)
|
||||||
|
],
|
||||||
|
proper_types:oneof([ {call, ?MODULE, F, ?MODULE:FArgs(State)}
|
||||||
|
|| {F, FArgs} <- Funs1
|
||||||
|
]).
|
||||||
|
|
||||||
|
precondition(S, {call, M, F, Args}) ->
|
||||||
|
Pre = list_to_atom(atom_to_list(F) ++ "_pre"),
|
||||||
|
case erlang:function_exported(M, Pre, 1) of
|
||||||
|
true -> M:Pre(S);
|
||||||
|
false -> true
|
||||||
|
end
|
||||||
|
andalso
|
||||||
|
case erlang:function_exported(M, Pre, 2) of
|
||||||
|
true -> M:Pre(S, Args);
|
||||||
|
false -> true
|
||||||
|
end.
|
||||||
|
|
||||||
|
next_state(S, Res, {call, M, F, Args}) ->
|
||||||
|
Next = list_to_atom(atom_to_list(F) ++ "_next"),
|
||||||
|
case erlang:function_exported(M, Next, 3) of
|
||||||
|
true -> M:Next(S, Res, Args);
|
||||||
|
false -> S
|
||||||
|
end.
|
||||||
|
|
||||||
|
postcondition(S, {call, M, F, Args}, Res) ->
|
||||||
|
Post = list_to_atom(atom_to_list(F) ++ "_post"),
|
||||||
|
case erlang:function_exported(M, Post, 3) of
|
||||||
|
true -> M:Post(S, Args, Res);
|
||||||
|
false -> true
|
||||||
|
end.
|
||||||
|
|
||||||
|
{{#operations}}
|
||||||
|
{{#operation}}
|
||||||
|
%%==============================================================================
|
||||||
|
%% {{operationId}}
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
{{operationId}}({{#allParams}}{{#required}}{{^-first}}, {{/-first}}{{paramName}}{{/required}}{{/allParams}}) ->
|
||||||
|
{{classname}}_api:{{operationId}}({{#allParams}}{{#required}}{{^-first}}, {{/-first}}{{paramName}}{{/required}}{{/allParams}}).
|
||||||
|
|
||||||
|
{{operationId}}_args(S) ->
|
||||||
|
Args = [{{#allParams}}{{#required}}{{^-first}}, {{/-first}}{{dataType}}{{/required}}{{/allParams}}],
|
||||||
|
case erlang:function_exported(?MODULE, '{{operationId}}_args_custom', 2) of
|
||||||
|
true -> ?MODULE:{{operationId}}_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
{{/operation}}
|
||||||
|
{{/operations}}
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% The statem's property
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
prop_main() ->
|
||||||
|
setup(),
|
||||||
|
?FORALL( Cmds
|
||||||
|
, proper_statem:commands(?MODULE)
|
||||||
|
, begin
|
||||||
|
cleanup(),
|
||||||
|
{ History
|
||||||
|
, State
|
||||||
|
, Result
|
||||||
|
} = proper_statem:run_commands(?MODULE, Cmds),
|
||||||
|
?WHENFAIL(
|
||||||
|
io:format("History: ~p\nState: ~p\nResult: ~p\nCmds: ~p\n",
|
||||||
|
[ History
|
||||||
|
, State
|
||||||
|
, Result
|
||||||
|
, proper_statem:command_names(Cmds)
|
||||||
|
]),
|
||||||
|
proper:aggregate( proper_statem:command_names(Cmds)
|
||||||
|
, Result =:= ok
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Include file with setup, cleanup, initial_state
|
||||||
|
%% and state transitions callbacks
|
||||||
|
%%==============================================================================
|
||||||
|
-include("{{classname}}_statem.hrl").
|
@ -0,0 +1,7 @@
|
|||||||
|
-module(prop_{{packageName}}).
|
||||||
|
|
||||||
|
-export([prop_test/0]).
|
||||||
|
|
||||||
|
prop_test() ->
|
||||||
|
{ok, _} = application:ensure_all_started({{packageName}}),
|
||||||
|
{{packageName}}_statem:prop_main().
|
@ -0,0 +1,66 @@
|
|||||||
|
-module({{packageName}}_utils).
|
||||||
|
|
||||||
|
-export([ request/2
|
||||||
|
, request/4
|
||||||
|
]).
|
||||||
|
|
||||||
|
-type response() :: #{ status := integer()
|
||||||
|
, headers := map()
|
||||||
|
, body := iolist()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-export_type([response/0]).
|
||||||
|
|
||||||
|
-spec request(atom(), string()) -> response().
|
||||||
|
request(Method, Url) ->
|
||||||
|
request(Method, Url, undefined, undefined).
|
||||||
|
|
||||||
|
-spec request(atom(), iolist(), iolist(), string()) -> response().
|
||||||
|
request(Method, Url0, Body, ContentType) ->
|
||||||
|
Url = binary_to_list(iolist_to_binary(Url0)),
|
||||||
|
Headers = headers(),
|
||||||
|
Request = case Body of
|
||||||
|
undefined -> {Url, Headers};
|
||||||
|
_ -> {Url, Headers, ContentType, Body}
|
||||||
|
end,
|
||||||
|
HTTPOptions = [{autoredirect, true}],
|
||||||
|
Options = [],
|
||||||
|
%% Disable pipelining to avoid the socket getting closed during long runs
|
||||||
|
ok = httpc:set_options([ {max_keep_alive_length, 0}
|
||||||
|
, {max_pipeline_length, 0}
|
||||||
|
, {max_sessions, 0}
|
||||||
|
]),
|
||||||
|
Result = httpc:request(Method, Request, HTTPOptions, Options),
|
||||||
|
{ok, {{=<% %>=}}{{_Ver, Status, _Phrase}, RespHeaders, RespBody}}<%={{ }}=%> = Result,
|
||||||
|
|
||||||
|
Response = #{ status => Status
|
||||||
|
, headers => maps:from_list(RespHeaders)
|
||||||
|
, body => RespBody
|
||||||
|
},
|
||||||
|
decode_body(Response).
|
||||||
|
|
||||||
|
-spec headers() -> [{string(), string()}].
|
||||||
|
headers() ->
|
||||||
|
[ {"Accept", "application/json"}
|
||||||
|
| basic_auth()
|
||||||
|
].
|
||||||
|
|
||||||
|
-spec basic_auth() -> [{string(), string()}].
|
||||||
|
basic_auth() ->
|
||||||
|
case application:get_env({{packageName}}, basic_auth, undefined) of
|
||||||
|
undefined -> [];
|
||||||
|
{Username, Password} ->
|
||||||
|
Credentials = base64:encode_to_string(Username ++ ":" ++ Password),
|
||||||
|
[{"Authorization", "Basic " ++ Credentials}]
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec decode_body(response()) -> response().
|
||||||
|
decode_body(#{ headers := #{"content-type" := "application/json"}
|
||||||
|
, body := Body
|
||||||
|
} = Response) ->
|
||||||
|
Json = jsx:decode( unicode:characters_to_binary(Body)
|
||||||
|
, [return_maps, {labels, atom}]
|
||||||
|
),
|
||||||
|
Response#{body_json => Json};
|
||||||
|
decode_body(Response) ->
|
||||||
|
Response.
|
@ -0,0 +1,23 @@
|
|||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
@ -0,0 +1 @@
|
|||||||
|
3.3.0-SNAPSHOT
|
6
samples/client/petstore/erlang-proper/README.md
Normal file
6
samples/client/petstore/erlang-proper/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# OpenAPI client library for Erlang with Erlang QuickCheck generators
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An Erlang client stub and Erlang QuickCheck generators, generated by
|
||||||
|
[OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
|
7
samples/client/petstore/erlang-proper/rebar.config
Normal file
7
samples/client/petstore/erlang-proper/rebar.config
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{erl_opts, [debug_info, warnings_as_errors]}.
|
||||||
|
|
||||||
|
{deps, [{jsx, "2.9.0"}, {proper, "1.3.0"}]}.
|
||||||
|
|
||||||
|
{shell, [{apps, [petstore]}]}.
|
||||||
|
|
||||||
|
{plugins, [rebar3_proper]}.
|
@ -0,0 +1,19 @@
|
|||||||
|
-module(petstore_api_response).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_api_response/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_api_response/0]).
|
||||||
|
|
||||||
|
-type petstore_api_response() ::
|
||||||
|
[ {'code', integer() }
|
||||||
|
| {'type', binary() }
|
||||||
|
| {'message', binary() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_api_response() ->
|
||||||
|
[ {'code', integer() }
|
||||||
|
, {'type', binary() }
|
||||||
|
, {'message', binary() }
|
||||||
|
].
|
@ -0,0 +1,17 @@
|
|||||||
|
-module(petstore_category).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_category/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_category/0]).
|
||||||
|
|
||||||
|
-type petstore_category() ::
|
||||||
|
[ {'id', integer() }
|
||||||
|
| {'name', binary() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_category() ->
|
||||||
|
[ {'id', integer() }
|
||||||
|
, {'name', binary() }
|
||||||
|
].
|
@ -0,0 +1,25 @@
|
|||||||
|
-module(petstore_order).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_order/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_order/0]).
|
||||||
|
|
||||||
|
-type petstore_order() ::
|
||||||
|
[ {'id', integer() }
|
||||||
|
| {'petId', integer() }
|
||||||
|
| {'quantity', integer() }
|
||||||
|
| {'shipDate', datetime() }
|
||||||
|
| {'status', binary() }
|
||||||
|
| {'complete', boolean() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_order() ->
|
||||||
|
[ {'id', integer() }
|
||||||
|
, {'petId', integer() }
|
||||||
|
, {'quantity', integer() }
|
||||||
|
, {'shipDate', datetime() }
|
||||||
|
, {'status', elements([<<"placed">>, <<"approved">>, <<"delivered">>]) }
|
||||||
|
, {'complete', boolean() }
|
||||||
|
].
|
@ -0,0 +1,25 @@
|
|||||||
|
-module(petstore_pet).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_pet/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_pet/0]).
|
||||||
|
|
||||||
|
-type petstore_pet() ::
|
||||||
|
[ {'id', integer() }
|
||||||
|
| {'category', petstore_category:petstore_category() }
|
||||||
|
| {'name', binary() }
|
||||||
|
| {'photoUrls', list(binary()) }
|
||||||
|
| {'tags', list(petstore_tag:petstore_tag()) }
|
||||||
|
| {'status', binary() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_pet() ->
|
||||||
|
[ {'id', integer() }
|
||||||
|
, {'category', petstore_category:petstore_category() }
|
||||||
|
, {'name', binary() }
|
||||||
|
, {'photoUrls', list(binary()) }
|
||||||
|
, {'tags', list(petstore_tag:petstore_tag()) }
|
||||||
|
, {'status', elements([<<"available">>, <<"pending">>, <<"sold">>]) }
|
||||||
|
].
|
@ -0,0 +1,17 @@
|
|||||||
|
-module(petstore_tag).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_tag/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_tag/0]).
|
||||||
|
|
||||||
|
-type petstore_tag() ::
|
||||||
|
[ {'id', integer() }
|
||||||
|
| {'name', binary() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_tag() ->
|
||||||
|
[ {'id', integer() }
|
||||||
|
, {'name', binary() }
|
||||||
|
].
|
@ -0,0 +1,29 @@
|
|||||||
|
-module(petstore_user).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
|
||||||
|
-export([petstore_user/0]).
|
||||||
|
|
||||||
|
-export_type([petstore_user/0]).
|
||||||
|
|
||||||
|
-type petstore_user() ::
|
||||||
|
[ {'id', integer() }
|
||||||
|
| {'username', binary() }
|
||||||
|
| {'firstName', binary() }
|
||||||
|
| {'lastName', binary() }
|
||||||
|
| {'email', binary() }
|
||||||
|
| {'password', binary() }
|
||||||
|
| {'phone', binary() }
|
||||||
|
| {'userStatus', integer() }
|
||||||
|
].
|
||||||
|
|
||||||
|
petstore_user() ->
|
||||||
|
[ {'id', integer() }
|
||||||
|
, {'username', binary() }
|
||||||
|
, {'firstName', binary() }
|
||||||
|
, {'lastName', binary() }
|
||||||
|
, {'email', binary() }
|
||||||
|
, {'password', binary() }
|
||||||
|
, {'phone', binary() }
|
||||||
|
, {'userStatus', integer() }
|
||||||
|
].
|
21
samples/client/petstore/erlang-proper/src/petstore.app.src
Normal file
21
samples/client/petstore/erlang-proper/src/petstore.app.src
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{ application, petstore
|
||||||
|
, [ {description, "This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters."}
|
||||||
|
, {vsn, "0.1.0"}
|
||||||
|
, {registered, []}
|
||||||
|
, { applications
|
||||||
|
, [ kernel
|
||||||
|
, stdlib
|
||||||
|
, ssl
|
||||||
|
, jsx
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, { env
|
||||||
|
, [ {host, "http://petstore.swagger.io"}
|
||||||
|
, {basic_auth, {"admin", "admin"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, {modules, []}
|
||||||
|
, {maintainers, []}
|
||||||
|
, {licenses, ["Apache-2.0"]}
|
||||||
|
, {links, []}
|
||||||
|
]}.
|
24
samples/client/petstore/erlang-proper/src/petstore.hrl
Normal file
24
samples/client/petstore/erlang-proper/src/petstore.hrl
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
-compile({no_auto_import,[date/0]}).
|
||||||
|
|
||||||
|
-import( petstore_gen
|
||||||
|
, [ binary/0
|
||||||
|
, binary/1
|
||||||
|
, binary/2
|
||||||
|
, integer/0
|
||||||
|
, integer/1
|
||||||
|
, integer/2
|
||||||
|
, boolean/0
|
||||||
|
, list/0
|
||||||
|
, list/1
|
||||||
|
, list/2
|
||||||
|
, list/3
|
||||||
|
, map/0
|
||||||
|
, date/0
|
||||||
|
, datetime/0
|
||||||
|
, any/0
|
||||||
|
, elements/1
|
||||||
|
]
|
||||||
|
).
|
||||||
|
|
||||||
|
-type date() :: calendar:date().
|
||||||
|
-type datetime() :: calendar:datetime().
|
119
samples/client/petstore/erlang-proper/src/petstore_api.erl
Normal file
119
samples/client/petstore/erlang-proper/src/petstore_api.erl
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
-module(petstore_api).
|
||||||
|
|
||||||
|
-export([ create_user/1
|
||||||
|
, create_users_with_array_input/1
|
||||||
|
, create_users_with_list_input/1
|
||||||
|
, delete_user/1
|
||||||
|
, get_user_by_name/1
|
||||||
|
, login_user/2
|
||||||
|
, logout_user/0
|
||||||
|
, update_user/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(BASE_URL, "/v2").
|
||||||
|
|
||||||
|
%% @doc Create user
|
||||||
|
%% This can only be done by the logged in user.
|
||||||
|
-spec create_user(petstore_user:petstore_user()) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
create_user(PetstoreUser) ->
|
||||||
|
Method = post,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user"],
|
||||||
|
Body = PetstoreUser,
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Creates list of users with given input array
|
||||||
|
%%
|
||||||
|
-spec create_users_with_array_input(list(petstore_user:petstore_user())) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
create_users_with_array_input(PetstoreUserArray) ->
|
||||||
|
Method = post,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/createWithArray"],
|
||||||
|
Body = PetstoreUserArray,
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Creates list of users with given input array
|
||||||
|
%%
|
||||||
|
-spec create_users_with_list_input(list(petstore_user:petstore_user())) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
create_users_with_list_input(PetstoreUserArray) ->
|
||||||
|
Method = post,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/createWithList"],
|
||||||
|
Body = PetstoreUserArray,
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Delete user
|
||||||
|
%% This can only be done by the logged in user.
|
||||||
|
-spec delete_user(binary()) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
delete_user(Username) ->
|
||||||
|
Method = delete,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/", Username, ""],
|
||||||
|
Body = [],
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Get user by user name
|
||||||
|
%%
|
||||||
|
-spec get_user_by_name(binary()) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
get_user_by_name(Username) ->
|
||||||
|
Method = get,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/", Username, ""],
|
||||||
|
Body = [],
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Logs user into the system
|
||||||
|
%%
|
||||||
|
-spec login_user(binary(), binary()) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
login_user(Username, Password) ->
|
||||||
|
Method = get,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/login"],
|
||||||
|
Body = [],
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
QueryString = [<<"username=">>, Username, <<"&">>, <<"password=">>, Password, <<"&">>],
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path, <<"?">>, QueryString], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Logs out current logged in user session
|
||||||
|
%%
|
||||||
|
-spec logout_user() ->
|
||||||
|
petstore_utils:response().
|
||||||
|
logout_user() ->
|
||||||
|
Method = get,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/logout"],
|
||||||
|
Body = [],
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
||||||
|
%% @doc Updated user
|
||||||
|
%% This can only be done by the logged in user.
|
||||||
|
-spec update_user(binary(), petstore_user:petstore_user()) ->
|
||||||
|
petstore_utils:response().
|
||||||
|
update_user(Username, PetstoreUser) ->
|
||||||
|
Method = put,
|
||||||
|
Host = application:get_env(petstore, host, "http://localhost:8080"),
|
||||||
|
Path = ["/user/", Username, ""],
|
||||||
|
Body = PetstoreUser,
|
||||||
|
ContentType = <<"text/plain">>,
|
||||||
|
|
||||||
|
petstore_utils:request(Method, [Host, ?BASE_URL, Path], jsx:encode(Body), ContentType).
|
||||||
|
|
157
samples/client/petstore/erlang-proper/src/petstore_gen.erl
Normal file
157
samples/client/petstore/erlang-proper/src/petstore_gen.erl
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
-module(petstore_gen).
|
||||||
|
|
||||||
|
-compile({no_auto_import,[date/0]}).
|
||||||
|
|
||||||
|
-include_lib("proper/include/proper_common.hrl").
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Exports
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
-export([ binary/0
|
||||||
|
, binary/1
|
||||||
|
, binary/2
|
||||||
|
, integer/0
|
||||||
|
, integer/1
|
||||||
|
, integer/2
|
||||||
|
, boolean/0
|
||||||
|
, list/0
|
||||||
|
, list/1
|
||||||
|
, list/2
|
||||||
|
, list/3
|
||||||
|
, map/0
|
||||||
|
, date/0
|
||||||
|
, datetime/0
|
||||||
|
, any/0
|
||||||
|
, elements/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-define(CHARS, [$a, $b, $c]).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Generators
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
binary() -> binary(10).
|
||||||
|
|
||||||
|
binary(Min, Max) ->
|
||||||
|
?LET( {X, N}
|
||||||
|
, { proper_types:elements(?CHARS)
|
||||||
|
, proper_types:choose(Min, Max)
|
||||||
|
}
|
||||||
|
, iolist_to_binary(lists:duplicate(N, X))
|
||||||
|
).
|
||||||
|
|
||||||
|
binary(N) ->
|
||||||
|
?LET( X
|
||||||
|
, proper_types:elements(?CHARS)
|
||||||
|
, iolist_to_binary(lists:duplicate(N, X))
|
||||||
|
).
|
||||||
|
|
||||||
|
integer() -> proper_types:int().
|
||||||
|
|
||||||
|
integer(0) -> proper_types:nat();
|
||||||
|
integer(Min) ->
|
||||||
|
?LET( N
|
||||||
|
, proper_types:nat()
|
||||||
|
, proper_types:choose(Min, Min + N)
|
||||||
|
).
|
||||||
|
|
||||||
|
integer(Min, Max) -> proper_types:choose(Min, Max).
|
||||||
|
|
||||||
|
boolean() -> proper_types:bool().
|
||||||
|
|
||||||
|
list() -> list(any()).
|
||||||
|
|
||||||
|
list(Type) -> proper_types:list(Type).
|
||||||
|
|
||||||
|
list(Type, Min) ->
|
||||||
|
?LET( N
|
||||||
|
, integer(0)
|
||||||
|
, ?LET(X, list(Type, Min, Min + N), X)
|
||||||
|
).
|
||||||
|
|
||||||
|
list(Type, Min, Max) when Min =< Max ->
|
||||||
|
?LET( {X, Y}
|
||||||
|
, { proper_types:vector(Min, Type)
|
||||||
|
, proper_types:resize(Max - Min, proper_types:list(Type))
|
||||||
|
}
|
||||||
|
, X ++ Y
|
||||||
|
).
|
||||||
|
|
||||||
|
map() -> proper_types:map(any(), any()).
|
||||||
|
|
||||||
|
date() ->
|
||||||
|
?LET( X
|
||||||
|
, ?SUCHTHAT( X
|
||||||
|
, { year()
|
||||||
|
, proper_types:choose(1, 12)
|
||||||
|
, proper_types:choose(1, 31)
|
||||||
|
}
|
||||||
|
, calendar:valid_date(X)
|
||||||
|
)
|
||||||
|
, begin
|
||||||
|
{Year, Month, Day} = X,
|
||||||
|
YearBin = num_binary_format(Year, "4"),
|
||||||
|
MonthBin = num_binary_format(Month, "2"),
|
||||||
|
DayBin = num_binary_format(Day, "2"),
|
||||||
|
<<YearBin/binary, "-", MonthBin/binary, "-", DayBin/binary>>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
datetime() ->
|
||||||
|
Date = date(),
|
||||||
|
Hour = hour(),
|
||||||
|
?LET( X
|
||||||
|
, {Date, Hour}
|
||||||
|
, begin
|
||||||
|
{D, H} = X,
|
||||||
|
<<D/binary, "T", H/binary, "+0000">>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
any() ->
|
||||||
|
Any = [ binary()
|
||||||
|
, integer()
|
||||||
|
, boolean()
|
||||||
|
%% We don't include lists and maps to avoid huge values
|
||||||
|
%% , list()
|
||||||
|
%% , map()
|
||||||
|
, date()
|
||||||
|
, datetime()
|
||||||
|
],
|
||||||
|
proper_types:oneof(Any).
|
||||||
|
|
||||||
|
elements(Items) ->
|
||||||
|
proper_types:elements(Items).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Internal
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
year() ->
|
||||||
|
?LET( X
|
||||||
|
, proper_types:nat()
|
||||||
|
, 1970 + X
|
||||||
|
).
|
||||||
|
|
||||||
|
hour() ->
|
||||||
|
?LET( X
|
||||||
|
, { proper_types:choose(0, 23)
|
||||||
|
, proper_types:choose(0, 59)
|
||||||
|
, proper_types:choose(0, 59)
|
||||||
|
, proper_types:choose(0, 999)
|
||||||
|
}
|
||||||
|
, begin
|
||||||
|
{Hours, Mins, Secs, Millis} = X,
|
||||||
|
HoursBin = num_binary_format(Hours, "2"),
|
||||||
|
MinsBin = num_binary_format(Mins, "2"),
|
||||||
|
SecsBin = num_binary_format(Secs, "2"),
|
||||||
|
MillisBin = num_binary_format(Millis, "3"),
|
||||||
|
<<HoursBin/binary, ":", MinsBin/binary, ":",
|
||||||
|
SecsBin/binary, ".", MillisBin/binary>>
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
num_binary_format(X, N) ->
|
||||||
|
list_to_binary(io_lib:format("~" ++ N ++ "..0B", [X])).
|
199
samples/client/petstore/erlang-proper/src/petstore_statem.erl
Normal file
199
samples/client/petstore/erlang-proper/src/petstore_statem.erl
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
-module(petstore_statem).
|
||||||
|
|
||||||
|
-behaviour(proper_statem).
|
||||||
|
|
||||||
|
-include("petstore.hrl").
|
||||||
|
-include_lib("proper/include/proper_common.hrl").
|
||||||
|
-include_lib("stdlib/include/assert.hrl").
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% PropEr callbacks
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
command(State) ->
|
||||||
|
Funs0 = [ {F, list_to_atom(atom_to_list(F) ++ "_args")}
|
||||||
|
|| {F, _} <- ?MODULE:module_info(exports)
|
||||||
|
],
|
||||||
|
|
||||||
|
Funs1 = [ X || {_, FArgs} = X <- Funs0,
|
||||||
|
erlang:function_exported(?MODULE, FArgs, 1)
|
||||||
|
],
|
||||||
|
proper_types:oneof([ {call, ?MODULE, F, ?MODULE:FArgs(State)}
|
||||||
|
|| {F, FArgs} <- Funs1
|
||||||
|
]).
|
||||||
|
|
||||||
|
precondition(S, {call, M, F, Args}) ->
|
||||||
|
Pre = list_to_atom(atom_to_list(F) ++ "_pre"),
|
||||||
|
case erlang:function_exported(M, Pre, 1) of
|
||||||
|
true -> M:Pre(S);
|
||||||
|
false -> true
|
||||||
|
end
|
||||||
|
andalso
|
||||||
|
case erlang:function_exported(M, Pre, 2) of
|
||||||
|
true -> M:Pre(S, Args);
|
||||||
|
false -> true
|
||||||
|
end.
|
||||||
|
|
||||||
|
next_state(S, Res, {call, M, F, Args}) ->
|
||||||
|
Next = list_to_atom(atom_to_list(F) ++ "_next"),
|
||||||
|
case erlang:function_exported(M, Next, 3) of
|
||||||
|
true -> M:Next(S, Res, Args);
|
||||||
|
false -> S
|
||||||
|
end.
|
||||||
|
|
||||||
|
postcondition(S, {call, M, F, Args}, Res) ->
|
||||||
|
Post = list_to_atom(atom_to_list(F) ++ "_post"),
|
||||||
|
case erlang:function_exported(M, Post, 3) of
|
||||||
|
true -> M:Post(S, Args, Res);
|
||||||
|
false -> true
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% create_user
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
create_user(PetstoreUser) ->
|
||||||
|
petstore_api:create_user(PetstoreUser).
|
||||||
|
|
||||||
|
create_user_args(S) ->
|
||||||
|
Args = [petstore_user:petstore_user()],
|
||||||
|
case erlang:function_exported(?MODULE, 'create_user_args_custom', 2) of
|
||||||
|
true -> ?MODULE:create_user_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% create_users_with_array_input
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
create_users_with_array_input(PetstoreUserArray) ->
|
||||||
|
petstore_api:create_users_with_array_input(PetstoreUserArray).
|
||||||
|
|
||||||
|
create_users_with_array_input_args(S) ->
|
||||||
|
Args = [list(petstore_user:petstore_user())],
|
||||||
|
case erlang:function_exported(?MODULE, 'create_users_with_array_input_args_custom', 2) of
|
||||||
|
true -> ?MODULE:create_users_with_array_input_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% create_users_with_list_input
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
create_users_with_list_input(PetstoreUserArray) ->
|
||||||
|
petstore_api:create_users_with_list_input(PetstoreUserArray).
|
||||||
|
|
||||||
|
create_users_with_list_input_args(S) ->
|
||||||
|
Args = [list(petstore_user:petstore_user())],
|
||||||
|
case erlang:function_exported(?MODULE, 'create_users_with_list_input_args_custom', 2) of
|
||||||
|
true -> ?MODULE:create_users_with_list_input_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% delete_user
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
delete_user(Username) ->
|
||||||
|
petstore_api:delete_user(Username).
|
||||||
|
|
||||||
|
delete_user_args(S) ->
|
||||||
|
Args = [binary()],
|
||||||
|
case erlang:function_exported(?MODULE, 'delete_user_args_custom', 2) of
|
||||||
|
true -> ?MODULE:delete_user_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% get_user_by_name
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
get_user_by_name(Username) ->
|
||||||
|
petstore_api:get_user_by_name(Username).
|
||||||
|
|
||||||
|
get_user_by_name_args(S) ->
|
||||||
|
Args = [binary()],
|
||||||
|
case erlang:function_exported(?MODULE, 'get_user_by_name_args_custom', 2) of
|
||||||
|
true -> ?MODULE:get_user_by_name_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% login_user
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
login_user(Username, Password) ->
|
||||||
|
petstore_api:login_user(Username, Password).
|
||||||
|
|
||||||
|
login_user_args(S) ->
|
||||||
|
Args = [binary(), binary()],
|
||||||
|
case erlang:function_exported(?MODULE, 'login_user_args_custom', 2) of
|
||||||
|
true -> ?MODULE:login_user_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% logout_user
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
logout_user() ->
|
||||||
|
petstore_api:logout_user().
|
||||||
|
|
||||||
|
logout_user_args(S) ->
|
||||||
|
Args = [],
|
||||||
|
case erlang:function_exported(?MODULE, 'logout_user_args_custom', 2) of
|
||||||
|
true -> ?MODULE:logout_user_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% update_user
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
update_user(Username, PetstoreUser) ->
|
||||||
|
petstore_api:update_user(Username, PetstoreUser).
|
||||||
|
|
||||||
|
update_user_args(S) ->
|
||||||
|
Args = [binary(), petstore_user:petstore_user()],
|
||||||
|
case erlang:function_exported(?MODULE, 'update_user_args_custom', 2) of
|
||||||
|
true -> ?MODULE:update_user_args_custom(S, Args);
|
||||||
|
false -> Args
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% The statem's property
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
prop_main() ->
|
||||||
|
setup(),
|
||||||
|
?FORALL( Cmds
|
||||||
|
, proper_statem:commands(?MODULE)
|
||||||
|
, begin
|
||||||
|
cleanup(),
|
||||||
|
{ History
|
||||||
|
, State
|
||||||
|
, Result
|
||||||
|
} = proper_statem:run_commands(?MODULE, Cmds),
|
||||||
|
?WHENFAIL(
|
||||||
|
io:format("History: ~p\nState: ~p\nResult: ~p\nCmds: ~p\n",
|
||||||
|
[ History
|
||||||
|
, State
|
||||||
|
, Result
|
||||||
|
, proper_statem:command_names(Cmds)
|
||||||
|
]),
|
||||||
|
proper:aggregate( proper_statem:command_names(Cmds)
|
||||||
|
, Result =:= ok
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
).
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Include file with setup, cleanup, initial_state
|
||||||
|
%% and state transitions callbacks
|
||||||
|
%%==============================================================================
|
||||||
|
-include("petstore_statem.hrl").
|
@ -0,0 +1,25 @@
|
|||||||
|
%%==============================================================================
|
||||||
|
%% Setup
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
setup() -> ok.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Cleanup
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
cleanup() -> ok.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% Initial State
|
||||||
|
%%==============================================================================
|
||||||
|
|
||||||
|
initial_state() -> #{}.
|
||||||
|
|
||||||
|
%%==============================================================================
|
||||||
|
%% State transitions callbacks
|
||||||
|
%%
|
||||||
|
%% operation_pre(State) -> true.
|
||||||
|
%% operation_next(State, Result, Args) -> State.
|
||||||
|
%% operation_post(State, Args, Result) -> true.
|
||||||
|
%%==============================================================================
|
66
samples/client/petstore/erlang-proper/src/petstore_utils.erl
Normal file
66
samples/client/petstore/erlang-proper/src/petstore_utils.erl
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
-module(petstore_utils).
|
||||||
|
|
||||||
|
-export([ request/2
|
||||||
|
, request/4
|
||||||
|
]).
|
||||||
|
|
||||||
|
-type response() :: #{ status := integer()
|
||||||
|
, headers := map()
|
||||||
|
, body := iolist()
|
||||||
|
}.
|
||||||
|
|
||||||
|
-export_type([response/0]).
|
||||||
|
|
||||||
|
-spec request(atom(), string()) -> response().
|
||||||
|
request(Method, Url) ->
|
||||||
|
request(Method, Url, undefined, undefined).
|
||||||
|
|
||||||
|
-spec request(atom(), iolist(), iolist(), string()) -> response().
|
||||||
|
request(Method, Url0, Body, ContentType) ->
|
||||||
|
Url = binary_to_list(iolist_to_binary(Url0)),
|
||||||
|
Headers = headers(),
|
||||||
|
Request = case Body of
|
||||||
|
undefined -> {Url, Headers};
|
||||||
|
_ -> {Url, Headers, ContentType, Body}
|
||||||
|
end,
|
||||||
|
HTTPOptions = [{autoredirect, true}],
|
||||||
|
Options = [],
|
||||||
|
%% Disable pipelining to avoid the socket getting closed during long runs
|
||||||
|
ok = httpc:set_options([ {max_keep_alive_length, 0}
|
||||||
|
, {max_pipeline_length, 0}
|
||||||
|
, {max_sessions, 0}
|
||||||
|
]),
|
||||||
|
Result = httpc:request(Method, Request, HTTPOptions, Options),
|
||||||
|
{ok, {{_Ver, Status, _Phrase}, RespHeaders, RespBody}} = Result,
|
||||||
|
|
||||||
|
Response = #{ status => Status
|
||||||
|
, headers => maps:from_list(RespHeaders)
|
||||||
|
, body => RespBody
|
||||||
|
},
|
||||||
|
decode_body(Response).
|
||||||
|
|
||||||
|
-spec headers() -> [{string(), string()}].
|
||||||
|
headers() ->
|
||||||
|
[ {"Accept", "application/json"}
|
||||||
|
| basic_auth()
|
||||||
|
].
|
||||||
|
|
||||||
|
-spec basic_auth() -> [{string(), string()}].
|
||||||
|
basic_auth() ->
|
||||||
|
case application:get_env(petstore, basic_auth, undefined) of
|
||||||
|
undefined -> [];
|
||||||
|
{Username, Password} ->
|
||||||
|
Credentials = base64:encode_to_string(Username ++ ":" ++ Password),
|
||||||
|
[{"Authorization", "Basic " ++ Credentials}]
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec decode_body(response()) -> response().
|
||||||
|
decode_body(#{ headers := #{"content-type" := "application/json"}
|
||||||
|
, body := Body
|
||||||
|
} = Response) ->
|
||||||
|
Json = jsx:decode( unicode:characters_to_binary(Body)
|
||||||
|
, [return_maps, {labels, atom}]
|
||||||
|
),
|
||||||
|
Response#{body_json => Json};
|
||||||
|
decode_body(Response) ->
|
||||||
|
Response.
|
@ -0,0 +1,7 @@
|
|||||||
|
-module(prop_petstore).
|
||||||
|
|
||||||
|
-export([prop_test/0]).
|
||||||
|
|
||||||
|
prop_test() ->
|
||||||
|
{ok, _} = application:ensure_all_started(petstore),
|
||||||
|
petstore_statem:prop_main().
|
Loading…
x
Reference in New Issue
Block a user