Merge branch 'cchafer-akka-scala' into develop_2.0

This commit is contained in:
Tony Tam 2015-05-20 21:41:24 -07:00
commit ef5cb5021b
85 changed files with 4918 additions and 22 deletions

View File

@ -11,6 +11,6 @@ public class CodegenModel {
public String defaultValue;
public List<CodegenProperty> vars = new ArrayList<CodegenProperty>();
public Set<String> imports = new HashSet<String>();
public Boolean hasVars, emptyVars, hasMoreModels;
public Boolean hasVars, emptyVars, hasMoreModels, hasEnums;
public ExternalDocs externalDocs;
}

View File

@ -8,10 +8,12 @@ public class CodegenResponse {
public List<Map<String, String>> examples;
public final List<CodegenProperty> headers = new ArrayList<CodegenProperty>();
public String dataType, baseType, containerType;
public Boolean isDefault;
public Boolean simpleType;
public Boolean primitiveType;
public Boolean isMapContainer;
public Boolean isListContainer;
public Object schema;
public String jsonSchema;
public boolean isWildcard() { return "0".equals(code) || "default".equals(code); }
}

View File

@ -1,11 +1,10 @@
package com.wordnik.swagger.codegen;
public class CodegenSecurity {
String name;
String type;
Boolean hasMore, isBasic, isOAuth, isApiKey;
public String name;
public String type;
public Boolean hasMore, isBasic, isOAuth, isApiKey;
// ApiKey specific
String keyParamName;
Boolean isKeyInQuery, isKeyInHeader;
public String keyParamName;
public Boolean isKeyInQuery, isKeyInHeader;
}

View File

@ -166,6 +166,9 @@ public class DefaultCodegen {
return name;
}
public String toEnumName(CodegenProperty property) {
return StringUtils.capitalize(property.name) + "Enum";
}
public String escapeReservedWord(String name) {
throw new RuntimeException("reserved word " + name + " not allowed");
@ -470,6 +473,7 @@ public class DefaultCodegen {
}
if(impl.getProperties() != null && impl.getProperties().size() > 0) {
m.hasVars = true;
m.hasEnums = false;
for(String key: impl.getProperties().keySet()) {
Property prop = impl.getProperties().get(key);
@ -497,6 +501,8 @@ public class DefaultCodegen {
}
m.vars.add(cp);
count += 1;
if (cp.isEnum)
m.hasEnums = true;
if(count != impl.getProperties().keySet().size())
cp.hasMore = new Boolean(true);
if(cp.isContainer != null) {
@ -589,7 +595,7 @@ public class DefaultCodegen {
// this can cause issues for clients which don't support enums
if(property.isEnum)
property.datatypeWithEnum = StringUtils.capitalize(property.name) + "Enum";
property.datatypeWithEnum = toEnumName(property);
else
property.datatypeWithEnum = property.datatype;
@ -722,7 +728,6 @@ public class DefaultCodegen {
if (operation.getResponses() != null && !operation.getResponses().isEmpty()) {
Response methodResponse = findMethodResponse(operation.getResponses());
CodegenResponse methodCodegenResponse = null;
for (Map.Entry<String, Response> entry : operation.getResponses().entrySet()) {
Response response = entry.getValue();
@ -732,9 +737,7 @@ public class DefaultCodegen {
!defaultIncludes.contains(r.baseType) &&
!languageSpecificPrimitives.contains(r.baseType))
imports.add(r.baseType);
if (response == methodResponse)
methodCodegenResponse = r;
r.isDefault = response == methodResponse;
op.responses.add(r);
}
op.responses.get(op.responses.size() - 1).hasMore = false;
@ -918,6 +921,19 @@ public class DefaultCodegen {
collectionFormat = qp.getCollectionFormat();
CodegenProperty pr = fromProperty("inner", inner);
p.baseType = pr.datatype;
p.isContainer = true;
imports.add(pr.baseType);
}
else if("object".equals(qp.getType())) {
Property inner = qp.getItems();
if(inner == null) {
LOGGER.warn("warning! No inner type supplied for map parameter \"" + qp.getName() + "\", using String");
inner = new StringProperty().description("//TODO automatically added by swagger-codegen");
}
property = new MapProperty(inner);
collectionFormat = qp.getCollectionFormat();
CodegenProperty pr = fromProperty("inner", inner);
p.baseType = pr.datatype;
imports.add(pr.baseType);
}
else
@ -926,6 +942,7 @@ public class DefaultCodegen {
LOGGER.warn("warning! Property type \"" + qp.getType() + "\" not found for parameter \"" + param.getName() + "\", using String");
property = new StringProperty().description("//TODO automatically added by swagger-codegen. Type was " + qp.getType() + " but not supported");
}
property.setRequired(param.getRequired());
CodegenProperty model = fromProperty(qp.getName(), property);
p.collectionFormat = collectionFormat;
p.dataType = model.datatype;
@ -949,6 +966,7 @@ public class DefaultCodegen {
else {
// TODO: missing format, so this will not always work
Property prop = PropertyBuilder.build(impl.getType(), null, null);
prop.setRequired(bp.getRequired());
CodegenProperty cp = fromProperty("property", prop);
if(cp != null) {
p.dataType = cp.datatype;
@ -962,6 +980,7 @@ public class DefaultCodegen {
CodegenModel cm = fromModel(bp.getName(), impl);
// get the single property
ArrayProperty ap = new ArrayProperty().items(impl.getItems());
ap.setRequired(param.getRequired());
CodegenProperty cp = fromProperty("inner", ap);
if(cp.complexType != null) {
imports.add(cp.complexType);
@ -1169,4 +1188,4 @@ public class DefaultCodegen {
}
}
}

View File

@ -0,0 +1,365 @@
package com.wordnik.swagger.codegen.languages;
import com.google.common.base.CaseFormat;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import com.wordnik.swagger.codegen.*;
import com.wordnik.swagger.models.auth.SecuritySchemeDefinition;
import com.wordnik.swagger.models.properties.*;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
public class AkkaScalaClientCodegen extends DefaultCodegen implements CodegenConfig {
Logger LOGGER = LoggerFactory.getLogger(AkkaScalaClientCodegen.class);
protected String mainPackage = "io.swagger.client";
protected String invokerPackage = mainPackage + ".core";
protected String groupId = "com.wordnik";
protected String artifactId = "swagger-client";
protected String artifactVersion = "1.0.0";
protected String sourceFolder = "src/main/scala";
protected String resourcesFolder = "src/main/resources";
protected String configKey = "apiRequest";
protected int defaultTimeoutInMs = 5000;
protected String configKeyPath = mainPackage;
protected boolean registerNonStandardStatusCodes = true;
protected boolean renderJavadoc = true;
protected boolean removeOAuthSecurities = true;
/**
* If set to true, only the default response (the one with le lowest 2XX code) will be considered as a success, and all
* others as ApiErrors.
* If set to false, all responses defined in the model will be considered as a success upon reception. Only http errors,
* unmarshalling problems and any other RuntimeException will be considered as ApiErrors.
*/
protected boolean onlyOneSuccess = true;
public CodegenType getTag() {
return CodegenType.CLIENT;
}
public String getName() {
return "akka-scala";
}
public String getHelp() {
return "Generates a Scala client library base on Akka/Spray.";
}
public AkkaScalaClientCodegen() {
super();
outputFolder = "generated-code/scala";
modelTemplateFiles.put("model.mustache", ".scala");
apiTemplateFiles.put("api.mustache", ".scala");
templateDir = "akka-scala";
apiPackage = mainPackage + ".api";
modelPackage = mainPackage + ".model";
reservedWords = new HashSet<String>(
Arrays.asList(
"abstract", "case", "catch", "class", "def", "do", "else", "extends",
"false", "final", "finally", "for", "forSome", "if", "implicit",
"import", "lazy", "match", "new", "null", "object", "override", "package",
"private", "protected", "return", "sealed", "super", "this", "throw",
"trait", "try", "true", "type", "val", "var", "while", "with", "yield")
);
additionalProperties.put("invokerPackage", invokerPackage);
additionalProperties.put("groupId", groupId);
additionalProperties.put("artifactId", artifactId);
additionalProperties.put("artifactVersion", artifactVersion);
additionalProperties.put("configKey", configKey);
additionalProperties.put("configKeyPath", configKeyPath);
additionalProperties.put("defaultTimeout", defaultTimeoutInMs);
if (renderJavadoc)
additionalProperties.put("javadocRenderer", new JavadocLambda());
additionalProperties.put("fnCapitalize", new CapitalizeLambda());
additionalProperties.put("fnCamelize", new CamelizeLambda(false));
additionalProperties.put("fnEnumEntry", new EnumEntryLambda());
additionalProperties.put("onlyOneSuccess", onlyOneSuccess);
supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("reference.mustache", resourcesFolder, "reference.conf"));
final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
supportingFiles.add(new SupportingFile("apiRequest.mustache", invokerFolder, "ApiRequest.scala"));
supportingFiles.add(new SupportingFile("apiInvoker.mustache", invokerFolder, "ApiInvoker.scala"));
supportingFiles.add(new SupportingFile("requests.mustache", invokerFolder, "requests.scala"));
supportingFiles.add(new SupportingFile("apiSettings.mustache", invokerFolder, "ApiSettings.scala"));
final String apiFolder = (sourceFolder + File.separator + apiPackage).replace(".", File.separator);
supportingFiles.add(new SupportingFile("enumsSerializers.mustache", apiFolder, "EnumsSerializers.scala"));
importMapping.remove("Seq");
importMapping.remove("List");
importMapping.remove("Set");
importMapping.remove("Map");
importMapping.put("DateTime", "org.joda.time.DateTime");
typeMapping = new HashMap<String, String>();
typeMapping.put("array", "Seq");
typeMapping.put("set", "Set");
typeMapping.put("boolean", "Boolean");
typeMapping.put("string", "String");
typeMapping.put("int", "Int");
typeMapping.put("integer", "Int");
typeMapping.put("long", "Long");
typeMapping.put("float", "Float");
typeMapping.put("byte", "Byte");
typeMapping.put("short", "Short");
typeMapping.put("char", "Char");
typeMapping.put("long", "Long");
typeMapping.put("double", "Double");
typeMapping.put("object", "Any");
typeMapping.put("file", "File");
typeMapping.put("number", "Double");
languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"String",
"boolean",
"Boolean",
"Double",
"Int",
"Long",
"Float",
"Object",
"List",
"Seq",
"Map")
);
instantiationTypes.put("array", "ListBuffer");
instantiationTypes.put("map", "Map");
}
@Override
public String escapeReservedWord(String name) {
return "`" + name + "`";
}
@Override
public String apiFileFolder() {
return outputFolder + "/" + sourceFolder + "/" + apiPackage().replace('.', File.separatorChar);
}
public String modelFileFolder() {
return outputFolder + "/" + sourceFolder + "/" + modelPackage().replace('.', File.separatorChar);
}
@Override
public Map<String, Object> postProcessOperations(Map<String, Object> objs) {
if (registerNonStandardStatusCodes) {
try {
@SuppressWarnings("unchecked")
Map<String, ArrayList<CodegenOperation>> opsMap = (Map<String, ArrayList<CodegenOperation>>) objs.get("operations");
HashSet<Integer> unknownCodes = new HashSet<Integer>();
for (CodegenOperation operation : opsMap.get("operation")) {
for (CodegenResponse response : operation.responses) {
if ("default".equals(response.code))
continue;
try {
int code = Integer.parseInt(response.code);
if (code >= 600) {
unknownCodes.add(code);
}
} catch (NumberFormatException e) {
LOGGER.error("Status code is not an integer : response.code", e);
}
}
}
if (!unknownCodes.isEmpty()) {
additionalProperties.put("unknownStatusCodes", unknownCodes);
}
} catch (Exception e) {
LOGGER.error("Unable to find operations List", e);
}
}
return super.postProcessOperations(objs);
}
@Override
public String getTypeDeclaration(Property p) {
if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
Property inner = ap.getItems();
return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]";
} else if (p instanceof MapProperty) {
MapProperty mp = (MapProperty) p;
Property inner = mp.getAdditionalProperties();
return getSwaggerType(p) + "[String, " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@Override
public List<CodegenSecurity> fromSecurity(Map<String, SecuritySchemeDefinition> schemes) {
final List<CodegenSecurity> codegenSecurities = super.fromSecurity(schemes);
if (!removeOAuthSecurities)
return codegenSecurities;
// Remove OAuth securities
Iterator<CodegenSecurity> it = codegenSecurities.iterator();
while (it.hasNext()) {
final CodegenSecurity security = it.next();
if (security.isOAuth)
it.remove();
}
// Adapt 'hasMore'
it = codegenSecurities.iterator();
while (it.hasNext()) {
final CodegenSecurity security = it.next();
security.hasMore = it.hasNext();
}
if (codegenSecurities.isEmpty())
return null;
return codegenSecurities;
}
@Override
public String toOperationId(String operationId) {
return super.toOperationId(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, operationId));
}
private String formatIdentifier(String name, boolean capitalized) {
String identifier = camelize(name, true);
if (capitalized)
identifier = StringUtils.capitalize(identifier);
if (identifier.matches("[a-zA-Z_$][\\w_$]+") && !reservedWords.contains(identifier))
return identifier;
return escapeReservedWord(identifier);
}
@Override
public String toParamName(String name) { return formatIdentifier(name, false); }
@Override
public String toVarName(String name) {
return formatIdentifier(name, false);
}
@Override
public String toEnumName(CodegenProperty property)
{
return formatIdentifier(property.baseName, true);
}
@Override
public String getSwaggerType(Property p) {
String swaggerType = super.getSwaggerType(p);
String type;
if (typeMapping.containsKey(swaggerType)) {
type = typeMapping.get(swaggerType);
if (languageSpecificPrimitives.contains(type))
return toModelName(type);
} else
type = swaggerType;
return toModelName(type);
}
@Override
public String toInstantiationType(Property p) {
if (p instanceof MapProperty) {
MapProperty ap = (MapProperty) p;
String inner = getSwaggerType(ap.getAdditionalProperties());
return instantiationTypes.get("map") + "[String, " + inner + "]";
} else if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
String inner = getSwaggerType(ap.getItems());
return instantiationTypes.get("array") + "[" + inner + "]";
} else
return null;
}
public String toDefaultValue(Property p) {
if (!p.getRequired())
return "None";
if (p instanceof StringProperty)
return "null";
else if (p instanceof BooleanProperty)
return "null";
else if (p instanceof DateProperty)
return "null";
else if (p instanceof DateTimeProperty)
return "null";
else if (p instanceof DoubleProperty)
return "null";
else if (p instanceof FloatProperty)
return "null";
else if (p instanceof IntegerProperty)
return "null";
else if (p instanceof LongProperty)
return "null";
else if (p instanceof MapProperty) {
MapProperty ap = (MapProperty) p;
String inner = getSwaggerType(ap.getAdditionalProperties());
return "Map[String, " + inner + "].empty ";
} else if (p instanceof ArrayProperty) {
ArrayProperty ap = (ArrayProperty) p;
String inner = getSwaggerType(ap.getItems());
return "Seq[" + inner + "].empty ";
} else
return "null";
}
private static abstract class CustomLambda implements Mustache.Lambda {
@Override
public void execute(Template.Fragment frag, Writer out) throws IOException {
final StringWriter tempWriter = new StringWriter();
frag.execute(tempWriter);
out.write(formatFragment(tempWriter.toString()));
}
public abstract String formatFragment(String fragment);
}
private static class JavadocLambda extends CustomLambda {
@Override
public String formatFragment(String fragment) {
final String[] lines = fragment.split("\\r?\\n");
final StringBuilder sb = new StringBuilder();
sb.append(" /**\n");
for (String line : lines) {
sb.append(" * ").append(line).append("\n");
}
sb.append(" */\n");
return sb.toString();
}
}
private static class CapitalizeLambda extends CustomLambda {
@Override
public String formatFragment(String fragment) {
return StringUtils.capitalize(fragment);
}
}
private static class CamelizeLambda extends CustomLambda {
private final boolean capitalizeFirst;
public CamelizeLambda(boolean capitalizeFirst) {
this.capitalizeFirst = capitalizeFirst;
}
@Override
public String formatFragment(String fragment) {
return camelize(fragment, !capitalizeFirst);
}
}
private class EnumEntryLambda extends CustomLambda {
@Override
public String formatFragment(String fragment) {
return formatIdentifier(fragment, true);
}
}
}

View File

@ -19,3 +19,4 @@ com.wordnik.swagger.codegen.languages.StaticHtmlGenerator
com.wordnik.swagger.codegen.languages.SwaggerGenerator
com.wordnik.swagger.codegen.languages.SwaggerYamlGenerator
com.wordnik.swagger.codegen.languages.TizenClientCodegen
com.wordnik.swagger.codegen.languages.AkkaScalaClientCodegen

View File

@ -0,0 +1,43 @@
package {{package}}
{{#imports}}
import {{import}}
{{/imports}}
import {{invokerPackage}}._
import {{invokerPackage}}.CollectionFormats._
import {{invokerPackage}}.ApiKeyLocations._
{{#operations}}
object {{classname}} {
{{#operation}}
{{#javadocRenderer}}
{{>javadoc}}
{{/javadocRenderer}}
def {{operationId}}({{>methodParameters}}): ApiRequest[{{>operationReturnType}}] =
ApiRequest[{{>operationReturnType}}](ApiMethods.{{httpMethod.toUpperCase}}, "{{basePath}}", "{{path}}", {{#consumes.0}}"{{mediaType}}"{{/consumes.0}}{{^consumes}}"application/json"{{/consumes}})
{{#authMethods}}{{#isApiKey}}.withApiKey(apiKey, "{{keyParamName}}", {{#isKeyInQuery}}QUERY{{/isKeyInQuery}}{{#isKeyInHeader}}HEADER{{/isKeyInHeader}})
{{/isApiKey}}{{#isBasic}}.withCredentials(basicAuth)
{{/isBasic}}{{/authMethods}}{{#bodyParam}}.withBody({{paramName}})
{{/bodyParam}}{{#formParams}}.withFormParam({{>paramCreation}})
{{/formParams}}{{#queryParams}}.withQueryParam({{>paramCreation}})
{{/queryParams}}{{#pathParams}}.withPathParam({{>paramCreation}})
{{/pathParams}}{{#headerParams}}.withHeaderParam({{>paramCreation}})
{{/headerParams}}{{#responses}}{{^isWildcard}}{{#dataType}}.with{{>responseState}}Response[{{dataType}}]({{code}})
{{/dataType}}{{^dataType}}.with{{>responseState}}Response[Unit]({{code}})
{{/dataType}}{{/isWildcard}}{{/responses}}{{#responses}}{{#isWildcard}}{{#dataType}}.withDefault{{>responseState}}Response[{{dataType}}]
{{/dataType}}{{^dataType}}.withDefault{{>responseState}}Response[Unit]
{{/dataType}}{{/isWildcard}}{{/responses}}{{^responseHeaders.isEmpty}}
object {{#fnCapitalize}}{{operationId}}{{/fnCapitalize}}Headers { {{#responseHeaders}}
def {{name}}(r: ApiReturnWithHeaders) = r.get{{^isContainer}}{{baseType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}Header("{{baseName}}"){{/responseHeaders}}
}
{{/responseHeaders.isEmpty}}
{{/operation}}
{{#unknownStatusCodes}}
ApiInvoker.addCustomStatusCode({{value}}, isSuccess = false)
{{/unknownStatusCodes}}
}
{{/operations}}

View File

@ -0,0 +1,323 @@
package {{invokerPackage}}
import java.io.File
import java.security.cert.X509Certificate
import javax.net.ssl._
import akka.actor.ActorSystem
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.json4s.JsonAST.JString
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining
import spray.client.pipelining._
import spray.http.HttpEncodings._
import spray.http.HttpHeaders.{RawHeader, `Accept-Encoding`}
import spray.http.Uri.Query
import spray.http._
import spray.http.parser.HttpParser
import spray.httpx.encoding.{Deflate, Encoder, Gzip}
import spray.httpx.unmarshalling._
import spray.io.ClientSSLEngineProvider
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
import scala.util.control.NonFatal
object ApiInvoker {
def apply()(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer)
def apply(serializers: Traversable[Serializer[_]])(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer ++ serializers)
def apply(formats: Formats)(implicit system: ActorSystem): ApiInvoker = new ApiInvoker(formats)
case class CustomStatusCode(value: Int, reason: String = "Application-defined status code", isSuccess: Boolean = true)
def addCustomStatusCode(code: CustomStatusCode): Unit = addCustomStatusCode(code.value, code.reason, code.isSuccess)
def addCustomStatusCode(code: Int, reason: String = "Application defined code", isSuccess: Boolean = true) = {
StatusCodes.getForKey(code) foreach { c =>
StatusCodes.registerCustom(code, reason, reason, isSuccess, allowsEntity = true)
}
}
/**
* Allows request execution without calling apiInvoker.execute(request)
* request.response can be used to get a future of the ApiResponse generated.
* request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a
* Future will failed with a ClassCastException
* @param request the apiRequest to be executed
*/
implicit class ApiRequestImprovements[T](request: ApiRequest[T]) {
def response(invoker: ApiInvoker)(implicit ec: ExecutionContext, system: ActorSystem): Future[ApiResponse[T]] =
response(ec, system, invoker)
def response(implicit ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[ApiResponse[T]] =
invoker.execute(request)
def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[U] =
invoker.execute(request).map(_.content).mapTo[U]
}
/**
* Allows transformation from ApiMethod to spray HttpMethods
* @param method the ApiMethod to be converted
*/
implicit class ApiMethodExtensions(val method: ApiMethod) {
def toSprayMethod: HttpMethod = HttpMethods.getForKey(method.value).getOrElse(HttpMethods.GET)
}
case object DateTimeSerializer extends CustomSerializer[DateTime](format => ( {
case JString(s) =>
ISODateTimeFormat.dateTimeParser().parseDateTime(s)
}, {
case d: DateTime =>
JString(ISODateTimeFormat.dateTimeParser().print(d))
}))
}
class ApiInvoker(formats: Formats)(implicit system: ActorSystem) extends UntrustedSslContext with CustomContentTypes {
import io.swagger.client.core.ApiInvoker._
import io.swagger.client.core.ParametersMap._
implicit val ec = system.dispatcher
implicit val jsonFormats = formats
def settings = ApiSettings(system)
import spray.http.MessagePredicate._
val CompressionFilter = MessagePredicate({ _ => settings.compressionEnabled}) &&
Encoder.DefaultFilter &&
minEntitySize(settings.compressionSizeThreshold)
settings.customCodes.foreach(addCustomStatusCode)
private def addAuthentication(credentialsSeq: Seq[Credentials]): pipelining.RequestTransformer =
request =>
credentialsSeq.foldLeft(request) {
case (req, BasicCredentials(login, password)) =>
req ~> addCredentials(BasicHttpCredentials(login, password))
case (req, ApiKeyCredentials(keyValue, keyName, ApiKeyLocations.HEADER)) =>
req ~> addHeader(RawHeader(keyName, keyValue.value))
case (req, _) => req
}
private def addHeaders(headers: Map[String, Any]): pipelining.RequestTransformer = { request =>
val rawHeaders = for {
(name, value) <- headers.asFormattedParams
header = RawHeader(name, String.valueOf(value))
} yield header
request.withHeaders(rawHeaders.toList)
}
private def bodyPart(name: String, value: Any): BodyPart = {
value match {
case f: File =>
BodyPart(f, name)
case v: String =>
BodyPart(HttpEntity(String.valueOf(v)))
case NumericValue(v) =>
BodyPart(HttpEntity(String.valueOf(v)))
case m: ApiModel =>
BodyPart(HttpEntity(Serialization.write(m)))
}
}
private def formDataContent(request: ApiRequest[_]) = {
val params = request.formParams.asFormattedParams
if (params.isEmpty)
None
else
Some(
normalizedContentType(request.contentType).mediaType match {
case MediaTypes.`multipart/form-data` =>
MultipartFormData(params.map { case (name, value) => (name, bodyPart(name, value))})
case MediaTypes.`application/x-www-form-urlencoded` =>
FormData(params.mapValues(String.valueOf))
case m: MediaType => // Default : application/x-www-form-urlencoded.
FormData(params.mapValues(String.valueOf))
}
)
}
private def bodyContent(request: ApiRequest[_]): Option[Any] = {
request.bodyParam.map(Extraction.decompose).map(compact)
}
private def createRequest(uri: Uri, request: ApiRequest[_]): HttpRequest = {
val builder = new RequestBuilder(request.method.toSprayMethod)
val httpRequest = request.method.toSprayMethod match {
case HttpMethods.GET | HttpMethods.DELETE => builder.apply(uri)
case HttpMethods.POST | HttpMethods.PUT =>
formDataContent(request) orElse bodyContent(request) match {
case Some(c: FormData) =>
builder.apply(uri, c)
case Some(c: MultipartFormData) =>
builder.apply(uri, c)
case Some(c: String) =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), c))
case _ =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), " "))
}
case _ => builder.apply(uri)
}
httpRequest ~>
addHeaders(request.headerParams) ~>
addAuthentication(request.credentials) ~>
encode(Gzip(CompressionFilter))
}
def makeQuery(r: ApiRequest[_]): Query = {
r.credentials.foldLeft(r.queryParams) {
case (params, ApiKeyCredentials(key, keyName, ApiKeyLocations.QUERY)) =>
params + (keyName -> key.value)
case (params, _) => params
}.asFormattedParams
.mapValues(String.valueOf)
.foldRight[Query](Uri.Query.Empty) {
case ((name, value), acc) => acc.+:(name, value)
}
}
def makeUri(r: ApiRequest[_]): Uri = {
val opPath = r.operationPath.replaceAll("\\{format\\}", "json")
val opPathWithParams = r.pathParams.asFormattedParams
.mapValues(String.valueOf)
.foldLeft(opPath) {
case (path, (name, value)) => path.replaceAll(s"\\{$name\\}", value)
}
val query = makeQuery(r)
Uri(r.basePath + opPathWithParams).withQuery(query)
}
def execute[T](r: ApiRequest[T]): Future[ApiResponse[T]] = {
try {
implicit val timeout: Timeout = settings.connectionTimeout
val uri = makeUri(r)
val connector = HostConnectorSetup(
uri.authority.host.toString,
uri.effectivePort,
sslEncryption = "https".equals(uri.scheme),
defaultHeaders = settings.defaultHeaders ++ List(`Accept-Encoding`(gzip, deflate)))
val request = createRequest(uri, r)
for {
Http.HostConnectorInfo(hostConnector, _) <- IO(Http) ? connector
response <- hostConnector.ask(request).mapTo[HttpResponse]
} yield {
response ~> decode(Deflate) ~> decode(Gzip) ~> unmarshallApiResponse(r)
}
}
catch {
case NonFatal(x) => Future.failed(x)
}
}
def unmarshallApiResponse[T](request: ApiRequest[T])(response: HttpResponse): ApiResponse[T] = {
request.responseForCode(response.status.intValue) match {
case Some( (manifest: Manifest[T], state: ResponseState) ) =>
entityUnmarshaller(manifest)(response.entity) match {
case Right(value) ⇒
state match {
case ResponseState.Success =>
ApiResponse(response.status.intValue, value, response.headers.map(header => (header.name, header.value)).toMap)
case ResponseState.Error =>
throw new ApiError(response.status.intValue, "Error response received",
Some(value),
headers = response.headers.map(header => (header.name, header.value)).toMap)
}
case Left(MalformedContent(error, Some(cause))) ⇒
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString), cause)
case Left(MalformedContent(error, None)) ⇒
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString))
case Left(ContentExpected) ⇒
throw new ApiError(response.status.intValue, s"Unable to unmarshall empty response to [$manifest]", Some(response.entity.toString))
}
case _ => throw new ApiError(response.status.intValue, "Unexpected response code", Some(response.entity.toString))
}
}
def entityUnmarshaller[T](implicit mf: Manifest[T]): Unmarshaller[T] =
Unmarshaller[T](MediaTypes.`application/json`) {
case x: HttpEntity.NonEmpty ⇒
parse(x.asString(defaultCharset = HttpCharsets.`UTF-8`))
.noNulls
.camelizeKeys
.extract[T]
}
}
sealed trait CustomContentTypes {
def normalizedContentType(original: String): ContentType =
MediaTypes.forExtension(original) map (ContentType(_)) getOrElse parseContentType(original)
def parseContentType(contentType: String): ContentType = {
val contentTypeAsRawHeader = HttpHeaders.RawHeader("Content-Type", contentType)
val parsedContentTypeHeader = HttpParser.parseHeader(contentTypeAsRawHeader)
(parsedContentTypeHeader: @unchecked) match {
case Right(ct: HttpHeaders.`Content-Type`) =>
ct.contentType
case Left(error: ErrorInfo) =>
throw new IllegalArgumentException(
s"Error converting '$contentType' to a ContentType header: '${error.summary}'")
}
}
}
sealed trait UntrustedSslContext {
this: ApiInvoker =>
implicit lazy val trustfulSslContext: SSLContext = {
settings.alwaysTrustCertificates match {
case false =>
SSLContext.getDefault
case true =>
class IgnoreX509TrustManager extends X509TrustManager {
def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def getAcceptedIssuers = null
}
val context = SSLContext.getInstance("TLS")
context.init(null, Array(new IgnoreX509TrustManager), null)
context
}
}
implicit val clientSSLEngineProvider =
ClientSSLEngineProvider {
_ =>
val engine = trustfulSslContext.createSSLEngine()
engine.setUseClientMode(true)
engine
}
}

View File

@ -0,0 +1,50 @@
package {{invokerPackage}}
sealed trait ResponseState
object ResponseState {
case object Success extends ResponseState
case object Error extends ResponseState
}
case class ApiRequest[U](
// required fields
method: ApiMethod,
basePath: String,
operationPath: String,
contentType: String,
// optional fields
responses: Map[Int, (Manifest[_], ResponseState)] = Map.empty,
bodyParam: Option[Any] = None,
formParams: Map[String, Any] = Map.empty,
pathParams: Map[String, Any] = Map.empty,
queryParams: Map[String, Any] = Map.empty,
headerParams: Map[String, Any] = Map.empty,
credentials: Seq[Credentials] = List.empty) {
def withCredentials(cred: Credentials) = copy[U](credentials = credentials :+ cred)
def withApiKey(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) = withCredentials(ApiKeyCredentials(key, keyName, location))
def withSuccessResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Success)))
def withErrorResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Error)))
def withDefaultSuccessResponse[T](implicit m: Manifest[T]) = withSuccessResponse[T](0)
def withDefaultErrorResponse[T](implicit m: Manifest[T]) = withErrorResponse[T](0)
def responseForCode(statusCode: Int): Option[(Manifest[_], ResponseState)] = responses.get(statusCode) orElse responses.get(0)
def withoutBody() = copy[U](bodyParam = None)
def withBody(body: Any) = copy[U](bodyParam = Some(body))
def withFormParam(name: String, value: Any) = copy[U](formParams = formParams + (name -> value))
def withPathParam(name: String, value: Any) = copy[U](pathParams = pathParams + (name -> value))
def withQueryParam(name: String, value: Any) = copy[U](queryParams = queryParams + (name -> value))
def withHeaderParam(name: String, value: Any) = copy[U](headerParams = headerParams + (name -> value))
}

View File

@ -0,0 +1,32 @@
package {{invokerPackage}}
import java.util.concurrent.TimeUnit
import akka.actor.{ExtendedActorSystem, Extension, ExtensionKey}
import com.typesafe.config.Config
import io.swagger.client.core.ApiInvoker.CustomStatusCode
import spray.http.HttpHeaders.RawHeader
import scala.collection.JavaConversions._
import scala.concurrent.duration.FiniteDuration
class ApiSettings(config: Config) extends Extension {
def this(system: ExtendedActorSystem) = this(system.settings.config)
private def cfg = config.getConfig("io.swagger.client.apiRequest")
val alwaysTrustCertificates = cfg.getBoolean("trust-certificates")
val defaultHeaders = cfg.getConfig("default-headers").entrySet.toList.map(c => RawHeader(c.getKey, c.getValue.render))
val connectionTimeout = FiniteDuration(cfg.getDuration("connection-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
val compressionEnabled = cfg.getBoolean("compression.enabled")
val compressionSizeThreshold = cfg.getBytes("compression.size-threshold").toInt
val customCodes = cfg.getConfigList("custom-codes").toList.map { c => CustomStatusCode(
c.getInt("code"),
c.getString("reason"),
c.getBoolean("success"))
}
}
object ApiSettings extends ExtensionKey[ApiSettings]

View File

@ -0,0 +1,42 @@
package {{apiPackage}}
import {{modelPackage}}._
import org.json4s._
import scala.reflect.ClassTag
object EnumsSerializers {
def all = Seq[Serializer[_]](){{#models}}{{#model}}{{#hasEnums}}{{#vars}}{{#isEnum}} :+
new EnumNameSerializer({{classname}}Enums.{{datatypeWithEnum}}){{/isEnum}}{{/vars}}{{/hasEnums}}{{/model}}{{/models}}
private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: E)
extends Serializer[E#Value] {
import JsonDSL._
val EnumerationClass = classOf[E#Value]
def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), E#Value] = {
case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) => {
json match {
case JString(value) =>
enum.withName(value)
case value =>
throw new MappingException(s"Can't convert $value to $EnumerationClass")
}
}
}
private[this] def isValid(json: JValue) = json match {
case JString(value) if enum.values.exists(_.toString == value) => true
case _ => false
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: E#Value => i.toString
}
}
}

View File

@ -0,0 +1,21 @@
{{^notes.isEmpty}}
{{notes}}
{{/notes.isEmpty}}
Expected answers:
{{#responses}}
code {{code}} : {{dataType}} {{^message.isEmpty}}({{message}}){{/message.isEmpty}}{{^headers.isEmpty}}
Headers :{{#headers}}
{{baseName}} - {{description}}{{/headers}}{{/headers.isEmpty}}
{{/responses}}
{{#authMethods.0}}
Available security schemes:
{{#authMethods}}
{{name}} ({{type}})
{{/authMethods}}
{{/authMethods.0}}
{{#allParams}}
@param {{paramName}} {{description}}
{{/allParams}}

View File

@ -0,0 +1 @@
{{#allParams}}{{paramName}}: {{#required}}{{dataType}}{{/required}}{{^required}}{{#isContainer}}{{dataType}}{{/isContainer}}{{^isContainer}}Option[{{dataType}}]{{/isContainer}}{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{^required}}{{^isContainer}} = None{{/isContainer}}{{/required}}{{/defaultValue}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#authMethods.0}})(implicit {{#authMethods}}{{#isApiKey}}apiKey: ApiKeyValue{{/isApiKey}}{{#isBasic}}basicAuth: BasicCredentials{{/isBasic}}{{#hasMore}}, {{/hasMore}}{{/authMethods}}{{/authMethods.0}}

View File

@ -0,0 +1,30 @@
package {{package}}
import {{invokerPackage}}.ApiModel
import org.joda.time.DateTime
{{#models}}
{{#model}}
case class {{classname}} (
{{#vars}}{{#description}}/* {{{description}}} */
{{/description}}{{name}}: {{^required}}Option[{{/required}}{{^isEnum}}{{datatype}}{{/isEnum}}{{#isEnum}}{{classname}}Enums.{{datatypeWithEnum}}{{/isEnum}}{{^required}}]{{/required}}{{#hasMore}},{{/hasMore}}{{^hasMore}}){{/hasMore}}
{{/vars}} extends ApiModel
{{#hasEnums}}
object {{classname}}Enums {
{{#vars}}{{#isEnum}}type {{datatypeWithEnum}} = {{datatypeWithEnum}}.Value
{{/isEnum}}{{/vars}}
{{#vars}}{{#isEnum}}object {{datatypeWithEnum}} extends Enumeration {
{{#_enum}}
val {{#fnEnumEntry}}{{.}}{{/fnEnumEntry}} = Value("{{.}}")
{{/_enum}}
}
{{/isEnum}}{{/vars}}
}
{{/hasEnums}}
{{/model}}
{{/models}}

View File

@ -0,0 +1 @@
{{#onlyOneSuccess}}{{^defaultResponse}}Unit{{/defaultResponse}}{{#defaultResponse}}{{#responses}}{{#isDefault}}{{#dataType}}{{dataType}}{{/dataType}}{{^dataType}}Unit{{/dataType}}{{/isDefault}}{{/responses}}{{/defaultResponse}}{{/onlyOneSuccess}}{{^onlyOneSuccess}}{{#responses}}{{#-first}}{{^hasMore}}{{#dataType}}{{dataType}}{{/dataType}}{{^dataType}}Unit{{/dataType}}{{/hasMore}}{{#hasMore}}Any{{/hasMore}}{{/-first}}{{/responses}}{{/onlyOneSuccess}}

View File

@ -0,0 +1 @@
"{{baseName}}", {{#isContainer}}ArrayValues({{paramName}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}

View File

@ -0,0 +1,227 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>{{groupId}}</groupId>
<artifactId>{{artifactId}}</artifactId>
<packaging>jar</packaging>
<name>{{artifactId}}</name>
<version>{{artifactVersion}}</version>
<prerequisites>
<maven>2.2.0</maven>
</prerequisites>
<pluginRepositories>
<pluginRepository>
<id>maven-mongodb-plugin-repo</id>
<name>maven mongodb plugin repository</name>
<url>http://maven-mongodb-plugin.googlecode.com/svn/maven/repo</url>
<layout>default</layout>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<systemProperties>
<property>
<name>loggerPath</name>
<value>conf/log4j.properties</value>
</property>
</systemProperties>
<argLine>-Xms512m -Xmx1500m</argLine>
<parallel>methods</parallel>
<forkMode>pertest</forkMode>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- attach test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add_sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>
src/main/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add_test_sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>
src/test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>
1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala-maven-plugin-version}</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-feature</arg>
</args>
<jvmArgs>
<jvmArg>-Xms128m</jvmArg>
<jvmArg>-Xmx1500m</jvmArg>
</jvmArgs>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>${scala-version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala-version}</version>
</dependency>
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-core</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.10</artifactId>
<version>${scala-test-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time-version}</version>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-convert</artifactId>
<version>${joda-version}</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.10</artifactId>
<version>${akka-version}</version>
</dependency>
<dependency>
<groupId>io.spray</groupId>
<artifactId>spray-client</artifactId>
<version>${spray-version}</version>
</dependency>
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_2.10</artifactId>
<version>${json4s-jackson-version}</version>
</dependency>
</dependencies>
<properties>
<scala-version>2.10.4</scala-version>
<json4s-jackson-version>3.2.11</json4s-jackson-version>
<json4s-ext-version>3.2.11</json4s-ext-version>
<spray-version>1.3.1</spray-version>
<akka-version>2.3.9</akka-version>
<joda-version>1.2</joda-version>
<joda-time-version>2.2</joda-time-version>
<swagger-core-version>1.5.0-M1</swagger-core-version>
<maven-plugin.version>1.0.0</maven-plugin.version>
<junit-version>4.8.1</junit-version>
<scala-maven-plugin-version>3.1.5</scala-maven-plugin-version>
<scala-test-version>2.1.3</scala-test-version>
</properties>
</project>

View File

@ -0,0 +1,24 @@
{{configKeyPath}} {
{{configKey}} {
compression {
enabled: false
size-threshold: 0
}
trust-certificates: true
connection-timeout: {{defaultTimeout}}ms
default-headers {
"userAgent": "{{artifactId}}_{{artifactVersion}}"
}
// let you define custom http status code, as in :
// { code: 601, reason: "some custom http status code", success: false }
custom-codes : []
}
}
spray.can.host-connector.max-redirects = 10

View File

@ -0,0 +1,166 @@
package {{invokerPackage}}
import java.io.File
import java.net.URLEncoder
import scala.util.Try
sealed trait ApiReturnWithHeaders {
def headers: Map[String, String]
def header(name: String): Option[String] = headers.get(name)
def getStringHeader(name: String) = header(name)
def getIntHeader(name: String) = castedHeader(name, java.lang.Integer.parseInt)
def getLongHeader(name: String) = castedHeader(name, java.lang.Long.parseLong)
def getFloatHeader(name: String) = castedHeader(name, java.lang.Float.parseFloat)
def getDoubleHeader(name: String) = castedHeader(name, java.lang.Double.parseDouble)
def getBooleanHeader(name: String) = castedHeader(name, java.lang.Boolean.parseBoolean)
private def castedHeader[U](name: String, conversion: String => U): Option[U] = { Try { header(name).map( conversion ) }.get }
}
sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty)
extends ApiReturnWithHeaders
sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty)
extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause)
with ApiReturnWithHeaders
sealed case class ApiMethod(value: String)
object ApiMethods {
val CONNECT = ApiMethod("CONNECT")
val DELETE = ApiMethod("DELETE")
val GET = ApiMethod("GET")
val HEAD = ApiMethod("HEAD")
val OPTIONS = ApiMethod("OPTIONS")
val PATCH = ApiMethod("PATCH")
val POST = ApiMethod("POST")
val PUT = ApiMethod("PUT")
val TRACE = ApiMethod("TRACE")
}
/**
* This trait needs to be added to any model defined by the api.
*/
trait ApiModel
/**
* Single trait defining a credential that can be transformed to a paramName / paramValue tupple
*/
sealed trait Credentials {
def asQueryParam: Option[(String, String)] = None
}
sealed case class BasicCredentials(user: String, password: String) extends Credentials
sealed case class ApiKeyCredentials(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) extends Credentials {
override def asQueryParam: Option[(String, String)] = location match {
case ApiKeyLocations.QUERY => Some((keyName, key.value))
case _ => None
}
}
sealed case class ApiKeyValue(value: String)
sealed trait ApiKeyLocation
object ApiKeyLocations {
case object QUERY extends ApiKeyLocation
case object HEADER extends ApiKeyLocation
}
/**
* Case class used to unapply numeric values only in pattern matching
* @param value the string representation of the numeric value
*/
sealed case class NumericValue(value: String) {
override def toString = value
}
object NumericValue {
def unapply(n: Any): Option[NumericValue] = n match {
case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n)))
case _ => None
}
}
/**
* Used for params being arrays
*/
sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV)
object ArrayValues {
def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues =
ArrayValues(values.getOrElse(Seq.empty), format)
def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV)
}
/**
* Defines how arrays should be rendered in query strings.
*/
sealed trait CollectionFormat
trait MergedArrayFormat extends CollectionFormat {
def separator: String
}
object CollectionFormats {
case object CSV extends MergedArrayFormat {
override val separator = ","
}
case object TSV extends MergedArrayFormat {
override val separator = "\t"
}
case object SSV extends MergedArrayFormat {
override val separator = " "
}
case object PIPES extends MergedArrayFormat {
override val separator = "|"
}
case object MULTI extends CollectionFormat
}
object ParametersMap {
/**
* Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples,
* with valid url-encoding, arrays handling, files preservation, ...
*/
implicit class ParametersMapImprovements(val m: Map[String, Any]) {
def asFormattedParamsList = m.toList.flatMap(formattedParams)
def asFormattedParams = m.flatMap(formattedParams)
private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20")
private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2)
private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match {
case arr: ArrayValues =>
arr.format match {
case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _))
case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator)))
}
case None => Seq.empty
case Some(opt) =>
formattedParams(name, opt)
case s: Seq[Any] =>
formattedParams(name, ArrayValues(s))
case v: String => Seq((name, urlEncode(v)))
case NumericValue(v) => Seq((name, urlEncode(v)))
case f: File => Seq((name, f))
case m: ApiModel => Seq((name, m))
}
}
}

View File

@ -0,0 +1 @@
{{#onlyOneSuccess}}{{#isDefault}}Success{{/isDefault}}{{^isDefault}}Error{{/isDefault}}{{/onlyOneSuccess}}{{^onlyOneSuccess}}Success{{/onlyOneSuccess}}

View File

@ -0,0 +1,93 @@
{
"swagger": "2.0",
"info": {
"description": "This is a sample server Petstore server. You can find out more about Swagger at <a href=\"http://swagger.io\">http://swagger.io</a> or on irc.freenode.net, #swagger. For this sample, you can use the api key \"special-key\" to test the authorization filters",
"version": "1.0.0",
"title": "Swagger Petstore",
"termsOfService": "http://helloreverb.com/terms/",
"contact": {
"email": "apiteam@wordnik.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"host": "petstore.swagger.io",
"basePath": "/v2",
"schemes": [
"http"
],
"paths": {
"/tests/requiredParams": {
"get": {
"tags": [
"tests"
],
"summary": "Operation with required parameters",
"description": "",
"operationId": "requiredParams",
"produces": [
"application/json"
],
"parameters": [
{
"name": "param1",
"in": "formData",
"description": "Some required parameter",
"required": true,
"type": "integer",
"format": "int64"
},
{
"name": "param2",
"in": "formData",
"description": "Some optional parameter",
"required": false,
"type": "string"
}
],
"responses": {
"200": {
"description": "successful operation. Retuning a simple int.",
"schema": {
"type": "integer",
"format": "int64"
}
}
}
}
}
},
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
},
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
},
"definitions": {
"CustomModel": {
"required": ["id"],
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"name": {
"type": "string",
"example": "doggie"
}
}
}
}
}

View File

@ -1,15 +1,9 @@
import com.wordnik.swagger.models._
import com.wordnik.swagger.util.Json
import io.swagger.parser._
import com.wordnik.swagger.codegen.DefaultCodegen
import com.wordnik.swagger.models.properties.Property
import io.swagger.parser._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.FlatSpec
import org.scalatest.Matchers
import scala.collection.JavaConverters._
import org.scalatest.{FlatSpec, Matchers}
@RunWith(classOf[JUnitRunner])
class CodegenTest extends FlatSpec with Matchers {
@ -95,6 +89,32 @@ class CodegenTest extends FlatSpec with Matchers {
statusParam.hasMore should be (null)
}
it should "handle required parameters from a 2.0 spec as required when figuring out Swagger types" in {
val model = new SwaggerParser()
.read("src/test/resources/2_0/requiredTest.json")
val codegen = new DefaultCodegen() {
override def getSwaggerType(p: Property) = Option(p) match {
case Some(property) if !property.getRequired =>
"Optional<" + super.getSwaggerType(p) + ">"
case other => super.getSwaggerType(p)
}
}
val path = "/tests/requiredParams"
val p = model.getPaths().get(path).getGet()
val op = codegen.fromOperation(path, "get", p, model.getDefinitions)
val formParams = op.formParams
formParams.size should be(2)
val requiredParam = formParams.get(0)
requiredParam.dataType should be("Long")
val optionalParam = formParams.get(1)
optionalParam.dataType should be("Optional<string>")
op.returnType should be("Long")
}
it should "select main response from a 2.0 spec using the lowest 2XX code" in {
val model = new SwaggerParser()
.read("src/test/resources/2_0/responseSelectionTest.json")

View File

@ -0,0 +1,227 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wordnik</groupId>
<artifactId>swagger-client</artifactId>
<packaging>jar</packaging>
<name>swagger-client</name>
<version>1.0.0</version>
<prerequisites>
<maven>2.2.0</maven>
</prerequisites>
<pluginRepositories>
<pluginRepository>
<id>maven-mongodb-plugin-repo</id>
<name>maven mongodb plugin repository</name>
<url>http://maven-mongodb-plugin.googlecode.com/svn/maven/repo</url>
<layout>default</layout>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<systemProperties>
<property>
<name>loggerPath</name>
<value>conf/log4j.properties</value>
</property>
</systemProperties>
<argLine>-Xms512m -Xmx1500m</argLine>
<parallel>methods</parallel>
<forkMode>pertest</forkMode>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- attach test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add_sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>
src/main/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add_test_sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>
src/test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>
1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala-maven-plugin-version}</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-feature</arg>
</args>
<jvmArgs>
<jvmArg>-Xms128m</jvmArg>
<jvmArg>-Xmx1500m</jvmArg>
</jvmArgs>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>${scala-version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala-version}</version>
</dependency>
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-core</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.10</artifactId>
<version>${scala-test-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time-version}</version>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-convert</artifactId>
<version>${joda-version}</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.10</artifactId>
<version>${akka-version}</version>
</dependency>
<dependency>
<groupId>io.spray</groupId>
<artifactId>spray-client</artifactId>
<version>${spray-version}</version>
</dependency>
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_2.10</artifactId>
<version>${json4s-jackson-version}</version>
</dependency>
</dependencies>
<properties>
<scala-version>2.10.4</scala-version>
<json4s-jackson-version>3.2.11</json4s-jackson-version>
<json4s-ext-version>3.2.11</json4s-ext-version>
<spray-version>1.3.1</spray-version>
<akka-version>2.3.9</akka-version>
<joda-version>1.2</joda-version>
<joda-time-version>2.2</joda-time-version>
<swagger-core-version>1.5.0-M1</swagger-core-version>
<maven-plugin.version>1.0.0</maven-plugin.version>
<junit-version>4.8.1</junit-version>
<scala-maven-plugin-version>3.1.5</scala-maven-plugin-version>
<scala-test-version>2.1.3</scala-test-version>
</properties>
</project>

View File

@ -0,0 +1,24 @@
io.swagger.client {
apiRequest {
compression {
enabled: false
size-threshold: 0
}
trust-certificates: true
connection-timeout: 5000ms
default-headers {
"userAgent": "swagger-client_1.0.0"
}
// let you define custom http status code, as in :
// { code: 601, reason: "some custom http status code", success: false }
custom-codes : []
}
}
spray.can.host-connector.max-redirects = 10

View File

@ -0,0 +1,43 @@
package io.swagger.client.api
import io.swagger.client.model._
import org.json4s._
import scala.reflect.ClassTag
object EnumsSerializers {
def all = Seq[Serializer[_]]() :+
new EnumNameSerializer(PetEnums.Status) :+
new EnumNameSerializer(OrderEnums.Status)
private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: E)
extends Serializer[E#Value] {
import JsonDSL._
val EnumerationClass = classOf[E#Value]
def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), E#Value] = {
case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) => {
json match {
case JString(value) =>
enum.withName(value)
case value =>
throw new MappingException(s"Can't convert $value to $EnumerationClass")
}
}
}
private[this] def isValid(json: JValue) = json match {
case JString(value) if enum.values.exists(_.toString == value) => true
case _ => false
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: E#Value => i.toString
}
}
}

View File

@ -0,0 +1,139 @@
package io.swagger.client.api
import io.swagger.client.model.Pet
import java.io.File
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object PetApi {
/**
*
* Expected answers:
* code 405 : (Validation exception)
* code 404 : (Pet not found)
* code 400 : (Invalid ID supplied)
*
* @param body Pet object that needs to be added to the store
*/
def updatePet(body: Option[Pet] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.PUT, "http://petstore.swagger.io/v2", "/pet", "application/json")
.withBody(body)
.withErrorResponse[Unit](405)
.withErrorResponse[Unit](404)
.withErrorResponse[Unit](400)
/**
*
* Expected answers:
* code 405 : (Invalid input)
*
* @param body Pet object that needs to be added to the store
*/
def addPet(body: Option[Pet] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/pet", "application/json")
.withBody(body)
.withErrorResponse[Unit](405)
/**
* Multiple status values can be provided with comma seperated strings
*
* Expected answers:
* code 200 : Seq[Pet] (successful operation)
* code 400 : (Invalid status value)
*
* @param status Status values that need to be considered for filter
*/
def findPetsByStatus(status: Seq[String]): ApiRequest[Seq[Pet]] =
ApiRequest[Seq[Pet]](ApiMethods.GET, "http://petstore.swagger.io/v2", "/pet/findByStatus", "application/json")
.withQueryParam("status", ArrayValues(status, MULTI))
.withSuccessResponse[Seq[Pet]](200)
.withErrorResponse[Unit](400)
/**
* Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.
*
* Expected answers:
* code 200 : Seq[Pet] (successful operation)
* code 400 : (Invalid tag value)
*
* @param tags Tags to filter by
*/
def findPetsByTags(tags: Seq[String]): ApiRequest[Seq[Pet]] =
ApiRequest[Seq[Pet]](ApiMethods.GET, "http://petstore.swagger.io/v2", "/pet/findByTags", "application/json")
.withQueryParam("tags", ArrayValues(tags, MULTI))
.withSuccessResponse[Seq[Pet]](200)
.withErrorResponse[Unit](400)
/**
* Returns a pet when ID &lt; 10. ID &gt; 10 or nonintegers will simulate API error conditions
*
* Expected answers:
* code 404 : (Pet not found)
* code 200 : Pet (successful operation)
* code 400 : (Invalid ID supplied)
*
* Available security schemes:
* api_key (apiKey)
*
* @param petId ID of pet that needs to be fetched
*/
def getPetById(petId: Long)(implicit apiKey: ApiKeyValue): ApiRequest[Pet] =
ApiRequest[Pet](ApiMethods.GET, "http://petstore.swagger.io/v2", "/pet/{petId}", "application/json")
.withApiKey(apiKey, "api_key", HEADER)
.withPathParam("petId", petId)
.withErrorResponse[Unit](404)
.withSuccessResponse[Pet](200)
.withErrorResponse[Unit](400)
/**
*
* Expected answers:
* code 405 : (Invalid input)
*
* @param petId ID of pet that needs to be updated
* @param name Updated name of the pet
* @param status Updated status of the pet
*/
def updatePetWithForm(petId: String, name: Option[String] = None, status: Option[String] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/pet/{petId}", "application/x-www-form-urlencoded")
.withFormParam("name", name)
.withFormParam("status", status)
.withPathParam("petId", petId)
.withErrorResponse[Unit](405)
/**
*
* Expected answers:
* code 400 : (Invalid pet value)
*
* @param apiKey
* @param petId Pet id to delete
*/
def deletePet(apiKey: Option[String] = None, petId: Long): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.DELETE, "http://petstore.swagger.io/v2", "/pet/{petId}", "application/json")
.withPathParam("petId", petId)
.withHeaderParam("api_key", apiKey)
.withErrorResponse[Unit](400)
/**
*
* Expected answers:
* code 0 : (successful operation)
*
* @param petId ID of pet to update
* @param additionalMetadata Additional data to pass to server
* @param file file to upload
*/
def uploadFile(petId: Long, additionalMetadata: Option[String] = None, file: Option[File] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/pet/{petId}/uploadImage", "multipart/form-data")
.withFormParam("additionalMetadata", additionalMetadata)
.withFormParam("file", file)
.withPathParam("petId", petId)
.withDefaultSuccessResponse[Unit]
}

View File

@ -0,0 +1,73 @@
package io.swagger.client.api
import io.swagger.client.model.Order
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object StoreApi {
/**
* Returns a map of status codes to quantities
*
* Expected answers:
* code 200 : Map[String, Int] (successful operation)
*
* Available security schemes:
* api_key (apiKey)
*/
def getInventory()(implicit apiKey: ApiKeyValue): ApiRequest[Map[String, Int]] =
ApiRequest[Map[String, Int]](ApiMethods.GET, "http://petstore.swagger.io/v2", "/store/inventory", "application/json")
.withApiKey(apiKey, "api_key", HEADER)
.withSuccessResponse[Map[String, Int]](200)
/**
*
* Expected answers:
* code 200 : Order (successful operation)
* code 400 : (Invalid Order)
*
* @param body order placed for purchasing the pet
*/
def placeOrder(body: Option[Order] = None): ApiRequest[Order] =
ApiRequest[Order](ApiMethods.POST, "http://petstore.swagger.io/v2", "/store/order", "application/json")
.withBody(body)
.withSuccessResponse[Order](200)
.withErrorResponse[Unit](400)
/**
* For valid response try integer IDs with value &lt;= 5 or &gt; 10. Other values will generated exceptions
*
* Expected answers:
* code 404 : (Order not found)
* code 200 : Order (successful operation)
* code 400 : (Invalid ID supplied)
*
* @param orderId ID of pet that needs to be fetched
*/
def getOrderById(orderId: String): ApiRequest[Order] =
ApiRequest[Order](ApiMethods.GET, "http://petstore.swagger.io/v2", "/store/order/{orderId}", "application/json")
.withPathParam("orderId", orderId)
.withErrorResponse[Unit](404)
.withSuccessResponse[Order](200)
.withErrorResponse[Unit](400)
/**
* For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors
*
* Expected answers:
* code 404 : (Order not found)
* code 400 : (Invalid ID supplied)
*
* @param orderId ID of the order that needs to be deleted
*/
def deleteOrder(orderId: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.DELETE, "http://petstore.swagger.io/v2", "/store/order/{orderId}", "application/json")
.withPathParam("orderId", orderId)
.withErrorResponse[Unit](404)
.withErrorResponse[Unit](400)
}

View File

@ -0,0 +1,123 @@
package io.swagger.client.api
import io.swagger.client.model.User
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object UserApi {
/**
* This can only be done by the logged in user.
*
* Expected answers:
* code 0 : (successful operation)
*
* @param body Created user object
*/
def createUser(body: Option[User] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/user", "application/json")
.withBody(body)
.withDefaultSuccessResponse[Unit]
/**
*
* Expected answers:
* code 0 : (successful operation)
*
* @param body List of user object
*/
def createUsersWithArrayInput(body: Seq[User]): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/user/createWithArray", "application/json")
.withBody(body)
.withDefaultSuccessResponse[Unit]
/**
*
* Expected answers:
* code 0 : (successful operation)
*
* @param body List of user object
*/
def createUsersWithListInput(body: Seq[User]): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "http://petstore.swagger.io/v2", "/user/createWithList", "application/json")
.withBody(body)
.withDefaultSuccessResponse[Unit]
/**
*
* Expected answers:
* code 200 : String (successful operation)
* code 400 : (Invalid username/password supplied)
*
* @param username The user name for login
* @param password The password for login in clear text
*/
def loginUser(username: Option[String] = None, password: Option[String] = None): ApiRequest[String] =
ApiRequest[String](ApiMethods.GET, "http://petstore.swagger.io/v2", "/user/login", "application/json")
.withQueryParam("username", username)
.withQueryParam("password", password)
.withSuccessResponse[String](200)
.withErrorResponse[Unit](400)
/**
*
* Expected answers:
* code 0 : (successful operation)
*/
def logoutUser(): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "http://petstore.swagger.io/v2", "/user/logout", "application/json")
.withDefaultSuccessResponse[Unit]
/**
*
* Expected answers:
* code 404 : (User not found)
* code 200 : User (successful operation)
* code 400 : (Invalid username supplied)
*
* @param username The name that needs to be fetched. Use user1 for testing.
*/
def getUserByName(username: String): ApiRequest[User] =
ApiRequest[User](ApiMethods.GET, "http://petstore.swagger.io/v2", "/user/{username}", "application/json")
.withPathParam("username", username)
.withErrorResponse[Unit](404)
.withSuccessResponse[User](200)
.withErrorResponse[Unit](400)
/**
* This can only be done by the logged in user.
*
* Expected answers:
* code 404 : (User not found)
* code 400 : (Invalid user supplied)
*
* @param username name that need to be deleted
* @param body Updated user object
*/
def updateUser(username: String, body: Option[User] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.PUT, "http://petstore.swagger.io/v2", "/user/{username}", "application/json")
.withBody(body)
.withPathParam("username", username)
.withErrorResponse[Unit](404)
.withErrorResponse[Unit](400)
/**
* This can only be done by the logged in user.
*
* Expected answers:
* code 404 : (User not found)
* code 400 : (Invalid username supplied)
*
* @param username The name that needs to be deleted
*/
def deleteUser(username: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.DELETE, "http://petstore.swagger.io/v2", "/user/{username}", "application/json")
.withPathParam("username", username)
.withErrorResponse[Unit](404)
.withErrorResponse[Unit](400)
}

View File

@ -0,0 +1,323 @@
package io.swagger.client.core
import java.io.File
import java.security.cert.X509Certificate
import javax.net.ssl._
import akka.actor.ActorSystem
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.json4s.JsonAST.JString
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining
import spray.client.pipelining._
import spray.http.HttpEncodings._
import spray.http.HttpHeaders.{RawHeader, `Accept-Encoding`}
import spray.http.Uri.Query
import spray.http._
import spray.http.parser.HttpParser
import spray.httpx.encoding.{Deflate, Encoder, Gzip}
import spray.httpx.unmarshalling._
import spray.io.ClientSSLEngineProvider
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
import scala.util.control.NonFatal
object ApiInvoker {
def apply()(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer)
def apply(serializers: Traversable[Serializer[_]])(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer ++ serializers)
def apply(formats: Formats)(implicit system: ActorSystem): ApiInvoker = new ApiInvoker(formats)
case class CustomStatusCode(value: Int, reason: String = "Application-defined status code", isSuccess: Boolean = true)
def addCustomStatusCode(code: CustomStatusCode): Unit = addCustomStatusCode(code.value, code.reason, code.isSuccess)
def addCustomStatusCode(code: Int, reason: String = "Application defined code", isSuccess: Boolean = true) = {
StatusCodes.getForKey(code) foreach { c =>
StatusCodes.registerCustom(code, reason, reason, isSuccess, allowsEntity = true)
}
}
/**
* Allows request execution without calling apiInvoker.execute(request)
* request.response can be used to get a future of the ApiResponse generated.
* request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a
* Future will failed with a ClassCastException
* @param request the apiRequest to be executed
*/
implicit class ApiRequestImprovements[T](request: ApiRequest[T]) {
def response(invoker: ApiInvoker)(implicit ec: ExecutionContext, system: ActorSystem): Future[ApiResponse[T]] =
response(ec, system, invoker)
def response(implicit ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[ApiResponse[T]] =
invoker.execute(request)
def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[U] =
invoker.execute(request).map(_.content).mapTo[U]
}
/**
* Allows transformation from ApiMethod to spray HttpMethods
* @param method the ApiMethod to be converted
*/
implicit class ApiMethodExtensions(val method: ApiMethod) {
def toSprayMethod: HttpMethod = HttpMethods.getForKey(method.value).getOrElse(HttpMethods.GET)
}
case object DateTimeSerializer extends CustomSerializer[DateTime](format => ( {
case JString(s) =>
ISODateTimeFormat.dateTimeParser().parseDateTime(s)
}, {
case d: DateTime =>
JString(ISODateTimeFormat.dateTimeParser().print(d))
}))
}
class ApiInvoker(formats: Formats)(implicit system: ActorSystem) extends UntrustedSslContext with CustomContentTypes {
import io.swagger.client.core.ApiInvoker._
import io.swagger.client.core.ParametersMap._
implicit val ec = system.dispatcher
implicit val jsonFormats = formats
def settings = ApiSettings(system)
import spray.http.MessagePredicate._
val CompressionFilter = MessagePredicate({ _ => settings.compressionEnabled}) &&
Encoder.DefaultFilter &&
minEntitySize(settings.compressionSizeThreshold)
settings.customCodes.foreach(addCustomStatusCode)
private def addAuthentication(credentialsSeq: Seq[Credentials]): pipelining.RequestTransformer =
request =>
credentialsSeq.foldLeft(request) {
case (req, BasicCredentials(login, password)) =>
req ~> addCredentials(BasicHttpCredentials(login, password))
case (req, ApiKeyCredentials(keyValue, keyName, ApiKeyLocations.HEADER)) =>
req ~> addHeader(RawHeader(keyName, keyValue.value))
case (req, _) => req
}
private def addHeaders(headers: Map[String, Any]): pipelining.RequestTransformer = { request =>
val rawHeaders = for {
(name, value) <- headers.asFormattedParams
header = RawHeader(name, String.valueOf(value))
} yield header
request.withHeaders(rawHeaders.toList)
}
private def bodyPart(name: String, value: Any): BodyPart = {
value match {
case f: File =>
BodyPart(f, name)
case v: String =>
BodyPart(HttpEntity(String.valueOf(v)))
case NumericValue(v) =>
BodyPart(HttpEntity(String.valueOf(v)))
case m: ApiModel =>
BodyPart(HttpEntity(Serialization.write(m)))
}
}
private def formDataContent(request: ApiRequest[_]) = {
val params = request.formParams.asFormattedParams
if (params.isEmpty)
None
else
Some(
normalizedContentType(request.contentType).mediaType match {
case MediaTypes.`multipart/form-data` =>
MultipartFormData(params.map { case (name, value) => (name, bodyPart(name, value))})
case MediaTypes.`application/x-www-form-urlencoded` =>
FormData(params.mapValues(String.valueOf))
case m: MediaType => // Default : application/x-www-form-urlencoded.
FormData(params.mapValues(String.valueOf))
}
)
}
private def bodyContent(request: ApiRequest[_]): Option[Any] = {
request.bodyParam.map(Extraction.decompose).map(compact)
}
private def createRequest(uri: Uri, request: ApiRequest[_]): HttpRequest = {
val builder = new RequestBuilder(request.method.toSprayMethod)
val httpRequest = request.method.toSprayMethod match {
case HttpMethods.GET | HttpMethods.DELETE => builder.apply(uri)
case HttpMethods.POST | HttpMethods.PUT =>
formDataContent(request) orElse bodyContent(request) match {
case Some(c: FormData) =>
builder.apply(uri, c)
case Some(c: MultipartFormData) =>
builder.apply(uri, c)
case Some(c: String) =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), c))
case _ =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), " "))
}
case _ => builder.apply(uri)
}
httpRequest ~>
addHeaders(request.headerParams) ~>
addAuthentication(request.credentials) ~>
encode(Gzip(CompressionFilter))
}
def makeQuery(r: ApiRequest[_]): Query = {
r.credentials.foldLeft(r.queryParams) {
case (params, ApiKeyCredentials(key, keyName, ApiKeyLocations.QUERY)) =>
params + (keyName -> key.value)
case (params, _) => params
}.asFormattedParams
.mapValues(String.valueOf)
.foldRight[Query](Uri.Query.Empty) {
case ((name, value), acc) => acc.+:(name, value)
}
}
def makeUri(r: ApiRequest[_]): Uri = {
val opPath = r.operationPath.replaceAll("\\{format\\}", "json")
val opPathWithParams = r.pathParams.asFormattedParams
.mapValues(String.valueOf)
.foldLeft(opPath) {
case (path, (name, value)) => path.replaceAll(s"\\{$name\\}", value)
}
val query = makeQuery(r)
Uri(r.basePath + opPathWithParams).withQuery(query)
}
def execute[T](r: ApiRequest[T]): Future[ApiResponse[T]] = {
try {
implicit val timeout: Timeout = settings.connectionTimeout
val uri = makeUri(r)
val connector = HostConnectorSetup(
uri.authority.host.toString,
uri.effectivePort,
sslEncryption = "https".equals(uri.scheme),
defaultHeaders = settings.defaultHeaders ++ List(`Accept-Encoding`(gzip, deflate)))
val request = createRequest(uri, r)
for {
Http.HostConnectorInfo(hostConnector, _) <- IO(Http) ? connector
response <- hostConnector.ask(request).mapTo[HttpResponse]
} yield {
response ~> decode(Deflate) ~> decode(Gzip) ~> unmarshallApiResponse(r)
}
}
catch {
case NonFatal(x) => Future.failed(x)
}
}
def unmarshallApiResponse[T](request: ApiRequest[T])(response: HttpResponse): ApiResponse[T] = {
request.responseForCode(response.status.intValue) match {
case Some( (manifest: Manifest[T], state: ResponseState) ) =>
entityUnmarshaller(manifest)(response.entity) match {
case Right(value)
state match {
case ResponseState.Success =>
ApiResponse(response.status.intValue, value, response.headers.map(header => (header.name, header.value)).toMap)
case ResponseState.Error =>
throw new ApiError(response.status.intValue, "Error response received",
Some(value),
headers = response.headers.map(header => (header.name, header.value)).toMap)
}
case Left(MalformedContent(error, Some(cause)))
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString), cause)
case Left(MalformedContent(error, None))
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString))
case Left(ContentExpected)
throw new ApiError(response.status.intValue, s"Unable to unmarshall empty response to [$manifest]", Some(response.entity.toString))
}
case _ => throw new ApiError(response.status.intValue, "Unexpected response code", Some(response.entity.toString))
}
}
def entityUnmarshaller[T](implicit mf: Manifest[T]): Unmarshaller[T] =
Unmarshaller[T](MediaTypes.`application/json`) {
case x: HttpEntity.NonEmpty
parse(x.asString(defaultCharset = HttpCharsets.`UTF-8`))
.noNulls
.camelizeKeys
.extract[T]
}
}
sealed trait CustomContentTypes {
def normalizedContentType(original: String): ContentType =
MediaTypes.forExtension(original) map (ContentType(_)) getOrElse parseContentType(original)
def parseContentType(contentType: String): ContentType = {
val contentTypeAsRawHeader = HttpHeaders.RawHeader("Content-Type", contentType)
val parsedContentTypeHeader = HttpParser.parseHeader(contentTypeAsRawHeader)
(parsedContentTypeHeader: @unchecked) match {
case Right(ct: HttpHeaders.`Content-Type`) =>
ct.contentType
case Left(error: ErrorInfo) =>
throw new IllegalArgumentException(
s"Error converting '$contentType' to a ContentType header: '${error.summary}'")
}
}
}
sealed trait UntrustedSslContext {
this: ApiInvoker =>
implicit lazy val trustfulSslContext: SSLContext = {
settings.alwaysTrustCertificates match {
case false =>
SSLContext.getDefault
case true =>
class IgnoreX509TrustManager extends X509TrustManager {
def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def getAcceptedIssuers = null
}
val context = SSLContext.getInstance("TLS")
context.init(null, Array(new IgnoreX509TrustManager), null)
context
}
}
implicit val clientSSLEngineProvider =
ClientSSLEngineProvider {
_ =>
val engine = trustfulSslContext.createSSLEngine()
engine.setUseClientMode(true)
engine
}
}

View File

@ -0,0 +1,50 @@
package io.swagger.client.core
sealed trait ResponseState
object ResponseState {
case object Success extends ResponseState
case object Error extends ResponseState
}
case class ApiRequest[U](
// required fields
method: ApiMethod,
basePath: String,
operationPath: String,
contentType: String,
// optional fields
responses: Map[Int, (Manifest[_], ResponseState)] = Map.empty,
bodyParam: Option[Any] = None,
formParams: Map[String, Any] = Map.empty,
pathParams: Map[String, Any] = Map.empty,
queryParams: Map[String, Any] = Map.empty,
headerParams: Map[String, Any] = Map.empty,
credentials: Seq[Credentials] = List.empty) {
def withCredentials(cred: Credentials) = copy[U](credentials = credentials :+ cred)
def withApiKey(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) = withCredentials(ApiKeyCredentials(key, keyName, location))
def withSuccessResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Success)))
def withErrorResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Error)))
def withDefaultSuccessResponse[T](implicit m: Manifest[T]) = withSuccessResponse[T](0)
def withDefaultErrorResponse[T](implicit m: Manifest[T]) = withErrorResponse[T](0)
def responseForCode(statusCode: Int): Option[(Manifest[_], ResponseState)] = responses.get(statusCode) orElse responses.get(0)
def withoutBody() = copy[U](bodyParam = None)
def withBody(body: Any) = copy[U](bodyParam = Some(body))
def withFormParam(name: String, value: Any) = copy[U](formParams = formParams + (name -> value))
def withPathParam(name: String, value: Any) = copy[U](pathParams = pathParams + (name -> value))
def withQueryParam(name: String, value: Any) = copy[U](queryParams = queryParams + (name -> value))
def withHeaderParam(name: String, value: Any) = copy[U](headerParams = headerParams + (name -> value))
}

View File

@ -0,0 +1,32 @@
package io.swagger.client.core
import java.util.concurrent.TimeUnit
import akka.actor.{ExtendedActorSystem, Extension, ExtensionKey}
import com.typesafe.config.Config
import io.swagger.client.core.ApiInvoker.CustomStatusCode
import spray.http.HttpHeaders.RawHeader
import scala.collection.JavaConversions._
import scala.concurrent.duration.FiniteDuration
class ApiSettings(config: Config) extends Extension {
def this(system: ExtendedActorSystem) = this(system.settings.config)
private def cfg = config.getConfig("io.swagger.client.apiRequest")
val alwaysTrustCertificates = cfg.getBoolean("trust-certificates")
val defaultHeaders = cfg.getConfig("default-headers").entrySet.toList.map(c => RawHeader(c.getKey, c.getValue.render))
val connectionTimeout = FiniteDuration(cfg.getDuration("connection-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
val compressionEnabled = cfg.getBoolean("compression.enabled")
val compressionSizeThreshold = cfg.getBytes("compression.size-threshold").toInt
val customCodes = cfg.getConfigList("custom-codes").toList.map { c => CustomStatusCode(
c.getInt("code"),
c.getString("reason"),
c.getBoolean("success"))
}
}
object ApiSettings extends ExtensionKey[ApiSettings]

View File

@ -0,0 +1,166 @@
package io.swagger.client.core
import java.io.File
import java.net.URLEncoder
import scala.util.Try
sealed trait ApiReturnWithHeaders {
def headers: Map[String, String]
def header(name: String): Option[String] = headers.get(name)
def getStringHeader(name: String) = header(name)
def getIntHeader(name: String) = castedHeader(name, java.lang.Integer.parseInt)
def getLongHeader(name: String) = castedHeader(name, java.lang.Long.parseLong)
def getFloatHeader(name: String) = castedHeader(name, java.lang.Float.parseFloat)
def getDoubleHeader(name: String) = castedHeader(name, java.lang.Double.parseDouble)
def getBooleanHeader(name: String) = castedHeader(name, java.lang.Boolean.parseBoolean)
private def castedHeader[U](name: String, conversion: String => U): Option[U] = { Try { header(name).map( conversion ) }.get }
}
sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty)
extends ApiReturnWithHeaders
sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty)
extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause)
with ApiReturnWithHeaders
sealed case class ApiMethod(value: String)
object ApiMethods {
val CONNECT = ApiMethod("CONNECT")
val DELETE = ApiMethod("DELETE")
val GET = ApiMethod("GET")
val HEAD = ApiMethod("HEAD")
val OPTIONS = ApiMethod("OPTIONS")
val PATCH = ApiMethod("PATCH")
val POST = ApiMethod("POST")
val PUT = ApiMethod("PUT")
val TRACE = ApiMethod("TRACE")
}
/**
* This trait needs to be added to any model defined by the api.
*/
trait ApiModel
/**
* Single trait defining a credential that can be transformed to a paramName / paramValue tupple
*/
sealed trait Credentials {
def asQueryParam: Option[(String, String)] = None
}
sealed case class BasicCredentials(user: String, password: String) extends Credentials
sealed case class ApiKeyCredentials(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) extends Credentials {
override def asQueryParam: Option[(String, String)] = location match {
case ApiKeyLocations.QUERY => Some((keyName, key.value))
case _ => None
}
}
sealed case class ApiKeyValue(value: String)
sealed trait ApiKeyLocation
object ApiKeyLocations {
case object QUERY extends ApiKeyLocation
case object HEADER extends ApiKeyLocation
}
/**
* Case class used to unapply numeric values only in pattern matching
* @param value the string representation of the numeric value
*/
sealed case class NumericValue(value: String) {
override def toString = value
}
object NumericValue {
def unapply(n: Any): Option[NumericValue] = n match {
case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n)))
case _ => None
}
}
/**
* Used for params being arrays
*/
sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV)
object ArrayValues {
def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues =
ArrayValues(values.getOrElse(Seq.empty), format)
def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV)
}
/**
* Defines how arrays should be rendered in query strings.
*/
sealed trait CollectionFormat
trait MergedArrayFormat extends CollectionFormat {
def separator: String
}
object CollectionFormats {
case object CSV extends MergedArrayFormat {
override val separator = ","
}
case object TSV extends MergedArrayFormat {
override val separator = "\t"
}
case object SSV extends MergedArrayFormat {
override val separator = " "
}
case object PIPES extends MergedArrayFormat {
override val separator = "|"
}
case object MULTI extends CollectionFormat
}
object ParametersMap {
/**
* Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples,
* with valid url-encoding, arrays handling, files preservation, ...
*/
implicit class ParametersMapImprovements(val m: Map[String, Any]) {
def asFormattedParamsList = m.toList.flatMap(formattedParams)
def asFormattedParams = m.flatMap(formattedParams)
private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20")
private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2)
private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match {
case arr: ArrayValues =>
arr.format match {
case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _))
case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator)))
}
case None => Seq.empty
case Some(opt) =>
formattedParams(name, opt)
case s: Seq[Any] =>
formattedParams(name, ArrayValues(s))
case v: String => Seq((name, urlEncode(v)))
case NumericValue(v) => Seq((name, urlEncode(v)))
case f: File => Seq((name, f))
case m: ApiModel => Seq((name, m))
}
}
}

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Category (
id: Option[Long],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,29 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Order (
id: Option[Long],
petId: Option[Long],
quantity: Option[Int],
shipDate: Option[DateTime],
/* Order Status */
status: Option[OrderEnums.Status],
complete: Option[Boolean])
extends ApiModel
object OrderEnums {
type Status = Status.Value
object Status extends Enumeration {
val Placed = Value("placed")
val Approved = Value("approved")
val Delivered = Value("delivered")
}
}

View File

@ -0,0 +1,29 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Pet (
id: Option[Long],
category: Option[Category],
name: String,
photoUrls: Seq[String],
tags: Option[Seq[Tag]],
/* pet status in the store */
status: Option[PetEnums.Status])
extends ApiModel
object PetEnums {
type Status = Status.Value
object Status extends Enumeration {
val Available = Value("available")
val Pending = Value("pending")
val Sold = Value("sold")
}
}

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Tag (
id: Option[Long],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,19 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class User (
id: Option[Long],
username: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String],
password: Option[String],
phone: Option[String],
/* User Status */
userStatus: Option[Int])
extends ApiModel

View File

@ -0,0 +1,227 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wordnik</groupId>
<artifactId>swagger-client</artifactId>
<packaging>jar</packaging>
<name>swagger-client</name>
<version>1.0.0</version>
<prerequisites>
<maven>2.2.0</maven>
</prerequisites>
<pluginRepositories>
<pluginRepository>
<id>maven-mongodb-plugin-repo</id>
<name>maven mongodb plugin repository</name>
<url>http://maven-mongodb-plugin.googlecode.com/svn/maven/repo</url>
<layout>default</layout>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12</version>
<configuration>
<systemProperties>
<property>
<name>loggerPath</name>
<value>conf/log4j.properties</value>
</property>
</systemProperties>
<argLine>-Xms512m -Xmx1500m</argLine>
<parallel>methods</parallel>
<forkMode>pertest</forkMode>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- attach test jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add_sources</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>
src/main/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add_test_sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>
src/test/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>
1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>${scala-maven-plugin-version}</version>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<args>
<arg>-feature</arg>
</args>
<jvmArgs>
<jvmArg>-Xms128m</jvmArg>
<jvmArg>-Xmx1500m</jvmArg>
</jvmArgs>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<configuration>
<scalaVersion>${scala-version}</scalaVersion>
</configuration>
</plugin>
</plugins>
</reporting>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala-version}</version>
</dependency>
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-core</artifactId>
<version>${swagger-core-version}</version>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.10</artifactId>
<version>${scala-test-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time-version}</version>
</dependency>
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-convert</artifactId>
<version>${joda-version}</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.10</artifactId>
<version>${akka-version}</version>
</dependency>
<dependency>
<groupId>io.spray</groupId>
<artifactId>spray-client</artifactId>
<version>${spray-version}</version>
</dependency>
<dependency>
<groupId>org.json4s</groupId>
<artifactId>json4s-jackson_2.10</artifactId>
<version>${json4s-jackson-version}</version>
</dependency>
</dependencies>
<properties>
<scala-version>2.10.4</scala-version>
<json4s-jackson-version>3.2.11</json4s-jackson-version>
<json4s-ext-version>3.2.11</json4s-ext-version>
<spray-version>1.3.1</spray-version>
<akka-version>2.3.9</akka-version>
<joda-version>1.2</joda-version>
<joda-time-version>2.2</joda-time-version>
<swagger-core-version>1.5.0-M1</swagger-core-version>
<maven-plugin.version>1.0.0</maven-plugin.version>
<junit-version>4.8.1</junit-version>
<scala-maven-plugin-version>3.1.5</scala-maven-plugin-version>
<scala-test-version>2.1.3</scala-test-version>
</properties>
</project>

View File

@ -0,0 +1,24 @@
io.swagger.client {
apiRequest {
compression {
enabled: false
size-threshold: 0
}
trust-certificates: true
connection-timeout: 5000ms
default-headers {
"userAgent": "swagger-client_1.0.0"
}
// let you define custom http status code, as in :
// { code: 601, reason: "some custom http status code", success: false }
custom-codes : []
}
}
spray.can.host-connector.max-redirects = 10

View File

@ -0,0 +1,109 @@
package io.swagger.client.api
import io.swagger.client.model.ApiTokenStatus
import io.swagger.client.model.AuthenticationToken
import io.swagger.client.model.User
import io.swagger.client.model.WordList
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object AccountApi {
/**
*
*
* Expected answers:
* code 200 : ApiTokenStatus (Usage statistics for the supplied API key)
* code 400 : (No token supplied.)
* code 404 : (No API account with supplied token.)
*
* @param apiKey Wordnik authentication token
*/
def getApiTokenStatus(apiKey: Option[String] = None): ApiRequest[ApiTokenStatus] =
ApiRequest[ApiTokenStatus](ApiMethods.GET, "https://api.wordnik.com/v4", "/account.json/apiTokenStatus", "application/json")
.withHeaderParam("api_key", apiKey)
.withSuccessResponse[ApiTokenStatus](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : AuthenticationToken (A valid authentication token)
* code 403 : (Account not available.)
* code 404 : (User not found.)
*
* @param username A confirmed Wordnik username
* @param password The user&#39;s password
*/
def authenticate(username: String, password: String): ApiRequest[AuthenticationToken] =
ApiRequest[AuthenticationToken](ApiMethods.GET, "https://api.wordnik.com/v4", "/account.json/authenticate/{username}", "application/json")
.withQueryParam("password", password)
.withPathParam("username", username)
.withSuccessResponse[AuthenticationToken](200)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : AuthenticationToken (A valid authentication token)
* code 403 : (Account not available.)
* code 404 : (User not found.)
*
* @param username A confirmed Wordnik username
* @param body The user&#39;s password
*/
def authenticatePost(username: String, body: String): ApiRequest[AuthenticationToken] =
ApiRequest[AuthenticationToken](ApiMethods.POST, "https://api.wordnik.com/v4", "/account.json/authenticate/{username}", "application/json")
.withBody(body)
.withPathParam("username", username)
.withSuccessResponse[AuthenticationToken](200)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
* Requires a valid auth_token to be set.
*
* Expected answers:
* code 200 : User (The logged-in user)
* code 403 : (Not logged in.)
* code 404 : (User not found.)
*
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def getLoggedInUser(authToken: String): ApiRequest[User] =
ApiRequest[User](ApiMethods.GET, "https://api.wordnik.com/v4", "/account.json/user", "application/json")
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[User](200)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : Seq[WordList] (success)
* code 403 : (Not authenticated.)
* code 404 : (User account not found.)
*
* @param authToken auth_token of logged-in user
* @param skip Results to skip
* @param limit Maximum number of results to return
*/
def getWordListsForLoggedInUser(authToken: String, skip: Option[Int] = None, limit: Option[Int] = None): ApiRequest[Seq[WordList]] =
ApiRequest[Seq[WordList]](ApiMethods.GET, "https://api.wordnik.com/v4", "/account.json/wordLists", "application/json")
.withQueryParam("skip", skip)
.withQueryParam("limit", limit)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Seq[WordList]](200)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
}

View File

@ -0,0 +1,41 @@
package io.swagger.client.api
import io.swagger.client.model._
import org.json4s._
import scala.reflect.ClassTag
object EnumsSerializers {
def all = Seq[Serializer[_]]()
private class EnumNameSerializer[E <: Enumeration: ClassTag](enum: E)
extends Serializer[E#Value] {
import JsonDSL._
val EnumerationClass = classOf[E#Value]
def deserialize(implicit format: Formats):
PartialFunction[(TypeInfo, JValue), E#Value] = {
case (t @ TypeInfo(EnumerationClass, _), json) if isValid(json) => {
json match {
case JString(value) =>
enum.withName(value)
case value =>
throw new MappingException(s"Can't convert $value to $EnumerationClass")
}
}
}
private[this] def isValid(json: JValue) = json match {
case JString(value) if enum.values.exists(_.toString == value) => true
case _ => false
}
def serialize(implicit format: Formats): PartialFunction[Any, JValue] = {
case i: E#Value => i.toString
}
}
}

View File

@ -0,0 +1,253 @@
package io.swagger.client.api
import io.swagger.client.model.WordObject
import io.swagger.client.model.AudioFile
import io.swagger.client.model.Definition
import io.swagger.client.model.FrequencySummary
import io.swagger.client.model.Bigram
import io.swagger.client.model.Example
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object WordApi {
/**
*
*
* Expected answers:
* code 200 : WordObject (success)
* code 400 : (Invalid word supplied.)
*
* @param word String value of WordObject to return
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param includeSuggestions Return suggestions (for correct spelling, case variants, etc.)
*/
def getWord(word: String, useCanonical: Option[String] = None, includeSuggestions: Option[String] = None): ApiRequest[WordObject] =
ApiRequest[WordObject](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("includeSuggestions", includeSuggestions)
.withPathParam("word", word)
.withSuccessResponse[WordObject](200)
.withErrorResponse[Unit](400)
/**
* The metadata includes a time-expiring fileUrl which allows reading the audio file directly from the API. Currently only audio pronunciations from the American Heritage Dictionary in mp3 format are supported.
*
* Expected answers:
* code 200 : Seq[AudioFile] (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to get audio for.
* @param useCanonical Use the canonical form of the word
* @param limit Maximum number of results to return
*/
def getAudio(word: String, useCanonical: Option[String] = None, limit: Option[Int] = None): ApiRequest[Seq[AudioFile]] =
ApiRequest[Seq[AudioFile]](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/audio", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("limit", limit)
.withPathParam("word", word)
.withSuccessResponse[Seq[AudioFile]](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : Seq[Definition] (success)
* code 400 : (Invalid word supplied.)
* code 404 : (No definitions found.)
*
* @param word Word to return definitions for
* @param limit Maximum number of results to return
* @param partOfSpeech CSV list of part-of-speech types
* @param includeRelated Return related words with definitions
* @param sourceDictionaries Source dictionary to return definitions from. If &#39;all&#39; is received, results are returned from all sources. If multiple values are received (e.g. &#39;century,wiktionary&#39;), results are returned from the first specified dictionary that has definitions. If left blank, results are returned from the first dictionary that has definitions. By default, dictionaries are searched in this order: ahd, wiktionary, webster, century, wordnet
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param includeTags Return a closed set of XML tags in response
*/
def getDefinitions(word: String, limit: Option[Int] = None, partOfSpeech: Option[String] = None, includeRelated: Option[String] = None, sourceDictionaries: Seq[String], useCanonical: Option[String] = None, includeTags: Option[String] = None): ApiRequest[Seq[Definition]] =
ApiRequest[Seq[Definition]](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/definitions", "application/json")
.withQueryParam("limit", limit)
.withQueryParam("partOfSpeech", partOfSpeech)
.withQueryParam("includeRelated", includeRelated)
.withQueryParam("sourceDictionaries", ArrayValues(sourceDictionaries, CSV))
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("includeTags", includeTags)
.withPathParam("word", word)
.withSuccessResponse[Seq[Definition]](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : Seq[String] (success)
* code 400 : (Invalid word supplied.)
* code 404 : (No definitions found.)
*
* @param word Word to return
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
*/
def getEtymologies(word: String, useCanonical: Option[String] = None): ApiRequest[Seq[String]] =
ApiRequest[Seq[String]](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/etymologies", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withPathParam("word", word)
.withSuccessResponse[Seq[String]](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to return examples for
* @param includeDuplicates Show duplicate examples from different sources
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param skip Results to skip
* @param limit Maximum number of results to return
*/
def getExamples(word: String, includeDuplicates: Option[String] = None, useCanonical: Option[String] = None, skip: Option[Int] = None, limit: Option[Int] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/examples", "application/json")
.withQueryParam("includeDuplicates", includeDuplicates)
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("skip", skip)
.withQueryParam("limit", limit)
.withPathParam("word", word)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : FrequencySummary (success)
* code 400 : (Invalid word supplied.)
* code 404 : (No results.)
*
* @param word Word to return
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param startYear Starting Year
* @param endYear Ending Year
*/
def getWordFrequency(word: String, useCanonical: Option[String] = None, startYear: Option[Int] = None, endYear: Option[Int] = None): ApiRequest[FrequencySummary] =
ApiRequest[FrequencySummary](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/frequency", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("startYear", startYear)
.withQueryParam("endYear", endYear)
.withPathParam("word", word)
.withSuccessResponse[FrequencySummary](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to get syllables for
* @param useCanonical If true will try to return a correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param sourceDictionary Get from a single dictionary. Valid options: ahd, century, wiktionary, webster, and wordnet.
* @param limit Maximum number of results to return
*/
def getHyphenation(word: String, useCanonical: Option[String] = None, sourceDictionary: Option[String] = None, limit: Option[Int] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/hyphenation", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("sourceDictionary", sourceDictionary)
.withQueryParam("limit", limit)
.withPathParam("word", word)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : Seq[Bigram] (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to fetch phrases for
* @param limit Maximum number of results to return
* @param wlmi Minimum WLMI for the phrase
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
*/
def getPhrases(word: String, limit: Option[Int] = None, wlmi: Option[Int] = None, useCanonical: Option[String] = None): ApiRequest[Seq[Bigram]] =
ApiRequest[Seq[Bigram]](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/phrases", "application/json")
.withQueryParam("limit", limit)
.withQueryParam("wlmi", wlmi)
.withQueryParam("useCanonical", useCanonical)
.withPathParam("word", word)
.withSuccessResponse[Seq[Bigram]](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to get pronunciations for
* @param useCanonical If true will try to return a correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param sourceDictionary Get from a single dictionary
* @param typeFormat Text pronunciation type
* @param limit Maximum number of results to return
*/
def getTextPronunciations(word: String, useCanonical: Option[String] = None, sourceDictionary: Option[String] = None, typeFormat: Option[String] = None, limit: Option[Int] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/pronunciations", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("sourceDictionary", sourceDictionary)
.withQueryParam("typeFormat", typeFormat)
.withQueryParam("limit", limit)
.withPathParam("word", word)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to fetch relationships for
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
* @param relationshipTypes Limits the total results per type of relationship type
* @param limitPerRelationshipType Restrict to the supplied relationship types
*/
def getRelatedWords(word: String, useCanonical: Option[String] = None, relationshipTypes: Option[String] = None, limitPerRelationshipType: Option[Int] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/relatedWords", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withQueryParam("relationshipTypes", relationshipTypes)
.withQueryParam("limitPerRelationshipType", limitPerRelationshipType)
.withPathParam("word", word)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : Example (success)
* code 400 : (Invalid word supplied.)
*
* @param word Word to fetch examples for
* @param useCanonical If true will try to return the correct word root (&#39;cats&#39; -&gt; &#39;cat&#39;). If false returns exactly what was requested.
*/
def getTopExample(word: String, useCanonical: Option[String] = None): ApiRequest[Example] =
ApiRequest[Example](ApiMethods.GET, "https://api.wordnik.com/v4", "/word.json/{word}/topExample", "application/json")
.withQueryParam("useCanonical", useCanonical)
.withPathParam("word", word)
.withSuccessResponse[Example](200)
.withErrorResponse[Unit](400)
}

View File

@ -0,0 +1,154 @@
package io.swagger.client.api
import io.swagger.client.model.WordList
import io.swagger.client.model.StringValue
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object WordListApi {
/**
*
*
* Expected answers:
* code 200 : WordList (success)
* code 400 : (Invalid ID supplied)
* code 403 : (Not Authorized to access WordList)
* code 404 : (WordList not found)
*
* @param permalink permalink of WordList to fetch
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def getWordListByPermalink(permalink: String, authToken: String): ApiRequest[WordList] =
ApiRequest[WordList](ApiMethods.GET, "https://api.wordnik.com/v4", "/wordList.json/{permalink}", "application/json")
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[WordList](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid ID supplied)
* code 403 : (Not Authorized to update WordList)
* code 404 : (WordList not found)
*
* @param permalink permalink of WordList to update
* @param body Updated WordList
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def updateWordList(permalink: String, body: Option[WordList] = None, authToken: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.PUT, "https://api.wordnik.com/v4", "/wordList.json/{permalink}", "application/json")
.withBody(body)
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid ID supplied)
* code 403 : (Not Authorized to delete WordList)
* code 404 : (WordList not found)
*
* @param permalink ID of WordList to delete
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def deleteWordList(permalink: String, authToken: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.DELETE, "https://api.wordnik.com/v4", "/wordList.json/{permalink}", "application/json")
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid permalink supplied)
* code 403 : (Not Authorized to modify WordList)
* code 404 : (WordList not found)
*
* @param permalink permalink of WordList to use
* @param body Words to remove from WordList
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def deleteWordsFromWordList(permalink: String, body: Seq[StringValue], authToken: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "https://api.wordnik.com/v4", "/wordList.json/{permalink}/deleteWords", "application/json")
.withBody(body)
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid ID supplied)
* code 403 : (Not Authorized to access WordList)
* code 404 : (WordList not found)
*
* @param permalink ID of WordList to use
* @param sortBy Field to sort by
* @param sortOrder Direction to sort
* @param skip Results to skip
* @param limit Maximum number of results to return
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def getWordListWords(permalink: String, sortBy: Option[String] = None, sortOrder: Option[String] = None, skip: Option[Int] = None, limit: Option[Int] = None, authToken: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/wordList.json/{permalink}/words", "application/json")
.withQueryParam("sortBy", sortBy)
.withQueryParam("sortOrder", sortOrder)
.withQueryParam("skip", skip)
.withQueryParam("limit", limit)
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid permalink supplied)
* code 403 : (Not Authorized to access WordList)
* code 404 : (WordList not found)
*
* @param permalink permalink of WordList to user
* @param body Array of words to add to WordList
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def addWordsToWordList(permalink: String, body: Seq[StringValue], authToken: String): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.POST, "https://api.wordnik.com/v4", "/wordList.json/{permalink}/words", "application/json")
.withBody(body)
.withPathParam("permalink", permalink)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
}

View File

@ -0,0 +1,34 @@
package io.swagger.client.api
import io.swagger.client.model.WordList
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object WordListsApi {
/**
*
*
* Expected answers:
* code 200 : WordList (success)
* code 400 : (Invalid WordList supplied or mandatory fields are missing)
* code 403 : (Not authenticated)
* code 404 : (WordList owner not found)
*
* @param body WordList to create
* @param authToken The auth token of the logged-in user, obtained by calling /account.json/authenticate/{username} (described above)
*/
def createWordList(body: Option[WordList] = None, authToken: String): ApiRequest[WordList] =
ApiRequest[WordList](ApiMethods.POST, "https://api.wordnik.com/v4", "/wordLists.json", "application/json")
.withBody(body)
.withHeaderParam("auth_token", authToken)
.withSuccessResponse[WordList](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](403)
.withErrorResponse[Unit](404)
}

View File

@ -0,0 +1,181 @@
package io.swagger.client.api
import io.swagger.client.model.WordObject
import io.swagger.client.model.DefinitionSearchResults
import io.swagger.client.model.WordSearchResults
import io.swagger.client.model.WordOfTheDay
import io.swagger.client.core._
import io.swagger.client.core.CollectionFormats._
import io.swagger.client.core.ApiKeyLocations._
object WordsApi {
/**
*
*
* Expected answers:
* code 200 : WordObject (success)
* code 404 : (No word found.)
*
* @param hasDictionaryDef Only return words with dictionary definitions
* @param includePartOfSpeech CSV part-of-speech values to include
* @param excludePartOfSpeech CSV part-of-speech values to exclude
* @param minCorpusCount Minimum corpus frequency for terms
* @param maxCorpusCount Maximum corpus frequency for terms
* @param minDictionaryCount Minimum dictionary count
* @param maxDictionaryCount Maximum dictionary count
* @param minLength Minimum word length
* @param maxLength Maximum word length
*/
def getRandomWord(hasDictionaryDef: Option[String] = None, includePartOfSpeech: Option[String] = None, excludePartOfSpeech: Option[String] = None, minCorpusCount: Option[Int] = None, maxCorpusCount: Option[Int] = None, minDictionaryCount: Option[Int] = None, maxDictionaryCount: Option[Int] = None, minLength: Option[Int] = None, maxLength: Option[Int] = None): ApiRequest[WordObject] =
ApiRequest[WordObject](ApiMethods.GET, "https://api.wordnik.com/v4", "/words.json/randomWord", "application/json")
.withQueryParam("hasDictionaryDef", hasDictionaryDef)
.withQueryParam("includePartOfSpeech", includePartOfSpeech)
.withQueryParam("excludePartOfSpeech", excludePartOfSpeech)
.withQueryParam("minCorpusCount", minCorpusCount)
.withQueryParam("maxCorpusCount", maxCorpusCount)
.withQueryParam("minDictionaryCount", minDictionaryCount)
.withQueryParam("maxDictionaryCount", maxDictionaryCount)
.withQueryParam("minLength", minLength)
.withQueryParam("maxLength", maxLength)
.withSuccessResponse[WordObject](200)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : (success)
* code 400 : (Invalid term supplied.)
* code 404 : (No results.)
*
* @param hasDictionaryDef Only return words with dictionary definitions
* @param includePartOfSpeech CSV part-of-speech values to include
* @param excludePartOfSpeech CSV part-of-speech values to exclude
* @param minCorpusCount Minimum corpus frequency for terms
* @param maxCorpusCount Maximum corpus frequency for terms
* @param minDictionaryCount Minimum dictionary count
* @param maxDictionaryCount Maximum dictionary count
* @param minLength Minimum word length
* @param maxLength Maximum word length
* @param sortBy Attribute to sort by
* @param sortOrder Sort direction
* @param limit Maximum number of results to return
*/
def getRandomWords(hasDictionaryDef: Option[String] = None, includePartOfSpeech: Option[String] = None, excludePartOfSpeech: Option[String] = None, minCorpusCount: Option[Int] = None, maxCorpusCount: Option[Int] = None, minDictionaryCount: Option[Int] = None, maxDictionaryCount: Option[Int] = None, minLength: Option[Int] = None, maxLength: Option[Int] = None, sortBy: Option[String] = None, sortOrder: Option[String] = None, limit: Option[Int] = None): ApiRequest[Unit] =
ApiRequest[Unit](ApiMethods.GET, "https://api.wordnik.com/v4", "/words.json/randomWords", "application/json")
.withQueryParam("hasDictionaryDef", hasDictionaryDef)
.withQueryParam("includePartOfSpeech", includePartOfSpeech)
.withQueryParam("excludePartOfSpeech", excludePartOfSpeech)
.withQueryParam("minCorpusCount", minCorpusCount)
.withQueryParam("maxCorpusCount", maxCorpusCount)
.withQueryParam("minDictionaryCount", minDictionaryCount)
.withQueryParam("maxDictionaryCount", maxDictionaryCount)
.withQueryParam("minLength", minLength)
.withQueryParam("maxLength", maxLength)
.withQueryParam("sortBy", sortBy)
.withQueryParam("sortOrder", sortOrder)
.withQueryParam("limit", limit)
.withSuccessResponse[Unit](200)
.withErrorResponse[Unit](400)
.withErrorResponse[Unit](404)
/**
*
*
* Expected answers:
* code 200 : DefinitionSearchResults (success)
* code 400 : (Invalid term supplied.)
*
* @param query Search term
* @param findSenseForWord Restricts words and finds closest sense
* @param includeSourceDictionaries Only include these comma-delimited source dictionaries
* @param excludeSourceDictionaries Exclude these comma-delimited source dictionaries
* @param includePartOfSpeech Only include these comma-delimited parts of speech
* @param excludePartOfSpeech Exclude these comma-delimited parts of speech
* @param minCorpusCount Minimum corpus frequency for terms
* @param maxCorpusCount Maximum corpus frequency for terms
* @param minLength Minimum word length
* @param maxLength Maximum word length
* @param expandTerms Expand terms
* @param includeTags Return a closed set of XML tags in response
* @param sortBy Attribute to sort by
* @param sortOrder Sort direction
* @param skip Results to skip
* @param limit Maximum number of results to return
*/
def reverseDictionary(query: String, findSenseForWord: Option[String] = None, includeSourceDictionaries: Option[String] = None, excludeSourceDictionaries: Option[String] = None, includePartOfSpeech: Option[String] = None, excludePartOfSpeech: Option[String] = None, minCorpusCount: Option[Int] = None, maxCorpusCount: Option[Int] = None, minLength: Option[Int] = None, maxLength: Option[Int] = None, expandTerms: Option[String] = None, includeTags: Option[String] = None, sortBy: Option[String] = None, sortOrder: Option[String] = None, skip: Option[String] = None, limit: Option[Int] = None): ApiRequest[DefinitionSearchResults] =
ApiRequest[DefinitionSearchResults](ApiMethods.GET, "https://api.wordnik.com/v4", "/words.json/reverseDictionary", "application/json")
.withQueryParam("query", query)
.withQueryParam("findSenseForWord", findSenseForWord)
.withQueryParam("includeSourceDictionaries", includeSourceDictionaries)
.withQueryParam("excludeSourceDictionaries", excludeSourceDictionaries)
.withQueryParam("includePartOfSpeech", includePartOfSpeech)
.withQueryParam("excludePartOfSpeech", excludePartOfSpeech)
.withQueryParam("minCorpusCount", minCorpusCount)
.withQueryParam("maxCorpusCount", maxCorpusCount)
.withQueryParam("minLength", minLength)
.withQueryParam("maxLength", maxLength)
.withQueryParam("expandTerms", expandTerms)
.withQueryParam("includeTags", includeTags)
.withQueryParam("sortBy", sortBy)
.withQueryParam("sortOrder", sortOrder)
.withQueryParam("skip", skip)
.withQueryParam("limit", limit)
.withSuccessResponse[DefinitionSearchResults](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 200 : WordSearchResults (success)
* code 400 : (Invalid query supplied.)
*
* @param query Search query
* @param caseSensitive Search case sensitive
* @param includePartOfSpeech Only include these comma-delimited parts of speech
* @param excludePartOfSpeech Exclude these comma-delimited parts of speech
* @param minCorpusCount Minimum corpus frequency for terms
* @param maxCorpusCount Maximum corpus frequency for terms
* @param minDictionaryCount Minimum number of dictionary entries for words returned
* @param maxDictionaryCount Maximum dictionary definition count
* @param minLength Minimum word length
* @param maxLength Maximum word length
* @param skip Results to skip
* @param limit Maximum number of results to return
*/
def searchWords(query: String, caseSensitive: Option[String] = None, includePartOfSpeech: Option[String] = None, excludePartOfSpeech: Option[String] = None, minCorpusCount: Option[Int] = None, maxCorpusCount: Option[Int] = None, minDictionaryCount: Option[Int] = None, maxDictionaryCount: Option[Int] = None, minLength: Option[Int] = None, maxLength: Option[Int] = None, skip: Option[Int] = None, limit: Option[Int] = None): ApiRequest[WordSearchResults] =
ApiRequest[WordSearchResults](ApiMethods.GET, "https://api.wordnik.com/v4", "/words.json/search/{query}", "application/json")
.withQueryParam("caseSensitive", caseSensitive)
.withQueryParam("includePartOfSpeech", includePartOfSpeech)
.withQueryParam("excludePartOfSpeech", excludePartOfSpeech)
.withQueryParam("minCorpusCount", minCorpusCount)
.withQueryParam("maxCorpusCount", maxCorpusCount)
.withQueryParam("minDictionaryCount", minDictionaryCount)
.withQueryParam("maxDictionaryCount", maxDictionaryCount)
.withQueryParam("minLength", minLength)
.withQueryParam("maxLength", maxLength)
.withQueryParam("skip", skip)
.withQueryParam("limit", limit)
.withPathParam("query", query)
.withSuccessResponse[WordSearchResults](200)
.withErrorResponse[Unit](400)
/**
*
*
* Expected answers:
* code 0 : WordOfTheDay (success)
*
* @param date Fetches by date in yyyy-MM-dd
*/
def getWordOfTheDay(date: Option[String] = None): ApiRequest[WordOfTheDay] =
ApiRequest[WordOfTheDay](ApiMethods.GET, "https://api.wordnik.com/v4", "/words.json/wordOfTheDay", "application/json")
.withQueryParam("date", date)
.withDefaultSuccessResponse[WordOfTheDay]
}

View File

@ -0,0 +1,323 @@
package io.swagger.client.core
import java.io.File
import java.security.cert.X509Certificate
import javax.net.ssl._
import akka.actor.ActorSystem
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.json4s.JsonAST.JString
import org.json4s._
import org.json4s.jackson.JsonMethods._
import org.json4s.jackson.Serialization
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining
import spray.client.pipelining._
import spray.http.HttpEncodings._
import spray.http.HttpHeaders.{RawHeader, `Accept-Encoding`}
import spray.http.Uri.Query
import spray.http._
import spray.http.parser.HttpParser
import spray.httpx.encoding.{Deflate, Encoder, Gzip}
import spray.httpx.unmarshalling._
import spray.io.ClientSSLEngineProvider
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
import scala.util.control.NonFatal
object ApiInvoker {
def apply()(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer)
def apply(serializers: Traversable[Serializer[_]])(implicit system: ActorSystem): ApiInvoker =
apply(DefaultFormats + DateTimeSerializer ++ serializers)
def apply(formats: Formats)(implicit system: ActorSystem): ApiInvoker = new ApiInvoker(formats)
case class CustomStatusCode(value: Int, reason: String = "Application-defined status code", isSuccess: Boolean = true)
def addCustomStatusCode(code: CustomStatusCode): Unit = addCustomStatusCode(code.value, code.reason, code.isSuccess)
def addCustomStatusCode(code: Int, reason: String = "Application defined code", isSuccess: Boolean = true) = {
StatusCodes.getForKey(code) foreach { c =>
StatusCodes.registerCustom(code, reason, reason, isSuccess, allowsEntity = true)
}
}
/**
* Allows request execution without calling apiInvoker.execute(request)
* request.response can be used to get a future of the ApiResponse generated.
* request.result can be used to get a future of the expected ApiResponse content. If content doesn't match, a
* Future will failed with a ClassCastException
* @param request the apiRequest to be executed
*/
implicit class ApiRequestImprovements[T](request: ApiRequest[T]) {
def response(invoker: ApiInvoker)(implicit ec: ExecutionContext, system: ActorSystem): Future[ApiResponse[T]] =
response(ec, system, invoker)
def response(implicit ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[ApiResponse[T]] =
invoker.execute(request)
def result[U <: T](implicit c: ClassTag[U], ec: ExecutionContext, system: ActorSystem, invoker: ApiInvoker): Future[U] =
invoker.execute(request).map(_.content).mapTo[U]
}
/**
* Allows transformation from ApiMethod to spray HttpMethods
* @param method the ApiMethod to be converted
*/
implicit class ApiMethodExtensions(val method: ApiMethod) {
def toSprayMethod: HttpMethod = HttpMethods.getForKey(method.value).getOrElse(HttpMethods.GET)
}
case object DateTimeSerializer extends CustomSerializer[DateTime](format => ( {
case JString(s) =>
ISODateTimeFormat.dateTimeParser().parseDateTime(s)
}, {
case d: DateTime =>
JString(ISODateTimeFormat.dateTimeParser().print(d))
}))
}
class ApiInvoker(formats: Formats)(implicit system: ActorSystem) extends UntrustedSslContext with CustomContentTypes {
import io.swagger.client.core.ApiInvoker._
import io.swagger.client.core.ParametersMap._
implicit val ec = system.dispatcher
implicit val jsonFormats = formats
def settings = ApiSettings(system)
import spray.http.MessagePredicate._
val CompressionFilter = MessagePredicate({ _ => settings.compressionEnabled}) &&
Encoder.DefaultFilter &&
minEntitySize(settings.compressionSizeThreshold)
settings.customCodes.foreach(addCustomStatusCode)
private def addAuthentication(credentialsSeq: Seq[Credentials]): pipelining.RequestTransformer =
request =>
credentialsSeq.foldLeft(request) {
case (req, BasicCredentials(login, password)) =>
req ~> addCredentials(BasicHttpCredentials(login, password))
case (req, ApiKeyCredentials(keyValue, keyName, ApiKeyLocations.HEADER)) =>
req ~> addHeader(RawHeader(keyName, keyValue.value))
case (req, _) => req
}
private def addHeaders(headers: Map[String, Any]): pipelining.RequestTransformer = { request =>
val rawHeaders = for {
(name, value) <- headers.asFormattedParams
header = RawHeader(name, String.valueOf(value))
} yield header
request.withHeaders(rawHeaders.toList)
}
private def bodyPart(name: String, value: Any): BodyPart = {
value match {
case f: File =>
BodyPart(f, name)
case v: String =>
BodyPart(HttpEntity(String.valueOf(v)))
case NumericValue(v) =>
BodyPart(HttpEntity(String.valueOf(v)))
case m: ApiModel =>
BodyPart(HttpEntity(Serialization.write(m)))
}
}
private def formDataContent(request: ApiRequest[_]) = {
val params = request.formParams.asFormattedParams
if (params.isEmpty)
None
else
Some(
normalizedContentType(request.contentType).mediaType match {
case MediaTypes.`multipart/form-data` =>
MultipartFormData(params.map { case (name, value) => (name, bodyPart(name, value))})
case MediaTypes.`application/x-www-form-urlencoded` =>
FormData(params.mapValues(String.valueOf))
case m: MediaType => // Default : application/x-www-form-urlencoded.
FormData(params.mapValues(String.valueOf))
}
)
}
private def bodyContent(request: ApiRequest[_]): Option[Any] = {
request.bodyParam.map(Extraction.decompose).map(compact)
}
private def createRequest(uri: Uri, request: ApiRequest[_]): HttpRequest = {
val builder = new RequestBuilder(request.method.toSprayMethod)
val httpRequest = request.method.toSprayMethod match {
case HttpMethods.GET | HttpMethods.DELETE => builder.apply(uri)
case HttpMethods.POST | HttpMethods.PUT =>
formDataContent(request) orElse bodyContent(request) match {
case Some(c: FormData) =>
builder.apply(uri, c)
case Some(c: MultipartFormData) =>
builder.apply(uri, c)
case Some(c: String) =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), c))
case _ =>
builder.apply(uri, HttpEntity(normalizedContentType(request.contentType), " "))
}
case _ => builder.apply(uri)
}
httpRequest ~>
addHeaders(request.headerParams) ~>
addAuthentication(request.credentials) ~>
encode(Gzip(CompressionFilter))
}
def makeQuery(r: ApiRequest[_]): Query = {
r.credentials.foldLeft(r.queryParams) {
case (params, ApiKeyCredentials(key, keyName, ApiKeyLocations.QUERY)) =>
params + (keyName -> key.value)
case (params, _) => params
}.asFormattedParams
.mapValues(String.valueOf)
.foldRight[Query](Uri.Query.Empty) {
case ((name, value), acc) => acc.+:(name, value)
}
}
def makeUri(r: ApiRequest[_]): Uri = {
val opPath = r.operationPath.replaceAll("\\{format\\}", "json")
val opPathWithParams = r.pathParams.asFormattedParams
.mapValues(String.valueOf)
.foldLeft(opPath) {
case (path, (name, value)) => path.replaceAll(s"\\{$name\\}", value)
}
val query = makeQuery(r)
Uri(r.basePath + opPathWithParams).withQuery(query)
}
def execute[T](r: ApiRequest[T]): Future[ApiResponse[T]] = {
try {
implicit val timeout: Timeout = settings.connectionTimeout
val uri = makeUri(r)
val connector = HostConnectorSetup(
uri.authority.host.toString,
uri.effectivePort,
sslEncryption = "https".equals(uri.scheme),
defaultHeaders = settings.defaultHeaders ++ List(`Accept-Encoding`(gzip, deflate)))
val request = createRequest(uri, r)
for {
Http.HostConnectorInfo(hostConnector, _) <- IO(Http) ? connector
response <- hostConnector.ask(request).mapTo[HttpResponse]
} yield {
response ~> decode(Deflate) ~> decode(Gzip) ~> unmarshallApiResponse(r)
}
}
catch {
case NonFatal(x) => Future.failed(x)
}
}
def unmarshallApiResponse[T](request: ApiRequest[T])(response: HttpResponse): ApiResponse[T] = {
request.responseForCode(response.status.intValue) match {
case Some( (manifest: Manifest[T], state: ResponseState) ) =>
entityUnmarshaller(manifest)(response.entity) match {
case Right(value)
state match {
case ResponseState.Success =>
ApiResponse(response.status.intValue, value, response.headers.map(header => (header.name, header.value)).toMap)
case ResponseState.Error =>
throw new ApiError(response.status.intValue, "Error response received",
Some(value),
headers = response.headers.map(header => (header.name, header.value)).toMap)
}
case Left(MalformedContent(error, Some(cause)))
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString), cause)
case Left(MalformedContent(error, None))
throw new ApiError(response.status.intValue, s"Unable to unmarshall content to [$manifest]", Some(response.entity.toString))
case Left(ContentExpected)
throw new ApiError(response.status.intValue, s"Unable to unmarshall empty response to [$manifest]", Some(response.entity.toString))
}
case _ => throw new ApiError(response.status.intValue, "Unexpected response code", Some(response.entity.toString))
}
}
def entityUnmarshaller[T](implicit mf: Manifest[T]): Unmarshaller[T] =
Unmarshaller[T](MediaTypes.`application/json`) {
case x: HttpEntity.NonEmpty
parse(x.asString(defaultCharset = HttpCharsets.`UTF-8`))
.noNulls
.camelizeKeys
.extract[T]
}
}
sealed trait CustomContentTypes {
def normalizedContentType(original: String): ContentType =
MediaTypes.forExtension(original) map (ContentType(_)) getOrElse parseContentType(original)
def parseContentType(contentType: String): ContentType = {
val contentTypeAsRawHeader = HttpHeaders.RawHeader("Content-Type", contentType)
val parsedContentTypeHeader = HttpParser.parseHeader(contentTypeAsRawHeader)
(parsedContentTypeHeader: @unchecked) match {
case Right(ct: HttpHeaders.`Content-Type`) =>
ct.contentType
case Left(error: ErrorInfo) =>
throw new IllegalArgumentException(
s"Error converting '$contentType' to a ContentType header: '${error.summary}'")
}
}
}
sealed trait UntrustedSslContext {
this: ApiInvoker =>
implicit lazy val trustfulSslContext: SSLContext = {
settings.alwaysTrustCertificates match {
case false =>
SSLContext.getDefault
case true =>
class IgnoreX509TrustManager extends X509TrustManager {
def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {}
def getAcceptedIssuers = null
}
val context = SSLContext.getInstance("TLS")
context.init(null, Array(new IgnoreX509TrustManager), null)
context
}
}
implicit val clientSSLEngineProvider =
ClientSSLEngineProvider {
_ =>
val engine = trustfulSslContext.createSSLEngine()
engine.setUseClientMode(true)
engine
}
}

View File

@ -0,0 +1,50 @@
package io.swagger.client.core
sealed trait ResponseState
object ResponseState {
case object Success extends ResponseState
case object Error extends ResponseState
}
case class ApiRequest[U](
// required fields
method: ApiMethod,
basePath: String,
operationPath: String,
contentType: String,
// optional fields
responses: Map[Int, (Manifest[_], ResponseState)] = Map.empty,
bodyParam: Option[Any] = None,
formParams: Map[String, Any] = Map.empty,
pathParams: Map[String, Any] = Map.empty,
queryParams: Map[String, Any] = Map.empty,
headerParams: Map[String, Any] = Map.empty,
credentials: Seq[Credentials] = List.empty) {
def withCredentials(cred: Credentials) = copy[U](credentials = credentials :+ cred)
def withApiKey(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) = withCredentials(ApiKeyCredentials(key, keyName, location))
def withSuccessResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Success)))
def withErrorResponse[T](code: Int)(implicit m: Manifest[T]) = copy[U](responses = responses + (code -> (m, ResponseState.Error)))
def withDefaultSuccessResponse[T](implicit m: Manifest[T]) = withSuccessResponse[T](0)
def withDefaultErrorResponse[T](implicit m: Manifest[T]) = withErrorResponse[T](0)
def responseForCode(statusCode: Int): Option[(Manifest[_], ResponseState)] = responses.get(statusCode) orElse responses.get(0)
def withoutBody() = copy[U](bodyParam = None)
def withBody(body: Any) = copy[U](bodyParam = Some(body))
def withFormParam(name: String, value: Any) = copy[U](formParams = formParams + (name -> value))
def withPathParam(name: String, value: Any) = copy[U](pathParams = pathParams + (name -> value))
def withQueryParam(name: String, value: Any) = copy[U](queryParams = queryParams + (name -> value))
def withHeaderParam(name: String, value: Any) = copy[U](headerParams = headerParams + (name -> value))
}

View File

@ -0,0 +1,32 @@
package io.swagger.client.core
import java.util.concurrent.TimeUnit
import akka.actor.{ExtendedActorSystem, Extension, ExtensionKey}
import com.typesafe.config.Config
import io.swagger.client.core.ApiInvoker.CustomStatusCode
import spray.http.HttpHeaders.RawHeader
import scala.collection.JavaConversions._
import scala.concurrent.duration.FiniteDuration
class ApiSettings(config: Config) extends Extension {
def this(system: ExtendedActorSystem) = this(system.settings.config)
private def cfg = config.getConfig("io.swagger.client.apiRequest")
val alwaysTrustCertificates = cfg.getBoolean("trust-certificates")
val defaultHeaders = cfg.getConfig("default-headers").entrySet.toList.map(c => RawHeader(c.getKey, c.getValue.render))
val connectionTimeout = FiniteDuration(cfg.getDuration("connection-timeout", TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
val compressionEnabled = cfg.getBoolean("compression.enabled")
val compressionSizeThreshold = cfg.getBytes("compression.size-threshold").toInt
val customCodes = cfg.getConfigList("custom-codes").toList.map { c => CustomStatusCode(
c.getInt("code"),
c.getString("reason"),
c.getBoolean("success"))
}
}
object ApiSettings extends ExtensionKey[ApiSettings]

View File

@ -0,0 +1,166 @@
package io.swagger.client.core
import java.io.File
import java.net.URLEncoder
import scala.util.Try
sealed trait ApiReturnWithHeaders {
def headers: Map[String, String]
def header(name: String): Option[String] = headers.get(name)
def getStringHeader(name: String) = header(name)
def getIntHeader(name: String) = castedHeader(name, java.lang.Integer.parseInt)
def getLongHeader(name: String) = castedHeader(name, java.lang.Long.parseLong)
def getFloatHeader(name: String) = castedHeader(name, java.lang.Float.parseFloat)
def getDoubleHeader(name: String) = castedHeader(name, java.lang.Double.parseDouble)
def getBooleanHeader(name: String) = castedHeader(name, java.lang.Boolean.parseBoolean)
private def castedHeader[U](name: String, conversion: String => U): Option[U] = { Try { header(name).map( conversion ) }.get }
}
sealed case class ApiResponse[T](code: Int, content: T, headers: Map[String, String] = Map.empty)
extends ApiReturnWithHeaders
sealed case class ApiError[T](code: Int, message: String, responseContent: Option[T], cause: Throwable = null, headers: Map[String, String] = Map.empty)
extends Throwable(s"($code) $message.${responseContent.map(s => s" Content : $s").getOrElse("")}", cause)
with ApiReturnWithHeaders
sealed case class ApiMethod(value: String)
object ApiMethods {
val CONNECT = ApiMethod("CONNECT")
val DELETE = ApiMethod("DELETE")
val GET = ApiMethod("GET")
val HEAD = ApiMethod("HEAD")
val OPTIONS = ApiMethod("OPTIONS")
val PATCH = ApiMethod("PATCH")
val POST = ApiMethod("POST")
val PUT = ApiMethod("PUT")
val TRACE = ApiMethod("TRACE")
}
/**
* This trait needs to be added to any model defined by the api.
*/
trait ApiModel
/**
* Single trait defining a credential that can be transformed to a paramName / paramValue tupple
*/
sealed trait Credentials {
def asQueryParam: Option[(String, String)] = None
}
sealed case class BasicCredentials(user: String, password: String) extends Credentials
sealed case class ApiKeyCredentials(key: ApiKeyValue, keyName: String, location: ApiKeyLocation) extends Credentials {
override def asQueryParam: Option[(String, String)] = location match {
case ApiKeyLocations.QUERY => Some((keyName, key.value))
case _ => None
}
}
sealed case class ApiKeyValue(value: String)
sealed trait ApiKeyLocation
object ApiKeyLocations {
case object QUERY extends ApiKeyLocation
case object HEADER extends ApiKeyLocation
}
/**
* Case class used to unapply numeric values only in pattern matching
* @param value the string representation of the numeric value
*/
sealed case class NumericValue(value: String) {
override def toString = value
}
object NumericValue {
def unapply(n: Any): Option[NumericValue] = n match {
case (_: Int | _: Long | _: Float | _: Double | _: Boolean | _: Byte) => Some(NumericValue(String.valueOf(n)))
case _ => None
}
}
/**
* Used for params being arrays
*/
sealed case class ArrayValues(values: Seq[Any], format: CollectionFormat = CollectionFormats.CSV)
object ArrayValues {
def apply(values: Option[Seq[Any]], format: CollectionFormat): ArrayValues =
ArrayValues(values.getOrElse(Seq.empty), format)
def apply(values: Option[Seq[Any]]): ArrayValues = ArrayValues(values, CollectionFormats.CSV)
}
/**
* Defines how arrays should be rendered in query strings.
*/
sealed trait CollectionFormat
trait MergedArrayFormat extends CollectionFormat {
def separator: String
}
object CollectionFormats {
case object CSV extends MergedArrayFormat {
override val separator = ","
}
case object TSV extends MergedArrayFormat {
override val separator = "\t"
}
case object SSV extends MergedArrayFormat {
override val separator = " "
}
case object PIPES extends MergedArrayFormat {
override val separator = "|"
}
case object MULTI extends CollectionFormat
}
object ParametersMap {
/**
* Pimp parameters maps (Map[String, Any]) in order to transform them in a sequence of String -> Any tupples,
* with valid url-encoding, arrays handling, files preservation, ...
*/
implicit class ParametersMapImprovements(val m: Map[String, Any]) {
def asFormattedParamsList = m.toList.flatMap(formattedParams)
def asFormattedParams = m.flatMap(formattedParams)
private def urlEncode(v: Any) = URLEncoder.encode(String.valueOf(v), "utf-8").replaceAll("\\+", "%20")
private def formattedParams(tuple: (String, Any)): Seq[(String, Any)] = formattedParams(tuple._1, tuple._2)
private def formattedParams(name: String, value: Any): Seq[(String, Any)] = value match {
case arr: ArrayValues =>
arr.format match {
case CollectionFormats.MULTI => arr.values.flatMap(formattedParams(name, _))
case format: MergedArrayFormat => Seq((name, arr.values.mkString(format.separator)))
}
case None => Seq.empty
case Some(opt) =>
formattedParams(name, opt)
case s: Seq[Any] =>
formattedParams(name, ArrayValues(s))
case v: String => Seq((name, urlEncode(v)))
case NumericValue(v) => Seq((name, urlEncode(v)))
case f: File => Seq((name, f))
case m: ApiModel => Seq((name, m))
}
}
}

View File

@ -0,0 +1,16 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class ApiTokenStatus (
valid: Option[Boolean],
token: Option[String],
resetsInMillis: Option[Long],
remainingCalls: Option[Long],
expiresInMillis: Option[Long],
totalRequests: Option[Long])
extends ApiModel

View File

@ -0,0 +1,24 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class AudioFile (
attributionUrl: Option[String],
commentCount: Option[Int],
voteCount: Option[Int],
fileUrl: Option[String],
audioType: Option[String],
id: Option[Long],
duration: Option[Double],
attributionText: Option[String],
createdBy: Option[String],
description: Option[String],
createdAt: Option[DateTime],
voteWeightedAverage: Option[Float],
voteAverage: Option[Float],
word: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class AudioType (
id: Option[Int],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class AuthenticationToken (
token: Option[String],
userId: Option[Long],
userSignature: Option[String])
extends ApiModel

View File

@ -0,0 +1,15 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Bigram (
count: Option[Long],
gram2: Option[String],
gram1: Option[String],
wlmi: Option[Double],
mi: Option[Double])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Category (
id: Option[Long],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Citation (
cite: Option[String],
source: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class ContentProvider (
id: Option[Int],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,26 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Definition (
extendedText: Option[String],
text: Option[String],
sourceDictionary: Option[String],
citations: Option[Seq[Citation]],
labels: Option[Seq[Label]],
score: Option[Float],
exampleUses: Option[Seq[ExampleUsage]],
attributionUrl: Option[String],
seqString: Option[String],
attributionText: Option[String],
relatedWords: Option[Seq[Related]],
sequence: Option[String],
word: Option[String],
notes: Option[Seq[Note]],
textProns: Option[Seq[TextPron]],
partOfSpeech: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class DefinitionSearchResults (
results: Option[Seq[Definition]],
totalResults: Option[Int])
extends ApiModel

View File

@ -0,0 +1,22 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Example (
id: Option[Long],
exampleId: Option[Long],
title: Option[String],
text: Option[String],
score: Option[ScoredWord],
sentence: Option[Sentence],
word: Option[String],
provider: Option[ContentProvider],
year: Option[Int],
rating: Option[Float],
documentId: Option[Long],
url: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class ExampleSearchResults (
facets: Option[Seq[Facet]],
examples: Option[Seq[Example]])
extends ApiModel

View File

@ -0,0 +1,11 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class ExampleUsage (
text: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Facet (
facetValues: Option[Seq[FacetValue]],
name: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class FacetValue (
count: Option[Long],
value: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Frequency (
count: Option[Long],
year: Option[Int])
extends ApiModel

View File

@ -0,0 +1,15 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class FrequencySummary (
unknownYearCount: Option[Int],
totalCount: Option[Long],
frequencyString: Option[String],
word: Option[String],
frequency: Option[Seq[Frequency]])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Label (
text: Option[String],
`type`: Option[String])
extends ApiModel

View File

@ -0,0 +1,14 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Note (
noteType: Option[String],
appliesTo: Option[Seq[String]],
value: Option[String],
pos: Option[Int])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class PartOfSpeech (
roots: Option[Seq[Root]],
storageAbbr: Option[Seq[String]],
allCategories: Option[Seq[Category]])
extends ApiModel

View File

@ -0,0 +1,17 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Related (
label1: Option[String],
relationshipType: Option[String],
label2: Option[String],
label3: Option[String],
words: Option[Seq[String]],
gram: Option[String],
label4: Option[String])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Root (
id: Option[Long],
name: Option[String],
categories: Option[Seq[Category]])
extends ApiModel

View File

@ -0,0 +1,21 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class ScoredWord (
position: Option[Int],
id: Option[Long],
docTermCount: Option[Int],
lemma: Option[String],
wordType: Option[String],
score: Option[Float],
sentenceId: Option[Long],
word: Option[String],
stopword: Option[Boolean],
baseWordScore: Option[Double],
partOfSpeech: Option[String])
extends ApiModel

View File

@ -0,0 +1,16 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Sentence (
hasScoredWords: Option[Boolean],
id: Option[Long],
scoredWords: Option[Seq[ScoredWord]],
display: Option[String],
rating: Option[Int],
documentMetadataId: Option[Long])
extends ApiModel

View File

@ -0,0 +1,14 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class SimpleDefinition (
text: Option[String],
source: Option[String],
note: Option[String],
partOfSpeech: Option[String])
extends ApiModel

View File

@ -0,0 +1,14 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class SimpleExample (
id: Option[Long],
title: Option[String],
text: Option[String],
url: Option[String])
extends ApiModel

View File

@ -0,0 +1,11 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class StringValue (
word: Option[String])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class Syllable (
text: Option[String],
seq: Option[Int],
`type`: Option[String])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class TextPron (
raw: Option[String],
seq: Option[Int],
rawType: Option[String])
extends ApiModel

View File

@ -0,0 +1,18 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class User (
id: Option[Long],
username: Option[String],
email: Option[String],
status: Option[Int],
faceBookId: Option[String],
userName: Option[String],
displayName: Option[String],
password: Option[String])
extends ApiModel

View File

@ -0,0 +1,21 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordList (
id: Option[Long],
permalink: Option[String],
name: Option[String],
createdAt: Option[DateTime],
updatedAt: Option[DateTime],
lastActivityAt: Option[DateTime],
username: Option[String],
userId: Option[Long],
description: Option[String],
numberWordsInList: Option[Long],
`type`: Option[String])
extends ApiModel

View File

@ -0,0 +1,17 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordListWord (
id: Option[Long],
word: Option[String],
username: Option[String],
userId: Option[Long],
createdAt: Option[DateTime],
numberCommentsOnWord: Option[Long],
numberLists: Option[Long])
extends ApiModel

View File

@ -0,0 +1,16 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordObject (
id: Option[Long],
word: Option[String],
originalWord: Option[String],
suggestions: Option[Seq[String]],
canonicalForm: Option[String],
vulgar: Option[String])
extends ApiModel

View File

@ -0,0 +1,22 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordOfTheDay (
id: Option[Long],
parentId: Option[String],
category: Option[String],
createdBy: Option[String],
createdAt: Option[DateTime],
contentProvider: Option[ContentProvider],
htmlExtra: Option[String],
word: Option[String],
definitions: Option[Seq[SimpleDefinition]],
examples: Option[Seq[SimpleExample]],
note: Option[String],
publishDate: Option[DateTime])
extends ApiModel

View File

@ -0,0 +1,13 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordSearchResult (
count: Option[Long],
lexicality: Option[Double],
word: Option[String])
extends ApiModel

View File

@ -0,0 +1,12 @@
package io.swagger.client.model
import io.swagger.client.core.ApiModel
import org.joda.time.DateTime
case class WordSearchResults (
searchResults: Option[Seq[WordSearchResult]],
totalResults: Option[Int])
extends ApiModel