All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vrap.codegen.languages.csharp.requests.CsharpHttpRequestRenderer.kt Maven / Gradle / Ivy

Go to download

RAML API client code generators based on the REST Modeling Framework. https://github.com/vrapio/rest-modeling-framework

The newest version!
package io.vrap.codegen.languages.csharp.requests

import com.google.common.collect.Lists
import com.google.common.net.MediaType
import io.vrap.codegen.languages.csharp.CsharpBaseTypes
import io.vrap.codegen.languages.csharp.extensions.*
import io.vrap.codegen.languages.extensions.*
import io.vrap.rmf.codegen.firstUpperCase
import io.vrap.rmf.codegen.firstLowerCase
import io.vrap.rmf.codegen.io.TemplateFile
import io.vrap.rmf.codegen.rendering.MethodRenderer
import io.vrap.rmf.codegen.rendering.utils.escapeAll
import io.vrap.rmf.codegen.rendering.utils.keepIndentation
import io.vrap.rmf.codegen.types.*
import io.vrap.rmf.raml.model.resources.Method
import io.vrap.rmf.raml.model.resources.Trait
import io.vrap.rmf.raml.model.resources.impl.ResourceImpl
import io.vrap.rmf.raml.model.types.*
import io.vrap.rmf.raml.model.types.Annotation
import io.vrap.rmf.raml.model.util.StringCaseFormat
import org.eclipse.emf.ecore.EObject
import java.util.*

const val PLACEHOLDER_PARAM_ANNOTATION = "placeholderParam"

class CsharpHttpRequestRenderer constructor(override val vrapTypeProvider: VrapTypeProvider, private val basePackagePrefix: String) : MethodRenderer, CsharpObjectTypeExtensions, EObjectExtensions {

    override fun render(type: Method): TemplateFile {


        val vrapType = vrapTypeProvider.doSwitch(type as EObject) as VrapObjectType
        var entityFolder = (type.resource() as ResourceImpl).GetNameAsPlural()
        val cPackage = vrapType.requestBuildersPackage(entityFolder)

        val implements = Lists.newArrayList()
            .plus(
                when (val ex = type.getAnnotation("csharp-implements") ) {
                    is Annotation -> {
                        (ex.value as StringInstance).value.escapeAll()
                    }
                    else -> null
                }
            )
            .plus(
                type.`is`.distinctBy { it.trait.name }.map { "${it.trait.className()}<${type.toRequestName()}>".escapeAll() }
            )
            .filterNotNull()
        val hasReturnPayload = type.hasReturnPayload();

        val content = """
            |using System;
            |using System.Globalization;
            |using System.IO;
            |using System.Collections.Generic;
            |using System.Linq;
            |using System.Net;
            |using System.Net.Http;
            |using System.Net.Http.Headers;
            |using System.Text;
            |using System.Threading.Tasks;
            |using System.Threading;
            |using System.Text.Json;
            |using commercetools.Base.Client;
            |using commercetools.Base.Serialization;
            |${type.usings()}
            |
            |// ReSharper disable CheckNamespace
            |namespace ${cPackage}
            |{
            |   ${if (type.markDeprecated()) "[Obsolete(\"usage of this endpoint has been deprecated.\", false)]" else ""}
            |   public partial class ${type.toRequestName()} : ApiMethod\<${type.toRequestName()}\>, IApiMethod\<${type.toRequestName()}, ${if (hasReturnPayload) type.csharpReturnType(vrapTypeProvider) else "string"}\>${if (implements.isNotEmpty()) ", ${implements.joinToString(", ")}" else ""} {
            |
            |       <${type.properties()}>
            |   
            |       <${type.constructor()}>
            |   
            |       <${type.queryParamsGetters()}>
            |   
            |       <${type.queryParamsSetters()}>
            |       
            |       <${type.queryParamsTemplateSetters()}>
            |
            |       <${type.executeAndBuild()}>
            |
            |       <${type.formParamMethods()}>
            |   }
            |}
        """.trimMargin()
                .keepIndentation()

        val relativePath = cPackage
                .replace(basePackagePrefix, "").replace(".", "/")
                .trimStart('/').trimEnd('/')

        return TemplateFile(
                relativePath = "${relativePath}/${type.toRequestName()}.cs",
                content = content
        )
    }

    private fun Trait.className(): String {
        val vrapType = vrapTypeProvider.doSwitch(this as EObject) as VrapObjectType

        return "${vrapType.`package`.toCsharpPackage().replace("Clients", "Client")}.I${vrapType.simpleClassName}"
    }

    private fun Method.properties(): String? {

        var props = "private IClient ApiHttpClient { get; }"+ "\n\n" + """public override HttpMethod Method =\>\ HttpMethod.${methodName.firstUpperCase()};"""
        //only for post methods
        if (this.methodName.lowercase(Locale.getDefault()) == "post") {
            props = "private ISerializerService SerializerService { get; }\n\n$props";
        }
        val pathArgs = props + "\n\n" + this.pathArguments().map { "private string ${it.firstUpperCase()} { get; }" }.joinToString(separator = "\n\n")

        val body: String = if(this.bodies != null && this.bodies.isNotEmpty()){
            if(this.bodies[0].type.toVrapType() is VrapObjectType){
                val methodBodyVrapType = this.bodies[0].type.toVrapType() as VrapObjectType
                if(methodBodyVrapType.`package`=="")
                    "private ${methodBodyVrapType.simpleClassName} ${methodBodyVrapType.simpleClassName.firstUpperCase()};"
                else
                    "private ${methodBodyVrapType.`package`.toCsharpPackage()}.I${methodBodyVrapType.simpleClassName} ${methodBodyVrapType.simpleClassName.firstUpperCase()};"
            } else if (this.bodies[0].contentMediaType.`is`(MediaType.FORM_DATA)) {
                "private List> _formParams;".escapeAll()
            }else {
                "private JsonElement? jsonNode;"
            }
        }else{
            ""
        }

        return """|
            |<$pathArgs>
            |
            |<$body>
        """.trimMargin()
    }

    private fun Method.constructor(): String? {
        val pathArguments = this.pathArguments().map { "{$it}" }
        var requestUrl = this.resource().fullUri.template
        pathArguments.forEach { requestUrl = requestUrl.replace(it, "{"+it.replace("{","").replace("}","").firstUpperCase()+"}") }

        val constructorArguments = mutableListOf("IClient apiHttpClient")
        val constructorAssignments = mutableListOf("this.ApiHttpClient = apiHttpClient;")
        //only for post methods
        if (this.methodName.lowercase(Locale.getDefault()) == "post") {
            constructorArguments.add("ISerializerService serializerService")
            constructorAssignments.add("this.SerializerService = serializerService;")
        }

        this.pathArguments().map { "string ${it.lowerCamelCase()}" }.forEach { constructorArguments.add(it) }
        this.pathArguments().map { "this.${it.firstUpperCase()} = ${it.lowerCamelCase()};" }.forEach { constructorAssignments.add(it) }

        if(this.bodies != null && this.bodies.isNotEmpty()){
            if(this.bodies[0].type.toVrapType() is VrapObjectType) {
                val methodBodyVrapType = this.bodies[0].type.toVrapType() as VrapObjectType
                val methodBodyArgument: String
                if(methodBodyVrapType.`package`=="")
                    methodBodyArgument = "${methodBodyVrapType.simpleClassName} ${methodBodyVrapType.simpleClassName.firstLowerCase()}"
                else
                    methodBodyArgument = "${methodBodyVrapType.`package`.toCsharpPackage()}.I${methodBodyVrapType.simpleClassName} ${methodBodyVrapType.simpleClassName.firstLowerCase()}"
                constructorArguments.add(methodBodyArgument)
                val methodBodyAssignment = "this.${methodBodyVrapType.simpleClassName.firstUpperCase()} = ${methodBodyVrapType.simpleClassName.firstLowerCase()};"
                constructorAssignments.add(methodBodyAssignment)
            } else if (this.bodies[0].contentMediaType.`is`(MediaType.FORM_DATA)){
                constructorArguments.add("List> formParams = null".escapeAll())
                constructorAssignments.add("this._formParams = formParams ?? new List>();".escapeAll())
            }else {
                constructorArguments.add("JsonElement? jsonNode")
                constructorAssignments.add("this.jsonNode = jsonNode;")
            }
        }

        constructorAssignments.add("this.RequestUrl = $\"${requestUrl}\";")

        return """
            |public ${this.toRequestName()}(${constructorArguments.joinToString(separator = ", ")}) {
            |    <${constructorAssignments.joinToString(separator = "\n")}>
            |}
        """.trimMargin().keepIndentation().escapeAll()

    }

    private fun Method.pathArguments() : List {
        return this.resource().fullUri.variables.toList()
    }

    private fun QueryParameter.fieldName(): String {
        return StringCaseFormat.LOWER_CAMEL_CASE.apply(this.name.replace(".", "-"))
    }

    private fun QueryParameter.fieldNameAsString(type: String): String {
        var fieldName = this.fieldName()
        if(this.type.toVrapType() is VrapEnumType)
            return "$fieldName.JsonName"
        if(type == "string")
            return fieldName
        if(type == "decimal" || type == "int" || type == "long") {
            return "$fieldName.ToString(CultureInfo.InvariantCulture)"
        }
        else
            return "$fieldName.ToString()"
    }

    private fun Method.queryParamsGetters() : String = this.queryParameters
            .filter { it.getAnnotation(PLACEHOLDER_PARAM_ANNOTATION, true) == null }
            .map { """
                |public List Get${it.fieldName().firstUpperCase()}() {
                |    return this.GetQueryParam("${it.name}");
                |}
                """.trimMargin().escapeAll() }
            .joinToString(separator = "\n\n")

    private fun Method.queryParamsSetters() : String = this.queryParameters
            .filter { it.getAnnotation(PLACEHOLDER_PARAM_ANNOTATION, true) == null }
            .map { """
                |public ${this.toRequestName()} With${it.fieldName().firstUpperCase()}(${it.witherType()} ${it.fieldName()}){
                |    return this.AddQueryParam("${it.name}", ${it.fieldNameAsString(it.witherType())});
                |}
            """.trimMargin().escapeAll() }
            .joinToString(separator = "\n\n")

    private fun QueryParameter.witherType() : String {
        return when (val type = this.type) {
            is ArrayType -> type.items.toVrapType().simpleName()
            else -> {
                val vrapType = type.toVrapType().simpleName()
                if (vrapType == CsharpBaseTypes.integerType.simpleName())
                    CsharpBaseTypes.longType.simpleName()
                else vrapType
            }
        }
    }

    private fun Method.executeAndBuild() : String {
        val hasReturnPayload = this.hasReturnPayload();
        val executeBlock =
                """
            |public async Task\<${if (hasReturnPayload) this.csharpReturnType(vrapTypeProvider) else "string"}\> ExecuteAsync(CancellationToken cancellationToken = default)
            |{
            |   ${if (this.hasReturnPayload()) """
            |   var requestMessage = Build();
            |   return await ApiHttpClient.ExecuteAsync\<${this.csharpReturnType(vrapTypeProvider)}\>(requestMessage, cancellationToken);
            |   """ else """
            |   return await ExecuteAsJsonAsync(cancellationToken);
            |   """}
            |}
            |
            |public async Task\ ExecuteAsJsonAsync(CancellationToken cancellationToken = default)
            |{
            |   var requestMessage = Build();
            |   return await ApiHttpClient.ExecuteAsJsonAsync(requestMessage, cancellationToken);
            |}
            |
            |public async Task\\> SendAsync(CancellationToken cancellationToken = default)
            |{
            |   ${if (this.hasReturnPayload()) """
            |   var requestMessage = Build();
            |   return await ApiHttpClient.SendAsync\<${this.csharpReturnType(vrapTypeProvider)}\>(requestMessage, cancellationToken);
            |   """ else """
            |   return await SendAsJsonAsync(cancellationToken);
            |   """}
            |}
            |
            |public async Task\\> SendAsJsonAsync(CancellationToken cancellationToken = default)
            |{
            |   var requestMessage = Build();
            |   return await ApiHttpClient.SendAsJsonAsync(requestMessage, cancellationToken);
            |}
        """.trimMargin()

        var bodyBlock = "";
        val bodyName : String? = if(this.bodies != null && this.bodies.isNotEmpty()){
            if(this.bodies[0].type.toVrapType() is VrapObjectType) {
                val methodBodyVrapType = this.bodies[0].type.toVrapType() as VrapObjectType
                methodBodyVrapType.simpleClassName.firstUpperCase()
            } else {
                "jsonNode"
            }
        }else {
            null
        }
        //only for post methods
        if(this.methodName.lowercase(Locale.getDefault()) == "post" && bodyName != null)
        {
            if(this.bodies[0].type.isFile())
            {
                bodyBlock = """
                    |
                    |public override HttpRequestMessage Build()
                    |{
                    |   var request = base.Build();
                    |   if ($bodyName != null && $bodyName.Length \> 0)
                    |   {
                    |       request.Content = new StreamContent($bodyName);
                    |       if (Headers.HasHeader(ApiHttpHeaders.CONTENT_TYPE))
                    |       {
                    |           request.Content.Headers.ContentType =
                    |               new MediaTypeHeaderValue(Headers.GetFirst(ApiHttpHeaders.CONTENT_TYPE));
                    |       }
                    |   }
                    |   return request;
                    |}
                """.trimMargin()
            }
            else if (this.bodies[0].contentMediaType.`is`(MediaType.FORM_DATA))
                bodyBlock = """
                    |
                    |public override HttpRequestMessage Build()
                    |{
                    |   var request = base.Build();
                    |
                    |   request.Content = new FormUrlEncodedContent(_formParams);
                    |   return request;
                    |}
                """.trimMargin()
            else
            {
                bodyBlock = """
                    |
                    |public override HttpRequestMessage Build()
                    |{
                    |   var request = base.Build();
                    |   if (SerializerService != null)
                    |   {
                    |       var body = this.SerializerService.Serialize(${bodyName});
                    |       if(!string.IsNullOrEmpty(body))
                    |       {
                    |           request.Content = new StringContent(body, Encoding.UTF8, "application/json");
                    |       }
                    |   }
                    |   return request;
                    |}
                """.trimMargin()
            }
        }
        return executeBlock + bodyBlock
    }

    private fun Method.formParamMethods() : String =
        if (this.bodies != null && !this.bodies.isEmpty() && this.bodies[0].contentMediaType.`is`(MediaType.FORM_DATA)) {
            """
                public ${this.toRequestName()} AddFormParam(string key, TValue value) {
                    this._formParams.Add(new KeyValuePair(key, value.ToString()));
                    return this;
                }
            
                public ${this.toRequestName()} WithFormParam(string key, TValue value) {
                    return WithoutFormParam(key).AddFormParam(key, value);
                }
            
                /**
                 * removes the specified form parameter
                 * @param key form parameter name
                 * @return T
                 */
                public ${this.toRequestName()} WithoutFormParam(string key) {
                    this._formParams = this._formParams.FindAll(pair => !pair.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase));
                    return this;
                }
            
                /**
                 * set the form parameters
                 * @param formParams list of form parameters
                 * @return T
                 */
                public ${this.toRequestName()} WithFormParams(List> formParams) {
                    this._formParams = formParams;
                    return this;
                }
            
                public List> GetFormParams() {
                    return this._formParams.ToList();
                }
            
                public List GetFormParam(string key) {
                    return this._formParams.FindAll(pair => pair.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)).Select(pair => pair.Value).ToList();
                }
                
                public List GetFormParamUriStrings() {
                    return this._formParams.Select(ToUriString).ToList();
                }

                public string GetFormParamUriString() {
                    return string.Join('&', this._formParams.Select(ToUriString));
                }
                
                private static string ToUriString(KeyValuePair entry) {
                    return entry.Key + "=" + WebUtility.UrlEncode(entry.Value);
                }

                #nullable enable
                public string? GetFirstFormParam(string key)
                {
                    return this._formParams
                        .FirstOrDefault(pair => pair.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase)).Value;
                }
                #nullable disable
            """.trimIndent().escapeAll()
        } else ""

    private fun Method.usings(): String {
        return this.queryParameters
                .map {
                    it.type.toVrapType()
                }
                .filter { it !is VrapScalarType }
                .map {
                    getUsingsForType(it)
                }
                .filter { !it.isNullOrBlank() }
                .map { "using ${it};" }
                .joinToString(separator = "\n")

    }

    private fun Method.markDeprecated() : Boolean {
        val anno = this.getAnnotation("markDeprecated")
        return (anno != null && (anno.value as BooleanInstance).value)
    }

    private fun Method.queryParamsTemplateSetters() : String = this.queryParameters
            .filter { it.getAnnotation(PLACEHOLDER_PARAM_ANNOTATION, true) != null }
            .map {
                val anno = it.getAnnotation("placeholderParam", true)
                val o = anno.value as ObjectInstance
                val paramName = o.value.stream().filter { propertyValue -> propertyValue.name == "paramName" }.findFirst().orElse(null).value as StringInstance
                val placeholder = o.value.stream().filter { propertyValue -> propertyValue.name == "placeholder" }.findFirst().orElse(null).value as StringInstance

                val template = o.value.stream().filter { propertyValue -> propertyValue.name == "template" }.findFirst().orElse(null).value as StringInstance
                val value = "$\"" + template.value.replace("<" + placeholder.value + ">", "{"+ placeholder.value + "}") + "\""

                val methodName = StringCaseFormat.UPPER_CAMEL_CASE.apply(paramName.value)
                val parameters =  "string " + StringCaseFormat.LOWER_CAMEL_CASE.apply(placeholder.value) + ", ${it.witherType()} " + paramName.value

                return """
                |public ${this.toRequestName()} With$methodName($parameters){
                |    return this.AddQueryParam($value, ${paramName.value});
                |}
            """.trimMargin().escapeAll()

            }
            .joinToString(separator = "\n\n")
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy