mirror of
https://github.com/valitydev/openapi-generator.git
synced 2024-11-06 10:35:25 +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.ElmClientCodegen
|
||||
org.openapitools.codegen.languages.ErlangClientCodegen
|
||||
org.openapitools.codegen.languages.ErlangProperCodegen
|
||||
org.openapitools.codegen.languages.ErlangServerCodegen
|
||||
org.openapitools.codegen.languages.FlashClientCodegen
|
||||
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…
Reference in New Issue
Block a user