Merge branch 'master' of github.com:wordnik/swagger-codegen

This commit is contained in:
Tony Tam
2014-08-02 16:04:55 -07:00
6 changed files with 193 additions and 31 deletions

View File

@@ -45,6 +45,7 @@
// query params
var queryParams = new Dictionary<String, String>();
var headerParams = new Dictionary<String, String>();
var formParams = new Dictionary<String, object>();
{{#requiredParamCount}}
// verify required params are set
@@ -53,9 +54,8 @@
}
{{/requiredParamCount}}
string paramStr = null;
{{#queryParams}}if ({{paramName}} != null){
paramStr = ({{paramName}} != null && {{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
queryParams.Add("{{paramName}}", paramStr);
}
{{/queryParams}}
@@ -63,17 +63,32 @@
{{#headerParams}}headerParams.Add("{{paramName}}", {{paramName}});
{{/headerParams}}
try {
var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams);
if(response != null){
return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}};
{{#formParams}}if ({{paramName}} != null){
if({{paramName}} is byte[]) {
formParams.Add("{{paramName}}", {{paramName}});
} else {
string paramStr = ({{paramName}} is DateTime) ? ((DateTime)(object){{paramName}}).ToString("u") : Convert.ToString({{paramName}});
formParams.Add("{{paramName}}", paramStr);
}
else {
return {{#returnType}}null{{/returnType}};
}
{{/formParams}}
try {
if (typeof({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}) == typeof(byte[])) {
var response = apiInvoker.invokeBinaryAPI(basePath, path, "GET", queryParams, null, headerParams, formParams);
return {{#returnType}}((object)response) as {{{returnType}}}{{/returnType}};
} else {
var response = apiInvoker.invokeAPI(basePath, path, "{{httpMethod}}", queryParams, {{#bodyParam}}{{bodyParam}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}}, headerParams, formParams);
if(response != null){
return {{#returnType}}({{{returnType}}}) ApiInvoker.deserialize(response, typeof({{{returnType}}})){{/returnType}};
}
else {
return {{#returnType}}null{{/returnType}};
}
}
} catch (ApiException ex) {
if(ex.ErrorCode == 404) {
return {{#returnType}} null{{/returnType}};
return {{#returnType}}null{{/returnType}};
}
else {
throw ex;

View File

@@ -44,7 +44,17 @@
}
}
public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams) {
public string invokeAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams)
{
return invokeAPIInternal(host, path, method, false, queryParams, body, headerParams, formParams) as string;
}
public byte[] invokeBinaryAPI(string host, string path, string method, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams)
{
return invokeAPIInternal(host, path, method, true, queryParams, body, headerParams, formParams) as byte[];
}
private object invokeAPIInternal(string host, string path, string method, bool binaryResponse, Dictionary<String, String> queryParams, object body, Dictionary<String, String> headerParams, Dictionary<String, object> formParams) {
var b = new StringBuilder();
foreach (var queryParamItem in queryParams)
@@ -60,9 +70,21 @@
host = host.EndsWith("/") ? host.Substring(0, host.Length - 1) : host;
var client = WebRequest.Create(host + path + querystring);
client.ContentType = "application/json";
client.Method = method;
byte[] formData = null;
if (formParams.Count > 0)
{
string formDataBoundary = String.Format("----------{0:N}", Guid.NewGuid());
client.ContentType = "multipart/form-data; boundary=" + formDataBoundary;
formData = GetMultipartFormData(formParams, formDataBoundary);
client.ContentLength = formData.Length;
}
else
{
client.ContentType = "application/json";
}
foreach (var headerParamsItem in headerParams)
{
client.Headers.Add(headerParamsItem.Key, headerParamsItem.Value);
@@ -79,9 +101,17 @@
case "POST":
case "PUT":
case "DELETE":
var swRequestWriter = new StreamWriter(client.GetRequestStream());
swRequestWriter.Write(serialize(body));
swRequestWriter.Close();
using (Stream requestStream = client.GetRequestStream())
{
if (formData != null)
{
requestStream.Write(formData, 0, formData.Length);
}
var swRequestWriter = new StreamWriter(requestStream);
swRequestWriter.Write(serialize(body));
swRequestWriter.Close();
}
break;
default:
throw new ApiException(500, "unknown method type " + method);
@@ -96,10 +126,22 @@
throw new ApiException((int)webResponse.StatusCode, webResponse.StatusDescription);
}
var responseReader = new StreamReader(webResponse.GetResponseStream());
var responseData = responseReader.ReadToEnd();
responseReader.Close();
return responseData;
if (binaryResponse)
{
using (var memoryStream = new MemoryStream())
{
webResponse.GetResponseStream().CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
else
{
using (var responseReader = new StreamReader(webResponse.GetResponseStream()))
{
var responseData = responseReader.ReadToEnd();
return responseData;
}
}
}
catch(WebException ex)
{
@@ -113,5 +155,53 @@
throw new ApiException(statusCode, ex.Message);
}
}
private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary)
{
Stream formDataStream = new System.IO.MemoryStream();
bool needsCLRF = false;
foreach (var param in postParameters)
{
// Thanks to feedback from commenters, add a CRLF to allow multiple parameters to be added.
// Skip it on the first parameter, add it to subsequent parameters.
if (needsCLRF)
formDataStream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, Encoding.UTF8.GetByteCount("\r\n"));
needsCLRF = true;
if (param.Value is byte[])
{
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{1}\"\r\nContent-Type: {2}\r\n\r\n",
boundary,
param.Key,
"application/octet-stream");
formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
// Write the file data directly to the Stream, rather than serializing it to a string.
formDataStream.Write((param.Value as byte[]), 0, (param.Value as byte[]).Length);
}
else
{
string postData = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}",
boundary,
param.Key,
param.Value);
formDataStream.Write(Encoding.UTF8.GetBytes(postData), 0, Encoding.UTF8.GetByteCount(postData));
}
}
// Add the end of the request. Start with a newline
string footer = "\r\n--" + boundary + "--\r\n";
formDataStream.Write(Encoding.UTF8.GetBytes(footer), 0, Encoding.UTF8.GetByteCount(footer));
// Dump the Stream into a byte[]
formDataStream.Position = 0;
byte[] formData = new byte[formDataStream.Length];
formDataStream.Read(formData, 0, formData.Length);
formDataStream.Close();
return formData;
}
}
}

View File

@@ -41,7 +41,7 @@ class BasicCSharpGenerator extends BasicGenerator {
* We are using csharp objects instead of primitives to avoid showing default
* primitive values when the API returns missing data. For instance, having a
* {"count":0} != count is unknown. You can change this to use primitives if you
* desire, but update the default values as well or they'll be set to null in
* desire, but update the default values as well or they'll be set to null in
* variable declarations.
*/
override def typeMapping = Map(
@@ -54,7 +54,9 @@ class BasicCSharpGenerator extends BasicGenerator {
"double" -> "double?",
"object" -> "object",
"Date" -> "DateTime?",
"date" -> "DateTime?")
"date" -> "DateTime?",
"File" -> "byte[]",
"file" -> "byte[]")
// location of templates
override def templateDir = "csharp"
@@ -70,11 +72,11 @@ class BasicCSharpGenerator extends BasicGenerator {
// template used for models
apiTemplateFiles += "api.mustache" -> ".cs"
override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert",
"default", "if", "package", "synchronized", "do", "goto", "private", "this", "break",
"implements", "protected", "throw", "else", "import", "public", "throws", "case",
"enum", "instanceof", "return", "transient", "catch", "extends", "try", "final",
"interface", "static", "void", "class", "finally", "strictfp", "volatile", "const",
override def reservedWords = Set("abstract", "continue", "for", "new", "switch", "assert",
"default", "if", "package", "synchronized", "do", "goto", "private", "this", "break",
"implements", "protected", "throw", "else", "import", "public", "throws", "case",
"enum", "instanceof", "return", "transient", "catch", "extends", "try", "final",
"interface", "static", "void", "class", "finally", "strictfp", "volatile", "const",
"native", "super", "while")
// import/require statements for specific datatypes
@@ -180,7 +182,7 @@ class BasicCSharpGenerator extends BasicGenerator {
}
override def escapeReservedWord(word: String) = {
if (reservedWords.contains(word))
if (reservedWords.contains(word))
throw new Exception("reserved word " + "\"" + word + "\" not allowed")
else word
}
@@ -193,8 +195,8 @@ class BasicCSharpGenerator extends BasicGenerator {
capitalize(name)
}
def capitalize(s: String) = {
s(0).toUpper + s.substring(1, s.length).toLowerCase
def capitalize(s: String) = {
s(0).toUpper + s.substring(1, s.length).toLowerCase
}*/
// supporting classes

View File

@@ -309,7 +309,7 @@ class Codegen(config: CodegenConfig) {
val ComplexTypeMatcher(basePart) = operation.responseClass
properties += "returnType" -> config.processResponseDeclaration(operation.responseClass.replaceAll(basePart, config.processResponseClass(basePart).get))
properties += "returnContainer" -> (operation.responseClass.substring(0, n))
properties += "returnContainer" -> config.processResponseClass(operation.responseClass.substring(0, n))
properties += "returnBaseType" -> config.processResponseClass(basePart)
properties += "returnTypeIsPrimitive" -> {
(config.languageSpecificPrimitives.contains(basePart) || primitives.contains(basePart)) match {

View File

@@ -211,7 +211,7 @@ class BasicScalaGeneratorTest extends FlatSpec with ShouldMatchers {
m("returnType") should be (Some("List[Pet]"))
m("returnTypeIsPrimitive") should be (None)
m("pathParams").asInstanceOf[List[_]].size should be (0)
m("returnContainer") should be ("List")
m("returnContainer") should be (Some("List"))
m("requiredParamCount") should be ("1")
val queryParams = m("queryParams").asInstanceOf[List[_]]
@@ -248,7 +248,7 @@ class BasicScalaGeneratorTest extends FlatSpec with ShouldMatchers {
m("returnType") should be (Some("List[Pet]"))
m("returnTypeIsPrimitive") should be (None)
m("pathParams").asInstanceOf[List[_]].size should be (0)
m("returnContainer") should be ("List")
m("returnContainer") should be (Some("List"))
m("requiredParamCount") should be ("1")
val queryParams = m("queryParams").asInstanceOf[List[_]]

View File

@@ -0,0 +1,55 @@
/**
* Copyright 2014 Wordnik, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import com.wordnik.swagger.codegen.Codegen
import com.wordnik.swagger.codegen.BasicJavaGenerator
import com.wordnik.swagger.codegen.model._
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import scala.reflect.BeanProperty
@RunWith(classOf[JUnitRunner])
class CodegenTest extends FlatSpec with ShouldMatchers {
val subject = new Codegen(new BasicJavaGenerator)
val testOp = new Operation("GET",
"List All Contacts",
"",
"Array[ContactData]",
"listContacts",
0,
List.empty,
List.empty,
List.empty,
List.empty,
List.empty,
List.empty,
None)
behavior of "Codegen"
/*
* A return specified as "Array" should map to "List"
*/
it should "recognize the returnContainer as a List" in {
val map = subject.apiToMap("/contacts", testOp)
map("returnContainer") should be (Some("List"))
}
}