mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2025-07-03 06:00:52 +00:00
Use CLI option
This commit is contained in:
parent
65af735d6c
commit
91af76cf41
@ -183,7 +183,7 @@ StaticDocCodegen.java
|
||||
StaticHtmlGenerator.java
|
||||
SwaggerGenerator.java
|
||||
SwaggerYamlGenerator.java
|
||||
SwiftGenerator.java
|
||||
SwiftCodegen.java
|
||||
TizenClientCodegen.java
|
||||
```
|
||||
|
||||
|
4
bin/swift-petstore.json
Normal file
4
bin/swift-petstore.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"projectName": "PetstoreClient",
|
||||
"responseAs": "PromiseKit"
|
||||
}
|
@ -26,6 +26,6 @@ fi
|
||||
|
||||
# if you've executed sbt assembly previously it will use that instead.
|
||||
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
|
||||
ags="$@ generate -t modules/swagger-codegen/src/main/resources/swift -i modules/swagger-codegen/src/test/resources/2_0/petstore.json -l swift -o samples/client/petstore/swift"
|
||||
ags="$@ generate -t modules/swagger-codegen/src/main/resources/swift -i modules/swagger-codegen/src/test/resources/2_0/petstore.json -l swift -c ./bin/swift-petstore.json -o samples/client/petstore/swift"
|
||||
|
||||
java -DsuppressRequired=true -DappName=PetstoreClient $JAVA_OPTS -jar $executable $ags
|
||||
java $JAVA_OPTS -jar $executable $ags
|
||||
|
@ -11,6 +11,7 @@ import io.swagger.models.parameters.Parameter;
|
||||
import io.swagger.models.properties.ArrayProperty;
|
||||
import io.swagger.models.properties.MapProperty;
|
||||
import io.swagger.models.properties.Property;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -19,8 +20,13 @@ import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SwiftGenerator extends DefaultCodegen implements CodegenConfig {
|
||||
public class SwiftCodegen extends DefaultCodegen implements CodegenConfig {
|
||||
private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\{[a-zA-Z_]+\\}");
|
||||
protected static final String LIBRARY_PROMISE_KIT = "PromiseKit";
|
||||
protected static final String[] RESPONSE_LIBRARIES = { LIBRARY_PROMISE_KIT };
|
||||
protected String projectName = "SwaggerClient";
|
||||
protected boolean unwrapRequired = false;
|
||||
protected String[] responseAs = new String[0];
|
||||
protected String sourceFolder = "Classes" + File.separator + "Swaggers";
|
||||
|
||||
public CodegenType getTag() {
|
||||
@ -35,7 +41,7 @@ public class SwiftGenerator extends DefaultCodegen implements CodegenConfig {
|
||||
return "Generates a swift client library.";
|
||||
}
|
||||
|
||||
public SwiftGenerator() {
|
||||
public SwiftCodegen() {
|
||||
super();
|
||||
outputFolder = "generated-code" + File.separator + "swift";
|
||||
modelTemplateFiles.put("model.mustache", ".swift");
|
||||
@ -44,34 +50,6 @@ public class SwiftGenerator extends DefaultCodegen implements CodegenConfig {
|
||||
apiPackage = File.separator + "APIs";
|
||||
modelPackage = File.separator + "Models";
|
||||
|
||||
// Inject application name
|
||||
String appName = System.getProperty("appName");
|
||||
if (appName == null) {
|
||||
appName = "SwaggerClient";
|
||||
}
|
||||
additionalProperties.put("projectName", appName);
|
||||
|
||||
// Inject base url override
|
||||
String basePathOverride = System.getProperty("basePathOverride");
|
||||
if (basePathOverride != null) {
|
||||
additionalProperties.put("basePathOverride", basePathOverride);
|
||||
}
|
||||
|
||||
// Make all the variable optional
|
||||
String suppressRequired = System.getProperty("suppressRequired");
|
||||
if (suppressRequired != null) {
|
||||
additionalProperties.put("suppressRequired", suppressRequired);
|
||||
}
|
||||
|
||||
sourceFolder = appName + File.separator + sourceFolder;
|
||||
|
||||
supportingFiles.add(new SupportingFile("Cartfile.mustache", "", "Cartfile"));
|
||||
supportingFiles.add(new SupportingFile("APIHelper.mustache", sourceFolder, "APIHelper.swift"));
|
||||
supportingFiles.add(new SupportingFile("AlamofireImplementations.mustache", sourceFolder, "AlamofireImplementations.swift"));
|
||||
supportingFiles.add(new SupportingFile("Extensions.mustache", sourceFolder, "Extensions.swift"));
|
||||
supportingFiles.add(new SupportingFile("Models.mustache", sourceFolder, "Models.swift"));
|
||||
supportingFiles.add(new SupportingFile("APIs.mustache", sourceFolder, "APIs.swift"));
|
||||
|
||||
languageSpecificPrimitives = new HashSet<String>(
|
||||
Arrays.asList(
|
||||
"Int",
|
||||
@ -125,6 +103,53 @@ public class SwiftGenerator extends DefaultCodegen implements CodegenConfig {
|
||||
typeMapping.put("file", "NSData");
|
||||
|
||||
importMapping = new HashMap<String, String>();
|
||||
|
||||
cliOptions.add(new CliOption("projectName", "Project name in Xcode"));
|
||||
cliOptions.add(new CliOption("responseAs", "Optionally use libraries to manage response. Currently " +
|
||||
StringUtils.join(RESPONSE_LIBRARIES, ", ") + " are available."));
|
||||
cliOptions.add(new CliOption("unwrapRequired", "Treat 'required' properties in response as non-optional " +
|
||||
"(which would crash the app if api returns null as opposed to required option specified in json schema"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processOpts() {
|
||||
super.processOpts();
|
||||
|
||||
// Setup project name
|
||||
if (additionalProperties.containsKey("projectName")) {
|
||||
projectName = (String) additionalProperties.get("projectName");
|
||||
} else {
|
||||
additionalProperties.put("projectName", projectName);
|
||||
}
|
||||
sourceFolder = projectName + File.separator + sourceFolder;
|
||||
|
||||
// Setup unwrapRequired option, which makes all the properties with "required" non-optional
|
||||
if (additionalProperties.containsKey("unwrapRequired")) {
|
||||
unwrapRequired = Boolean.parseBoolean(String.valueOf(additionalProperties.get("unwrapRequired")));
|
||||
}
|
||||
additionalProperties.put("unwrapRequired", unwrapRequired);
|
||||
|
||||
// Setup unwrapRequired option, which makes all the properties with "required" non-optional
|
||||
if (additionalProperties.containsKey("responseAs")) {
|
||||
Object responseAsObject = additionalProperties.get("responseAs");
|
||||
if (responseAsObject instanceof String) {
|
||||
responseAs = ((String)responseAsObject).split(",");
|
||||
} else {
|
||||
responseAs = (String[]) responseAsObject;
|
||||
}
|
||||
}
|
||||
additionalProperties.put("responseAs", responseAs);
|
||||
if (ArrayUtils.contains(responseAs, LIBRARY_PROMISE_KIT)) {
|
||||
additionalProperties.put("usePromiseKit", true);
|
||||
}
|
||||
|
||||
supportingFiles.add(new SupportingFile("Cartfile.mustache", "", "Cartfile"));
|
||||
supportingFiles.add(new SupportingFile("APIHelper.mustache", sourceFolder, "APIHelper.swift"));
|
||||
supportingFiles.add(new SupportingFile("AlamofireImplementations.mustache", sourceFolder,
|
||||
"AlamofireImplementations.swift"));
|
||||
supportingFiles.add(new SupportingFile("Extensions.mustache", sourceFolder, "Extensions.swift"));
|
||||
supportingFiles.add(new SupportingFile("Models.mustache", sourceFolder, "Models.swift"));
|
||||
supportingFiles.add(new SupportingFile("APIs.mustache", sourceFolder, "APIs.swift"));
|
||||
}
|
||||
|
||||
@Override
|
@ -21,7 +21,7 @@ io.swagger.codegen.languages.StaticDocCodegen
|
||||
io.swagger.codegen.languages.StaticHtmlGenerator
|
||||
io.swagger.codegen.languages.SwaggerGenerator
|
||||
io.swagger.codegen.languages.SwaggerYamlGenerator
|
||||
io.swagger.codegen.languages.SwiftGenerator
|
||||
io.swagger.codegen.languages.SwiftCodegen
|
||||
io.swagger.codegen.languages.TizenClientCodegen
|
||||
io.swagger.codegen.languages.TypeScriptAngularClientCodegen
|
||||
io.swagger.codegen.languages.TypeScriptNodeClientCodegen
|
||||
|
@ -5,10 +5,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
class {{projectName}}API {
|
||||
static let basePath = "{{^basePathOverride}}{{basePath}}{{/basePathOverride}}{{basePathOverride}}"
|
||||
class OneteamAPI {
|
||||
static let basePath = "http://ec2-52-68-31-200.ap-northeast-1.compute.amazonaws.com/"
|
||||
static var credential: NSURLCredential?
|
||||
static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
|
||||
}
|
||||
@ -44,7 +43,7 @@ class RequestBuilder<T> {
|
||||
self.isBody = isBody
|
||||
}
|
||||
|
||||
func execute() -> Promise<Response<T>> { fatalError("Not implemented") }
|
||||
func execute(completion: (response: Response<T>?, erorr: NSError?) -> Void) { }
|
||||
|
||||
func addHeader(#name: String, value: String) -> Self {
|
||||
if !value.isEmpty {
|
||||
@ -54,7 +53,7 @@ class RequestBuilder<T> {
|
||||
}
|
||||
|
||||
func addCredential() -> Self {
|
||||
self.credential = {{projectName}}API.credential
|
||||
self.credential = OneteamAPI.credential
|
||||
return self
|
||||
}
|
||||
}
|
||||
@ -63,4 +62,3 @@ protocol RequestBuilderFactory {
|
||||
func getBuilder<T>() -> RequestBuilder<T>.Type
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
//
|
||||
|
||||
import Alamofire
|
||||
import PromiseKit
|
||||
|
||||
class AlamofireRequestBuilderFactory: RequestBuilderFactory {
|
||||
func getBuilder<T>() -> RequestBuilder<T>.Type {
|
||||
@ -21,7 +20,7 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
super.init(method: method, URLString: URLString, parameters: parameters, isBody: isBody)
|
||||
}
|
||||
|
||||
override func execute() -> Promise<Response<T>> {
|
||||
override func execute(completion: (response: Response<T>?, erorr: NSError?) -> Void) {
|
||||
let managerId = NSUUID().UUIDString
|
||||
// Create a new manager for each request to customize its request header
|
||||
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
|
||||
@ -35,43 +34,41 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
request.authenticate(usingCredential: credential)
|
||||
}
|
||||
|
||||
let defer = Promise<Response<T>>.defer()
|
||||
request.responseJSON(options: .AllowFragments) { (req, res, json, error) in
|
||||
managerStore.removeValueForKey(managerId)
|
||||
|
||||
if let error = error {
|
||||
defer.reject(error)
|
||||
completion(response: nil, erorr: error)
|
||||
return
|
||||
}
|
||||
if res!.statusCode >= 400 {
|
||||
//TODO: Add error entity
|
||||
let userInfo: [NSObject : AnyObject] = (json != nil) ? ["data": json!] : [:]
|
||||
let error = NSError(domain: res!.URL!.URLString, code: res!.statusCode, userInfo: userInfo)
|
||||
defer.reject(error)
|
||||
completion(response: nil, erorr: error)
|
||||
return
|
||||
}
|
||||
|
||||
if () is T {
|
||||
let response = Response(response: res!, body: () as! T)
|
||||
defer.fulfill(response)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
}
|
||||
if let json: AnyObject = json {
|
||||
let body = Decoders.decode(clazz: T.self, source: json)
|
||||
let response = Response(response: res!, body: body)
|
||||
defer.fulfill(response)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
} else if "" is T {
|
||||
// swagger-parser currently doesn't support void, which will be fixed in future swagger-parser release
|
||||
// https://github.com/swagger-api/swagger-parser/pull/34
|
||||
let response = Response(response: res!, body: "" as! T)
|
||||
defer.fulfill(response)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
}
|
||||
|
||||
defer.reject(NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"]))
|
||||
completion(response: nil, erorr: NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"]))
|
||||
}
|
||||
return defer.promise
|
||||
}
|
||||
|
||||
private func buildHeaders() -> [String: AnyObject] {
|
||||
@ -83,4 +80,3 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -4,8 +4,8 @@
|
||||
// https://github.com/swagger-api/swagger-codegen
|
||||
//
|
||||
|
||||
import Alamofire
|
||||
import PromiseKit
|
||||
import Alamofire{{#usePromiseKit}}
|
||||
import PromiseKit{{/usePromiseKit}}
|
||||
|
||||
extension Bool: JSONEncodable {
|
||||
func encodeToJSON() -> AnyObject { return self }
|
||||
@ -63,3 +63,17 @@ extension NSDate: JSONEncodable {
|
||||
return dateFormatter.stringFromDate(self)
|
||||
}
|
||||
}
|
||||
|
||||
{{#usePromiseKit}}extension RequestBuilder {
|
||||
func execute() -> Promise<Response<T>> {
|
||||
let deferred = Promise<Response<T>>.defer()
|
||||
self.execute { (response: Response<T>?, error: NSError?) in
|
||||
if let response = response {
|
||||
deferred.fulfill(response)
|
||||
} else {
|
||||
deferred.reject(error!)
|
||||
}
|
||||
}
|
||||
return deferred.promise
|
||||
}
|
||||
}{{/usePromiseKit}}
|
||||
|
@ -121,8 +121,8 @@ class Decoders {
|
||||
Decoders.addDecoder(clazz: {{{classname}}}.self) { (source: AnyObject) -> {{{classname}}} in
|
||||
let sourceDictionary = source as! [NSObject:AnyObject]
|
||||
var instance = {{classname}}(){{#vars}}{{#isEnum}}
|
||||
instance.{{name}} = (sourceDictionary["{{name}}"] as? String).map { {{classname}}.{{datatypeWithEnum}}(rawValue: $0)! }{{^suppressRequired}}{{#required}}!{{/required}}{{/suppressRequired}} {{/isEnum}}{{^isEnum}}
|
||||
instance.{{name}} = Decoders.decode{{#suppressRequired}}Optional{{/suppressRequired}}{{^suppressRequired}}{{^required}}Optional{{/required}}{{/suppressRequired}}(clazz: {{{baseType}}}.self, source: sourceDictionary["{{name}}"]{{^suppressRequired}}{{#required}}!{{/required}}{{/suppressRequired}}){{/isEnum}}{{/vars}}
|
||||
instance.{{name}} = (sourceDictionary["{{name}}"] as? String).map { {{classname}}.{{datatypeWithEnum}}(rawValue: $0)! }{{#unwrapRequired}}{{#required}}!{{/required}}{{/unwrapRequired}} {{/isEnum}}{{^isEnum}}
|
||||
instance.{{name}} = Decoders.decode{{^unwrapRequired}}Optional{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}Optional{{/required}}{{/unwrapRequired}}(clazz: {{{baseType}}}.self, source: sourceDictionary["{{name}}"]{{#unwrapRequired}}{{#required}}!{{/required}}{{/unwrapRequired}}){{/isEnum}}{{/vars}}
|
||||
return instance
|
||||
}{{/model}}
|
||||
{{/models}}
|
||||
|
@ -17,17 +17,17 @@ class {{classname}}: JSONEncodable {
|
||||
}
|
||||
{{/isEnum}}{{/vars}}
|
||||
{{#vars}}{{#isEnum}}{{#description}}/** {{description}} */
|
||||
{{/description}}var {{name}}: {{{datatypeWithEnum}}}{{#suppressRequired}}?{{/suppressRequired}}{{^suppressRequired}}{{^required}}?{{/required}}{{#required}}!{{/required}}{{/suppressRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/isEnum}}{{^isEnum}}{{#description}}/** {{description}} */
|
||||
{{/description}}var {{name}}: {{{datatype}}}{{#suppressRequired}}?{{/suppressRequired}}{{^suppressRequired}}{{^required}}?{{/required}}{{#required}}!{{/required}}{{/suppressRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/isEnum}}
|
||||
{{/description}}var {{name}}: {{{datatypeWithEnum}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{#required}}!{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/isEnum}}{{^isEnum}}{{#description}}/** {{description}} */
|
||||
{{/description}}var {{name}}: {{{datatype}}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{#required}}!{{/required}}{{/unwrapRequired}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/isEnum}}
|
||||
{{/vars}}
|
||||
|
||||
// MARK: JSONEncodable
|
||||
func encodeToJSON() -> AnyObject {
|
||||
var nillableDictionary = [String:AnyObject?](){{#vars}}{{#isNotContainer}}{{#isPrimitiveType}}{{^isEnum}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{/isEnum}}{{/isPrimitiveType}}{{#isEnum}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{#suppressRequired}}?{{/suppressRequired}}{{^suppressRequired}}{{^required}}?{{/required}}{{/suppressRequired}}.rawValue{{/isEnum}}{{^isPrimitiveType}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{#suppressRequired}}?{{/suppressRequired}}{{^suppressRequired}}{{^required}}?{{/required}}{{/suppressRequired}}.encodeToJSON(){{/isPrimitiveType}}{{/isNotContainer}}{{#isContainer}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{#suppressRequired}}?{{/suppressRequired}}{{^suppressRequired}}{{^required}}?{{/required}}{{/suppressRequired}}.encodeToJSON(){{/isContainer}}{{/vars}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}.rawValue{{/isEnum}}{{^isPrimitiveType}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}.encodeToJSON(){{/isPrimitiveType}}{{/isNotContainer}}{{#isContainer}}
|
||||
nillableDictionary["{{name}}"] = self.{{name}}{{^unwrapRequired}}?{{/unwrapRequired}}{{#unwrapRequired}}{{^required}}?{{/required}}{{/unwrapRequired}}.encodeToJSON(){{/isContainer}}{{/vars}}
|
||||
let dictionary: [String:AnyObject] = APIHelper.rejectNil(nillableDictionary) ?? [:]
|
||||
return dictionary
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
github "Alamofire/Alamofire" >= 1.2
|
||||
github "mxcl/PromiseKit"
|
@ -5,10 +5,9 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import PromiseKit
|
||||
|
||||
class PetstoreClientAPI {
|
||||
static let basePath = "http://petstore.swagger.io/v2"
|
||||
class OneteamAPI {
|
||||
static let basePath = "http://ec2-52-68-31-200.ap-northeast-1.compute.amazonaws.com/"
|
||||
static var credential: NSURLCredential?
|
||||
static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
|
||||
}
|
||||
@ -44,7 +43,7 @@ class RequestBuilder<T> {
|
||||
self.isBody = isBody
|
||||
}
|
||||
|
||||
func execute() -> Promise<Response<T>> { fatalError("Not implemented") }
|
||||
func execute(completion: (response: Response<T>?, erorr: NSError?) -> Void) { }
|
||||
|
||||
func addHeader(#name: String, value: String) -> Self {
|
||||
if !value.isEmpty {
|
||||
@ -54,7 +53,7 @@ class RequestBuilder<T> {
|
||||
}
|
||||
|
||||
func addCredential() -> Self {
|
||||
self.credential = PetstoreClientAPI.credential
|
||||
self.credential = OneteamAPI.credential
|
||||
return self
|
||||
}
|
||||
}
|
||||
@ -63,4 +62,3 @@ protocol RequestBuilderFactory {
|
||||
func getBuilder<T>() -> RequestBuilder<T>.Type
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,8 +71,44 @@ extension PetstoreClientAPI {
|
||||
- OAuth:
|
||||
- type: oauth2
|
||||
- name: petstore_auth
|
||||
- examples: [{example=[ {\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n} ], contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n} ], contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
} ], contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
} ], contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
|
||||
:param: status (query) Status values that need to be considered for filter
|
||||
|
||||
@ -101,8 +137,44 @@ extension PetstoreClientAPI {
|
||||
- OAuth:
|
||||
- type: oauth2
|
||||
- name: petstore_auth
|
||||
- examples: [{example=[ {\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n} ], contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n} ], contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
} ], contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
- examples: [{example=[ {
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
} ], contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
|
||||
:param: tags (query) Tags to filter by
|
||||
|
||||
@ -134,14 +206,50 @@ extension PetstoreClientAPI {
|
||||
- OAuth:
|
||||
- type: oauth2
|
||||
- name: petstore_auth
|
||||
- examples: [{example={\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n}, contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example={\n "tags" : [ {\n "id" : 123456789,\n "name" : "aeiou"\n } ],\n "id" : 123456789,\n "category" : {\n "id" : 123456789,\n "name" : "aeiou"\n },\n "status" : "aeiou",\n "name" : "doggie",\n "photoUrls" : [ "aeiou" ]\n}, contentType=application/json}, {example=<Pet>\n <id>123456</id>\n <Category>\n <id>123456</id>\n <name>string</name>\n </Category>\n <name>doggie</name>\n <photoUrls>string</photoUrls>\n <Tag>\n <id>123456</id>\n <name>string</name>\n </Tag>\n <status>string</status>\n</Pet>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
}, contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"tags" : [ {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
} ],
|
||||
"id" : 123456789,
|
||||
"category" : {
|
||||
"id" : 123456789,
|
||||
"name" : "aeiou"
|
||||
},
|
||||
"status" : "aeiou",
|
||||
"name" : "doggie",
|
||||
"photoUrls" : [ "aeiou" ]
|
||||
}, contentType=application/json}, {example=<Pet>
|
||||
<id>123456</id>
|
||||
<name>doggie</name>
|
||||
<photoUrls>string</photoUrls>
|
||||
<status>string</status>
|
||||
</Pet>, contentType=application/xml}]
|
||||
|
||||
:param: petId (path) ID of pet that needs to be fetched
|
||||
|
||||
:returns: Promise<Response<Pet>>
|
||||
*/
|
||||
func getPetById(#petId: Int?) -> RequestBuilder<Pet> {
|
||||
func getPetById(#petId: Int) -> RequestBuilder<Pet> {
|
||||
var path = "/pet/{petId}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{petId}", withString: "\(petId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -170,7 +278,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func updatePetWithForm(#petId: String?, name: String?, status: String?) -> RequestBuilder<Void> {
|
||||
func updatePetWithForm(#petId: String, name: String?, status: String?) -> RequestBuilder<Void> {
|
||||
var path = "/pet/{petId}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{petId}", withString: "\(petId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -197,7 +305,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func deletePet(#petId: Int?) -> RequestBuilder<Void> {
|
||||
func deletePet(#petId: Int) -> RequestBuilder<Void> {
|
||||
var path = "/pet/{petId}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{petId}", withString: "\(petId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -226,7 +334,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func uploadFile(#petId: Int?, additionalMetadata: String?, file: NSData?) -> RequestBuilder<Void> {
|
||||
func uploadFile(#petId: Int, additionalMetadata: String?, file: NSData?) -> RequestBuilder<Void> {
|
||||
var path = "/pet/{petId}/uploadImage"
|
||||
path = path.stringByReplacingOccurrencesOfString("{petId}", withString: "\(petId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
|
@ -21,8 +21,12 @@ extension PetstoreClientAPI {
|
||||
- API Key:
|
||||
- type: apiKey api_key
|
||||
- name: api_key
|
||||
- examples: [{example={\n "key" : 123\n}, contentType=application/json}, {example=not implemented com.wordnik.swagger.models.properties.MapProperty@5c7e707e, contentType=application/xml}]
|
||||
- examples: [{example={\n "key" : 123\n}, contentType=application/json}, {example=not implemented com.wordnik.swagger.models.properties.MapProperty@5c7e707e, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"key" : 123
|
||||
}, contentType=application/json}, {example=not implemented io.swagger.models.properties.MapProperty@3e, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"key" : 123
|
||||
}, contentType=application/json}, {example=not implemented io.swagger.models.properties.MapProperty@3e, contentType=application/xml}]
|
||||
|
||||
:returns: Promise<Response<[String:Int]>>
|
||||
*/
|
||||
@ -44,8 +48,36 @@ extension PetstoreClientAPI {
|
||||
|
||||
- POST /store/order
|
||||
-
|
||||
- examples: [{example={\n "id" : 123456789,\n "petId" : 123456789,\n "complete" : true,\n "status" : "aeiou",\n "quantity" : 123,\n "shipDate" : "2015-05-27T04:22:21.814+0000"\n}, contentType=application/json}, {example=<Order>\n <id>123456</id>\n <petId>123456</petId>\n <quantity>0</quantity>\n <shipDate>2015-05-27T13:22:21.817Z</shipDate>\n <status>string</status>\n <complete>true</complete>\n</Order>, contentType=application/xml}]
|
||||
- examples: [{example={\n "id" : 123456789,\n "petId" : 123456789,\n "complete" : true,\n "status" : "aeiou",\n "quantity" : 123,\n "shipDate" : "2015-05-27T04:22:21.814+0000"\n}, contentType=application/json}, {example=<Order>\n <id>123456</id>\n <petId>123456</petId>\n <quantity>0</quantity>\n <shipDate>2015-05-27T13:22:21.817Z</shipDate>\n <status>string</status>\n <complete>true</complete>\n</Order>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"id" : 123456789,
|
||||
"petId" : 123456789,
|
||||
"complete" : true,
|
||||
"status" : "aeiou",
|
||||
"quantity" : 123,
|
||||
"shipDate" : "2015-06-27T13:41:28.102+0000"
|
||||
}, contentType=application/json}, {example=<Order>
|
||||
<id>123456</id>
|
||||
<petId>123456</petId>
|
||||
<quantity>0</quantity>
|
||||
<shipDate>2015-06-27T22:41:28.105Z</shipDate>
|
||||
<status>string</status>
|
||||
<complete>true</complete>
|
||||
</Order>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"id" : 123456789,
|
||||
"petId" : 123456789,
|
||||
"complete" : true,
|
||||
"status" : "aeiou",
|
||||
"quantity" : 123,
|
||||
"shipDate" : "2015-06-27T13:41:28.102+0000"
|
||||
}, contentType=application/json}, {example=<Order>
|
||||
<id>123456</id>
|
||||
<petId>123456</petId>
|
||||
<quantity>0</quantity>
|
||||
<shipDate>2015-06-27T22:41:28.105Z</shipDate>
|
||||
<status>string</status>
|
||||
<complete>true</complete>
|
||||
</Order>, contentType=application/xml}]
|
||||
|
||||
:param: body (body) order placed for purchasing the pet
|
||||
|
||||
@ -68,14 +100,42 @@ extension PetstoreClientAPI {
|
||||
|
||||
- GET /store/order/{orderId}
|
||||
- For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
|
||||
- examples: [{example={\n "id" : 123456789,\n "petId" : 123456789,\n "complete" : true,\n "status" : "aeiou",\n "quantity" : 123,\n "shipDate" : "2015-05-27T04:22:21.818+0000"\n}, contentType=application/json}, {example=<Order>\n <id>123456</id>\n <petId>123456</petId>\n <quantity>0</quantity>\n <shipDate>2015-05-27T13:22:21.818Z</shipDate>\n <status>string</status>\n <complete>true</complete>\n</Order>, contentType=application/xml}]
|
||||
- examples: [{example={\n "id" : 123456789,\n "petId" : 123456789,\n "complete" : true,\n "status" : "aeiou",\n "quantity" : 123,\n "shipDate" : "2015-05-27T04:22:21.818+0000"\n}, contentType=application/json}, {example=<Order>\n <id>123456</id>\n <petId>123456</petId>\n <quantity>0</quantity>\n <shipDate>2015-05-27T13:22:21.818Z</shipDate>\n <status>string</status>\n <complete>true</complete>\n</Order>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"id" : 123456789,
|
||||
"petId" : 123456789,
|
||||
"complete" : true,
|
||||
"status" : "aeiou",
|
||||
"quantity" : 123,
|
||||
"shipDate" : "2015-06-27T13:41:28.106+0000"
|
||||
}, contentType=application/json}, {example=<Order>
|
||||
<id>123456</id>
|
||||
<petId>123456</petId>
|
||||
<quantity>0</quantity>
|
||||
<shipDate>2015-06-27T22:41:28.106Z</shipDate>
|
||||
<status>string</status>
|
||||
<complete>true</complete>
|
||||
</Order>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"id" : 123456789,
|
||||
"petId" : 123456789,
|
||||
"complete" : true,
|
||||
"status" : "aeiou",
|
||||
"quantity" : 123,
|
||||
"shipDate" : "2015-06-27T13:41:28.106+0000"
|
||||
}, contentType=application/json}, {example=<Order>
|
||||
<id>123456</id>
|
||||
<petId>123456</petId>
|
||||
<quantity>0</quantity>
|
||||
<shipDate>2015-06-27T22:41:28.106Z</shipDate>
|
||||
<status>string</status>
|
||||
<complete>true</complete>
|
||||
</Order>, contentType=application/xml}]
|
||||
|
||||
:param: orderId (path) ID of pet that needs to be fetched
|
||||
|
||||
:returns: Promise<Response<Order>>
|
||||
*/
|
||||
func getOrderById(#orderId: String?) -> RequestBuilder<Order> {
|
||||
func getOrderById(#orderId: String) -> RequestBuilder<Order> {
|
||||
var path = "/store/order/{orderId}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{orderId}", withString: "\(orderId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -99,7 +159,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func deleteOrder(#orderId: String?) -> RequestBuilder<Void> {
|
||||
func deleteOrder(#orderId: String) -> RequestBuilder<Void> {
|
||||
var path = "/store/order/{orderId}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{orderId}", withString: "\(orderId)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
|
@ -134,14 +134,22 @@ extension PetstoreClientAPI {
|
||||
|
||||
- GET /user/{username}
|
||||
-
|
||||
- examples: [{example={\n "id" : 123456789,\n "lastName" : "aeiou",\n "phone" : "aeiou",\n "username" : "aeiou",\n "email" : "aeiou",\n "userStatus" : 123,\n "firstName" : "aeiou",\n "password" : "aeiou"\n}, contentType=application/json}, {example=<User>\n <id>123456</id>\n <username>string</username>\n <firstName>string</firstName>\n <lastName>string</lastName>\n <email>string</email>\n <password>string</password>\n <phone>string</phone>\n <userStatus>0</userStatus>\n</User>, contentType=application/xml}]
|
||||
- examples: [{example={\n "id" : 123456789,\n "lastName" : "aeiou",\n "phone" : "aeiou",\n "username" : "aeiou",\n "email" : "aeiou",\n "userStatus" : 123,\n "firstName" : "aeiou",\n "password" : "aeiou"\n}, contentType=application/json}, {example=<User>\n <id>123456</id>\n <username>string</username>\n <firstName>string</firstName>\n <lastName>string</lastName>\n <email>string</email>\n <password>string</password>\n <phone>string</phone>\n <userStatus>0</userStatus>\n</User>, contentType=application/xml}]
|
||||
- examples: [{example={
|
||||
"id" : 1,
|
||||
"username" : "johnp",
|
||||
"firstName" : "John",
|
||||
"lastName" : "Public",
|
||||
"email" : "johnp@swagger.io",
|
||||
"password" : "-secret-",
|
||||
"phone" : "0123456789",
|
||||
"userStatus" : 0
|
||||
}, contentType=application/json}]
|
||||
|
||||
:param: username (path) The name that needs to be fetched. Use user1 for testing.
|
||||
|
||||
:returns: Promise<Response<User>>
|
||||
*/
|
||||
func getUserByName(#username: String?) -> RequestBuilder<User> {
|
||||
func getUserByName(#username: String) -> RequestBuilder<User> {
|
||||
var path = "/user/{username}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{username}", withString: "\(username)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -166,7 +174,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func updateUser(#username: String?, body: User?) -> RequestBuilder<Void> {
|
||||
func updateUser(#username: String, body: User?) -> RequestBuilder<Void> {
|
||||
var path = "/user/{username}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{username}", withString: "\(username)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
@ -189,7 +197,7 @@ extension PetstoreClientAPI {
|
||||
|
||||
:returns: Promise<Response<Void>>
|
||||
*/
|
||||
func deleteUser(#username: String?) -> RequestBuilder<Void> {
|
||||
func deleteUser(#username: String) -> RequestBuilder<Void> {
|
||||
var path = "/user/{username}"
|
||||
path = path.stringByReplacingOccurrencesOfString("{username}", withString: "\(username)", options: .LiteralSearch, range: nil)
|
||||
let url = PetstoreClientAPI.basePath + path
|
||||
|
@ -5,7 +5,6 @@
|
||||
//
|
||||
|
||||
import Alamofire
|
||||
import PromiseKit
|
||||
|
||||
class AlamofireRequestBuilderFactory: RequestBuilderFactory {
|
||||
func getBuilder<T>() -> RequestBuilder<T>.Type {
|
||||
@ -21,7 +20,7 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
super.init(method: method, URLString: URLString, parameters: parameters, isBody: isBody)
|
||||
}
|
||||
|
||||
override func execute() -> Promise<Response<T>> {
|
||||
override func execute(completion: (response: Response<T>?, erorr: NSError?) -> Void) {
|
||||
let managerId = NSUUID().UUIDString
|
||||
// Create a new manager for each request to customize its request header
|
||||
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
|
||||
@ -35,37 +34,41 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
request.authenticate(usingCredential: credential)
|
||||
}
|
||||
|
||||
let defer = Promise<Response<T>>.defer()
|
||||
request.responseJSON(options: .AllowFragments) { (req, res, json, error) in
|
||||
managerStore.removeValueForKey(managerId)
|
||||
|
||||
if let error = error {
|
||||
defer.reject(error)
|
||||
completion(response: nil, erorr: error)
|
||||
return
|
||||
}
|
||||
if res!.statusCode >= 400 {
|
||||
//TODO: Add error entity
|
||||
let userInfo: [NSObject : AnyObject] = (json != nil) ? ["data": json!] : [:]
|
||||
let error = NSError(domain: res!.URL!.URLString, code: res!.statusCode, userInfo: userInfo)
|
||||
defer.reject(error)
|
||||
completion(response: nil, erorr: error)
|
||||
return
|
||||
}
|
||||
|
||||
if () is T {
|
||||
let response = Response(response: res!, body: () as! T)
|
||||
defer.fulfill(response)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
}
|
||||
if let json: AnyObject = json {
|
||||
let body = Decoders.decode(clazz: T.self, source: json)
|
||||
let response = Response(response: res!, body: body)
|
||||
defer.fulfill(response)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
} else if "" is T {
|
||||
// swagger-parser currently doesn't support void, which will be fixed in future swagger-parser release
|
||||
// https://github.com/swagger-api/swagger-parser/pull/34
|
||||
let response = Response(response: res!, body: "" as! T)
|
||||
completion(response: response, erorr: nil)
|
||||
return
|
||||
}
|
||||
|
||||
defer.reject(NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"]))
|
||||
completion(response: nil, erorr: NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"]))
|
||||
}
|
||||
return defer.promise
|
||||
}
|
||||
|
||||
private func buildHeaders() -> [String: AnyObject] {
|
||||
@ -77,4 +80,3 @@ class AlamofireRequestBuilder<T>: RequestBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -63,3 +63,17 @@ extension NSDate: JSONEncodable {
|
||||
return dateFormatter.stringFromDate(self)
|
||||
}
|
||||
}
|
||||
|
||||
extension RequestBuilder {
|
||||
func execute() -> Promise<Response<T>> {
|
||||
let deferred = Promise<Response<T>>.defer()
|
||||
self.execute { (response: Response<T>?, error: NSError?) in
|
||||
if let response = response {
|
||||
deferred.fulfill(response)
|
||||
} else {
|
||||
deferred.reject(error!)
|
||||
}
|
||||
}
|
||||
return deferred.promise
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user