diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95c6b4e803..53115a967e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,11 +41,11 @@ jobs: - name: Test Native and JS run: | - ./gradlew -Dkjs=true -Dknative=true -Pswift=false samples:native:build samples:js:build samples:multi-target:build --stacktrace --warning-mode all + ./gradlew -Dkjs=true -Dkwasm=true -Dknative=true -Pswift=false samples:native:build samples:js:build samples:multi-target:build --stacktrace --warning-mode all - name: Test run: | - ./gradlew -Dkjs=false -Dknative=false -Pswift=false build --stacktrace --warning-mode all -x samples:native:build -x samples:js:build -x samples:multi-target:build + ./gradlew -Dkjs=false -Dkwasm=false -Dknative=false -Pswift=false build --stacktrace --warning-mode all -x samples:native:build -x samples:js:build -x samples:multi-target:build multiplatform: runs-on: macos-latest diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 360d2ce69e..3f61c31754 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -62,6 +62,7 @@ kotlin-test-annotations = { module = "org.jetbrains.kotlin:kotlin-test-annotatio kotlin-test-common = { module = "org.jetbrains.kotlin:kotlin-test-common" } kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js" } kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit" } +kotlin-test-wasm-js = { module = "org.jetbrains.kotlin:kotlin-test-wasm-js" } kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } misk = { module = "com.squareup.misk:misk", version = "2024.10.13.223258-0dacf1d" } moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" } diff --git a/samples/wasm/build.gradle.kts b/samples/wasm/build.gradle.kts new file mode 100644 index 0000000000..baf6b4f278 --- /dev/null +++ b/samples/wasm/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("multiplatform") + id("com.squareup.wire") +} + +repositories { + mavenCentral() +} + +kotlin { + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } +} + +wire { + protoLibrary = true + + kotlin { + buildersOnly = true + } +} + + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("com.squareup.wire:wire-gradle-plugin") + } +} diff --git a/samples/wasm/src/commonMain/proto/human/person.proto b/samples/wasm/src/commonMain/proto/human/person.proto new file mode 100644 index 0000000000..1806742839 --- /dev/null +++ b/samples/wasm/src/commonMain/proto/human/person.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package human; + +message Person { + string name = 1; + PhoneNumber phone_number = 2; + + message PhoneNumber { + string area = 1; + string number = 2; + } +} diff --git a/samples/wasm/src/wasmJsMain/kotlin/Main.kt b/samples/wasm/src/wasmJsMain/kotlin/Main.kt new file mode 100644 index 0000000000..c915aefcd1 --- /dev/null +++ b/samples/wasm/src/wasmJsMain/kotlin/Main.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Square, 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 + * + * https://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 human.Person + +fun main() { + val phoneNumber = Person.PhoneNumber.Builder() + .area("519") + .number("5550202") + .build() + val person = Person.Builder().name("Jacques").phone_number(phoneNumber).build() + println("Hello, Kotlin/Native! Here is ${person.name} and their number: ${person.phone_number}") +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 1740430e13..3581783cbf 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -76,6 +76,7 @@ include(":samples:android-app-variants-sample") include(":samples:android-lib-java-sample") include(":samples:android-lib-kotlin-sample") include(":samples:js") +include(":samples:wasm") include(":samples:multi-target") include(":samples:native") include(":samples:simple-sample") diff --git a/wire-grpc-client/build.gradle.kts b/wire-grpc-client/build.gradle.kts index 80cd17bae5..f586bad0f9 100644 --- a/wire-grpc-client/build.gradle.kts +++ b/wire-grpc-client/build.gradle.kts @@ -23,6 +23,11 @@ kotlin { browser() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcClient.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcClient.kt new file mode 100644 index 0000000000..cfd6b41d9f --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcClient.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual abstract class GrpcClient { + actual abstract fun newCall(method: GrpcMethod): GrpcCall + actual abstract fun newStreamingCall(method: GrpcMethod): GrpcStreamingCall +} diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHeaders.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHeaders.kt new file mode 100644 index 0000000000..b31a389465 --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHeaders.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual class GrpcHeaders { + actual operator fun get(name: String): String? = TODO() +} diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHttpUrl.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHttpUrl.kt new file mode 100644 index 0000000000..69714cbf4d --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcHttpUrl.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual class GrpcHttpUrl { + actual fun resolve(link: String): GrpcHttpUrl? = TODO("Not yet implemented") +} + +actual fun String.toHttpUrl(): GrpcHttpUrl = TODO("Not yet implemented") diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequest.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequest.kt new file mode 100644 index 0000000000..fba5417072 --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequest.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual class GrpcRequest + +actual open class GrpcRequestBuilder { + actual open fun url(url: GrpcHttpUrl): GrpcRequestBuilder = TODO("Not yet implemented") + actual open fun addHeader( + name: String, + value: String, + ): GrpcRequestBuilder = TODO("Not yet implemented") + actual open fun method( + method: String, + body: GrpcRequestBody?, + ): GrpcRequestBuilder = TODO("Not yet implemented") + actual open fun build(): GrpcRequest = TODO("Not yet implemented") +} diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequestBody.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequestBody.kt new file mode 100644 index 0000000000..64a54e1288 --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcRequestBody.kt @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual abstract class GrpcRequestBody diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponse.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponse.kt new file mode 100644 index 0000000000..97da212f6c --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponse.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +internal actual class GrpcResponse { + actual val body: GrpcResponseBody? + get() = TODO("Not yet implemented") + + actual fun header( + name: String, + defaultValue: String?, + ): String? { + TODO("Not yet implemented") + } + + actual fun trailers(): GrpcHeaders = TODO("Not yet implemented") + + actual fun close() { + TODO("Not yet implemented") + } +} diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponseBody.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponseBody.kt new file mode 100644 index 0000000000..f46dc85c8b --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/GrpcResponseBody.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import okio.BufferedSource + +actual abstract class GrpcResponseBody { + actual abstract fun source(): BufferedSource +} diff --git a/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/internal/platform.kt b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/internal/platform.kt new file mode 100644 index 0000000000..32c42f5c82 --- /dev/null +++ b/wire-grpc-client/src/wasmJsMain/kotlin/com/squareup/wire/internal/platform.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire.internal + +import com.squareup.wire.GrpcResponse +import okio.Sink +import okio.Source + +internal actual interface Call { + actual fun cancel() + actual fun execute(): GrpcResponse +} + +internal actual fun Sink.asGzip(): Sink { + throw UnsupportedOperationException("Gzip not implemented for WAsm JS") +} + +internal actual fun Source.asGzip(): Source { + throw UnsupportedOperationException("Gzip not implemented for WAsm JS") +} + +internal actual fun Throwable.addSuppressed(other: Throwable) { +} diff --git a/wire-runtime/build.gradle.kts b/wire-runtime/build.gradle.kts index 330de16feb..8ad1838ac2 100644 --- a/wire-runtime/build.gradle.kts +++ b/wire-runtime/build.gradle.kts @@ -25,6 +25,11 @@ kotlin { browser() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() @@ -69,6 +74,13 @@ kotlin { } } } + if (System.getProperty("kwasm", "true").toBoolean()) { + val wasmJsTest by getting { + dependencies { + implementation(libs.kotlin.test.wasm.js) + } + } + } } } diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Duration.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Duration.kt new file mode 100644 index 0000000000..36d531ff40 --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Duration.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import com.squareup.wire.internal.NANOS_PER_SECOND +import com.squareup.wire.internal.addExactLong +import com.squareup.wire.internal.commonEquals +import com.squareup.wire.internal.commonHashCode +import com.squareup.wire.internal.floorDivLong +import com.squareup.wire.internal.floorModLong + +actual class Duration internal constructor( + private val seconds: Long, + private val nanos: Int, +) { + actual fun getSeconds(): Long = seconds + actual fun getNano(): Int = nanos + + override fun equals(other: Any?): Boolean = commonEquals(other) + + override fun hashCode(): Int = commonHashCode() +} + +actual fun durationOfSeconds( + seconds: Long, + nano: Long, +): Duration { + val secs = addExactLong(seconds, floorDivLong(nano, NANOS_PER_SECOND)) + val nos = floorModLong(nano, NANOS_PER_SECOND).toInt() + return Duration(secs, nos) +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/EnumAdapter.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/EnumAdapter.kt new file mode 100644 index 0000000000..3400b9b586 --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/EnumAdapter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import kotlin.reflect.KClass + +/** + * An abstract [ProtoAdapter] that converts values of an enum to and from integers. + */ +actual abstract class EnumAdapter protected actual constructor( + type: KClass, + syntax: Syntax, + identity: E?, +) : ProtoAdapter(FieldEncoding.VARINT, type, null, syntax, identity) { + actual override fun encodedSize(value: E): Int = commonEncodedSize(value) + + actual override fun encode(writer: ProtoWriter, value: E) { + commonEncode(writer, value) + } + + actual override fun encode(writer: ReverseProtoWriter, value: E) { + commonEncode(writer, value) + } + + actual override fun decode(reader: ProtoReader): E = commonDecode(reader, this::fromValue) + + actual override fun decode(reader: ProtoReader32): E = commonDecode(reader, this::fromValue) + + actual override fun redact(value: E): E = commonRedact(value) + + /** + * Converts an integer to an enum. + * Returns null if there is no corresponding enum. + */ + protected actual abstract fun fromValue(value: Int): E? +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Instant.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Instant.kt new file mode 100644 index 0000000000..33d1b65de6 --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Instant.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import com.squareup.wire.internal.NANOS_PER_SECOND +import com.squareup.wire.internal.addExactLong +import com.squareup.wire.internal.commonEquals +import com.squareup.wire.internal.commonHashCode +import com.squareup.wire.internal.floorDivLong +import com.squareup.wire.internal.floorModLong + +actual class Instant internal constructor( + private val epochSeconds: Long, + private val nanos: Int, +) { + actual fun getEpochSecond(): Long = epochSeconds + actual fun getNano(): Int = nanos + + override fun equals(other: Any?): Boolean = commonEquals(other) + + override fun hashCode(): Int = commonHashCode() +} + +actual fun ofEpochSecond(epochSecond: Long, nano: Long): Instant { + val secs = addExactLong(epochSecond, floorDivLong(nano, NANOS_PER_SECOND)) + val nos = floorModLong(nano, NANOS_PER_SECOND).toInt() + return Instant(secs, nos) +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Message.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Message.kt new file mode 100644 index 0000000000..cc2a2ad40e --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/Message.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import okio.Buffer +import okio.BufferedSink +import okio.ByteString + +/** A protocol buffer message. */ +actual abstract class Message, B : Message.Builder> +protected actual constructor( + /** The [ProtoAdapter] for encoding and decoding messages of this type. */ + actual val adapter: ProtoAdapter, + /** + * Returns a byte string containing the proto encoding of this message's unknown fields. Returns + * an empty byte string if this message has no unknown fields. + */ + actual val unknownFields: ByteString, +) { + /** If non-zero, the hash code of this message. Accessed by generated code. */ + @JsName("cachedHashCode") + protected actual var hashCode = 0 + + /** + * Returns a new builder initialized with the data in this message. + */ + actual abstract fun newBuilder(): B + + /** Encode this message and write it to `stream`. */ + actual fun encode(sink: BufferedSink) { + @Suppress("UNCHECKED_CAST") + adapter.encode(sink, this as M) + } + + /** Encode this message as a `byte[]`. */ + actual fun encode(): ByteArray { + @Suppress("UNCHECKED_CAST") + return adapter.encode(this as M) + } + + /** Encode this message as a `ByteString`. */ + actual fun encodeByteString(): ByteString { + @Suppress("UNCHECKED_CAST") + return adapter.encodeByteString(this as M) + } + + /** + * Superclass for protocol buffer message builders. + */ + actual abstract class Builder, B : Builder> protected actual constructor() { + internal actual var unknownFieldsByteString = ByteString.EMPTY + internal actual var unknownFieldsBuffer: Buffer? = null + internal actual var unknownFieldsWriter: ProtoWriter? = null + + actual fun addUnknownFields(unknownFields: ByteString): Builder = apply { + if (unknownFields.size > 0) { + prepareForNewUnknownFields() + unknownFieldsWriter!!.writeBytes(unknownFields) + } + } + + actual fun addUnknownField( + tag: Int, + fieldEncoding: FieldEncoding, + value: Any?, + ): Builder = apply { + prepareForNewUnknownFields() + @Suppress("UNCHECKED_CAST") + val protoAdapter = fieldEncoding.rawProtoAdapter() as ProtoAdapter + protoAdapter.encodeWithTag(unknownFieldsWriter!!, tag, value) + } + + actual fun clearUnknownFields(): Builder = apply { + unknownFieldsByteString = ByteString.EMPTY + if (unknownFieldsBuffer != null) { + unknownFieldsBuffer!!.clear() + unknownFieldsBuffer = null + } + unknownFieldsWriter = null + } + + actual fun buildUnknownFields(): ByteString { + if (unknownFieldsBuffer != null) { + // Reads and caches the unknown fields from the buffer. + unknownFieldsByteString = unknownFieldsBuffer!!.readByteString() + unknownFieldsBuffer = null + unknownFieldsWriter = null + } + return unknownFieldsByteString + } + + actual abstract fun build(): M + + private fun prepareForNewUnknownFields() { + if (unknownFieldsBuffer == null) { + unknownFieldsBuffer = Buffer() + unknownFieldsWriter = ProtoWriter(unknownFieldsBuffer!!) + // Writes the cached unknown fields to the buffer. + unknownFieldsWriter!!.writeBytes(unknownFieldsByteString) + unknownFieldsByteString = ByteString.EMPTY + } + } + } +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSink.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSink.kt new file mode 100644 index 0000000000..4817cd7135 --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSink.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual interface MessageSink { + actual fun write(message: T) + + actual fun cancel() + + actual fun close() +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSource.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSource.kt new file mode 100644 index 0000000000..87d837452a --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/MessageSource.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2019 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual interface MessageSource { + actual fun read(): T? + + actual fun close() +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/ProtoAdapter.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/ProtoAdapter.kt new file mode 100644 index 0000000000..61cf08291a --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/ProtoAdapter.kt @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +import kotlin.reflect.KClass +import okio.BufferedSink +import okio.BufferedSource +import okio.ByteString + +actual abstract class ProtoAdapter actual constructor( + internal actual val fieldEncoding: FieldEncoding, + actual val type: KClass<*>?, + actual val typeUrl: String?, + actual val syntax: Syntax, + actual val identity: E?, + actual val sourceFile: String?, +) { + internal actual val packedAdapter: ProtoAdapter>? = when { + this is PackedProtoAdapter<*> || this is RepeatedProtoAdapter<*> -> null + fieldEncoding == FieldEncoding.LENGTH_DELIMITED -> null + else -> commonCreatePacked() + } + internal actual val repeatedAdapter: ProtoAdapter>? = when { + this is RepeatedProtoAdapter<*> || this is PackedProtoAdapter<*> -> null + else -> commonCreateRepeated() + } + + /** Returns the redacted form of `value`. */ + actual abstract fun redact(value: E): E + + /** + * The size of the non-null data `value`. This does not include the size required for a + * length-delimited prefix (should the type require one). + */ + actual abstract fun encodedSize(value: E): Int + + /** + * The size of `tag` and `value` in the wire format. This size includes the tag, type, + * length-delimited prefix (should the type require one), and value. Returns 0 if `value` is + * null. + */ + actual open fun encodedSizeWithTag(tag: Int, value: E?): Int { + return commonEncodedSizeWithTag(tag, value) + } + + /** Write non-null `value` to `writer`. */ + actual abstract fun encode(writer: ProtoWriter, value: E) + + /** Write non-null `value` to `writer`. */ + actual open fun encode(writer: ReverseProtoWriter, value: E) { + delegateEncode(writer, value) + } + + /** Write `tag` and `value` to `writer`. If value is null this does nothing. */ + actual open fun encodeWithTag(writer: ProtoWriter, tag: Int, value: E?) { + commonEncodeWithTag(writer, tag, value) + } + + /** Write `tag` and `value` to `writer`. If value is null this does nothing. */ + actual open fun encodeWithTag(writer: ReverseProtoWriter, tag: Int, value: E?) { + commonEncodeWithTag(writer, tag, value) + } + + /** Encode `value` and write it to `stream`. */ + actual fun encode(sink: BufferedSink, value: E) { + commonEncode(sink, value) + } + + /** Encode `value` as a `byte[]`. */ + actual fun encode(value: E): ByteArray { + return commonEncode(value) + } + + /** Encode `value` as a [ByteString]. */ + actual fun encodeByteString(value: E): ByteString { + return commonEncodeByteString(value) + } + + /** Read a non-null value from `reader`. */ + actual abstract fun decode(reader: ProtoReader): E + + /** Read a non-null value from `reader`. */ + actual open fun decode(reader: ProtoReader32): E { + return decode(reader.asProtoReader()) + } + + /** Read an encoded message from `bytes`. */ + actual fun decode(bytes: ByteArray): E { + return commonDecode(bytes) + } + + /** Read an encoded message from `bytes`. */ + actual fun decode(bytes: ByteString): E { + return commonDecode(bytes) + } + + /** Read an encoded message from `source`. */ + actual fun decode(source: BufferedSource): E { + return commonDecode(source) + } + + actual fun tryDecode(reader: ProtoReader, destination: MutableList) { + return commonTryDecode(reader, destination) + } + + actual fun tryDecode(reader: ProtoReader32, destination: MutableList) { + return commonTryDecode(reader, destination) + } + + /** Returns a human-readable version of the given `value`. */ + actual open fun toString(value: E): String { + return commonToString(value) + } + + internal actual fun withLabel(label: WireField.Label): ProtoAdapter<*> { + return commonWithLabel(label) + } + + /** Returns an adapter for `E` but as a packed, repeated value. */ + actual fun asPacked(): ProtoAdapter> { + require(fieldEncoding != FieldEncoding.LENGTH_DELIMITED) { + "Unable to pack a length-delimited type." + } + return packedAdapter ?: throw UnsupportedOperationException( + "Can't create a packed adapter from a packed or repeated adapter.", + ) + } + + /** + * Returns an adapter for `E` but as a repeated value. + * + * Note: Repeated items are not required to be encoded sequentially. Thus, when decoding using + * the returned adapter, only single-element lists will be returned and it is the caller's + * responsibility to merge them into the final list. + */ + actual fun asRepeated(): ProtoAdapter> { + return repeatedAdapter ?: throw UnsupportedOperationException( + "Can't create a repeated adapter from a repeated or packed adapter.", + ) + } + + actual class EnumConstantNotFoundException actual constructor( + actual val value: Int, + type: KClass<*>?, + ) : IllegalArgumentException("Unknown enum tag $value for ${type?.simpleName}") + + actual companion object { + /** + * Creates a new proto adapter for a map using `keyAdapter` and `valueAdapter`. + * + * Note: Map entries are not required to be encoded sequentially. Thus, when decoding using + * the returned adapter, only single-element maps will be returned and it is the caller's + * responsibility to merge them into the final map. + */ + actual fun newMapAdapter( + keyAdapter: ProtoAdapter, + valueAdapter: ProtoAdapter, + ): ProtoAdapter> { + return commonNewMapAdapter(keyAdapter, valueAdapter) + } + + actual val BOOL: ProtoAdapter = commonBool() + + actual val INT32: ProtoAdapter = commonInt32() + actual val INT32_ARRAY: ProtoAdapter = IntArrayProtoAdapter(INT32) + actual val UINT32: ProtoAdapter = commonUint32() + actual val UINT32_ARRAY: ProtoAdapter = IntArrayProtoAdapter(UINT32) + actual val SINT32: ProtoAdapter = commonSint32() + actual val SINT32_ARRAY: ProtoAdapter = IntArrayProtoAdapter(SINT32) + actual val FIXED32: ProtoAdapter = commonFixed32() + actual val FIXED32_ARRAY: ProtoAdapter = IntArrayProtoAdapter(FIXED32) + actual val SFIXED32: ProtoAdapter = commonSfixed32() + actual val SFIXED32_ARRAY: ProtoAdapter = IntArrayProtoAdapter(SFIXED32) + actual val INT64: ProtoAdapter = commonInt64() + actual val INT64_ARRAY: ProtoAdapter = LongArrayProtoAdapter(INT64) + + /** + * Like INT64, but negative longs are interpreted as large positive values, and encoded that way + * in JSON. + */ + actual val UINT64: ProtoAdapter = commonUint64() + actual val UINT64_ARRAY: ProtoAdapter = LongArrayProtoAdapter(UINT64) + actual val SINT64: ProtoAdapter = commonSint64() + actual val SINT64_ARRAY: ProtoAdapter = LongArrayProtoAdapter(SINT64) + actual val FIXED64: ProtoAdapter = commonFixed64() + actual val FIXED64_ARRAY: ProtoAdapter = LongArrayProtoAdapter(FIXED64) + actual val SFIXED64: ProtoAdapter = commonSfixed64() + actual val SFIXED64_ARRAY: ProtoAdapter = LongArrayProtoAdapter(SFIXED64) + actual val FLOAT: ProtoAdapter = commonFloat() + actual val FLOAT_ARRAY: ProtoAdapter = FloatArrayProtoAdapter(FLOAT) + actual val DOUBLE: ProtoAdapter = commonDouble() + actual val DOUBLE_ARRAY: ProtoAdapter = DoubleArrayProtoAdapter(DOUBLE) + actual val BYTES: ProtoAdapter = commonBytes() + actual val STRING: ProtoAdapter = commonString() + actual val DURATION: ProtoAdapter = commonDuration() + actual val INSTANT: ProtoAdapter = commonInstant() + actual val EMPTY: ProtoAdapter = commonEmpty() + actual val STRUCT_MAP: ProtoAdapter?> = commonStructMap() + actual val STRUCT_LIST: ProtoAdapter?> = commonStructList() + actual val STRUCT_NULL: ProtoAdapter = commonStructNull() + actual val STRUCT_VALUE: ProtoAdapter = commonStructValue() + actual val DOUBLE_VALUE: ProtoAdapter = commonWrapper(DOUBLE, "type.googleapis.com/google.protobuf.DoubleValue") + actual val FLOAT_VALUE: ProtoAdapter = commonWrapper(FLOAT, "type.googleapis.com/google.protobuf.FloatValue") + actual val INT64_VALUE: ProtoAdapter = commonWrapper(INT64, "type.googleapis.com/google.protobuf.Int64Value") + actual val UINT64_VALUE: ProtoAdapter = commonWrapper(UINT64, "type.googleapis.com/google.protobuf.UInt64Value") + actual val INT32_VALUE: ProtoAdapter = commonWrapper(INT32, "type.googleapis.com/google.protobuf.Int32Value") + actual val UINT32_VALUE: ProtoAdapter = commonWrapper(UINT32, "type.googleapis.com/google.protobuf.UInt32Value") + actual val BOOL_VALUE: ProtoAdapter = commonWrapper(BOOL, "type.googleapis.com/google.protobuf.BoolValue") + actual val STRING_VALUE: ProtoAdapter = commonWrapper(STRING, "type.googleapis.com/google.protobuf.StringValue") + actual val BYTES_VALUE: ProtoAdapter = commonWrapper(BYTES, "type.googleapis.com/google.protobuf.BytesValue") + } +} diff --git a/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/internal/-Platform.kt b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/internal/-Platform.kt new file mode 100644 index 0000000000..0275f1e978 --- /dev/null +++ b/wire-runtime/src/wasmJsMain/kotlin/kotlin/com/squareup/wire/internal/-Platform.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Square, 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 + * + * https://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. + */ +package com.squareup.wire.internal + +import okio.IOException + +actual interface Serializable + +/** This annotation is an empty placeholder. */ +actual annotation class JvmField + +/** This annotation is an empty placeholder. */ +actual annotation class JvmSynthetic + +/** This annotation is an empty placeholder. */ +actual annotation class JvmStatic + +actual abstract class ObjectStreamException : IOException() + +actual class ProtocolException actual constructor(host: String) : IOException(host) + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +actual inline fun MutableList.toUnmodifiableList(): List = this + +@Suppress("NOTHING_TO_INLINE") // Syntactic sugar. +actual inline fun MutableMap.toUnmodifiableMap(): Map = this + +// TODO: Use code points to process each char. +actual fun camelCase(string: String, upperCamel: Boolean): String { + return buildString(string.length) { + var index = 0 + var uppercase = upperCamel + while (index < string.length) { + var char = string[index] + + index++ + + if (char == '_') { + uppercase = true + continue + } + if (uppercase) { + if (char in 'a'..'z') char += 'A' - 'a' + } + append(char) + uppercase = false + } + } +} diff --git a/wire-schema-tests/build.gradle.kts b/wire-schema-tests/build.gradle.kts index 98db01743f..28446a6fb8 100644 --- a/wire-schema-tests/build.gradle.kts +++ b/wire-schema-tests/build.gradle.kts @@ -23,6 +23,11 @@ kotlin { browser() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() diff --git a/wire-schema/build.gradle.kts b/wire-schema/build.gradle.kts index 0f9ff79c68..7385b028ec 100644 --- a/wire-schema/build.gradle.kts +++ b/wire-schema/build.gradle.kts @@ -22,6 +22,11 @@ kotlin { browser() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/CoreLoader.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/CoreLoader.kt new file mode 100644 index 0000000000..51c58acd57 --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/CoreLoader.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema + +actual object CoreLoader : Loader { + actual override fun load(path: String): ProtoFile { + error("Wire cannot load $path on JavaScript. Please manually add it to the proto path.") + } + + actual override fun withErrors(errors: ErrorCollector): Loader = this +} diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Multimap.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Multimap.kt new file mode 100644 index 0000000000..522e874490 --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Multimap.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema + +actual interface Multimap { + actual fun size(): Int + actual fun isEmpty(): Boolean + actual fun containsKey(key: Any?): Boolean + actual fun containsValue(value: Any?): Boolean + actual operator fun get(key: K?): Collection + actual fun values(): Collection + actual fun asMap(): Map> +} + +private class SimpleMultimap(private val map: Map>) : Multimap { + override fun size() = map.values.sumOf { it.size } + override fun isEmpty() = map.isEmpty() + override fun containsKey(key: Any?) = map.containsKey(key) + override fun containsValue(value: Any?) = map.values.any { it.contains(value) } + override fun get(key: K?) = map[key] ?: emptyList() + override fun values() = map.values.flatten() + override fun asMap() = map +} + +internal actual fun Map>.toMultimap(): Multimap { + return SimpleMultimap(this) +} diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Profile.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Profile.kt new file mode 100644 index 0000000000..15ca0cf72a --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Profile.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2022 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema + +import com.squareup.wire.schema.internal.ProfileFileElement + +actual class Profile actual constructor(profileFiles: List) diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Roots.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Roots.kt new file mode 100644 index 0000000000..d950ba49f3 --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/Roots.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema + +import com.squareup.wire.schema.internal.parser.ProtoParser +import okio.FileSystem +import okio.IOException +import okio.Path + +internal actual fun Path.roots( + fileSystem: FileSystem, + location: Location, +): List { + val symlinkTarget = fileSystem.metadataOrNull(this)?.symlinkTarget + val path = symlinkTarget ?: this + return when { + fileSystem.metadataOrNull(path)?.isDirectory == true -> { + check(location.base.isEmpty()) + listOf(DirectoryRoot(location.path, fileSystem, path)) + } + + path.toString().endsWith(".proto") -> listOf(ProtoFilePath(location, fileSystem, path)) + + // Handle a .zip or .jar file by adding all .proto files within. + else -> throw IllegalArgumentException( + """ + |expected a directory, or .proto: $path + |archive (.zip / .jar / etc.) are not supported in JS + """.trimMargin(), + ) + } +} + +/** + * Returns the parsed proto file and the path that should be used to import it. + * + * This is a path like `squareup/dinosaurs/Dinosaur.proto` for a file based on its package name + * (like `squareup.dinosaurs`) and its file name (like `Dinosaur.proto`). + */ +internal actual fun ProtoFilePath.parse(): ProtoFile { + try { + fileSystem.read(path) { + val data = readUtf8() + val element = ProtoParser.parse(location, data) + return ProtoFile.get(element) + } + } catch (e: IOException) { + throw IOException("Failed to load $path", e) + } +} diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt new file mode 100644 index 0000000000..a9485c65b6 --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/SchemaLoader.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema + +import com.squareup.wire.schema.internal.CommonSchemaLoader +import okio.FileSystem + +actual class SchemaLoader : Loader, ProfileLoader { + private val delegate: CommonSchemaLoader + + actual constructor(fileSystem: FileSystem) { + delegate = CommonSchemaLoader(fileSystem) + } + + private constructor(enclosing: CommonSchemaLoader, errors: ErrorCollector) { + delegate = CommonSchemaLoader(enclosing, errors) + } + + actual override fun withErrors(errors: ErrorCollector): Loader = SchemaLoader(delegate, errors) + + /** Strict by default. Note that golang cannot build protos with package cycles. */ + actual var permitPackageCycles: Boolean + get() = delegate.permitPackageCycles + set(value) { + delegate.permitPackageCycles = value + } + + actual var opaqueTypes: List + get() = delegate.opaqueTypes + set(value) { + delegate.opaqueTypes = value + } + + /** + * If true, the schema loader will load the whole graph, including files and types not used by + * anything in the source path. + */ + actual var loadExhaustively: Boolean + get() = delegate.loadExhaustively + set(value) { + delegate.loadExhaustively = value + } + + /** Subset of the schema that was loaded from the source path. */ + actual val sourcePathFiles: List + get() = delegate.sourcePathFiles + + /** Initialize the [WireRun.sourcePath] and [WireRun.protoPath] from which files are loaded. */ + actual fun initRoots( + sourcePath: List, + protoPath: List, + ) { + delegate.initRoots(sourcePath, protoPath) + } + + actual override fun loadProfile(name: String, schema: Schema) = delegate.loadProfile(name, schema) + + actual override fun load(path: String) = delegate.load(path) + + actual fun loadSchema(): Schema = delegate.loadSchema() +} diff --git a/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/internal/UtilJS.kt b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/internal/UtilJS.kt new file mode 100644 index 0000000000..0995a3aedc --- /dev/null +++ b/wire-schema/src/wasmJsMain/kotlin/com/squareup/wire/schema/internal/UtilJS.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Square, 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 + * + * https://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. + */ +package com.squareup.wire.schema.internal + +internal actual fun Char.isDigit() = this in '0'..'9' + +internal actual fun String.toEnglishLowerCase() = toLowerCase() + +actual interface MutableQueue : MutableCollection { + actual fun poll(): T? +} + +internal actual fun mutableQueueOf(): MutableQueue { + val queue = mutableListOf() + return object : MutableQueue, MutableCollection by queue { + override fun poll() = firstOrNull() + } +} diff --git a/wire-test-utils/build.gradle.kts b/wire-test-utils/build.gradle.kts index 1501c30ac0..9a07e453ba 100644 --- a/wire-test-utils/build.gradle.kts +++ b/wire-test-utils/build.gradle.kts @@ -36,6 +36,11 @@ kotlin { browser() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() diff --git a/wire-tests/build.gradle.kts b/wire-tests/build.gradle.kts index 90711c63ba..84eaeea6ec 100644 --- a/wire-tests/build.gradle.kts +++ b/wire-tests/build.gradle.kts @@ -84,6 +84,11 @@ kotlin { nodejs() } } + if (System.getProperty("kwasm", "true").toBoolean()) { + wasmJs { + browser() + } + } if (System.getProperty("knative", "true").toBoolean()) { iosX64() iosArm64() @@ -128,6 +133,13 @@ kotlin { } } } + if (System.getProperty("kwasm", "true").toBoolean()) { + val wasmJsTest by getting { + dependencies { + implementation(libs.kotlin.test.wasm.js) + } + } + } } } diff --git a/wire-tests/src/commonTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt b/wire-tests/src/commonTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt new file mode 100644 index 0000000000..615e1a1494 --- /dev/null +++ b/wire-tests/src/commonTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +expect annotation class IgnoreWasmJs() diff --git a/wire-tests/src/commonTest/kotlin/com/squareup/wire/KotlinMapTest.kt b/wire-tests/src/commonTest/kotlin/com/squareup/wire/KotlinMapTest.kt index f42562f8a0..9f1c640ee4 100644 --- a/wire-tests/src/commonTest/kotlin/com/squareup/wire/KotlinMapTest.kt +++ b/wire-tests/src/commonTest/kotlin/com/squareup/wire/KotlinMapTest.kt @@ -41,6 +41,7 @@ class KotlinMapTest { } @IgnoreJs + @IgnoreWasmJs @IgnoreNative @Test fun mapsAreImmutable() { diff --git a/wire-tests/src/jsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt b/wire-tests/src/jsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt new file mode 100644 index 0000000000..a34f9e67c6 --- /dev/null +++ b/wire-tests/src/jsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreWasmJs diff --git a/wire-tests/src/jvmTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt b/wire-tests/src/jvmTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt new file mode 100644 index 0000000000..a34f9e67c6 --- /dev/null +++ b/wire-tests/src/jvmTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreWasmJs diff --git a/wire-tests/src/nativeTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt b/wire-tests/src/nativeTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt new file mode 100644 index 0000000000..a34f9e67c6 --- /dev/null +++ b/wire-tests/src/nativeTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2024 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +actual annotation class IgnoreWasmJs diff --git a/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/CustomGrpcClient.kt b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/CustomGrpcClient.kt new file mode 100644 index 0000000000..03ddd7c619 --- /dev/null +++ b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/CustomGrpcClient.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +/** + * This is an empty class solely to confirm we can implement a [GrpcClient] on JavaScript. + */ +class CustomGrpcClient : GrpcClient() { + override fun newCall(method: GrpcMethod): GrpcCall { + TODO("Not yet implemented") + } + + override fun newStreamingCall(method: GrpcMethod): GrpcStreamingCall { + TODO("Not yet implemented") + } +} diff --git a/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreJs.kt b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreJs.kt new file mode 100644 index 0000000000..6d018ceb6e --- /dev/null +++ b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreJs.kt @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual annotation class IgnoreJs diff --git a/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreNative.kt b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreNative.kt new file mode 100644 index 0000000000..4f98ada91d --- /dev/null +++ b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreNative.kt @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual annotation class IgnoreNative diff --git a/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt new file mode 100644 index 0000000000..dcb683ae16 --- /dev/null +++ b/wire-tests/src/wasmJsTest/kotlin/com/squareup/wire/IgnoreWasmJs.kt @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2024 Square, 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 + * + * https://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. + */ +package com.squareup.wire + +actual typealias IgnoreWasmJs = kotlin.test.Ignore