diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt index 0a67271b3a12..eee978ce4fea 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/internal/util/conversions.kt @@ -158,15 +158,16 @@ internal fun FunctionDeclaration.toInternal() = name, description, Schema( - properties = getParameters().associate { it.name to it.toInternal() }, - required = getParameters().map { it.name }, + properties = parameters.mapValues { it.value.toInternal() }, + required = parameters.keys.minus(optionalParameters.toSet()).toList(), type = "OBJECT", + nullable = false, ), ) -internal fun com.google.firebase.vertexai.type.Schema.toInternal(): Schema = +internal fun com.google.firebase.vertexai.type.Schema.toInternal(): Schema = Schema( - type.name, + type, description, format, nullable, diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt new file mode 100644 index 000000000000..e8949a4a6ba1 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclaration.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * http://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.google.firebase.vertexai.type + +/** + * A declared function that a model can be given access to in order to gain info or complete tasks. + * + * ``` + * val getExchangeRate = FunctionDeclaration( + * name = "getExchangeRate", + * description = "Get the exchange rate for currencies between countries.", + * parameters = mapOf( + * "currencyFrom" to Schema.str("The currency to convert from."), + * "currencyTo" to Schema.str("The currency to convert to.") + * ) + * ) + * ``` + * + * @param name The name of the function call, this should be clear and descriptive for the model. + * @param description A description of what the function does and its output. + * @param parameters A list of parameters that the function accepts. + * @param optionalParameters A list of parameters that can be omitted. + * @see Schema + */ +class FunctionDeclaration( + val name: String, + val description: String, + val parameters: Map, + val optionalParameters: List = emptyList(), +) diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclarations.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclarations.kt deleted file mode 100644 index 3d836bad27b6..000000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionDeclarations.kt +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * 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 - * - * http://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.google.firebase.vertexai.type - -import org.json.JSONObject - -/** - * A declared zero param function, including implementation, that a model can be given access to in - * order to gain info or complete tasks. - * - * @see [defineFunction] for how to create an instance of this class. - */ -class NoParameterFunction -internal constructor(name: String, description: String, val function: suspend () -> JSONObject) : - FunctionDeclaration(name, description) { - override fun getParameters() = listOf>() - - suspend fun execute() = function() - - override suspend fun execute(part: FunctionCallPart) = function() -} - -/** - * A declared one param function, including implementation, that a model can be given access to in - * order to gain info or complete tasks. - * - * @see [defineFunction] for how to create an instance of this class. - */ -class OneParameterFunction -internal constructor( - name: String, - description: String, - val param: Schema, - val function: suspend (T) -> JSONObject, -) : FunctionDeclaration(name, description) { - override fun getParameters() = listOf(param) - - override suspend fun execute(part: FunctionCallPart): JSONObject { - val arg1 = part.getArgOrThrow(param) - return function(arg1) - } -} - -/** - * A declared two param function, including implementation, that a model can be given access to in - * order to gain info or complete tasks. - * - * @see [defineFunction] for how to create an instance of this class. - */ -class TwoParameterFunction -internal constructor( - name: String, - description: String, - val param1: Schema, - val param2: Schema, - val function: suspend (T, U) -> JSONObject, -) : FunctionDeclaration(name, description) { - override fun getParameters() = listOf(param1, param2) - - override suspend fun execute(part: FunctionCallPart): JSONObject { - val arg1 = part.getArgOrThrow(param1) - val arg2 = part.getArgOrThrow(param2) - return function(arg1, arg2) - } -} - -/** - * A declared three param function, including implementation, that a model can be given access to in - * order to gain info or complete tasks. - * - * @see [defineFunction] for how to create an instance of this class. - */ -class ThreeParameterFunction -internal constructor( - name: String, - description: String, - val param1: Schema, - val param2: Schema, - val param3: Schema, - val function: suspend (T, U, V) -> JSONObject, -) : FunctionDeclaration(name, description) { - override fun getParameters() = listOf(param1, param2, param3) - - override suspend fun execute(part: FunctionCallPart): JSONObject { - val arg1 = part.getArgOrThrow(param1) - val arg2 = part.getArgOrThrow(param2) - val arg3 = part.getArgOrThrow(param3) - return function(arg1, arg2, arg3) - } -} - -/** - * A declared four param function, including implementation, that a model can be given access to in - * order to gain info or complete tasks. - * - * @see [defineFunction] for how to create an instance of this class. - */ -class FourParameterFunction -internal constructor( - name: String, - description: String, - val param1: Schema, - val param2: Schema, - val param3: Schema, - val param4: Schema, - val function: suspend (T, U, V, W) -> JSONObject, -) : FunctionDeclaration(name, description) { - override fun getParameters() = listOf(param1, param2, param3, param4) - - override suspend fun execute(part: FunctionCallPart): JSONObject { - val arg1 = part.getArgOrThrow(param1) - val arg2 = part.getArgOrThrow(param2) - val arg3 = part.getArgOrThrow(param3) - val arg4 = part.getArgOrThrow(param4) - return function(arg1, arg2, arg3, arg4) - } -} - -/** - * A declared function, including implementation, that a model can be given access to in order to - * gain info or complete tasks. - * - * @see [OneParameterFunction] - * @see [TwoParameterFunction] - * @see [ThreeParameterFunction] - * @see [FourParameterFunction] - */ -abstract class FunctionDeclaration(val name: String, val description: String) { - - /** The parameters of the attached function as a list of [Schema]. */ - abstract fun getParameters(): List> - - /** Run the attached function with the provided [arguments][part]. */ - abstract suspend fun execute(part: FunctionCallPart): JSONObject -} - -/** - * Represents a parameter for a declared function - * - * @property name: The name of the parameter - * @property description: The description of what the parameter should contain or represent - * @property format: format information for the parameter, this can include bitlength in the case of - * int/float or keywords like "enum" for the string type - * @property enum: contains the enum values for a string enum - * @property type: contains the type info and parser - * @property properties: if type is OBJECT, then this contains the description of the fields of the - * object by name - * @property required: if type is OBJECT, then this contains the list of required keys - * @property items: if the type is ARRAY, then this contains a description of the objects in the - * array - */ -class Schema( - val name: String, - val description: String, - val format: String? = null, - val nullable: Boolean? = null, - val enum: List? = null, - val properties: Map>? = null, - val required: List? = null, - val items: Schema? = null, - val type: FunctionType, -) { - /** - * Parses an instance of this [Schema] from the provided [String]. - * - * This is done via the [parse][FunctionType.parse] method of [type]. - */ - fun fromString(value: String?) = type.parse(value) - - companion object { - /** Registers a schema for a 32-bit integer number */ - fun int(name: String, description: String) = - Schema( - name = name, - description = description, - format = "int32", - type = FunctionType.INTEGER, - nullable = false, - ) - - /** Registers a schema for a 64-bit integer number */ - fun long(name: String, description: String) = - Schema( - name = name, - description = description, - type = FunctionType.LONG, - nullable = false, - ) - - /** Registers a schema for a string */ - fun str(name: String, description: String) = - Schema( - name = name, - description = description, - type = FunctionType.STRING, - nullable = false, - ) - - /** Registers a schema for a boolean */ - fun bool(name: String, description: String) = - Schema( - name = name, - description = description, - type = FunctionType.BOOLEAN, - nullable = false, - ) - - /** Registers a schema for a floating point number */ - @Deprecated( - message = "Use `double` instead.", - replaceWith = ReplaceWith("double(name, description)"), - ) - fun num(name: String, description: String) = - Schema( - name = name, - description = description, - type = FunctionType.NUMBER, - nullable = false, - ) - - /** Registers a schema for a floating point number */ - fun double(name: String, description: String) = - Schema( - name = name, - description = description, - type = FunctionType.NUMBER, - nullable = false, - ) - - /** - * Registers a schema for a complex object. In a function it will be returned as a [JSONObject] - */ - fun obj(name: String, description: String, vararg contents: Schema) = - Schema( - name = name, - description = description, - type = FunctionType.OBJECT, - required = contents.map { it.name }, - properties = contents.associateBy { it.name }.toMap(), - ) - - /** - * Registers a schema for an array. - * - * @param items can be used to specify the type of the array - */ - fun arr(name: String, description: String, items: Schema? = null) = - Schema>( - name = name, - description = description, - type = FunctionType.ARRAY, - items = items, - nullable = false, - ) - - /** Registers a schema for an enum */ - fun enum(name: String, description: String, values: List) = - Schema( - name = name, - description = description, - format = "enum", - enum = values, - type = FunctionType.STRING, - nullable = false, - ) - } -} - -/** - * Defines a function with zero parameters, including its implementation, that a model can be given - * access to in order to gain info or complete tasks. - * - * @param name The name of the function call, this should be clear and descriptive for the model - * @param description A description of what the function does and its output. - * @param function the function implementation - */ -fun defineFunction(name: String, description: String, function: suspend () -> JSONObject) = - NoParameterFunction(name, description, function) - -/** - * Defines a function with one parameter, including its implementation, that a model can be given - * access to in order to gain info or complete tasks. - * - * @param name The name of the function call, this should be clear and descriptive for the model - * @param description A description of what the function does and its output. - * @param arg1 A description of the first function parameter - * @param function the function implementation - */ -fun defineFunction( - name: String, - description: String, - arg1: Schema, - function: suspend (T) -> JSONObject, -) = OneParameterFunction(name, description, arg1, function) - -/** - * Defines a function with two parameters, including its implementation, that a model can be given - * access to in order to gain info or complete tasks. - * - * @param name The name of the function call, this should be clear and descriptive for the model - * @param description A description of what the function does and its output. - * @param arg1 A description of the first function parameter - * @param arg2 A description of the second function parameter - * @param function the function implementation - */ -fun defineFunction( - name: String, - description: String, - arg1: Schema, - arg2: Schema, - function: suspend (T, U) -> JSONObject, -) = TwoParameterFunction(name, description, arg1, arg2, function) - -/** - * Defines a function with three parameters, including its implementation, that a model can be given - * access to in order to gain info or complete tasks. - * - * @param name The name of the function call, this should be clear and descriptive for the model - * @param description A description of what the function does and its output. - * @param arg1 A description of the first function parameter - * @param arg2 A description of the second function parameter - * @param arg3 A description of the third function parameter - * @param function the function implementation - */ -fun defineFunction( - name: String, - description: String, - arg1: Schema, - arg2: Schema, - arg3: Schema, - function: suspend (T, U, W) -> JSONObject, -) = ThreeParameterFunction(name, description, arg1, arg2, arg3, function) - -/** - * Defines a function with four parameters, including its implementation, that a model can be given - * access to in order to gain info or complete tasks. - * - * @param name The name of the function call, this should be clear and descriptive for the model - * @param description A description of what the function does and its output. - * @param arg1 A description of the first function parameter - * @param arg2 A description of the second function parameter - * @param arg3 A description of the third function parameter - * @param arg4 A description of the fourth function parameter - * @param function the function implementation - */ -fun defineFunction( - name: String, - description: String, - arg1: Schema, - arg2: Schema, - arg3: Schema, - arg4: Schema, - function: suspend (T, U, W, Z) -> JSONObject, -) = FourParameterFunction(name, description, arg1, arg2, arg3, arg4, function) - -private fun FunctionCallPart.getArgOrThrow(param: Schema): T { - return param.fromString(args[param.name]) - ?: throw RuntimeException( - "Missing argument for parameter \"${param.name}\" for function \"$name\"" - ) -} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionParameter.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionParameter.kt deleted file mode 100644 index 08be1d9583c0..000000000000 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/FunctionParameter.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * 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 - * - * http://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.google.firebase.vertexai.type - -class FunctionParameter(val name: String, val description: String, val type: FunctionType) {} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt index 24c64998fa52..f582b645c8a4 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/GenerationConfig.kt @@ -78,7 +78,7 @@ private constructor( val maxOutputTokens: Int?, val stopSequences: List?, val responseMimeType: String?, - val responseSchema: Schema<*>? = null, + val responseSchema: Schema? = null, ) { /** @@ -112,7 +112,7 @@ private constructor( @JvmField var maxOutputTokens: Int? = null @JvmField var stopSequences: List? = null @JvmField var responseMimeType: String? = null - @JvmField var responseSchema: Schema<*>? = null + @JvmField var responseSchema: Schema? = null /** Create a new [GenerationConfig] with the attached arguments. */ fun build() = diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt new file mode 100644 index 000000000000..4a3383e5c794 --- /dev/null +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Schema.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * http://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.google.firebase.vertexai.type + +sealed class StringFormat(val value: String) { + class Custom(format: String) : StringFormat(format) +} + +/** Represents a schema */ +class Schema +internal constructor( + val type: String, + val description: String? = null, + val format: String? = null, + val nullable: Boolean? = null, + val enum: List? = null, + val properties: Map? = null, + val required: List? = null, + val items: Schema? = null, +) { + + companion object { + /** Returns a schema for a boolean */ + @JvmStatic + fun boolean(description: String? = null, nullable: Boolean = false) = + Schema( + description = description, + nullable = nullable, + type = "BOOLEAN", + ) + + /** + * Returns a schema for a 32-bit integer number + * + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + @JvmName("numInt") + fun integer(description: String? = null, nullable: Boolean = false) = + Schema( + description = description, + format = "int32", + nullable = nullable, + type = "INTEGER", + ) + + /** + * Returns a schema for a 64-bit integer number + * + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + @JvmName("numLong") + fun long(description: String? = null, nullable: Boolean = false) = + Schema( + description = description, + nullable = nullable, + type = "INTEGER", + ) + + /** + * Returns a schema for a floating point number + * + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + @JvmName("numDouble") + fun double(description: String? = null, nullable: Boolean = false) = + Schema(description = description, nullable = nullable, type = "NUMBER", format = "double") + + /** + * Returns a schema for a floating point number + * + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + @JvmName("numFloat") + fun float(description: String? = null, nullable: Boolean = false) = + Schema(description = description, nullable = nullable, type = "NUMBER", format = "float") + + /** + * Returns a schema for a string + * + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + * @param format: The pattern that values need to adhere to + */ + @JvmStatic + @JvmName("str") + fun string( + description: String? = null, + nullable: Boolean = false, + format: StringFormat? = null + ) = + Schema( + description = description, + format = format?.value, + nullable = nullable, + type = "STRING" + ) + + /** + * Returns a schema for a complex object. In a function, it will be returned as a [JSONObject]. + * + * @param properties: The map of the object's fields to their schema + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + fun obj( + properties: Map, + optionalProperties: List = emptyList(), + description: String? = null, + nullable: Boolean = false, + ) = + Schema( + description = description, + nullable = nullable, + properties = properties, + required = properties.keys.minus(optionalProperties.toSet()).toList(), + type = "OBJECT", + ) + + /** + * Returns a schema for an array. + * + * @param items: The schema of the elements of this array + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + fun array(items: Schema, description: String? = null, nullable: Boolean = false) = + Schema( + description = description, + nullable = nullable, + items = items, + type = "ARRAY", + ) + + /** + * Returns a schema for an enumeration + * + * @param values: The list of valid values for this enumeration + * @param description: The description of what the parameter should contain or represent + * @param nullable: Whether null is a valid value for this schema + */ + @JvmStatic + fun enumeration(values: List, description: String? = null, nullable: Boolean = false) = + Schema( + description = description, + format = "enum", + nullable = nullable, + enum = values, + type = "STRING", + ) + } +} diff --git a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt index 0a5afc09bdc8..ff33240aa24b 100644 --- a/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt +++ b/firebase-vertexai/src/main/kotlin/com/google/firebase/vertexai/type/Type.kt @@ -15,29 +15,3 @@ */ package com.google.firebase.vertexai.type - -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import org.json.JSONObject - -/** - * Represents and passes the type information for an automated function call. - * - * @property name: the enum name of the type - * @property parse: the deserialization function - * @property T: the type of the object that this maps to in code. - */ -class FunctionType(val name: String, val parse: (String?) -> T?) { - companion object { - val STRING = FunctionType("STRING") { it } - val INTEGER = FunctionType("INTEGER") { it?.toIntOrNull() } - val LONG = FunctionType("INTEGER") { it?.toLongOrNull() } - val NUMBER = FunctionType("NUMBER") { it?.toDoubleOrNull() } - val BOOLEAN = FunctionType("BOOLEAN") { it?.toBoolean() } - val ARRAY = - FunctionType>("ARRAY") { it -> - it?.let { Json.parseToJsonElement(it).jsonArray.map { element -> element.toString() } } - } - val OBJECT = FunctionType("OBJECT") { it?.let { JSONObject(it) } } - } -} diff --git a/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt new file mode 100644 index 000000000000..4de73aebcded --- /dev/null +++ b/firebase-vertexai/src/test/java/com/google/firebase/vertexai/SchemaTests.kt @@ -0,0 +1,222 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * http://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.google.firebase.vertexai + +import com.google.firebase.vertexai.internal.util.toInternal +import com.google.firebase.vertexai.type.Schema +import com.google.firebase.vertexai.type.StringFormat +import io.kotest.assertions.json.shouldEqualJson +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.junit.Test + +internal class SchemaTests { + @Test + fun `basic schema declaration`() { + val schemaDeclaration = + Schema.array( + Schema.obj( + mapOf( + "name" to Schema.string(), + "country" to Schema.string(), + "population" to Schema.integer(), + "coordinates" to + Schema.obj( + mapOf( + "latitude" to Schema.double(), + "longitude" to Schema.double(), + ) + ), + "hemisphere" to + Schema.obj( + mapOf( + "latitudinal" to Schema.enumeration(listOf("N", "S")), + "longitudinal" to Schema.enumeration(listOf("E", "W")), + ) + ), + "elevation" to Schema.double(), + "isCapital" to Schema.boolean(), + "foundingDate" to Schema.string(nullable = true, format = StringFormat.Custom("date")), + ), + optionalProperties = listOf("population") + ) + ) + + val expectedJson = + """ + { + "type": "ARRAY", + "items": { + "type": "OBJECT", + "properties": { + "name": {"type": "STRING"}, + "country": {"type": "STRING"}, + "population": {"type": "INTEGER", "format": "int32"}, + "coordinates": { + "type": "OBJECT", + "properties": { + "latitude": {"type": "NUMBER", "format": "double"}, + "longitude": {"type": "NUMBER","format": "double"} + }, + "required": ["latitude","longitude"] + }, + "hemisphere": { + "type": "OBJECT", + "properties": { + "latitudinal": {"type": "STRING","format": "enum","enum": ["N","S"]}, + "longitudinal": {"type": "STRING","format": "enum","enum": ["E","W"]} + }, + "required": ["latitudinal","longitudinal"] + }, + "elevation": {"type": "NUMBER","format": "double"}, + "isCapital": {"type": "BOOLEAN"}, + "foundingDate": {"type": "STRING","format": "date","nullable": true} + }, + "required": [ + "name","country","coordinates","hemisphere","elevation", + "isCapital","foundingDate"] + } + } + """ + .trimIndent() + + Json.encodeToString(schemaDeclaration.toInternal()).shouldEqualJson(expectedJson) + } + + @Test + fun `full schema declaration`() { + val schemaDeclaration = + Schema.array( + Schema.obj( + description = "generic description", + nullable = true, + properties = + mapOf( + "name" to Schema.string(description = null, nullable = false, format = null), + "country" to + Schema.string( + description = "country name", + nullable = true, + format = StringFormat.Custom("custom format") + ), + "population" to Schema.long(description = "population count", nullable = true), + "coordinates" to + Schema.obj( + description = "coordinates", + nullable = true, + properties = + mapOf( + "latitude" to Schema.double(description = "latitude", nullable = false), + "longitude" to Schema.double(description = "longitude", nullable = false), + ) + ), + "hemisphere" to + Schema.obj( + description = "hemisphere", + nullable = false, + properties = + mapOf( + "latitudinal" to + Schema.enumeration( + listOf("N", "S"), + description = "latitudinal", + nullable = true + ), + "longitudinal" to + Schema.enumeration( + listOf("E", "W"), + description = "longitudinal", + nullable = true + ), + ), + ), + "elevation" to Schema.float(description = "elevation", nullable = false), + "isCapital" to + Schema.boolean( + description = "True if the city is the capital of the country", + nullable = false + ), + "foundingDate" to + Schema.string( + description = "Founding date", + nullable = true, + format = StringFormat.Custom("date") + ), + ) + ) + ) + + val expectedJson = + """ + { + "type": "ARRAY", + "items": { + "type": "OBJECT", + "description": "generic description", + "nullable": true, + "properties": { + "name": {"type": "STRING"}, + "country": {"type": "STRING", "description": "country name", "format": "custom format", "nullable": true}, + "population": {"type": "INTEGER", "description": "population count", "nullable": true}, + "coordinates": { + "type": "OBJECT", + "description": "coordinates", + "nullable": true, + "properties": { + "latitude": {"type": "NUMBER", "description": "latitude", "format": "double"}, + "longitude": {"type": "NUMBER", "description": "longitude", "format": "double"} + }, + "required": ["latitude","longitude"] + }, + "hemisphere": { + "type": "OBJECT", + "description": "hemisphere", + "properties": { + "latitudinal": { + "type": "STRING", + "description": "latitudinal", + "format": "enum", + "nullable": true, + "enum": ["N","S"] + }, + "longitudinal": { + "type": "STRING", + "description": "longitudinal", + "format": "enum", + "nullable": true, + "enum": ["E","W"] + } + }, + "required": ["latitudinal","longitudinal"] + }, + "elevation": {"type": "NUMBER", "description": "elevation", "format": "float"}, + "isCapital": {"type": "BOOLEAN", "description": "True if the city is the capital of the country"}, + "foundingDate": {"type": "STRING", "description": "Founding date", "format": "date", "nullable": true} + }, + "required": [ + "name","country","population","coordinates","hemisphere", + "elevation","isCapital","foundingDate" + ] + } + } + + """ + .trimIndent() + + Json.encodeToString(schemaDeclaration.toInternal()).shouldEqualJson(expectedJson) + } +}