From d8e4d47c29fd1acc7c5731368129a9164525bbb0 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 17 Oct 2026 09:55:01 +0100 Subject: [PATCH 1/2] fix(kotlin,android): make the SDKs and their Java examples usable from Java Java callers could not write the code the docs show. The Java examples called methods declared @Throws(AppwriteException) without catching it, used List.of and Map.of without importing them, and left the client statement unterminated when a method has no auth headers. ID, Permission, Role, Query, Operator, Channel and InputFile helpers lived on companion objects without @JvmStatic, so Java had to go through Companion. Upload methods whose parameters are all required had no @JvmOverloads, so Java had to pass onProgress explicitly. On Android only the Map overload of a generic method carried @Throws, so a Java try/catch around listRows failed with "exception is never thrown". Every Android service method now declares it, matching the Kotlin SDK. The Kotlin and Android e2e suites now compile every generated Java example and run a Java class against the mock server. --- src/SDK/Language/Kotlin.php | 18 ++- templates/android/docs/java/example.md.twig | 68 +++++----- .../src/main/java/io/package/Channel.kt.twig | 16 +++ .../src/main/java/io/package/ID.kt.twig | 3 + .../src/main/java/io/package/Operator.kt.twig | 26 ++++ .../main/java/io/package/Permission.kt.twig | 5 + .../src/main/java/io/package/Query.kt.twig | 120 ++++++++++++++++++ .../src/main/java/io/package/Role.kt.twig | 10 ++ .../java/io/package/models/InputFile.kt.twig | 4 + .../java/io/package/services/Service.kt.twig | 5 +- templates/kotlin/docs/java/example.md.twig | 63 ++++----- .../src/main/kotlin/io/appwrite/ID.kt.twig | 3 + .../main/kotlin/io/appwrite/Operator.kt.twig | 26 ++++ .../kotlin/io/appwrite/Permission.kt.twig | 5 + .../src/main/kotlin/io/appwrite/Query.kt.twig | 120 ++++++++++++++++++ .../src/main/kotlin/io/appwrite/Role.kt.twig | 10 ++ .../io/appwrite/models/InputFile.kt.twig | 4 + .../appwrite/services/ServiceTemplate.kt.twig | 4 +- tests/e2e/Android16Java17Test.php | 4 + tests/e2e/Android5Java17Test.php | 4 + tests/e2e/Base.php | 7 + tests/e2e/KotlinJava17Test.php | 4 + tests/e2e/languages/android/Tests.kt | 2 + tests/e2e/languages/kotlin/JavaInterop.java | 53 ++++++++ tests/e2e/languages/kotlin/Tests.kt | 2 + tests/e2e/languages/kotlin/java-examples.php | 58 +++++++++ 26 files changed, 574 insertions(+), 70 deletions(-) create mode 100644 tests/e2e/languages/kotlin/JavaInterop.java create mode 100644 tests/e2e/languages/kotlin/java-examples.php diff --git a/src/SDK/Language/Kotlin.php b/src/SDK/Language/Kotlin.php index 80be2e7075..0ee1fae191 100644 --- a/src/SDK/Language/Kotlin.php +++ b/src/SDK/Language/Kotlin.php @@ -236,7 +236,7 @@ public function getParamExample(Schema|Parameter $param, string $lang = 'kotlin' if ($this->isPermissionString($example)) { $output .= $this->getPermissionExample($example, $lang); } else { - $output .= $this->getArrayExample($example, $lang); + $output .= $this->getArrayExample($example, $lang, $this->getArrayLeafType($param)); } break; case self::TYPE_BOOLEAN: @@ -337,7 +337,7 @@ protected function getJavaMapExample(array $data, int $indentLevel = 0): string * @param string $example Array example like '[1, 2, 3]' or '[{"key": "value"}]' * @param string $lang Language variant: 'kotlin' or 'java' */ - protected function getArrayExample(string $example, string $lang = 'kotlin'): string + protected function getArrayExample(string $example, string $lang = 'kotlin', ?string $leafType = null): string { // Try to decode as JSON to handle arrays of objects $decoded = json_decode($example, true); @@ -352,7 +352,7 @@ protected function getArrayExample(string $example, string $lang = 'kotlin'): st $arrayItems[] = $lang === 'java' ? $this->getJavaMapExample($item) : $this->getKotlinMapExample($item); } else { // It's a nested array, recursively convert it - $arrayItems[] = $this->getArrayExample(json_encode($item), $lang); + $arrayItems[] = $this->getArrayExample(json_encode($item), $lang, $leafType); } } elseif (is_string($item)) { // Primitive value @@ -361,6 +361,8 @@ protected function getArrayExample(string $example, string $lang = 'kotlin'): st $arrayItems[] = $item ? 'true' : 'false'; } elseif (is_null($item)) { $arrayItems[] = 'null'; + } elseif ((is_int($item) || is_float($item)) && ($leafType === self::TYPE_INTEGER || $leafType === self::TYPE_NUMBER)) { + $arrayItems[] = $this->getNumberLiteral($item, $leafType, $lang); } else { $arrayItems[] = (string)$item; } @@ -382,6 +384,16 @@ protected function getArrayExample(string $example, string $lang = 'kotlin'): st : 'listOf(' . $example . ')'; } + protected function getArrayLeafType(Schema|Parameter $param): ?string + { + $items = $this->getArraySchema($param); + while ($items instanceof ArraySchema) { + $items = $items->items; + } + + return $items instanceof Schema ? $this->getSchemaType($items) : null; + } + /** * Generate permission example for the given language * diff --git a/templates/android/docs/java/example.md.twig b/templates/android/docs/java/example.md.twig index a8882708ca..03c991df64 100644 --- a/templates/android/docs/java/example.md.twig +++ b/templates/android/docs/java/example.md.twig @@ -1,8 +1,13 @@ +{% set examples = [] %} +{% for parameter in (method | parameters('all')) %} +{% set examples = examples | merge([(parameter | enumValues) | length > 0 ? (parameter | javaEnumExample) : (parameter | javaParamExample)]) %} +{% endfor %} ```java import android.util.Log; import {{ sdk.namespace | caseDot }}.Client; import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback; +import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.info.title | caseUcfirst }}Exception; {% if (method | parameters('all')) | filter((param) => (param | schemaType) == 'file') | length > 0 %} import {{ sdk.namespace | caseDot }}.models.InputFile; {% endif %} @@ -20,44 +25,41 @@ import {{ sdk.namespace | caseDot }}.enums.{{ (parameter | enumName) | caseUcfir {% endif %} {% endif %} {% endfor %} +{% if 'List.of(' in (examples | join) %} +import java.util.List; +{% endif %} +{% if 'Map.of(' in (examples | join) %} +import java.util.Map; +{% endif %} +{% set schemes = method | securitySchemes %} Client client = new Client(context) - {%~ if [(method | securitySchemes)]|length > 0 %} - .setEndpoint("{{ endpointDocs | raw }}") // Your API Endpoint - {%~ for node in [(method | securitySchemes)] %} - {%~ for key,header in node|keys %} - .set{{header | caseUcfirst}}("{{node[header].extensions['x-appwrite']['demo'] | raw }}"){% if loop.last %};{% endif %} // {{ node[header].description }} - {%~ endfor %} - {%~ endfor %} - {%~ endif %} + .setEndpoint("{{ endpointDocs | raw }}"){% if schemes is empty %};{% endif %} // Your API Endpoint +{% for header in schemes | keys %} + .set{{ header | caseUcfirst }}("{{ schemes[header].extensions['x-appwrite']['demo'] | raw }}"){% if loop.last %};{% endif %} // {{ schemes[header].description }} +{% endfor %} {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); -{{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %}new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } - - Log.d("{{ spec.info.title | caseUcfirst }}", result.toString()); -}));{% endif %} - -{% for parameter in (method | parameters('all')) %} - {% if (parameter | enumValues) is not empty %}{{ (parameter | enumName) | caseUcfirst }}.{{ ((parameter | enumKeys)[0] ?? (parameter | enumValues)[0]) | caseEnumKey }}, // {{ parameter.name }} {% if not parameter.required %}(optional){% endif %} -{% else %}{{ parameter | javaParamExample }}, // {{ parameter.name }} {% if not parameter.required %}(optional){% endif %} -{% endif %} -{%~ if loop.last %} - - new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } +try { + {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}( + {%~ if (method | methodType) == 'webAuth' %} + activity, // activity + {%~ endif %} + {%~ for parameter in (method | parameters('all')) %} + {{ examples[loop.index0] | replace({"\n": "\n "}) | raw }}, // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} - Log.d("{{ spec.info.title | caseUcfirst }}", result.toString()); - }) -); -{% endif %} + {%~ endfor %} + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } -{% endfor %} + Log.d("{{ spec.info.title | caseUcfirst }}", result.toString()); + }) + ); +} catch ({{ spec.info.title | caseUcfirst }}Exception e) { + e.printStackTrace(); +} ``` diff --git a/templates/android/library/src/main/java/io/package/Channel.kt.twig b/templates/android/library/src/main/java/io/package/Channel.kt.twig index f92fdacc7e..73254afd33 100644 --- a/templates/android/library/src/main/java/io/package/Channel.kt.twig +++ b/templates/android/library/src/main/java/io/package/Channel.kt.twig @@ -61,39 +61,55 @@ class Channel private constructor( override fun toString(): String = segments.joinToString(".") companion object { + @JvmStatic fun database(id: String): Channel<_Database> = Channel(listOf("databases", normalize(id))) + @JvmStatic fun tablesdb(id: String): Channel<_TablesDB> = Channel(listOf("tablesdb", normalize(id))) + @JvmStatic fun bucket(id: String): Channel<_Bucket> = Channel(listOf("buckets", normalize(id))) + @JvmStatic fun execution(id: String): Channel<_Execution> = Channel(listOf("executions", normalize(id))) + @JvmStatic fun function(id: String): Channel<_Func> = Channel(listOf("functions", normalize(id))) + @JvmStatic fun team(id: String): Channel<_Team> = Channel(listOf("teams", normalize(id))) + @JvmStatic fun membership(id: String): Channel<_Membership> = Channel(listOf("memberships", normalize(id))) + @JvmStatic fun presence(id: String): Channel<_Presence> = Channel(listOf("presences", normalize(id))) + @JvmStatic fun account(): String = "account" // Global events + @JvmStatic fun documents(): String = "documents" + @JvmStatic fun rows(): String = "rows" + @JvmStatic fun files(): String = "files" + @JvmStatic fun executions(): String = "executions" + @JvmStatic fun teams(): String = "teams" + @JvmStatic fun memberships(): String = "memberships" + @JvmStatic fun presences(): String = "presences" } } diff --git a/templates/android/library/src/main/java/io/package/ID.kt.twig b/templates/android/library/src/main/java/io/package/ID.kt.twig index e5579f16d2..10c34fd18f 100644 --- a/templates/android/library/src/main/java/io/package/ID.kt.twig +++ b/templates/android/library/src/main/java/io/package/ID.kt.twig @@ -19,6 +19,7 @@ class ID { return hexTimestamp } + @JvmStatic fun custom(id: String): String = id @@ -28,6 +29,8 @@ class ID { * @param padding The number of characters to add to the ID * @returns The unique ID */ + @JvmStatic + @JvmOverloads fun unique(padding: Int = 7): String { val baseId = hexTimestamp() val randomPadding = (1..padding) diff --git a/templates/android/library/src/main/java/io/package/Operator.kt.twig b/templates/android/library/src/main/java/io/package/Operator.kt.twig index 512d78b10d..9d35bebdf0 100644 --- a/templates/android/library/src/main/java/io/package/Operator.kt.twig +++ b/templates/android/library/src/main/java/io/package/Operator.kt.twig @@ -30,6 +30,8 @@ class Operator( fun toJson(): String = toMap().toJson() companion object { + @JvmStatic + @JvmOverloads fun increment(value: Number = 1, max: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -38,6 +40,8 @@ class Operator( return Operator("increment", values).toJson() } + @JvmStatic + @JvmOverloads fun decrement(value: Number = 1, min: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } min?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Min cannot be NaN or Infinity" } } @@ -46,6 +50,8 @@ class Operator( return Operator("decrement", values).toJson() } + @JvmStatic + @JvmOverloads fun multiply(factor: Number, max: Number? = null): String { require(!factor.toDouble().isNaN() && !factor.toDouble().isInfinite()) { "Factor cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -54,6 +60,8 @@ class Operator( return Operator("multiply", values).toJson() } + @JvmStatic + @JvmOverloads fun divide(divisor: Number, min: Number? = null): String { require(!divisor.toDouble().isNaN() && !divisor.toDouble().isInfinite()) { "Divisor cannot be NaN or Infinity" } min?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Min cannot be NaN or Infinity" } } @@ -63,12 +71,15 @@ class Operator( return Operator("divide", values).toJson() } + @JvmStatic fun modulo(divisor: Number): String { require(!divisor.toDouble().isNaN() && !divisor.toDouble().isInfinite()) { "Divisor cannot be NaN or Infinity" } require(divisor.toDouble() != 0.0) { "Divisor cannot be zero" } return Operator("modulo", listOf(divisor)).toJson() } + @JvmStatic + @JvmOverloads fun power(exponent: Number, max: Number? = null): String { require(!exponent.toDouble().isNaN() && !exponent.toDouble().isInfinite()) { "Exponent cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -77,59 +88,74 @@ class Operator( return Operator("power", values).toJson() } + @JvmStatic fun arrayAppend(values: List): String { return Operator("arrayAppend", values).toJson() } + @JvmStatic fun arrayPrepend(values: List): String { return Operator("arrayPrepend", values).toJson() } + @JvmStatic fun arrayInsert(index: Int, value: Any): String { return Operator("arrayInsert", listOf(index, value)).toJson() } + @JvmStatic fun arrayRemove(value: Any): String { return Operator("arrayRemove", listOf(value)).toJson() } + @JvmStatic fun arrayUnique(): String { return Operator("arrayUnique", emptyList()).toJson() } + @JvmStatic fun arrayIntersect(values: List): String { return Operator("arrayIntersect", values).toJson() } + @JvmStatic fun arrayDiff(values: List): String { return Operator("arrayDiff", values).toJson() } + @JvmStatic + @JvmOverloads fun arrayFilter(condition: Condition, value: Any? = null): String { val values = listOf(condition.value, value) return Operator("arrayFilter", values).toJson() } + @JvmStatic fun stringConcat(value: Any): String { return Operator("stringConcat", listOf(value)).toJson() } + @JvmStatic fun stringReplace(search: String, replace: String): String { return Operator("stringReplace", listOf(search, replace)).toJson() } + @JvmStatic fun toggle(): String { return Operator("toggle", emptyList()).toJson() } + @JvmStatic fun dateAddDays(days: Int): String { return Operator("dateAddDays", listOf(days)).toJson() } + @JvmStatic fun dateSubDays(days: Int): String { return Operator("dateSubDays", listOf(days)).toJson() } + @JvmStatic fun dateSetNow(): String { return Operator("dateSetNow", emptyList()).toJson() } diff --git a/templates/android/library/src/main/java/io/package/Permission.kt.twig b/templates/android/library/src/main/java/io/package/Permission.kt.twig index 3af74e7de0..4b678ac118 100644 --- a/templates/android/library/src/main/java/io/package/Permission.kt.twig +++ b/templates/android/library/src/main/java/io/package/Permission.kt.twig @@ -12,6 +12,7 @@ class Permission { * @param role The role for which to generate the permission. * @returns The read permission string. */ + @JvmStatic fun read(role: String): String { return "read(\"${role}\")" } @@ -25,6 +26,7 @@ class Permission { * @param role The role for which to generate the permission. * @returns The write permission string. */ + @JvmStatic fun write(role: String): String { return "write(\"${role}\")" } @@ -35,6 +37,7 @@ class Permission { * @param role The role for which to generate the permission. * @returns The create permission string. */ + @JvmStatic fun create(role: String): String { return "create(\"${role}\")" } @@ -45,6 +48,7 @@ class Permission { * @param role The role for which to generate the permission. * @returns The update permission string. */ + @JvmStatic fun update(role: String): String { return "update(\"${role}\")" } @@ -55,6 +59,7 @@ class Permission { * @param role The role for which to generate the permission. * @returns The delete permission string. */ + @JvmStatic fun delete(role: String): String { return "delete(\"${role}\")" } diff --git a/templates/android/library/src/main/java/io/package/Query.kt.twig b/templates/android/library/src/main/java/io/package/Query.kt.twig index f080250e88..29f3d376a0 100644 --- a/templates/android/library/src/main/java/io/package/Query.kt.twig +++ b/templates/android/library/src/main/java/io/package/Query.kt.twig @@ -35,6 +35,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun equal(attribute: String, value: Any) = Query("equal", attribute, parseValue(value)).toJson() /** @@ -44,6 +45,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun notEqual(attribute: String, value: Any) = Query("notEqual", attribute, parseValue(value)).toJson() /** @@ -53,6 +55,7 @@ class Query( * @param pattern The regular expression pattern to match. * @returns The query string. */ + @JvmStatic fun regex(attribute: String, pattern: String) = Query("regex", attribute, parseValue(pattern)).toJson() /** @@ -62,6 +65,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun lessThan(attribute: String, value: Any) = Query("lessThan", attribute, parseValue(value)).toJson() /** @@ -71,6 +75,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun lessThanEqual(attribute: String, value: Any) = Query("lessThanEqual", attribute, parseValue(value)).toJson() /** @@ -80,6 +85,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun greaterThan(attribute: String, value: Any) = Query("greaterThan", attribute, parseValue(value)).toJson() /** @@ -89,6 +95,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun greaterThanEqual(attribute: String, value: Any) = Query("greaterThanEqual", attribute, parseValue(value)).toJson() /** @@ -98,6 +105,7 @@ class Query( * @param value The search value to match against. * @returns The query string. */ + @JvmStatic fun search(attribute: String, value: String) = Query("search", attribute, listOf(value)).toJson() /** @@ -106,6 +114,7 @@ class Query( * @param attribute The attribute to filter on. * @returns The query string. */ + @JvmStatic fun isNull(attribute: String) = Query("isNull", attribute).toJson() /** @@ -114,6 +123,7 @@ class Query( * @param attribute The attribute to filter on. * @returns The query string. */ + @JvmStatic fun isNotNull(attribute: String) = Query("isNotNull", attribute).toJson() /** @@ -122,6 +132,7 @@ class Query( * @param attributes The list of attributes that must exist. * @returns The query string. */ + @JvmStatic fun exists(attributes: List) = Query("exists", null, attributes).toJson() /** @@ -130,6 +141,7 @@ class Query( * @param attributes The list of attributes that must not exist. * @returns The query string. */ + @JvmStatic fun notExists(attributes: List) = Query("notExists", null, attributes).toJson() /** @@ -140,6 +152,7 @@ class Query( * @param end The end value of the range. * @returns The query string. */ + @JvmStatic fun between(attribute: String, start: Any, end: Any) = Query("between", attribute, listOf(start, end)).toJson() /** @@ -149,6 +162,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun startsWith(attribute: String, value: String) = Query("startsWith", attribute, listOf(value)).toJson() /** @@ -158,6 +172,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun endsWith(attribute: String, value: String) = Query("endsWith", attribute, listOf(value)).toJson() /** @@ -166,6 +181,7 @@ class Query( * @param attributes The list of attributes to select. * @returns The query string. */ + @JvmStatic fun select(attributes: List) = Query("select", null, attributes).toJson() /** @@ -174,6 +190,7 @@ class Query( * @param attribute The attribute to sort by. * @returns The query string. */ + @JvmStatic fun orderAsc(attribute: String) = Query("orderAsc", attribute).toJson() /** @@ -182,6 +199,7 @@ class Query( * @param attribute The attribute to sort by. * @returns The query string. */ + @JvmStatic fun orderDesc(attribute: String) = Query("orderDesc", attribute).toJson() /** @@ -189,6 +207,7 @@ class Query( * * @returns The query string. */ + @JvmStatic fun orderRandom() = Query("orderRandom").toJson() /** @@ -197,6 +216,7 @@ class Query( * @param documentId The document ID to use as cursor. * @returns The query string. */ + @JvmStatic fun cursorBefore(documentId: String) = Query("cursorBefore", null, listOf(documentId)).toJson() /** @@ -205,6 +225,7 @@ class Query( * @param documentId The document ID to use as cursor. * @returns The query string. */ + @JvmStatic fun cursorAfter(documentId: String) = Query("cursorAfter", null, listOf(documentId)).toJson() /** @@ -213,6 +234,7 @@ class Query( * @param limit The number of results to return. * @returns The query string. */ + @JvmStatic fun limit(limit: Int) = Query("limit", null, listOf(limit)).toJson() /** @@ -221,6 +243,7 @@ class Query( * @param offset The number of results to skip. * @returns The query string. */ + @JvmStatic fun offset(offset: Int) = Query("offset", null, listOf(offset)).toJson() /** @@ -232,6 +255,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun contains(attribute: String, value: Any) = Query("contains", attribute, parseValue(value)).toJson() /** @@ -243,6 +267,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun containsAny(attribute: String, value: List) = Query("containsAny", attribute, value).toJson() /** @@ -254,6 +279,7 @@ class Query( * @param value The values to match against. * @returns The query string. */ + @JvmStatic fun containsAll(attribute: String, value: List) = Query("containsAll", attribute, value).toJson() /** @@ -263,6 +289,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun notContains(attribute: String, value: Any) = Query("notContains", attribute, parseValue(value)).toJson() /** @@ -272,6 +299,7 @@ class Query( * @param value The search value to match against. * @returns The query string. */ + @JvmStatic fun notSearch(attribute: String, value: String) = Query("notSearch", attribute, listOf(value)).toJson() /** @@ -282,6 +310,7 @@ class Query( * @param end The end value of the range. * @returns The query string. */ + @JvmStatic fun notBetween(attribute: String, start: Any, end: Any) = Query("notBetween", attribute, listOf(start, end)).toJson() /** @@ -291,6 +320,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun notStartsWith(attribute: String, value: String) = Query("notStartsWith", attribute, listOf(value)).toJson() /** @@ -300,6 +330,7 @@ class Query( * @param value The value to compare against. * @returns The query string. */ + @JvmStatic fun notEndsWith(attribute: String, value: String) = Query("notEndsWith", attribute, listOf(value)).toJson() /** @@ -308,6 +339,7 @@ class Query( * @param value The date value to compare against. * @returns The query string. */ + @JvmStatic fun createdBefore(value: String) = lessThan("\$createdAt", value) /** @@ -316,6 +348,7 @@ class Query( * @param value The date value to compare against. * @returns The query string. */ + @JvmStatic fun createdAfter(value: String) = greaterThan("\$createdAt", value) /** @@ -325,6 +358,7 @@ class Query( * @param end The end date value. * @returns The query string. */ + @JvmStatic fun createdBetween(start: String, end: String) = between("\$createdAt", start, end) /** @@ -333,6 +367,7 @@ class Query( * @param value The date value to compare against. * @returns The query string. */ + @JvmStatic fun updatedBefore(value: String) = lessThan("\$updatedAt", value) /** @@ -341,6 +376,7 @@ class Query( * @param value The date value to compare against. * @returns The query string. */ + @JvmStatic fun updatedAfter(value: String) = greaterThan("\$updatedAt", value) /** @@ -350,6 +386,7 @@ class Query( * @param end The end date value. * @returns The query string. */ + @JvmStatic fun updatedBetween(start: String, end: String) = between("\$updatedAt", start, end) /** @@ -358,6 +395,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ + @JvmStatic fun or(queries: List) = Query("or", null, queries.map { it.fromJson>() }).toJson() /** @@ -366,6 +404,7 @@ class Query( * @param queries The list of query strings to combine. * @returns The query string. */ + @JvmStatic fun and(queries: List) = Query("and", null, queries.map { it.fromJson>() }).toJson() /** @@ -375,6 +414,7 @@ class Query( * @param queries The list of query strings to match against array elements. * @returns The query string. */ + @JvmStatic fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson>() }).toJson() /** @@ -384,6 +424,7 @@ class Query( * @param queries Filter, order, select, and pagination queries applied to related documents. * @returns The query string. */ + @JvmStatic fun relationship(attribute: String, queries: List) = Query("relationship", attribute, queries.map { it.fromJson>() }).toJson() /** @@ -395,6 +436,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceEqual", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -406,6 +449,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceNotEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceNotEqual", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -417,6 +462,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceGreaterThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceGreaterThan", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -428,6 +475,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceLessThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceLessThan", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -437,6 +486,7 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorDot(attribute: String, vector: List) = Query("vectorDot", attribute, listOf(vector)).toJson() /** @@ -446,6 +496,7 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorCosine(attribute: String, vector: List) = Query("vectorCosine", attribute, listOf(vector)).toJson() /** @@ -455,6 +506,7 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorEuclidean(attribute: String, vector: List) = Query("vectorEuclidean", attribute, listOf(vector)).toJson() /** @@ -464,6 +516,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun intersects(attribute: String, values: List) = Query("intersects", attribute, listOf(values)).toJson() /** @@ -473,6 +526,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notIntersects(attribute: String, values: List) = Query("notIntersects", attribute, listOf(values)).toJson() /** @@ -482,6 +536,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun crosses(attribute: String, values: List) = Query("crosses", attribute, listOf(values)).toJson() /** @@ -491,6 +546,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notCrosses(attribute: String, values: List) = Query("notCrosses", attribute, listOf(values)).toJson() /** @@ -500,6 +556,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun overlaps(attribute: String, values: List) = Query("overlaps", attribute, listOf(values)).toJson() /** @@ -509,6 +566,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notOverlaps(attribute: String, values: List) = Query("notOverlaps", attribute, listOf(values)).toJson() /** @@ -518,6 +576,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun touches(attribute: String, values: List) = Query("touches", attribute, listOf(values)).toJson() /** @@ -527,6 +586,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notTouches(attribute: String, values: List) = Query("notTouches", attribute, listOf(values)).toJson() /** @@ -536,6 +596,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun count(attribute: String = "*", alias: String = "") = Query("count", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -545,6 +607,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun countDistinct(attribute: String, alias: String = "") = Query("countDistinct", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -554,6 +618,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun sum(attribute: String, alias: String = "") = Query("sum", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -563,6 +629,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun avg(attribute: String, alias: String = "") = Query("avg", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -572,6 +640,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun min(attribute: String, alias: String = "") = Query("min", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -581,6 +651,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun max(attribute: String, alias: String = "") = Query("max", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -590,6 +662,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddev(attribute: String, alias: String = "") = Query("stddev", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -599,6 +673,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddevPop(attribute: String, alias: String = "") = Query("stddevPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -608,6 +684,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddevSamp(attribute: String, alias: String = "") = Query("stddevSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -617,6 +695,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun variance(attribute: String, alias: String = "") = Query("variance", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -626,6 +706,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun varPop(attribute: String, alias: String = "") = Query("varPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -635,6 +717,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun varSamp(attribute: String, alias: String = "") = Query("varSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -644,6 +728,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitAnd(attribute: String, alias: String = "") = Query("bitAnd", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -653,6 +739,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitOr(attribute: String, alias: String = "") = Query("bitOr", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -662,6 +750,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitXor(attribute: String, alias: String = "") = Query("bitXor", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -670,6 +760,7 @@ class Query( * @param attributes Attributes to group by. * @returns The query string. */ + @JvmStatic fun groupBy(attributes: List) = Query("groupBy", null, attributes).toJson() /** @@ -678,6 +769,7 @@ class Query( * @param queries Nested query strings. * @returns The query string. */ + @JvmStatic fun having(queries: List) = Query("having", null, queries.map { it.fromJson>() }).toJson() /** @@ -685,6 +777,7 @@ class Query( * * @returns The query string. */ + @JvmStatic fun distinct() = Query("distinct").toJson() /** @@ -695,6 +788,8 @@ class Query( * @param operator Comparison operator. Defaults to `=`. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** @@ -707,6 +802,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** @@ -716,6 +813,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun join(table: String, on: List) = nestedJoin("join", table, "", on) /** @@ -726,6 +824,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** @@ -738,6 +837,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** @@ -747,6 +848,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun leftJoin(table: String, on: List) = nestedJoin("leftJoin", table, "", on) /** @@ -757,6 +859,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** @@ -769,6 +872,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** @@ -778,6 +883,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun rightJoin(table: String, on: List) = nestedJoin("rightJoin", table, "", on) /** @@ -788,6 +894,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** @@ -800,6 +907,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** @@ -809,6 +918,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun fullOuterJoin(table: String, on: List) = nestedJoin("fullOuterJoin", table, "", on) /** @@ -819,6 +929,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** @@ -828,6 +939,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -837,6 +950,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun covers(attribute: String, values: List) = Query("covers", attribute, listOf(values)).toJson() /** @@ -846,6 +960,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notCovers(attribute: String, values: List) = Query("notCovers", attribute, listOf(values)).toJson() /** @@ -855,6 +970,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun spatialEquals(attribute: String, values: List) = Query("spatialEquals", attribute, listOf(values)).toJson() /** @@ -864,6 +980,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notSpatialEquals(attribute: String, values: List) = Query("notSpatialEquals", attribute, listOf(values)).toJson() /** @@ -873,6 +990,8 @@ class Query( * @param perPage Rows per page. Defaults to 25. * @returns Limit and offset query strings. */ + @JvmStatic + @JvmOverloads fun page(page: Int, perPage: Int = 25): List { require(page >= 1) { "Page must be >= 1, got $page" } require(perPage >= 1) { "Per page must be >= 1, got $perPage" } @@ -884,6 +1003,7 @@ class Query( * * @returns A new query builder. */ + @JvmStatic fun builder() = Builder() private fun joinQuery(method: String, table: String, left: String, right: String, operator: String, alias: String): String { diff --git a/templates/android/library/src/main/java/io/package/Role.kt.twig b/templates/android/library/src/main/java/io/package/Role.kt.twig index 8451ef3c7f..fd9cb5c3b3 100644 --- a/templates/android/library/src/main/java/io/package/Role.kt.twig +++ b/templates/android/library/src/main/java/io/package/Role.kt.twig @@ -11,6 +11,7 @@ class Role { * * This includes authenticated and unauthenticated users. */ + @JvmStatic fun any(): String = "any" /** @@ -19,6 +20,8 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ + @JvmStatic + @JvmOverloads fun user(id: String, status: String = ""): String = if (status.isEmpty()) { "user:$id" } else { @@ -31,6 +34,8 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ + @JvmStatic + @JvmOverloads fun users(status: String = ""): String = if (status.isEmpty()) { "users" } else { @@ -42,6 +47,7 @@ class Role { * * Authenticated users don't have access to this role. */ + @JvmStatic fun guests(): String = "guests" /** @@ -50,6 +56,8 @@ class Role { * You can optionally pass a role for [role] to target * team members with the specified role. */ + @JvmStatic + @JvmOverloads fun team(id: String, role: String = ""): String = if (role.isEmpty()) { "team:$id" } else { @@ -62,11 +70,13 @@ class Role { * When the member is removed from the team, they will * no longer have access. */ + @JvmStatic fun member(id: String): String = "member:$id" /** * Grants access to a user with the specified label. */ + @JvmStatic fun label(name: String): String = "label:$name" } } diff --git a/templates/android/library/src/main/java/io/package/models/InputFile.kt.twig b/templates/android/library/src/main/java/io/package/models/InputFile.kt.twig index 482a83fd60..d0d105db96 100644 --- a/templates/android/library/src/main/java/io/package/models/InputFile.kt.twig +++ b/templates/android/library/src/main/java/io/package/models/InputFile.kt.twig @@ -12,6 +12,7 @@ class InputFile private constructor() { lateinit var data: Any companion object { + @JvmStatic fun fromFile(file: File) = InputFile().apply { path = file.canonicalPath filename = file.name @@ -19,10 +20,13 @@ class InputFile private constructor() { sourceType = "file" } + @JvmStatic fun fromPath(path: String): InputFile = fromFile(File(path)).apply { sourceType = "path" } + @JvmStatic + @JvmOverloads fun fromBytes(bytes: ByteArray, filename: String = "", mimeType: String = "") = InputFile().apply { this.filename = filename this.mimeType = mimeType diff --git a/templates/android/library/src/main/java/io/package/services/Service.kt.twig b/templates/android/library/src/main/java/io/package/services/Service.kt.twig index 9c31f17ef9..baeca3f913 100644 --- a/templates/android/library/src/main/java/io/package/services/Service.kt.twig +++ b/templates/android/library/src/main/java/io/package/services/Service.kt.twig @@ -52,9 +52,10 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { ) {%~ endif %} {%~ endif %} - {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) %} + {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) or 'multipart/form-data' in (method | consumes) %} @JvmOverloads {%~ endif %} + @Throws({{ spec.info.title | caseUcfirst }}Exception::class) {%~ if allParameters is empty and (method | methodType) != "webAuth" and not ((method | responseModel) | hasGenericType(spec)) and 'multipart/form-data' not in (method | consumes) %} suspend fun {{ (method | methodName) | caseCamel }}(): {{ method | returnType(spec, sdk.namespace | caseDot) | raw }} { {%~ else %} @@ -279,7 +280,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { ) {%~ endif %} {%~ endif %} - {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) %} + {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) or 'multipart/form-data' in (method | consumes) %} @JvmOverloads {%~ endif %} @Throws({{ spec.info.title | caseUcfirst }}Exception::class) diff --git a/templates/kotlin/docs/java/example.md.twig b/templates/kotlin/docs/java/example.md.twig index a192b2652f..ecfaae159b 100644 --- a/templates/kotlin/docs/java/example.md.twig +++ b/templates/kotlin/docs/java/example.md.twig @@ -1,6 +1,11 @@ +{% set examples = [] %} +{% for parameter in (method | parameters('all')) %} +{% set examples = examples | merge([(parameter | enumValues) | length > 0 ? (parameter | javaEnumExample) : (parameter | javaParamExample)]) %} +{% endfor %} ```java import {{ sdk.namespace | caseDot }}.Client; import {{ sdk.namespace | caseDot }}.coroutines.CoroutineCallback; +import {{ sdk.namespace | caseDot }}.exceptions.{{ spec.info.title | caseUcfirst }}Exception; {% if (method | parameters('all')) | filter((param) => (param | schemaType) == 'file') | length > 0 %} import {{ sdk.namespace | caseDot }}.models.InputFile; {% endif %} @@ -18,40 +23,38 @@ import {{ sdk.namespace | caseDot }}.enums.{{ (parameter | enumName) | caseUcfir {% endif %} {% endif %} {% endfor %} +{% if 'List.of(' in (examples | join) %} +import java.util.List; +{% endif %} +{% if 'Map.of(' in (examples | join) %} +import java.util.Map; +{% endif %} +{% set schemes = method | securitySchemes %} Client client = new Client() -{% if [(method | securitySchemes)]|length > 0 %} - .setEndpoint("{{ endpointDocs | raw }}") // Your API Endpoint -{% for node in [(method | securitySchemes)] %} -{% for key,header in node|keys %} - .set{{header | caseUcfirst}}("{{node[header].extensions['x-appwrite']['demo'] | raw }}"){% if loop.last %};{% endif %} // {{node[header].description}} -{% endfor %}{% endfor %}{% endif %} + .setEndpoint("{{ endpointDocs | raw }}"){% if schemes is empty %};{% endif %} // Your API Endpoint +{% for header in schemes | keys %} + .set{{ header | caseUcfirst }}("{{ schemes[header].extensions['x-appwrite']['demo'] | raw }}"){% if loop.last %};{% endif %} // {{ schemes[header].description }} +{% endfor %} {{ service.name | caseUcfirst }} {{ service.name | caseCamel }} = new {{ service.name | caseUcfirst }}(client); -{{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}({% if (method | parameters('all')) | length == 0 %}new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } - - System.out.println(result); -}));{% endif %} - +try { + {{ service.name | caseCamel }}.{{ (method | methodName) | caseCamel }}( {%~ for parameter in (method | parameters('all')) %} - {% if (parameter | enumValues) | length > 0%}{{ parameter | javaEnumExample }}{% else %}{{ parameter | javaParamExample }}{% endif %}, // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} - {%~ if loop.last %} - - new CoroutineCallback<>((result, error) -> { - if (error != null) { - error.printStackTrace(); - return; - } - - System.out.println(result); - }) -); -{% endif %} - -{% endfor %} + {{ examples[loop.index0] | replace({"\n": "\n "}) | raw }}, // {{ parameter.name }}{% if not parameter.required %} (optional){% endif %} + + {%~ endfor %} + new CoroutineCallback<>((result, error) -> { + if (error != null) { + error.printStackTrace(); + return; + } + + System.out.println(result); + }) + ); +} catch ({{ spec.info.title | caseUcfirst }}Exception e) { + e.printStackTrace(); +} ``` diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/ID.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/ID.kt.twig index a9b59a825a..6ccbde2961 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/ID.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/ID.kt.twig @@ -18,9 +18,12 @@ class ID { return hexTimestamp } + @JvmStatic fun custom(id: String): String = id // Generate a unique ID with padding to have a longer ID + @JvmStatic + @JvmOverloads fun unique(padding: Int = 7): String { val baseId = ID.hexTimestamp() val randomPadding = (1..padding) diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Operator.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Operator.kt.twig index a58312f433..91d6388cbd 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Operator.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Operator.kt.twig @@ -23,6 +23,8 @@ class Operator( override fun toString() = this.toJson() companion object { + @JvmStatic + @JvmOverloads fun increment(value: Number = 1, max: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -31,6 +33,8 @@ class Operator( return Operator("increment", values).toJson() } + @JvmStatic + @JvmOverloads fun decrement(value: Number = 1, min: Number? = null): String { require(!value.toDouble().isNaN() && !value.toDouble().isInfinite()) { "Value cannot be NaN or Infinity" } min?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Min cannot be NaN or Infinity" } } @@ -39,6 +43,8 @@ class Operator( return Operator("decrement", values).toJson() } + @JvmStatic + @JvmOverloads fun multiply(factor: Number, max: Number? = null): String { require(!factor.toDouble().isNaN() && !factor.toDouble().isInfinite()) { "Factor cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -47,6 +53,8 @@ class Operator( return Operator("multiply", values).toJson() } + @JvmStatic + @JvmOverloads fun divide(divisor: Number, min: Number? = null): String { require(!divisor.toDouble().isNaN() && !divisor.toDouble().isInfinite()) { "Divisor cannot be NaN or Infinity" } min?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Min cannot be NaN or Infinity" } } @@ -56,12 +64,15 @@ class Operator( return Operator("divide", values).toJson() } + @JvmStatic fun modulo(divisor: Number): String { require(!divisor.toDouble().isNaN() && !divisor.toDouble().isInfinite()) { "Divisor cannot be NaN or Infinity" } require(divisor.toDouble() != 0.0) { "Divisor cannot be zero" } return Operator("modulo", listOf(divisor)).toJson() } + @JvmStatic + @JvmOverloads fun power(exponent: Number, max: Number? = null): String { require(!exponent.toDouble().isNaN() && !exponent.toDouble().isInfinite()) { "Exponent cannot be NaN or Infinity" } max?.let { require(!it.toDouble().isNaN() && !it.toDouble().isInfinite()) { "Max cannot be NaN or Infinity" } } @@ -70,59 +81,74 @@ class Operator( return Operator("power", values).toJson() } + @JvmStatic fun arrayAppend(values: List): String { return Operator("arrayAppend", values).toJson() } + @JvmStatic fun arrayPrepend(values: List): String { return Operator("arrayPrepend", values).toJson() } + @JvmStatic fun arrayInsert(index: Int, value: Any): String { return Operator("arrayInsert", listOf(index, value)).toJson() } + @JvmStatic fun arrayRemove(value: Any): String { return Operator("arrayRemove", listOf(value)).toJson() } + @JvmStatic fun arrayUnique(): String { return Operator("arrayUnique", emptyList()).toJson() } + @JvmStatic fun arrayIntersect(values: List): String { return Operator("arrayIntersect", values).toJson() } + @JvmStatic fun arrayDiff(values: List): String { return Operator("arrayDiff", values).toJson() } + @JvmStatic + @JvmOverloads fun arrayFilter(condition: Condition, value: Any? = null): String { val values = listOf(condition.value, value) return Operator("arrayFilter", values).toJson() } + @JvmStatic fun stringConcat(value: Any): String { return Operator("stringConcat", listOf(value)).toJson() } + @JvmStatic fun stringReplace(search: String, replace: String): String { return Operator("stringReplace", listOf(search, replace)).toJson() } + @JvmStatic fun toggle(): String { return Operator("toggle", emptyList()).toJson() } + @JvmStatic fun dateAddDays(days: Int): String { return Operator("dateAddDays", listOf(days)).toJson() } + @JvmStatic fun dateSubDays(days: Int): String { return Operator("dateSubDays", listOf(days)).toJson() } + @JvmStatic fun dateSetNow(): String { return Operator("dateSetNow", emptyList()).toJson() } diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Permission.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Permission.kt.twig index 2540ae0cb0..796fdd0f6a 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Permission.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Permission.kt.twig @@ -2,18 +2,23 @@ package {{ sdk.namespace | caseDot }} class Permission { companion object { + @JvmStatic fun read(role: String): String { return "read(\"${role}\")" } + @JvmStatic fun write(role: String): String { return "write(\"${role}\")" } + @JvmStatic fun create(role: String): String { return "create(\"${role}\")" } + @JvmStatic fun update(role: String): String { return "update(\"${role}\")" } + @JvmStatic fun delete(role: String): String { return "delete(\"${role}\")" } diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig index e9d9753214..245c59543e 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Query.kt.twig @@ -11,8 +11,10 @@ class Query( override fun toString() = this.toJson() companion object { + @JvmStatic fun equal(attribute: String, value: Any) = Query("equal", attribute, parseValue(value)).toJson() + @JvmStatic fun notEqual(attribute: String, value: Any) = Query("notEqual", attribute, parseValue(value)).toJson() /** @@ -22,20 +24,28 @@ class Query( * @param pattern The regular expression pattern to match. * @returns The query string. */ + @JvmStatic fun regex(attribute: String, pattern: String) = Query("regex", attribute, parseValue(pattern)).toJson() + @JvmStatic fun lessThan(attribute: String, value: Any) = Query("lessThan", attribute, parseValue(value)).toJson() + @JvmStatic fun lessThanEqual(attribute: String, value: Any) = Query("lessThanEqual", attribute, parseValue(value)).toJson() + @JvmStatic fun greaterThan(attribute: String, value: Any) = Query("greaterThan", attribute, parseValue(value)).toJson() + @JvmStatic fun greaterThanEqual(attribute: String, value: Any) = Query("greaterThanEqual", attribute, parseValue(value)).toJson() + @JvmStatic fun search(attribute: String, value: String) = Query("search", attribute, listOf(value)).toJson() + @JvmStatic fun isNull(attribute: String) = Query("isNull", attribute).toJson() + @JvmStatic fun isNotNull(attribute: String) = Query("isNotNull", attribute).toJson() /** @@ -44,6 +54,7 @@ class Query( * @param attributes The list of attributes that must exist. * @returns The query string. */ + @JvmStatic fun exists(attributes: List) = Query("exists", null, attributes).toJson() /** @@ -52,28 +63,40 @@ class Query( * @param attributes The list of attributes that must not exist. * @returns The query string. */ + @JvmStatic fun notExists(attributes: List) = Query("notExists", null, attributes).toJson() + @JvmStatic fun between(attribute: String, start: Any, end: Any) = Query("between", attribute, listOf(start, end)).toJson() + @JvmStatic fun startsWith(attribute: String, value: String) = Query("startsWith", attribute, listOf(value)).toJson() + @JvmStatic fun endsWith(attribute: String, value: String) = Query("endsWith", attribute, listOf(value)).toJson() + @JvmStatic fun select(attributes: List) = Query("select", null, attributes).toJson() + @JvmStatic fun orderAsc(attribute: String) = Query("orderAsc", attribute).toJson() + @JvmStatic fun orderDesc(attribute: String) = Query("orderDesc", attribute).toJson() + @JvmStatic fun orderRandom() = Query("orderRandom").toJson() + @JvmStatic fun cursorBefore(documentId: String) = Query("cursorBefore", null, listOf(documentId)).toJson() + @JvmStatic fun cursorAfter(documentId: String) = Query("cursorAfter", null, listOf(documentId)).toJson() + @JvmStatic fun limit(limit: Int) = Query("limit", null, listOf(limit)).toJson() + @JvmStatic fun offset(offset: Int) = Query("offset", null, listOf(offset)).toJson() /** @@ -82,6 +105,7 @@ class Query( * * Note: For array attributes, use containsAny or containsAll instead. */ + @JvmStatic fun contains(attribute: String, value: Any) = Query("contains", attribute, parseValue(value)).toJson() /** @@ -89,6 +113,7 @@ class Query( * For array and relationship attributes, matches documents where the attribute * contains at least one of the given values. */ + @JvmStatic fun containsAny(attribute: String, value: List) = Query("containsAny", attribute, value).toJson() /** @@ -96,32 +121,46 @@ class Query( * For array and relationship attributes, matches documents where the attribute * contains every one of the given values. */ + @JvmStatic fun containsAll(attribute: String, value: List) = Query("containsAll", attribute, value).toJson() + @JvmStatic fun notContains(attribute: String, value: Any) = Query("notContains", attribute, parseValue(value)).toJson() + @JvmStatic fun notSearch(attribute: String, value: String) = Query("notSearch", attribute, listOf(value)).toJson() + @JvmStatic fun notBetween(attribute: String, start: Any, end: Any) = Query("notBetween", attribute, listOf(start, end)).toJson() + @JvmStatic fun notStartsWith(attribute: String, value: String) = Query("notStartsWith", attribute, listOf(value)).toJson() + @JvmStatic fun notEndsWith(attribute: String, value: String) = Query("notEndsWith", attribute, listOf(value)).toJson() + @JvmStatic fun createdBefore(value: String) = lessThan("\$createdAt", value) + @JvmStatic fun createdAfter(value: String) = greaterThan("\$createdAt", value) + @JvmStatic fun createdBetween(start: String, end: String) = between("\$createdAt", start, end) + @JvmStatic fun updatedBefore(value: String) = lessThan("\$updatedAt", value) + @JvmStatic fun updatedAfter(value: String) = greaterThan("\$updatedAt", value) + @JvmStatic fun updatedBetween(start: String, end: String) = between("\$updatedAt", start, end) + @JvmStatic fun or(queries: List) = Query("or", null, queries.map { it.fromJson() }).toJson() + @JvmStatic fun and(queries: List) = Query("and", null, queries.map { it.fromJson() }).toJson() /** @@ -131,6 +170,7 @@ class Query( * @param queries The list of query strings to match against array elements. * @returns The query string. */ + @JvmStatic fun elemMatch(attribute: String, queries: List) = Query("elemMatch", attribute, queries.map { it.fromJson() }).toJson() /** @@ -140,6 +180,7 @@ class Query( * @param queries Filter, order, select, and pagination queries applied to related documents. * @returns The query string. */ + @JvmStatic fun relationship(attribute: String, queries: List) = Query("relationship", attribute, queries.map { it.fromJson() }).toJson() /** @@ -151,6 +192,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceEqual", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -162,6 +205,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceNotEqual(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceNotEqual", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -173,6 +218,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceGreaterThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceGreaterThan", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -184,6 +231,8 @@ class Query( * @param meters Whether the distance is in meters. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun distanceLessThan(attribute: String, values: List, distance: Number, meters: Boolean = true) = Query("distanceLessThan", attribute, listOf(listOf(values, distance, meters))).toJson() /** @@ -193,6 +242,7 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorDot(attribute: String, vector: List) = Query("vectorDot", attribute, listOf(vector)).toJson() /** @@ -202,6 +252,7 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorCosine(attribute: String, vector: List) = Query("vectorCosine", attribute, listOf(vector)).toJson() /** @@ -211,22 +262,31 @@ class Query( * @param vector The query vector. * @returns The query string. */ + @JvmStatic fun vectorEuclidean(attribute: String, vector: List) = Query("vectorEuclidean", attribute, listOf(vector)).toJson() + @JvmStatic fun intersects(attribute: String, values: List) = Query("intersects", attribute, listOf(values)).toJson() + @JvmStatic fun notIntersects(attribute: String, values: List) = Query("notIntersects", attribute, listOf(values)).toJson() + @JvmStatic fun crosses(attribute: String, values: List) = Query("crosses", attribute, listOf(values)).toJson() + @JvmStatic fun notCrosses(attribute: String, values: List) = Query("notCrosses", attribute, listOf(values)).toJson() + @JvmStatic fun overlaps(attribute: String, values: List) = Query("overlaps", attribute, listOf(values)).toJson() + @JvmStatic fun notOverlaps(attribute: String, values: List) = Query("notOverlaps", attribute, listOf(values)).toJson() + @JvmStatic fun touches(attribute: String, values: List) = Query("touches", attribute, listOf(values)).toJson() + @JvmStatic fun notTouches(attribute: String, values: List) = Query("notTouches", attribute, listOf(values)).toJson() /** @@ -236,6 +296,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun count(attribute: String = "*", alias: String = "") = Query("count", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -245,6 +307,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun countDistinct(attribute: String, alias: String = "") = Query("countDistinct", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -254,6 +318,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun sum(attribute: String, alias: String = "") = Query("sum", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -263,6 +329,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun avg(attribute: String, alias: String = "") = Query("avg", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -272,6 +340,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun min(attribute: String, alias: String = "") = Query("min", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -281,6 +351,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun max(attribute: String, alias: String = "") = Query("max", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -290,6 +362,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddev(attribute: String, alias: String = "") = Query("stddev", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -299,6 +373,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddevPop(attribute: String, alias: String = "") = Query("stddevPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -308,6 +384,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun stddevSamp(attribute: String, alias: String = "") = Query("stddevSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -317,6 +395,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun variance(attribute: String, alias: String = "") = Query("variance", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -326,6 +406,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun varPop(attribute: String, alias: String = "") = Query("varPop", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -335,6 +417,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun varSamp(attribute: String, alias: String = "") = Query("varSamp", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -344,6 +428,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitAnd(attribute: String, alias: String = "") = Query("bitAnd", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -353,6 +439,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitOr(attribute: String, alias: String = "") = Query("bitOr", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -362,6 +450,8 @@ class Query( * @param alias Optional result alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun bitXor(attribute: String, alias: String = "") = Query("bitXor", attribute, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -370,6 +460,7 @@ class Query( * @param attributes Attributes to group by. * @returns The query string. */ + @JvmStatic fun groupBy(attributes: List) = Query("groupBy", null, attributes).toJson() /** @@ -378,6 +469,7 @@ class Query( * @param queries Nested query strings. * @returns The query string. */ + @JvmStatic fun having(queries: List) = Query("having", null, queries.map { it.fromJson() }).toJson() /** @@ -385,6 +477,7 @@ class Query( * * @returns The query string. */ + @JvmStatic fun distinct() = Query("distinct").toJson() /** @@ -395,6 +488,8 @@ class Query( * @param operator Comparison operator. Defaults to `=`. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun on(left: String, right: String, operator: String = "=") = Query("on", values = listOf(left, operator, right)).toJson() /** @@ -407,6 +502,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun join(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("join", table, left, right, operator, alias) /** @@ -416,6 +513,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun join(table: String, on: List) = nestedJoin("join", table, "", on) /** @@ -426,6 +524,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun join(table: String, alias: String, on: List) = nestedJoin("join", table, alias, on) /** @@ -438,6 +537,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun leftJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("leftJoin", table, left, right, operator, alias) /** @@ -447,6 +548,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun leftJoin(table: String, on: List) = nestedJoin("leftJoin", table, "", on) /** @@ -457,6 +559,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun leftJoin(table: String, alias: String, on: List) = nestedJoin("leftJoin", table, alias, on) /** @@ -469,6 +572,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun rightJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("rightJoin", table, left, right, operator, alias) /** @@ -478,6 +583,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun rightJoin(table: String, on: List) = nestedJoin("rightJoin", table, "", on) /** @@ -488,6 +594,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun rightJoin(table: String, alias: String, on: List) = nestedJoin("rightJoin", table, alias, on) /** @@ -500,6 +607,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun fullOuterJoin(table: String, left: String, right: String, operator: String = "=", alias: String = "") = joinQuery("fullOuterJoin", table, left, right, operator, alias) /** @@ -509,6 +618,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun fullOuterJoin(table: String, on: List) = nestedJoin("fullOuterJoin", table, "", on) /** @@ -519,6 +629,7 @@ class Query( * @param on Nested ON queries, including [on]. * @returns The query string. */ + @JvmStatic fun fullOuterJoin(table: String, alias: String, on: List) = nestedJoin("fullOuterJoin", table, alias, on) /** @@ -528,6 +639,8 @@ class Query( * @param alias Optional table alias. Defaults to the table ID when that is a valid alias. * @returns The query string. */ + @JvmStatic + @JvmOverloads fun crossJoin(table: String, alias: String = "") = Query("crossJoin", table, if (alias.isNotEmpty()) listOf(alias) else null).toJson() /** @@ -537,6 +650,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun covers(attribute: String, values: List) = Query("covers", attribute, listOf(values)).toJson() /** @@ -546,6 +660,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notCovers(attribute: String, values: List) = Query("notCovers", attribute, listOf(values)).toJson() /** @@ -555,6 +670,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun spatialEquals(attribute: String, values: List) = Query("spatialEquals", attribute, listOf(values)).toJson() /** @@ -564,6 +680,7 @@ class Query( * @param values The coordinate values. * @returns The query string. */ + @JvmStatic fun notSpatialEquals(attribute: String, values: List) = Query("notSpatialEquals", attribute, listOf(values)).toJson() /** @@ -573,6 +690,8 @@ class Query( * @param perPage Rows per page. Defaults to 25. * @returns Limit and offset query strings. */ + @JvmStatic + @JvmOverloads fun page(page: Int, perPage: Int = 25): List { require(page >= 1) { "Page must be >= 1, got $page" } require(perPage >= 1) { "Per page must be >= 1, got $perPage" } @@ -584,6 +703,7 @@ class Query( * * @returns A new query builder. */ + @JvmStatic fun builder() = Builder() private fun joinQuery(method: String, table: String, left: String, right: String, operator: String, alias: String): String { diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig index 8451ef3c7f..fd9cb5c3b3 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/Role.kt.twig @@ -11,6 +11,7 @@ class Role { * * This includes authenticated and unauthenticated users. */ + @JvmStatic fun any(): String = "any" /** @@ -19,6 +20,8 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ + @JvmStatic + @JvmOverloads fun user(id: String, status: String = ""): String = if (status.isEmpty()) { "user:$id" } else { @@ -31,6 +34,8 @@ class Role { * You can optionally pass verified or unverified for * [status] to target specific types of users. */ + @JvmStatic + @JvmOverloads fun users(status: String = ""): String = if (status.isEmpty()) { "users" } else { @@ -42,6 +47,7 @@ class Role { * * Authenticated users don't have access to this role. */ + @JvmStatic fun guests(): String = "guests" /** @@ -50,6 +56,8 @@ class Role { * You can optionally pass a role for [role] to target * team members with the specified role. */ + @JvmStatic + @JvmOverloads fun team(id: String, role: String = ""): String = if (role.isEmpty()) { "team:$id" } else { @@ -62,11 +70,13 @@ class Role { * When the member is removed from the team, they will * no longer have access. */ + @JvmStatic fun member(id: String): String = "member:$id" /** * Grants access to a user with the specified label. */ + @JvmStatic fun label(name: String): String = "label:$name" } } diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/models/InputFile.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/models/InputFile.kt.twig index d73609f368..6bd7e78642 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/models/InputFile.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/models/InputFile.kt.twig @@ -14,6 +14,7 @@ class InputFile private constructor() { lateinit var data: Any companion object { + @JvmStatic fun fromFile(file: File) = InputFile().apply { path = file.canonicalPath filename = file.name @@ -23,10 +24,13 @@ class InputFile private constructor() { sourceType = "file" } + @JvmStatic fun fromPath(path: String): InputFile = fromFile(File(path)).apply { sourceType = "path" } + @JvmStatic + @JvmOverloads fun fromBytes(bytes: ByteArray, filename: String = "", mimeType: String = "") = InputFile().apply { this.filename = filename this.mimeType = mimeType diff --git a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig index 1556391edb..fc48b577f6 100644 --- a/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig +++ b/templates/kotlin/src/main/kotlin/io/appwrite/services/ServiceTemplate.kt.twig @@ -45,7 +45,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { ) {%~ endif %} {%~ endif %} - {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) %} + {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) or 'multipart/form-data' in (method | consumes) %} @JvmOverloads {%~ endif %} @Throws({{ spec.info.title | caseUcfirst }}Exception::class) @@ -159,7 +159,7 @@ class {{ service.name | caseUcfirst }}(client: Client) : Service(client) { ) {%~ endif %} {%~ endif %} - {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) %} + {%~ if (method | parameters('all')) | reduce((carry, param) => carry or not param.required) or 'multipart/form-data' in (method | consumes) %} @JvmOverloads {%~ endif %} @Throws({{ spec.info.title | caseUcfirst }}Exception::class) diff --git a/tests/e2e/Android16Java17Test.php b/tests/e2e/Android16Java17Test.php index 3755fcfcf6..0bbc23979f 100644 --- a/tests/e2e/Android16Java17Test.php +++ b/tests/e2e/Android16Java17Test.php @@ -26,6 +26,9 @@ final class Android16Java17Test extends Base protected array $build = [ 'mkdir -p tests/e2e/sdks/android/library/src/test/java', 'cp tests/e2e/languages/android/Tests.kt tests/e2e/sdks/android/library/src/test/java/Tests.kt', + 'mkdir -p tests/e2e/sdks/android/library/src/test/java/io/appwrite', + 'cp tests/e2e/languages/kotlin/JavaInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/JavaInterop.java', + 'php tests/e2e/languages/kotlin/java-examples.php tests/e2e/sdks/android/docs/examples/java tests/e2e/sdks/android/library/src/test/java/examples "android.content.Context context, androidx.activity.ComponentActivity activity"', 'cp tests/e2e/languages/android/r8-rules.pro tests/e2e/sdks/android/library/r8-rules.pro', 'cat tests/e2e/languages/android/r8.gradle.kts >> tests/e2e/sdks/android/library/build.gradle.kts', 'cat tests/e2e/languages/android/lint.gradle.kts >> tests/e2e/sdks/android/library/build.gradle.kts', @@ -63,6 +66,7 @@ final class Android16Java17Test extends Base Base::QUERY_TRANSPORT_RESPONSE, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::JAVA_INTEROP_RESPONSES, ...Base::TOPIC_HELPER_RESPONSES, ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES, diff --git a/tests/e2e/Android5Java17Test.php b/tests/e2e/Android5Java17Test.php index 7363b3320c..dfffbf05cc 100644 --- a/tests/e2e/Android5Java17Test.php +++ b/tests/e2e/Android5Java17Test.php @@ -26,6 +26,9 @@ final class Android5Java17Test extends Base protected array $build = [ 'mkdir -p tests/e2e/sdks/android/library/src/test/java', 'cp tests/e2e/languages/android/Tests.kt tests/e2e/sdks/android/library/src/test/java/Tests.kt', + 'mkdir -p tests/e2e/sdks/android/library/src/test/java/io/appwrite', + 'cp tests/e2e/languages/kotlin/JavaInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/JavaInterop.java', + 'php tests/e2e/languages/kotlin/java-examples.php tests/e2e/sdks/android/docs/examples/java tests/e2e/sdks/android/library/src/test/java/examples "android.content.Context context, androidx.activity.ComponentActivity activity"', 'chmod +x tests/e2e/sdks/android/gradlew', ]; #[Override] @@ -60,6 +63,7 @@ final class Android5Java17Test extends Base Base::QUERY_TRANSPORT_RESPONSE, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::JAVA_INTEROP_RESPONSES, ...Base::TOPIC_HELPER_RESPONSES, ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES, diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index b13ff729d1..1295c749da 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -517,6 +517,13 @@ abstract class Base extends TestCase 'custom_id' ]; + protected const JAVA_INTEROP_RESPONSES = [ + 'unique()', + 'read("any")', + 'Missing required parameter: "id"', + 'PUT:/v1/mock/tests/general/upload-generic:passed', + ]; + protected const TOPIC_HELPER_RESPONSES = [ 'user/123/notification', 'org/42/user/123/notification', diff --git a/tests/e2e/KotlinJava17Test.php b/tests/e2e/KotlinJava17Test.php index d4fcaaae93..80865eeb55 100644 --- a/tests/e2e/KotlinJava17Test.php +++ b/tests/e2e/KotlinJava17Test.php @@ -26,6 +26,9 @@ final class KotlinJava17Test extends Base protected array $build = [ 'mkdir -p tests/e2e/sdks/kotlin/src/test/kotlin', 'cp tests/e2e/languages/kotlin/Tests.kt tests/e2e/sdks/kotlin/src/test/kotlin/Tests.kt', + 'mkdir -p tests/e2e/sdks/kotlin/src/test/java/io/appwrite', + 'cp tests/e2e/languages/kotlin/JavaInterop.java tests/e2e/sdks/kotlin/src/test/java/io/appwrite/JavaInterop.java', + 'php tests/e2e/languages/kotlin/java-examples.php tests/e2e/sdks/kotlin/docs/examples/java tests/e2e/sdks/kotlin/src/test/java/examples', 'chmod +x tests/e2e/sdks/kotlin/gradlew', ]; #[Override] @@ -59,6 +62,7 @@ final class KotlinJava17Test extends Base Base::QUERY_TRANSPORT_RESPONSE, ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, + ...Base::JAVA_INTEROP_RESPONSES, ...Base::TOPIC_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES ]; diff --git a/tests/e2e/languages/android/Tests.kt b/tests/e2e/languages/android/Tests.kt index ba83e0de7a..9e5cb300af 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -516,6 +516,8 @@ class ServiceTest { writeToFile(ID.unique()) writeToFile(ID.custom("custom_id")) + JavaInterop.run(general, "../../../../resources/file.png").forEach { writeToFile(it) } + // Topic helper tests writeToFile(Topic.path(listOf("user", "123", "notification")).toString()) writeToFile(Topic.path(listOf("org", "42", "user", "123")).path(listOf("notification")).toString()) diff --git a/tests/e2e/languages/kotlin/JavaInterop.java b/tests/e2e/languages/kotlin/JavaInterop.java new file mode 100644 index 0000000000..11e5e0db22 --- /dev/null +++ b/tests/e2e/languages/kotlin/JavaInterop.java @@ -0,0 +1,53 @@ +package io.appwrite; + +import io.appwrite.coroutines.CoroutineCallback; +import io.appwrite.exceptions.AppwriteException; +import io.appwrite.models.InputFile; +import io.appwrite.models.Profile; +import io.appwrite.services.General; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import kotlin.coroutines.intrinsics.IntrinsicsKt; + +public final class JavaInterop { + private JavaInterop() { + } + + public static List run(General general, String file) throws Exception { + List lines = new ArrayList<>(); + + lines.add(ID.unique()); + lines.add(Permission.read(Role.any())); + + try { + general.validatePath("0", "", new CoroutineCallback<>((result, error) -> { + })); + lines.add("Empty path parameter was accepted"); + } catch (AppwriteException e) { + lines.add(e.getMessage()); + } + + CompletableFuture> profile = new CompletableFuture<>(); + try { + Object returned = general.uploadGeneric(InputFile.fromPath(file), Map.class, new CoroutineCallback<>((result, error) -> { + if (error != null) { + profile.completeExceptionally(error); + } else { + profile.complete(result); + } + })); + if (returned != IntrinsicsKt.getCOROUTINE_SUSPENDED()) { + profile.complete((Profile) returned); + } + } catch (AppwriteException e) { + profile.completeExceptionally(e); + } + Map data = (Map) profile.get(30, TimeUnit.SECONDS).getPrefs().getData(); + lines.add((String) data.get("result")); + + return lines; + } +} diff --git a/tests/e2e/languages/kotlin/Tests.kt b/tests/e2e/languages/kotlin/Tests.kt index b3a84e7367..c16b4e7759 100644 --- a/tests/e2e/languages/kotlin/Tests.kt +++ b/tests/e2e/languages/kotlin/Tests.kt @@ -361,6 +361,8 @@ class ServiceTest { writeToFile(ID.unique()) writeToFile(ID.custom("custom_id")) + JavaInterop.run(general, "../../../resources/file.png").forEach { writeToFile(it) } + // Topic helper tests writeToFile(Topic.path(listOf("user", "123", "notification")).toString()) writeToFile(Topic.path(listOf("org", "42", "user", "123")).path(listOf("notification")).toString()) diff --git a/tests/e2e/languages/kotlin/java-examples.php b/tests/e2e/languages/kotlin/java-examples.php new file mode 100644 index 0000000000..ce07c5fb27 --- /dev/null +++ b/tests/e2e/languages/kotlin/java-examples.php @@ -0,0 +1,58 @@ +getExtension() === 'md' && !in_array(substr((string) $file->getPathname(), strlen($source) + 1), UNSUPPORTED, true)) { + $paths[] = $file->getPathname(); + } +} +sort($paths); + +if ($paths === []) { + fwrite(STDERR, "No Java examples found in {$source}\n"); + exit(1); +} + +if (!is_dir($destination) && !mkdir($destination, 0777, true)) { + exit(1); +} + +foreach ($paths as $index => $path) { + if (!preg_match('/```java\n(.*?)```/s', (string) file_get_contents($path), $match)) { + fwrite(STDERR, "No Java block in {$path}\n"); + exit(1); + } + + $imports = []; + $statements = []; + foreach (explode("\n", $match[1]) as $line) { + if (str_starts_with($line, 'import ')) { + $imports[] = $line; + } else { + $statements[] = ' ' . $line; + } + } + + $class = 'Example' . $index; + file_put_contents( + "{$destination}/{$class}.java", + "package examples;\n\n" + . implode("\n", $imports) + . "\n\npublic class {$class} {\n public void run({$parameters}) {\n" + . implode("\n", $statements) + . "\n }\n}\n" + ); +} From 4da89ce2037bc882ad0c624b1f7453c522f48e6a Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 17 Oct 2026 10:32:41 +0100 Subject: [PATCH 2/2] test(kotlin,android): call the Query, Operator and Channel helpers from Java The Java interop test only reached ID, Permission and Role, so losing @JvmStatic or @JvmOverloads on the other companion helpers would go unnoticed. --- tests/e2e/Android16Java17Test.php | 2 ++ tests/e2e/Android5Java17Test.php | 2 ++ tests/e2e/Base.php | 9 +++++++++ tests/e2e/languages/android/ChannelInterop.java | 13 +++++++++++++ tests/e2e/languages/android/Tests.kt | 1 + tests/e2e/languages/kotlin/JavaInterop.java | 4 ++++ 6 files changed, 31 insertions(+) create mode 100644 tests/e2e/languages/android/ChannelInterop.java diff --git a/tests/e2e/Android16Java17Test.php b/tests/e2e/Android16Java17Test.php index 0bbc23979f..5e9cd3ee12 100644 --- a/tests/e2e/Android16Java17Test.php +++ b/tests/e2e/Android16Java17Test.php @@ -28,6 +28,7 @@ final class Android16Java17Test extends Base 'cp tests/e2e/languages/android/Tests.kt tests/e2e/sdks/android/library/src/test/java/Tests.kt', 'mkdir -p tests/e2e/sdks/android/library/src/test/java/io/appwrite', 'cp tests/e2e/languages/kotlin/JavaInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/JavaInterop.java', + 'cp tests/e2e/languages/android/ChannelInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/ChannelInterop.java', 'php tests/e2e/languages/kotlin/java-examples.php tests/e2e/sdks/android/docs/examples/java tests/e2e/sdks/android/library/src/test/java/examples "android.content.Context context, androidx.activity.ComponentActivity activity"', 'cp tests/e2e/languages/android/r8-rules.pro tests/e2e/sdks/android/library/r8-rules.pro', 'cat tests/e2e/languages/android/r8.gradle.kts >> tests/e2e/sdks/android/library/build.gradle.kts', @@ -67,6 +68,7 @@ final class Android16Java17Test extends Base ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, ...Base::JAVA_INTEROP_RESPONSES, + ...Base::ANDROID_JAVA_INTEROP_RESPONSES, ...Base::TOPIC_HELPER_RESPONSES, ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES, diff --git a/tests/e2e/Android5Java17Test.php b/tests/e2e/Android5Java17Test.php index dfffbf05cc..fc5f6081d2 100644 --- a/tests/e2e/Android5Java17Test.php +++ b/tests/e2e/Android5Java17Test.php @@ -28,6 +28,7 @@ final class Android5Java17Test extends Base 'cp tests/e2e/languages/android/Tests.kt tests/e2e/sdks/android/library/src/test/java/Tests.kt', 'mkdir -p tests/e2e/sdks/android/library/src/test/java/io/appwrite', 'cp tests/e2e/languages/kotlin/JavaInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/JavaInterop.java', + 'cp tests/e2e/languages/android/ChannelInterop.java tests/e2e/sdks/android/library/src/test/java/io/appwrite/ChannelInterop.java', 'php tests/e2e/languages/kotlin/java-examples.php tests/e2e/sdks/android/docs/examples/java tests/e2e/sdks/android/library/src/test/java/examples "android.content.Context context, androidx.activity.ComponentActivity activity"', 'chmod +x tests/e2e/sdks/android/gradlew', ]; @@ -64,6 +65,7 @@ final class Android5Java17Test extends Base ...Base::PERMISSION_HELPER_RESPONSES, ...Base::ID_HELPER_RESPONSES, ...Base::JAVA_INTEROP_RESPONSES, + ...Base::ANDROID_JAVA_INTEROP_RESPONSES, ...Base::TOPIC_HELPER_RESPONSES, ...Base::CHANNEL_HELPER_RESPONSES, ...Base::OPERATOR_HELPER_RESPONSES, diff --git a/tests/e2e/Base.php b/tests/e2e/Base.php index 1295c749da..c8111eb4ee 100644 --- a/tests/e2e/Base.php +++ b/tests/e2e/Base.php @@ -517,9 +517,18 @@ abstract class Base extends TestCase 'custom_id' ]; + protected const ANDROID_JAVA_INTEROP_RESPONSES = [ + 'databases.db1', + 'account', + ]; + protected const JAVA_INTEROP_RESPONSES = [ 'unique()', 'read("any")', + '{"method":"limit","values":[1]}', + '{"method":"count","attribute":"*"}', + '{"method":"increment","values":[1]}', + '{"method":"increment","values":[5]}', 'Missing required parameter: "id"', 'PUT:/v1/mock/tests/general/upload-generic:passed', ]; diff --git a/tests/e2e/languages/android/ChannelInterop.java b/tests/e2e/languages/android/ChannelInterop.java new file mode 100644 index 0000000000..abe1b46de8 --- /dev/null +++ b/tests/e2e/languages/android/ChannelInterop.java @@ -0,0 +1,13 @@ +package io.appwrite; + +import java.util.Arrays; +import java.util.List; + +public final class ChannelInterop { + private ChannelInterop() { + } + + public static List run() { + return Arrays.asList(Channel.database("db1").toString(), Channel.account()); + } +} diff --git a/tests/e2e/languages/android/Tests.kt b/tests/e2e/languages/android/Tests.kt index 9e5cb300af..6e9172f430 100644 --- a/tests/e2e/languages/android/Tests.kt +++ b/tests/e2e/languages/android/Tests.kt @@ -517,6 +517,7 @@ class ServiceTest { writeToFile(ID.custom("custom_id")) JavaInterop.run(general, "../../../../resources/file.png").forEach { writeToFile(it) } + ChannelInterop.run().forEach { writeToFile(it) } // Topic helper tests writeToFile(Topic.path(listOf("user", "123", "notification")).toString()) diff --git a/tests/e2e/languages/kotlin/JavaInterop.java b/tests/e2e/languages/kotlin/JavaInterop.java index 11e5e0db22..16822b2091 100644 --- a/tests/e2e/languages/kotlin/JavaInterop.java +++ b/tests/e2e/languages/kotlin/JavaInterop.java @@ -21,6 +21,10 @@ public static List run(General general, String file) throws Exception { lines.add(ID.unique()); lines.add(Permission.read(Role.any())); + lines.add(Query.limit(1)); + lines.add(Query.count()); + lines.add(Operator.increment()); + lines.add(Operator.increment(5)); try { general.validatePath("0", "", new CoroutineCallback<>((result, error) -> {