Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include(
"src:plugin:gradle",
"src:converter:avro",
"src:converter:openapi",
"src:converter:wsdl",
"src:integration:avro",
"src:integration:jackson",
"src:integration:wirespec",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ open class KotlinEmitter(

private fun String.sanitizeFirstIsDigit() = if (firstOrNull()?.isDigit() == true) "_${this}" else this

fun String.sanitizeEnum() = split("-", ", ", ".", " ", "//").joinToString("_").sanitizeFirstIsDigit()
fun String.sanitizeEnum() = split("-", ", ", ".", " ", "/", "//").joinToString("_").sanitizeFirstIsDigit()

fun String.sanitizeKeywords() = if (this in reservedKeywords) addBackticks() else this

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ object OpenApiV3Emitter : Emitter(noLogger) {
is Reference.Custom -> ReferenceObject(ref = Ref("#/components/schemas/${value}"))
is Reference.Primitive -> SchemaObject(type = type.emitType(), format = emitFormat())
is Reference.Any -> error("Cannot map Any")
is Reference.Unit -> error("Cannot map Unit")
is Reference.Unit -> SchemaObject()
}.let {
when {
isDictionary -> SchemaObject(
Expand Down
58 changes: 58 additions & 0 deletions src/converter/wsdl/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlinx.resources)
kotlin("plugin.serialization") version "2.0.0-RC1"
}

group = "${libs.versions.group.id.get()}.converter"
version = System.getenv(libs.versions.from.env.get()) ?: libs.versions.default.get()

repositories {
mavenCentral()
maven(uri("https://s01.oss.sonatype.org/service/local/repo_groups/public/content"))
}

kotlin {
macosX64()
macosArm64()
linuxX64()
js(IR) {
nodejs()
}
jvm {
withJava()
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.java.get()))
}
}
}
sourceSets {
commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.6.0")
implementation(project(":src:compiler:core"))
implementation(libs.kotlinx.serialization)
implementation(libs.kotlinx.openapi.bindings)
implementation("io.github.pdvrieze.xmlutil:core:0.86.3")
implementation("io.github.pdvrieze.xmlutil:serialization:0.86.3")
implementation("io.github.pdvrieze.xmlutil:serialutil:0.86.3")
}
}
commonTest {
dependencies {
implementation(libs.kotlinx.resources)
implementation(libs.kotlin.test)
implementation(libs.bundles.kotest)
implementation(project(":src:converter:openapi"))
}
}
val jvmTest by getting {
dependencies {
implementation(libs.bundles.jackson)
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package community.flock.wirespec.convert.wsdl

import community.flock.wirespec.compiler.core.emit.common.Emitter.Companion.firstToUpper
import community.flock.wirespec.compiler.core.parse.AST
import community.flock.wirespec.compiler.core.parse.DefinitionIdentifier
import community.flock.wirespec.compiler.core.parse.Endpoint
import community.flock.wirespec.compiler.core.parse.Enum
import community.flock.wirespec.compiler.core.parse.Field
import community.flock.wirespec.compiler.core.parse.FieldIdentifier
import community.flock.wirespec.compiler.core.parse.Reference
import community.flock.wirespec.compiler.core.parse.Type
import community.flock.wirespec.convert.wsdl.bindings.SchemaBindings
import community.flock.wirespec.convert.wsdl.bindings.WsdlBindings
import community.flock.wirespec.convert.wsdl.bindings.WsdlBindings.Definitions
import kotlinx.io.buffered
import kotlinx.io.files.Path
import kotlinx.io.files.SystemFileSystem
import kotlinx.io.readString
import kotlinx.serialization.decodeFromString
import nl.adaptivity.xmlutil.serialization.XML

object WsdlParser {

fun parseSchema(xml: String, path: Path): AST {
val res = XML { autoPolymorphic = true }.decodeFromString<SchemaBindings.Schema>(xml)

val import = res.import
.flatMap {
it.schemaLocation
?.let { location ->
val file = SystemFileSystem.resolve(Path(path, location))
val data = SystemFileSystem.source(file).buffered().readString()
parseSchema(data, file.parent ?: error("Missing schema"))
}
?: emptyList()
}

val simpleTypes = res.simpleType
.map { it.toType() }


val complexTypes = res.complexType
.map { it.toType() }

return import + simpleTypes + complexTypes
}

fun parseDefinitions(xml: String, path: Path): AST {
val res = XML { autoPolymorphic = true }.decodeFromString<Definitions>(xml)

val endpoints = res.portType
.flatMap { it.operation }
.map { it.toEndpoint() }

val importWsdl = res.import
.flatMap {
val file = SystemFileSystem.resolve(Path(path, it.location))
val data = SystemFileSystem.source(file).buffered().readString()
parseDefinitions(data, file.parent ?: error("Missing schema"))
}

val importSchema = res.types
.flatMap { it.schema }
.flatMap { it.import }
.flatMap {
it.schemaLocation
?.let { location ->
val file = SystemFileSystem.resolve(Path(path, location))
val data = SystemFileSystem.source(file).buffered().readString()
parseSchema(data, file.parent ?: error("Missing schema"))
}
?: emptyList()

}

val aliasMap = res.types
.flatMap { it.schema }
.flatMap { it.element }
.associate { it.name to it.type?.split(":")?.last() }

val messages = res.message.map { it.toType(aliasMap) }

val simpleTypes = res.types
.flatMap { it.schema }
.flatMap { it.simpleType }
.map { it.toType() }

val complexTypes = res.types
.flatMap { it.schema }
.flatMap { it.complexType }
.map { it.toType() }

val elementTypes = res.types
.flatMap { it.schema }
.flatMap { it.element }
.flatMap { elm -> elm.complexType.map { it.toType(elm.name) } }

return endpoints + importWsdl + importSchema + messages + simpleTypes + complexTypes + elementTypes
}

private fun WsdlBindings.Message.toType(aliasMap:Map<String,String?>) =
Type(
comment = null,
identifier = DefinitionIdentifier(name),
shape = Type.Shape(
value = part.map { part ->
Field(
identifier = FieldIdentifier(part.name),
reference = part.element?.split(":")?.last()
?.let { Reference.Custom(aliasMap[it]?:it, false, false) }
?: Reference.Unit(false, false),
isNullable = false
)
}
),
extends = emptyList()

)


fun SchemaBindings.SimpleType.toType(name: String? = null) = when {
restriction.isNotEmpty() -> Enum(
comment = null,
identifier = DefinitionIdentifier(this.name?.firstToUpper() ?: name ?: "NO_NAME"),
entries = restriction.flatMap { it.enumeration.map { it.value } }.toSet()
)

else -> TODO("Cannot read SimpleType")
}

fun SchemaBindings.ComplexType.toType(name: String? = null) = Type(
comment = null,
identifier = DefinitionIdentifier(this.name ?: name ?: "NO_NAME"),
shape = Type.Shape(sequence.flatMap { it.element }.map { it.toField() }),
extends = emptyList()
)

fun SchemaBindings.Element.toField(): Field {
val isIterable = this.maxOccurs != "1"
return Field(
identifier = FieldIdentifier(this.name),
isNullable = this.nillable ?: this.minOccurs?.let { it == "0" } ?: false,
reference = when (val t = type?.split(":")?.last()) {
"decimal" -> Reference.Primitive(
type = Reference.Primitive.Type.Number(Reference.Primitive.Type.Precision.P64),
isIterable = isIterable
)

"string", "date", "dateTime" -> Reference.Primitive(
type = Reference.Primitive.Type.String,
isIterable = isIterable
)

"int", "integer", "positiveInteger" -> Reference.Primitive(
type = Reference.Primitive.Type.Integer(Reference.Primitive.Type.Precision.P32),
isIterable = isIterable
)

"base64Binary" -> Reference.Primitive(type = Reference.Primitive.Type.String, isIterable = isIterable)
"anyType" -> Reference.Any(isIterable = isIterable)
null -> Reference.Unit(isIterable = isIterable)
else -> Reference.Custom(value = t, isIterable = isIterable)
}
)
}

fun WsdlBindings.WsdlOperation.toEndpoint() =
Endpoint(
comment = null,
identifier = DefinitionIdentifier(this.name + "Endpoint"),
method = Endpoint.Method.POST,
path = listOf(Endpoint.Segment.Literal(this.name)),
queries = emptyList(),
headers = emptyList(),
cookies = emptyList(),
requests = listOf(
Endpoint.Request(
content = Endpoint.Content(
type = "application/json",
reference = input.first().message
?.let {
Reference.Custom(
value = it.split(":").last(),
isIterable = false,
isDictionary = false
)
}
?: Reference.Unit(
isIterable = false,
isDictionary = false
)
)
)
),
responses = listOf(
Endpoint.Response(
status = "200",
headers = emptyList(),
content = Endpoint.Content(
type = "application/json",
reference = output.first().message
?.let {
Reference.Custom(
value = it.split(":").last(),
isIterable = false,
isDictionary = false
)
}
?: Reference.Unit(
isIterable = false,
isDictionary = false
)
)
)

)
)

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package community.flock.wirespec.convert.wsdl.bindings

import kotlinx.serialization.Serializable
import nl.adaptivity.xmlutil.serialization.XmlSerialName

object HttpBindings {
@Serializable
@XmlSerialName(
value = "binding",
namespace = "http://schemas.xmlsoap.org/wsdl/http/"
)
data class HttpBinding(
val verb: String,
):WsdlBindings.Binding

@Serializable
@XmlSerialName(
value = "operation",
namespace = "http://schemas.xmlsoap.org/wsdl/http/"
)
data class HttpOperation(
val location: String,
): WsdlBindings.Operation

@Serializable
@XmlSerialName(
value = "urlEncoded",
namespace = "http://schemas.xmlsoap.org/wsdl/http/"
)
data object UrlEncoded

@Serializable
@XmlSerialName(
value = "address",
namespace = "http://schemas.xmlsoap.org/wsdl/http/"
)
data class HttpAddress(
val location: String,
): WsdlBindings.Address
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package community.flock.wirespec.convert.wsdl.bindings

import kotlinx.serialization.Serializable
import nl.adaptivity.xmlutil.serialization.XmlCData
import nl.adaptivity.xmlutil.serialization.XmlSerialName
import nl.adaptivity.xmlutil.serialization.XmlValue

object JMSBindings {

@Serializable
@XmlSerialName(
value = "binding",
namespace = "http://www.w3.org/2008/07/soap/bindings/JMS/"
)
data class Binding(
val messageFormat: String,
): WsdlBindings.Binding

@Serializable
@XmlSerialName(
value = "connectionFactory",
namespace = "http://www.w3.org/2008/07/soap/bindings/JMS/"
)
data class ConnectionFactory(
@XmlValue(true)
val data: String? = null,
): WsdlBindings.ConnectionFactory

@Serializable
@XmlSerialName(
value = "targetAddress",
namespace = "http://www.w3.org/2008/07/soap/bindings/JMS/"
)
data class TargetAddress(
val destination: String,
@XmlValue(true)
val cData: String? = null,
)
}
Loading