From 281e0b8a920c369ff566935b4767ad888d42c27e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Fri, 20 Feb 2026 17:11:18 -0300 Subject: [PATCH 1/6] Improve error message of `EmbedBlockResult.Failure` We were accidentally converting the whole exception to a string, so the final error was something along the lines of "IllegalArgumentException: ". This commit corrects that. --- kson-lib/src/commonMain/kotlin/org/kson/Kson.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt b/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt index 58f20f13..00d0cfcc 100644 --- a/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt +++ b/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt @@ -216,7 +216,7 @@ class EmbedRule private constructor( return try { EmbedRuleResult.Success(EmbedRule(pathPattern, tag)) } catch (e: IllegalArgumentException) { - EmbedRuleResult.Failure(e.toString()) + EmbedRuleResult.Failure(e.message ?: "") } } } From c0c075ff75551d28d7cc107839881b59ce0c1d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Fri, 20 Feb 2026 17:20:52 -0300 Subject: [PATCH 2/6] Update to latest krossover --- kson-lib/build.gradle.kts | 2 +- lib-python/src/kson/__init__.py | 6 ++- lib-rust/kson/src/generated/mod.rs | 64 +++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/kson-lib/build.gradle.kts b/kson-lib/build.gradle.kts index 26d76bcf..6db44e4b 100644 --- a/kson-lib/build.gradle.kts +++ b/kson-lib/build.gradle.kts @@ -10,7 +10,7 @@ plugins { kotlin("multiplatform") id("com.vanniktech.maven.publish") version "0.30.0" id("org.jetbrains.dokka") version "2.0.0" - id("nl.ochagavia.krossover") version "1.0.5" + id("nl.ochagavia.krossover") version "1.0.6" } repositories { diff --git a/lib-python/src/kson/__init__.py b/lib-python/src/kson/__init__.py index 0f46f3c2..a3c32d29 100644 --- a/lib-python/src/kson/__init__.py +++ b/lib-python/src/kson/__init__.py @@ -2017,9 +2017,9 @@ def from_path_pattern( if path_pattern is None: raise ValueError("`path_pattern` cannot be None") - jni_ref = _access_static_field(b"org/kson/EmbedRule", b"INSTANCE", b"Lorg/kson/EmbedRule;") + jni_ref = _access_static_field(b"org/kson/EmbedRule", b"Companion", b"Lorg/kson/EmbedRule$Companion;") result = _call_method( - b"org/kson/EmbedRule", + b"org/kson/EmbedRule$Companion", jni_ref, b"fromPathPattern", b"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/EmbedRuleResult;", @@ -2117,6 +2117,8 @@ class _IndentType_Tabs(IndentType): _jni_ref: Any + def __init__(self): + self._jni_ref = _access_static_field(b"org/kson/IndentType$Tabs", b"INSTANCE", b"Lorg/kson/IndentType$Tabs;") def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) diff --git a/lib-rust/kson/src/generated/mod.rs b/lib-rust/kson/src/generated/mod.rs index 50c1b7df..dabaec17 100644 --- a/lib-rust/kson/src/generated/mod.rs +++ b/lib-rust/kson/src/generated/mod.rs @@ -266,6 +266,62 @@ impl std::hash::Hash for Position { } + +#[derive(Clone)] +pub struct Companion { + kotlin_ptr: KotlinPtr, +} + +impl FromKotlinObject for Companion { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } +} + +impl ToKotlinObject for Companion { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } +} + +impl AsKotlinObject for Companion { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } +} + +impl Companion { +} + + +impl Companion { +} + +impl std::fmt::Debug for Companion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/EmbedRule$Companion", &obj)) + } +} + +impl Eq for Companion {} +impl PartialEq for Companion { + fn eq(&self, other: &Companion) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } +} +impl std::hash::Hash for Companion { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } +} + + /// Represents a message logged during Kson processing #[derive(Clone)] pub struct Message { @@ -3581,7 +3637,7 @@ impl EmbedRule { path_pattern: &str, tag: Option<&str>, ) -> EmbedRuleResult { - let self_ptr = util::access_static_field(c"org/kson/EmbedRule", c"INSTANCE", c"Lorg/kson/EmbedRule;"); + let self_ptr = util::access_static_field(c"org/kson/EmbedRule", c"Companion", c"Lorg/kson/EmbedRule$Companion;"); let self_obj = self_ptr.as_kotlin_object(); let path_pattern_ptr = path_pattern.to_kotlin_object(); let path_pattern = path_pattern_ptr.as_kotlin_object(); @@ -3591,7 +3647,7 @@ impl EmbedRule { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRule", + c"org/kson/EmbedRule$Companion", c"fromPathPattern", c"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/EmbedRuleResult;", CallObjectMethod, @@ -3758,6 +3814,10 @@ pub mod indent_type { } impl Tabs { + pub fn new() -> Self { + let kotlin_ptr = util::access_static_field(c"org/kson/IndentType$Tabs", c"INSTANCE", c"Lorg/kson/IndentType$Tabs;"); + Self { kotlin_ptr } + } } From f242e2fd14a1abd31eb377c74b53739f6cb9ae1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Thu, 19 Feb 2026 23:34:23 -0300 Subject: [PATCH 3/6] Run `kson-lib` tests against `lib-python` Since the `kson-lib` tests are written in Kotlin, we run them against Python in an indirect way: - We LLM-generated a JSON Schema based on the `kson-lib` API (with minimal manual tweaks). - We LLM-generated a clone of `kson-lib`'s `Kson.kt` that internally sends POST requests to a server based on the JSON Schema (with minimal manual tweaks). We called it `kson-lib-http` and wrote a descriptive readme for it. - We LLM-generated a Python server that receives requests and uses the bindings to handle them (with minimal manual tweaks). - We created a `:lib-python:kson-lib-tests` project responsible for actually running the `kson-lib` tests against the Python server. This setup is a bit convoluted, and I'm not sure I'd choose it for a different project. However, next to it being useful for testing, it doubles as an experiment to see how the KSON API could look like if we were to expose it through a text-based API (currently JSON, in the future... KSON!) --- kson-lib-http/build.gradle.kts | 26 + kson-lib-http/kson-api-schema.json | 595 ++++++++++++++++++ kson-lib-http/readme.md | 24 + .../src/commonMain/kotlin/org/kson/Kson.kt | 432 +++++++++++++ lib-python/kson-lib-tests/build.gradle.kts | 54 ++ .../src/jvmTest/kotlin/ServerExtension.kt | 56 ++ .../org.junit.jupiter.api.extension.Extension | 1 + lib-python/tests/api_server.py | 280 +++++++++ settings.gradle.kts | 2 + 9 files changed, 1470 insertions(+) create mode 100644 kson-lib-http/build.gradle.kts create mode 100644 kson-lib-http/kson-api-schema.json create mode 100644 kson-lib-http/readme.md create mode 100644 kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt create mode 100644 lib-python/kson-lib-tests/build.gradle.kts create mode 100644 lib-python/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt create mode 100644 lib-python/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 lib-python/tests/api_server.py diff --git a/kson-lib-http/build.gradle.kts b/kson-lib-http/build.gradle.kts new file mode 100644 index 00000000..c5e3f5b9 --- /dev/null +++ b/kson-lib-http/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +repositories { + mavenCentral() +} + +val ktorVersion = "3.4.0" + +kotlin { + jvm { + } + + sourceSets { + commonMain { + dependencies { + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") + } + } + } +} + diff --git a/kson-lib-http/kson-api-schema.json b/kson-lib-http/kson-api-schema.json new file mode 100644 index 00000000..02cddad2 --- /dev/null +++ b/kson-lib-http/kson-api-schema.json @@ -0,0 +1,595 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://kson.org/api/schema", + "title": "KSON API Schema", + "description": "Schema for the KSON public API, supporting format, toJson, toYaml, analyze, parseSchema, validate, and validateEmbedRule commands.", + + "$defs": { + "Position": { + "type": "object", + "description": "A zero-based line/column position in a document.", + "properties": { + "line": { + "type": "integer", + "minimum": 0, + "description": "The line number (0-based)" + }, + "column": { + "type": "integer", + "minimum": 0, + "description": "The column number (0-based)" + } + }, + "required": ["line", "column"], + "additionalProperties": false + }, + + "Message": { + "type": "object", + "description": "A message logged during KSON processing.", + "properties": { + "message": { + "type": "string" + }, + "severity": { + "$ref": "#/$defs/MessageSeverity" + }, + "start": { + "$ref": "#/$defs/Position" + }, + "end": { + "$ref": "#/$defs/Position" + } + }, + "required": ["message", "severity", "start", "end"], + "additionalProperties": false + }, + + "MessageSeverity": { + "type": "string", + "enum": ["ERROR", "WARNING"] + }, + + "IndentType": { + "oneOf": [ + { + "type": "object", + "description": "Use spaces for indentation.", + "properties": { + "type": { "const": "spaces" }, + "size": { + "type": "integer", + "minimum": 1, + "default": 2, + "description": "Number of spaces per indent level" + } + }, + "required": ["type"], + "additionalProperties": false + }, + { + "type": "object", + "description": "Use tabs for indentation.", + "properties": { + "type": { "const": "tabs" } + }, + "required": ["type"], + "additionalProperties": false + } + ] + }, + + "FormattingStyle": { + "type": "string", + "enum": ["PLAIN", "DELIMITED", "COMPACT", "CLASSIC"] + }, + + "EmbedRule": { + "type": "object", + "description": "A rule for formatting string values at specific paths as embed blocks.", + "properties": { + "pathPattern": { + "type": "string", + "description": "A JsonPointerGlob pattern (e.g., \"/scripts/*\", \"/queries/**\")" + }, + "tag": { + "type": ["string", "null"], + "description": "Optional embed tag (e.g., \"yaml\", \"sql\", \"bash\")" + } + }, + "required": ["pathPattern"], + "additionalProperties": false + }, + + "FormatOptions": { + "type": "object", + "description": "Options for formatting KSON output.", + "properties": { + "indentType": { + "$ref": "#/$defs/IndentType" + }, + "formattingStyle": { + "$ref": "#/$defs/FormattingStyle" + }, + "embedBlockRules": { + "type": "array", + "items": { "$ref": "#/$defs/EmbedRule" } + } + }, + "additionalProperties": false + }, + + "TokenType": { + "type": "string", + "enum": [ + "CURLY_BRACE_L", + "CURLY_BRACE_R", + "SQUARE_BRACKET_L", + "SQUARE_BRACKET_R", + "ANGLE_BRACKET_L", + "ANGLE_BRACKET_R", + "COLON", + "DOT", + "END_DASH", + "COMMA", + "COMMENT", + "EMBED_OPEN_DELIM", + "EMBED_CLOSE_DELIM", + "EMBED_TAG", + "EMBED_PREAMBLE_NEWLINE", + "EMBED_CONTENT", + "FALSE", + "UNQUOTED_STRING", + "ILLEGAL_CHAR", + "LIST_DASH", + "NULL", + "NUMBER", + "STRING_OPEN_QUOTE", + "STRING_CLOSE_QUOTE", + "STRING_CONTENT", + "TRUE", + "WHITESPACE", + "EOF" + ] + }, + + "Token": { + "type": "object", + "description": "A token produced by the lexing phase.", + "properties": { + "tokenType": { "$ref": "#/$defs/TokenType" }, + "text": { "type": "string" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["tokenType", "text", "start", "end"], + "additionalProperties": false + }, + + "KsonValue": { + "oneOf": [ + { "$ref": "#/$defs/KsonObject" }, + { "$ref": "#/$defs/KsonArray" }, + { "$ref": "#/$defs/KsonString" }, + { "$ref": "#/$defs/KsonInteger" }, + { "$ref": "#/$defs/KsonDecimal" }, + { "$ref": "#/$defs/KsonBoolean" }, + { "$ref": "#/$defs/KsonNull" }, + { "$ref": "#/$defs/KsonEmbed" } + ] + }, + + "KsonObject": { + "type": "object", + "properties": { + "type": { "const": "OBJECT" }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#/$defs/KsonValue" }, + "description": "Key-value pairs of the object" + }, + "propertyKeys": { + "type": "object", + "additionalProperties": { "$ref": "#/$defs/KsonString" }, + "description": "Property key metadata (including positions)" + }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "properties", "propertyKeys", "start", "end"], + "additionalProperties": false + }, + + "KsonArray": { + "type": "object", + "properties": { + "type": { "const": "ARRAY" }, + "elements": { + "type": "array", + "items": { "$ref": "#/$defs/KsonValue" } + }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "elements", "start", "end"], + "additionalProperties": false + }, + + "KsonString": { + "type": "object", + "properties": { + "type": { "const": "STRING" }, + "value": { "type": "string" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "value", "start", "end"], + "additionalProperties": false + }, + + "KsonInteger": { + "type": "object", + "properties": { + "type": { "const": "INTEGER" }, + "value": { "type": "integer" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "value", "start", "end"], + "additionalProperties": false + }, + + "KsonDecimal": { + "type": "object", + "properties": { + "type": { "const": "DECIMAL" }, + "value": { "type": "number" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "value", "start", "end"], + "additionalProperties": false + }, + + "KsonBoolean": { + "type": "object", + "properties": { + "type": { "const": "BOOLEAN" }, + "value": { "type": "boolean" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "value", "start", "end"], + "additionalProperties": false + }, + + "KsonNull": { + "type": "object", + "properties": { + "type": { "const": "NULL" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "start", "end"], + "additionalProperties": false + }, + + "KsonEmbed": { + "type": "object", + "properties": { + "type": { "const": "EMBED" }, + "tag": { + "type": ["string", "null"], + "description": "Optional embed tag" + }, + "content": { "type": "string" }, + "start": { "$ref": "#/$defs/Position" }, + "end": { "$ref": "#/$defs/Position" } + }, + "required": ["type", "content", "start", "end"], + "additionalProperties": false + }, + + "FormatCommand": { + "type": "object", + "description": "Formats KSON source with the specified formatting options.", + "properties": { + "command": { "const": "format" }, + "kson": { + "type": "string", + "description": "The KSON source to format" + }, + "formatOptions": { + "$ref": "#/$defs/FormatOptions" + } + }, + "required": ["command", "kson"], + "additionalProperties": false + }, + + "ToJsonCommand": { + "type": "object", + "description": "Converts KSON to JSON.", + "properties": { + "command": { "const": "toJson" }, + "kson": { + "type": "string", + "description": "The KSON source to convert" + }, + "retainEmbedTags": { + "type": "boolean", + "default": true, + "description": "Whether to retain embed tags in the JSON output" + } + }, + "required": ["command", "kson"], + "additionalProperties": false + }, + + "ToYamlCommand": { + "type": "object", + "description": "Converts KSON to YAML, preserving comments.", + "properties": { + "command": { "const": "toYaml" }, + "kson": { + "type": "string", + "description": "The KSON source to convert" + }, + "retainEmbedTags": { + "type": "boolean", + "default": true, + "description": "Whether to retain embed tags in the YAML output" + } + }, + "required": ["command", "kson"], + "additionalProperties": false + }, + + "AnalyzeCommand": { + "type": "object", + "description": "Statically analyze KSON and return messages, tokens, and parsed value.", + "properties": { + "command": { "const": "analyze" }, + "kson": { + "type": "string", + "description": "The KSON source to analyze" + }, + "filepath": { + "type": ["string", "null"], + "description": "Optional filepath of the document being analyzed" + } + }, + "required": ["command", "kson"], + "additionalProperties": false + }, + + "ParseSchemaCommand": { + "type": "object", + "description": "Parses a KSON schema definition.", + "properties": { + "command": { "const": "parseSchema" }, + "schemaKson": { + "type": "string", + "description": "The KSON source defining a JSON Schema" + } + }, + "required": ["command", "schemaKson"], + "additionalProperties": false + }, + + "ValidateCommand": { + "type": "object", + "description": "Parses a schema and validates a KSON document against it in a single step.", + "properties": { + "command": { "const": "validate" }, + "schemaKson": { + "type": "string", + "description": "The KSON source defining a JSON Schema" + }, + "kson": { + "type": "string", + "description": "The KSON source to validate against the schema" + }, + "filepath": { + "type": ["string", "null"], + "description": "Optional filepath of the document being validated" + } + }, + "required": ["command", "schemaKson", "kson"], + "additionalProperties": false + }, + + "ValidateEmbedRuleCommand": { + "type": "object", + "description": "Validates that the given EmbedRule has a valid JsonPointerGlob pathPattern.", + "properties": { + "command": { "const": "validateEmbedRule" }, + "embedBlockRule": { + "$ref": "#/$defs/EmbedRule", + "description": "The embed rule to validate" + } + }, + "required": ["command", "embedBlockRule"], + "additionalProperties": false + }, + + "FormatResult": { + "type": "object", + "description": "Result of the format command.", + "properties": { + "command": { "const": "format" }, + "success": { "const": true }, + "output": { + "type": "string", + "description": "The formatted KSON source" + } + }, + "required": ["command", "success", "output"], + "additionalProperties": false + }, + + "TranspileSuccessResult": { + "type": "object", + "properties": { + "command": { + "type": "string", + "enum": ["toJson", "toYaml"] + }, + "success": { "const": true }, + "output": { + "type": "string", + "description": "The transpiled output (JSON or YAML)" + } + }, + "required": ["command", "success", "output"], + "additionalProperties": false + }, + + "TranspileFailureResult": { + "type": "object", + "properties": { + "command": { + "type": "string", + "enum": ["toJson", "toYaml"] + }, + "success": { "const": false }, + "errors": { + "type": "array", + "items": { "$ref": "#/$defs/Message" } + } + }, + "required": ["command", "success", "errors"], + "additionalProperties": false + }, + + "AnalyzeResult": { + "type": "object", + "description": "Result of the analyze command.", + "properties": { + "command": { "const": "analyze" }, + "errors": { + "type": "array", + "items": { "$ref": "#/$defs/Message" } + }, + "tokens": { + "type": "array", + "items": { "$ref": "#/$defs/Token" } + }, + "ksonValue": { + "oneOf": [ + { "$ref": "#/$defs/KsonValue" }, + { "type": "null" } + ] + } + }, + "required": ["command", "errors", "tokens", "ksonValue"], + "additionalProperties": false + }, + + "ParseSchemaSuccessResult": { + "type": "object", + "properties": { + "command": { "const": "parseSchema" }, + "success": { "const": true } + }, + "required": ["command", "success"], + "additionalProperties": false + }, + + "ParseSchemaFailureResult": { + "type": "object", + "properties": { + "command": { "const": "parseSchema" }, + "success": { "const": false }, + "errors": { + "type": "array", + "items": { "$ref": "#/$defs/Message" } + } + }, + "required": ["command", "success", "errors"], + "additionalProperties": false + }, + + "ValidateResult": { + "type": "object", + "description": "Result of the validate command.", + "properties": { + "command": { "const": "validate" }, + "success": { + "type": "boolean", + "description": "True if both schema parsing and validation succeeded with no errors" + }, + "errors": { + "type": "array", + "items": { "$ref": "#/$defs/Message" }, + "description": "Schema parsing errors or validation errors" + } + }, + "required": ["command", "success", "errors"], + "additionalProperties": false + }, + + "EmbedRuleSuccessResult": { + "type": "object", + "description": "Successful result of the validateEmbedRule command.", + "properties": { + "command": { "const": "validateEmbedRule" }, + "success": { "const": true } + }, + "required": ["command", "success"], + "additionalProperties": false + }, + + "EmbedRuleFailureResult": { + "type": "object", + "description": "Failed result of the validateEmbedRule command.", + "properties": { + "command": { "const": "validateEmbedRule" }, + "success": { "const": false }, + "error": { + "type": "string", + "description": "Error message for the invalid pathPattern" + } + }, + "required": ["command", "success", "error"], + "additionalProperties": false + } + }, + + "type": "object", + "properties": { + "request": { + "description": "A KSON API command request.", + "oneOf": [ + { "$ref": "#/$defs/FormatCommand" }, + { "$ref": "#/$defs/ToJsonCommand" }, + { "$ref": "#/$defs/ToYamlCommand" }, + { "$ref": "#/$defs/AnalyzeCommand" }, + { "$ref": "#/$defs/ParseSchemaCommand" }, + { "$ref": "#/$defs/ValidateCommand" }, + { "$ref": "#/$defs/ValidateEmbedRuleCommand" } + ], + "discriminator": { + "propertyName": "command" + } + }, + "response": { + "description": "A KSON API command response.", + "oneOf": [ + { "$ref": "#/$defs/FormatResult" }, + { "$ref": "#/$defs/TranspileSuccessResult" }, + { "$ref": "#/$defs/TranspileFailureResult" }, + { "$ref": "#/$defs/AnalyzeResult" }, + { "$ref": "#/$defs/ParseSchemaSuccessResult" }, + { "$ref": "#/$defs/ParseSchemaFailureResult" }, + { "$ref": "#/$defs/ValidateResult" }, + { "$ref": "#/$defs/EmbedRuleSuccessResult" }, + { "$ref": "#/$defs/EmbedRuleFailureResult" } + ], + "discriminator": { + "propertyName": "command" + } + } + }, + "additionalProperties": false +} diff --git a/kson-lib-http/readme.md b/kson-lib-http/readme.md new file mode 100644 index 00000000..c39a244b --- /dev/null +++ b/kson-lib-http/readme.md @@ -0,0 +1,24 @@ +# kson-lib-http + +A reimplementation of KSON's public API (see [kson-lib](../kson-lib)) based on +HTTP requests to a server that respects the [kson-api-schema](./kson-api-schema.json). +This code is meant to facilitate testing, so users of KSON should ignore it +and rely on `kson-lib` for all their KSON needs. We might implement a +user-facing, HTTP-based KSON API in the future. + +> [!WARNING] +> The JSON Schema is highly experimental, possibly inaccurate in some places, and +> subject to change without notice + +### Testing KSON implementations + +If you have an implementation of the KSON public API that you would like to +test, all you need to do is create an HTTP server that handles KSON requests +according to the [schema](./kson-api-schema.json) and internally dispatches +them to your actual KSON implementation. This sounds complicated, but a good +LLM can easily one-shot server creation based on the schema and your +implementation's source code. + +With that in place, you then need to instruct Gradle to run the `kson-lib` +tests against your server. For reference, see the [test project](../lib-python/kson-lib-tests/) +we use to run the `kson-lib` tests against our Python bindings. diff --git a/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt b/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt new file mode 100644 index 00000000..365b2f44 --- /dev/null +++ b/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt @@ -0,0 +1,432 @@ +package org.kson + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.* + +object Kson { + private val client = HttpClient(CIO) + private val json = Json { ignoreUnknownKeys = true } + private var baseUrl = "http://localhost:8080" + + fun setPort(port: Int) { + baseUrl = "http://localhost:$port" + } + + fun format(kson: String, formatOptions: FormatOptions = FormatOptions()): String { + val request = buildJsonObject { + put("command", "format") + put("kson", kson) + put("formatOptions", formatOptions.toJson()) + } + val response = post(request) + return response["output"]!!.jsonPrimitive.content + } + + + fun toJson(kson: String, options: TranspileOptions.Json = TranspileOptions.Json()): Result { + val request = buildJsonObject { + put("command", "toJson") + put("kson", kson) + put("retainEmbedTags", options.retainEmbedTags) + } + return parseTranspileResult(post(request)) + } + + + fun toYaml(kson: String, options: TranspileOptions.Yaml = TranspileOptions.Yaml()): Result { + val request = buildJsonObject { + put("command", "toYaml") + put("kson", kson) + put("retainEmbedTags", options.retainEmbedTags) + } + return parseTranspileResult(post(request)) + } + + + fun analyze(kson: String, filepath: String? = null): Analysis { + val request = buildJsonObject { + put("command", "analyze") + put("kson", kson) + filepath?.let { put("filepath", it) } + } + val response = post(request) + val errors = parseMessages(response["errors"]!!.jsonArray) + val tokens = parseTokens(response["tokens"]!!.jsonArray) + val ksonValue = response["ksonValue"]?.let { + if (it is JsonNull) null else parseKsonValue(it.jsonObject) + } + return Analysis(errors, tokens, ksonValue) + } + + + fun parseSchema(schemaKson: String): SchemaResult { + val request = buildJsonObject { + put("command", "parseSchema") + put("schemaKson", schemaKson) + } + val response = post(request) + val success = response["success"]!!.jsonPrimitive.boolean + return if (success) { + SchemaResult.Success(SchemaValidator(schemaKson)) + } else { + SchemaResult.Failure(parseMessages(response["errors"]!!.jsonArray)) + } + } + + internal fun post(body: JsonObject): JsonObject = runBlocking { + val response = client.post(baseUrl) { + contentType(ContentType.Application.Json) + setBody(body.toString()) + } + json.parseToJsonElement(response.bodyAsText()).jsonObject + } + + private fun parseTranspileResult(response: JsonObject): Result { + val success = response["success"]!!.jsonPrimitive.boolean + return if (success) { + Result.Success(response["output"]!!.jsonPrimitive.content) + } else { + Result.Failure(parseMessages(response["errors"]!!.jsonArray)) + } + } +} + + +sealed class Result { + class Success(val output: String) : Result() + class Failure(val errors: List) : Result() +} + + +sealed class SchemaResult { + class Success(val schemaValidator: SchemaValidator) : SchemaResult() + class Failure(val errors: List) : SchemaResult() +} + +class SchemaValidator internal constructor(private val schemaKson: String) { + + fun validate(kson: String, filepath: String? = null): List { + val request = buildJsonObject { + put("command", "validate") + put("schemaKson", schemaKson) + put("kson", kson) + filepath?.let { put("filepath", it) } + } + val response = Kson.post(request) + return parseMessages(response["errors"]!!.jsonArray) + } +} + + +class EmbedRule private constructor( + val pathPattern: String, + val tag: String? = null +) { + companion object { + fun fromPathPattern(pathPattern: String, tag: String? = null): EmbedRuleResult { + val request = buildJsonObject { + put("command", "validateEmbedRule") + put("embedBlockRule", buildJsonObject { + put("pathPattern", pathPattern) + tag?.let { put("tag", it) } + }) + } + val response = Kson.post(request) + val success = response["success"]?.jsonPrimitive?.boolean == true + return if (success) { + EmbedRuleResult.Success(EmbedRule(pathPattern, tag)) + } else { + val error = response["error"]!!.jsonPrimitive.content + EmbedRuleResult.Failure(error) + } + } + } +} + +sealed class EmbedRuleResult { + data class Success(val embedRule: EmbedRule) : EmbedRuleResult() + data class Failure(val message: String) : EmbedRuleResult() +} + + +class FormatOptions( + val indentType: IndentType = IndentType.Spaces(2), + val formattingStyle: FormattingStyle = FormattingStyle.PLAIN, + val embedBlockRules: List = emptyList() +) { + internal fun toJson(): JsonObject = buildJsonObject { + put("indentType", when (indentType) { + is IndentType.Spaces -> buildJsonObject { + put("type", "spaces") + put("size", indentType.size) + } + is IndentType.Tabs -> buildJsonObject { + put("type", "tabs") + } + }) + put("formattingStyle", formattingStyle.name) + put("embedBlockRules", buildJsonArray { + for (rule in embedBlockRules) { + add(buildJsonObject { + put("pathPattern", rule.pathPattern) + rule.tag?.let { put("tag", it) } + }) + } + }) + } +} + + +sealed class TranspileOptions { + abstract val retainEmbedTags: Boolean + + + class Json( + override val retainEmbedTags: Boolean = true + ) : TranspileOptions() + + + class Yaml( + override val retainEmbedTags: Boolean = true + ) : TranspileOptions() +} + + +enum class FormattingStyle { + PLAIN, + DELIMITED, + COMPACT, + CLASSIC +} + +sealed class IndentType { + + class Spaces(val size: Int = 2) : IndentType() + + + data object Tabs : IndentType() +} + + +class Analysis internal constructor( + val errors: List, + val tokens: List, + val ksonValue: KsonValue? +) + + +class Token internal constructor( + val tokenType: TokenType, + val text: String, + val start: Position, + val end: Position) + +enum class TokenType { + CURLY_BRACE_L, + CURLY_BRACE_R, + SQUARE_BRACKET_L, + SQUARE_BRACKET_R, + ANGLE_BRACKET_L, + ANGLE_BRACKET_R, + COLON, + DOT, + END_DASH, + COMMA, + COMMENT, + EMBED_OPEN_DELIM, + EMBED_CLOSE_DELIM, + EMBED_TAG, + EMBED_PREAMBLE_NEWLINE, + EMBED_CONTENT, + FALSE, + UNQUOTED_STRING, + ILLEGAL_CHAR, + LIST_DASH, + NULL, + NUMBER, + STRING_OPEN_QUOTE, + STRING_CLOSE_QUOTE, + STRING_CONTENT, + TRUE, + WHITESPACE, + EOF +} + + +class Message internal constructor(val message: String, val severity: MessageSeverity, val start: Position, val end: Position) + + +enum class MessageSeverity { + ERROR, + WARNING, +} + + +class Position internal constructor(val line: Int, val column: Int) + + +enum class KsonValueType { + OBJECT, + ARRAY, + STRING, + INTEGER, + DECIMAL, + BOOLEAN, + NULL, + EMBED +} + +sealed class KsonValue(val start: Position, val end: Position) { + + abstract val type: KsonValueType + + + @ConsistentCopyVisibility + data class KsonObject internal constructor( + val properties: Map, + val propertyKeys: Map, + private val internalStart: Position, + private val internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.OBJECT + } + + + class KsonArray internal constructor( + val elements: List, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.ARRAY + } + + + class KsonString internal constructor( + val value: String, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.STRING + } + + + sealed class KsonNumber(start: Position, end: Position) : KsonValue(start, end) { + + class Integer internal constructor( + val value: Int, + val internalStart: Position, + val internalEnd: Position + ) : KsonNumber(internalStart, internalEnd) { + override val type = KsonValueType.INTEGER + } + + class Decimal internal constructor( + val value: Double, + internalStart: Position, + internalEnd: Position + ) : KsonNumber(internalStart, internalEnd) { + override val type = KsonValueType.DECIMAL + } + } + + + class KsonBoolean internal constructor( + val value: Boolean, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.BOOLEAN + } + + + class KsonNull internal constructor( + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.NULL + } + + + class KsonEmbed internal constructor( + val tag: String?, + val content: String, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.EMBED + } +} + +// --- JSON response parsing helpers --- + +internal fun parsePosition(obj: JsonObject): Position { + return Position( + line = obj["line"]!!.jsonPrimitive.int, + column = obj["column"]!!.jsonPrimitive.int + ) +} + +internal fun parseMessages(array: JsonArray): List { + return array.map { element -> + val obj = element.jsonObject + Message( + message = obj["message"]!!.jsonPrimitive.content, + severity = MessageSeverity.valueOf(obj["severity"]!!.jsonPrimitive.content), + start = parsePosition(obj["start"]!!.jsonObject), + end = parsePosition(obj["end"]!!.jsonObject) + ) + } +} + +internal fun parseTokens(array: JsonArray): List { + return array.map { element -> + val obj = element.jsonObject + Token( + tokenType = TokenType.valueOf(obj["tokenType"]!!.jsonPrimitive.content), + text = obj["text"]!!.jsonPrimitive.content, + start = parsePosition(obj["start"]!!.jsonObject), + end = parsePosition(obj["end"]!!.jsonObject) + ) + } +} + +internal fun parseKsonValue(obj: JsonObject): KsonValue { + val start = parsePosition(obj["start"]!!.jsonObject) + val end = parsePosition(obj["end"]!!.jsonObject) + + return when (val type = obj["type"]!!.jsonPrimitive.content) { + "OBJECT" -> { + val properties = obj["properties"]!!.jsonObject.mapValues { (_, v) -> parseKsonValue(v.jsonObject) } + val propertyKeys = obj["propertyKeys"]!!.jsonObject.mapValues { (_, v) -> + val keyObj = v.jsonObject + KsonValue.KsonString( + value = keyObj["value"]!!.jsonPrimitive.content, + internalStart = parsePosition(keyObj["start"]!!.jsonObject), + internalEnd = parsePosition(keyObj["end"]!!.jsonObject) + ) + } + KsonValue.KsonObject(properties, propertyKeys, start, end) + } + "ARRAY" -> { + val elements = obj["elements"]!!.jsonArray.map { parseKsonValue(it.jsonObject) } + KsonValue.KsonArray(elements, start, end) + } + "STRING" -> KsonValue.KsonString(obj["value"]!!.jsonPrimitive.content, start, end) + "INTEGER" -> KsonValue.KsonNumber.Integer(obj["value"]!!.jsonPrimitive.int, start, end) + "DECIMAL" -> KsonValue.KsonNumber.Decimal(obj["value"]!!.jsonPrimitive.double, start, end) + "BOOLEAN" -> KsonValue.KsonBoolean(obj["value"]!!.jsonPrimitive.boolean, start, end) + "NULL" -> KsonValue.KsonNull(start, end) + "EMBED" -> KsonValue.KsonEmbed( + tag = obj["tag"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }, + content = obj["content"]!!.jsonPrimitive.content, + internalStart = start, + internalEnd = end + ) + else -> throw IllegalArgumentException("Unknown KsonValue type: $type") + } +} diff --git a/lib-python/kson-lib-tests/build.gradle.kts b/lib-python/kson-lib-tests/build.gradle.kts new file mode 100644 index 00000000..c4f82005 --- /dev/null +++ b/lib-python/kson-lib-tests/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() +} + +val syncCommonTestSources by tasks.registering(Sync::class) { + from(project(":kson-lib").file("src/commonTest/kotlin")) + into(layout.buildDirectory.dir("commonTestSources")) +} + +val syncJvmTestSources by tasks.registering(Sync::class) { + from(project(":kson-lib").file("src/jvmTest/kotlin")) + into(layout.buildDirectory.dir("jvmTestSources")) +} + +tasks.withType { + dependsOn(":lib-python:build") + + systemProperty("libPythonDir", project(":lib-python").projectDir.absolutePath) + + useJUnitPlatform() + jvmArgs("-Djunit.jupiter.extensions.autodetection.enabled=true") +} + +kotlin { + jvm() + + sourceSets { + commonTest { + dependencies { + implementation(project(":kson-lib-http")) + implementation(kotlin("test")) + } + + kotlin { + srcDir(syncCommonTestSources) + } + } + jvmTest { + dependencies { + implementation("org.junit.jupiter:junit-jupiter-api:5.14.2") + runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.2") + } + + kotlin { + srcDir(syncJvmTestSources) + } + } + } +} + diff --git a/lib-python/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt b/lib-python/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt new file mode 100644 index 00000000..1ab56b14 --- /dev/null +++ b/lib-python/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt @@ -0,0 +1,56 @@ +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.kson.Kson +import java.io.File +import java.net.Socket + +/** + * A JUnit extension that makes sure the Python KSON server is running before all tests and gets + * shut down at the end + */ +class ServerExtension : BeforeAllCallback, AutoCloseable { + + companion object { + private var started = false + private var process: Process? = null + } + + override fun beforeAll(context: ExtensionContext) { + if (!started) { + started = true + + // Register for cleanup when the root context closes (after all tests) + context.root.getStore(ExtensionContext.Namespace.GLOBAL) + .put("serverExtension", this) + + val libPythonDir = File(System.getProperty("libPythonDir")) + val isWindows = System.getProperty("os.name").lowercase().contains("win") + val uvwCommand = if (isWindows) listOf("cmd", "/c", "uvw.bat") else listOf("./uvw") + val port = 8082 + + Kson.setPort(port) + + process = ProcessBuilder(uvwCommand + listOf("run", "python", "tests/api_server.py", port.toString())) + .directory(libPythonDir) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectErrorStream(true) + .start() + + // Wait for readiness + repeat(30) { + try { + Socket("localhost", port).close() + return + } catch (_: Exception) { + Thread.sleep(1000) + } + } + throw RuntimeException("Server did not start in time") + } + } + + override fun close() { + process?.destroy() + process?.waitFor() + } +} diff --git a/lib-python/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/lib-python/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000..849fdf0a --- /dev/null +++ b/lib-python/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +ServerExtension diff --git a/lib-python/tests/api_server.py b/lib-python/tests/api_server.py new file mode 100644 index 00000000..38914f23 --- /dev/null +++ b/lib-python/tests/api_server.py @@ -0,0 +1,280 @@ +"""HTTP server exposing the KSON Python API, conforming to kson-api-schema.json.""" + +import json +import sys +from http.server import HTTPServer, BaseHTTPRequestHandler +from kson import ( + Kson, FormatOptions, IndentType, FormattingStyle, EmbedRule, EmbedRuleResult, + TranspileOptions, Result, SchemaResult, KsonValue, KsonValueType, + MessageSeverity, +) + + +def _serialize_position(pos): + return {"line": pos.line(), "column": pos.column()} + + +def _serialize_message(msg): + return { + "message": msg.message(), + "severity": msg.severity().name, + "start": _serialize_position(msg.start()), + "end": _serialize_position(msg.end()), + } + + +def _serialize_token(token): + return { + "tokenType": token.token_type().name, + "text": token.text(), + "start": _serialize_position(token.start()), + "end": _serialize_position(token.end()), + } + + +def _serialize_kson_value(value): + if value is None: + return None + + vtype = value.type() + + if vtype == KsonValueType.OBJECT: + props = value.properties() + prop_keys = value.property_keys() + return { + "type": "OBJECT", + "properties": {k: _serialize_kson_value(v) for k, v in props.items()}, + "propertyKeys": {k: _serialize_kson_value(v) for k, v in prop_keys.items()}, + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.ARRAY: + return { + "type": "ARRAY", + "elements": [_serialize_kson_value(e) for e in value.elements()], + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.STRING: + return { + "type": "STRING", + "value": value.value(), + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.INTEGER: + return { + "type": "INTEGER", + "value": value.value(), + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.DECIMAL: + return { + "type": "DECIMAL", + "value": value.value(), + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.BOOLEAN: + return { + "type": "BOOLEAN", + "value": bool(value.value()), + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.NULL: + return { + "type": "NULL", + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + elif vtype == KsonValueType.EMBED: + return { + "type": "EMBED", + "tag": value.tag(), + "content": value.content(), + "start": _serialize_position(value.start()), + "end": _serialize_position(value.end()), + } + + +def _parse_indent_type(obj): + if obj is None: + return IndentType.Spaces(2) + if obj["type"] == "tabs": + return IndentType.Tabs() + return IndentType.Spaces(obj.get("size", 2)) + + +def _parse_formatting_style(name): + if name is None: + return FormattingStyle.PLAIN + return FormattingStyle[name] + + +def _parse_embed_rule(obj): + result = EmbedRule.from_path_pattern(obj["pathPattern"], obj.get("tag")) + assert isinstance(result, EmbedRuleResult.Success) + return result.embed_rule() + + +def _parse_format_options(obj): + if obj is None: + return FormatOptions(IndentType.Spaces(2), FormattingStyle.PLAIN, []) + indent = _parse_indent_type(obj.get("indentType")) + style = _parse_formatting_style(obj.get("formattingStyle")) + rules = [_parse_embed_rule(r) for r in obj.get("embedBlockRules", [])] + return FormatOptions(indent, style, rules) + + +def handle_format(req): + options = _parse_format_options(req.get("formatOptions")) + output = Kson.format(req["kson"], options) + return {"command": "format", "success": True, "output": output} + + +def handle_to_json(req): + retain = req.get("retainEmbedTags", True) + result = Kson.to_json(req["kson"], TranspileOptions.Json(retain_embed_tags=retain)) + if isinstance(result, Result.Success): + return {"command": "toJson", "success": True, "output": result.output()} + else: + return { + "command": "toJson", + "success": False, + "errors": [_serialize_message(m) for m in result.errors()], + } + + +def handle_to_yaml(req): + retain = req.get("retainEmbedTags", True) + result = Kson.to_yaml(req["kson"], TranspileOptions.Yaml(retain_embed_tags=retain)) + if isinstance(result, Result.Success): + return {"command": "toYaml", "success": True, "output": result.output()} + else: + return { + "command": "toYaml", + "success": False, + "errors": [_serialize_message(m) for m in result.errors()], + } + + +def handle_analyze(req): + filepath = req.get("filepath") + analysis = Kson.analyze(req["kson"], filepath) + return { + "command": "analyze", + "errors": [_serialize_message(m) for m in analysis.errors()], + "tokens": [_serialize_token(t) for t in analysis.tokens()], + "ksonValue": _serialize_kson_value(analysis.kson_value()), + } + + +def handle_parse_schema(req): + result = Kson.parse_schema(req["schemaKson"]) + if isinstance(result, SchemaResult.Success): + return {"command": "parseSchema", "success": True} + else: + return { + "command": "parseSchema", + "success": False, + "errors": [_serialize_message(m) for m in result.errors()], + } + + +def handle_validate_embed_rule(req): + rule_obj = req["embedBlockRule"] + result = EmbedRule.from_path_pattern(rule_obj["pathPattern"], rule_obj.get("tag")) + if isinstance(result, EmbedRuleResult.Success): + return {"command": "validateEmbedRule", "success": True} + else: + return {"command": "validateEmbedRule", "success": False, "error": result.message()} + + +def handle_validate(req): + schema_result = Kson.parse_schema(req["schemaKson"]) + if isinstance(schema_result, SchemaResult.Failure): + return { + "command": "validate", + "success": False, + "errors": [_serialize_message(m) for m in schema_result.errors()], + } + + validator = schema_result.schema_validator() + errors = validator.validate(req["kson"], req.get("filepath")) + return { + "command": "validate", + "success": len(errors) == 0, + "errors": [_serialize_message(m) for m in errors], + } + + +HANDLERS = { + "format": handle_format, + "toJson": handle_to_json, + "toYaml": handle_to_yaml, + "analyze": handle_analyze, + "parseSchema": handle_parse_schema, + "validate": handle_validate, + "validateEmbedRule": handle_validate_embed_rule, +} + + +class KsonHandler(BaseHTTPRequestHandler): + def do_POST(self): + length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(length) + + try: + req = json.loads(body) + except json.JSONDecodeError as e: + self._send_error(400, f"Invalid JSON: {e}") + return + + command = req.get("command") + handler = HANDLERS.get(command) + if handler is None: + self._send_error(400, f"Unknown command: {command}") + return + + try: + response = handler(req) + except Exception as e: + self._send_error(500, str(e)) + return + + self._send_json(200, response) + + def _send_json(self, status, obj): + data = json.dumps(obj).encode("utf-8") + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + def _send_error(self, status, message): + self._send_json(status, {"internal_error": message}) + + def log_message(self, format, *args): + # Suppress default stderr logging + pass + + +def main(): + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080 + server = HTTPServer(("127.0.0.1", port), KsonHandler) + print(f"Listening on http://127.0.0.1:{port}", flush=True) + try: + server.serve_forever() + except KeyboardInterrupt: + pass + except BaseException as e: + print(f"Exception! {e}") + print("Shutting down...") + server.server_close() + + +if __name__ == "__main__": + main() diff --git a/settings.gradle.kts b/settings.gradle.kts index ed22c46e..37450566 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,8 +14,10 @@ pluginManagement { rootProject.name = "kson" include("native-tests") include("kson-lib") +include("kson-lib-http") include("kson-tooling-lib") include("lib-python") +include("lib-python:kson-lib-tests") include("lib-rust") include("tooling:jetbrains") include("tooling:language-server-protocol") From 9083bc10c90ad41ffd0f7606b8a7e9f5353f3acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Fri, 20 Feb 2026 17:17:46 -0300 Subject: [PATCH 4/6] Run `kson-lib` tests against `lib-rust` Here we follow the same approach used for testing the `lib-python` bindings. There is some code duplication in `ServerExtension.kt`, but I think we can tolerate that for now. Here is what we did: - We LLM-generated a Rust server that receives requests and uses the bindings to handle them (with minimal manual tweaks). - We created a `:lib-rust:kson-lib-tests` project responsible for actually running the `kson-lib` tests against the Rust server. --- lib-rust/.gitignore | 2 - lib-rust/kson-lib-tests/build.gradle.kts | 76 ++ .../src/jvmTest/kotlin/ServerExtension.kt | 56 + .../org.junit.jupiter.api.extension.Extension | 1 + lib-rust/kson-test-server/Cargo.lock | 1103 +++++++++++++++++ lib-rust/kson-test-server/Cargo.toml | 14 + lib-rust/kson-test-server/src/main.rs | 438 +++++++ settings.gradle.kts | 1 + 8 files changed, 1689 insertions(+), 2 deletions(-) delete mode 100644 lib-rust/.gitignore create mode 100644 lib-rust/kson-lib-tests/build.gradle.kts create mode 100644 lib-rust/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt create mode 100644 lib-rust/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension create mode 100644 lib-rust/kson-test-server/Cargo.lock create mode 100644 lib-rust/kson-test-server/Cargo.toml create mode 100644 lib-rust/kson-test-server/src/main.rs diff --git a/lib-rust/.gitignore b/lib-rust/.gitignore deleted file mode 100644 index 6cba219f..00000000 --- a/lib-rust/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -artifacts -kotlin diff --git a/lib-rust/kson-lib-tests/build.gradle.kts b/lib-rust/kson-lib-tests/build.gradle.kts new file mode 100644 index 00000000..4841c624 --- /dev/null +++ b/lib-rust/kson-lib-tests/build.gradle.kts @@ -0,0 +1,76 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() +} + +val releaseBuildDir: String = project(":lib-rust").projectDir.resolve("kson-test-server/target/release").absolutePath + +val syncCommonTestSources by tasks.registering(Sync::class) { + from(project(":kson-lib").file("src/commonTest/kotlin")) + into(layout.buildDirectory.dir("commonTestSources")) +} + +val syncJvmTestSources by tasks.registering(Sync::class) { + from(project(":kson-lib").file("src/jvmTest/kotlin")) + into(layout.buildDirectory.dir("jvmTestSources")) +} + +val buildTestServer by tasks.registering(Exec::class) { + dependsOn(":kson-lib:buildWithGraalVmNativeImage") + + val nativeArtifactsDir = project(":kson-lib").projectDir.resolve("build/kotlin/compileGraalVmNativeImage").absolutePath + + environment( + Pair("KSON_PREBUILT_BIN_DIR", nativeArtifactsDir), + Pair("KSON_COPY_SHARED_LIBRARY_TO_DIR", releaseBuildDir), + ) + + val cargoManifestPath = project(":lib-rust").projectDir.resolve("kson-test-server/Cargo.toml") + + group = "build" + workingDir = project(":lib-rust").projectDir + commandLine = "./pixiw run cargo build --release --manifest-path $cargoManifestPath".split(" ") + standardOutput = System.out + errorOutput = System.err + isIgnoreExitValue = false +} + +tasks.withType { + dependsOn(buildTestServer) + + systemProperty("releaseBuildDir", releaseBuildDir) + + useJUnitPlatform() + jvmArgs("-Djunit.jupiter.extensions.autodetection.enabled=true") +} + +kotlin { + jvm() + + sourceSets { + commonTest { + dependencies { + implementation(project(":kson-lib-http")) + implementation(kotlin("test")) + } + + kotlin { + srcDir(syncCommonTestSources) + } + } + jvmTest { + dependencies { + implementation("org.junit.jupiter:junit-jupiter-api:5.14.2") + runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.2") + } + + kotlin { + srcDir(syncJvmTestSources) + } + } + } +} + diff --git a/lib-rust/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt b/lib-rust/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt new file mode 100644 index 00000000..59133d62 --- /dev/null +++ b/lib-rust/kson-lib-tests/src/jvmTest/kotlin/ServerExtension.kt @@ -0,0 +1,56 @@ +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.kson.Kson +import java.io.File +import java.net.Socket + +/** + * A JUnit extension that makes sure a KSON server is running before all tests and gets + * shut down at the end + */ +class ServerExtension : BeforeAllCallback, AutoCloseable { + + companion object { + private var started = false + private var process: Process? = null + } + + override fun beforeAll(context: ExtensionContext) { + if (!started) { + started = true + + // Register for cleanup when the root context closes (after all tests) + context.root.getStore(ExtensionContext.Namespace.GLOBAL) + .put("serverExtension", this) + + val releaseBuildDir = File(System.getProperty("releaseBuildDir")) + val port = 8081 + Kson.setPort(port) + + val processBuilder = ProcessBuilder(listOf("./kson-test-server", port.toString())) + .directory(releaseBuildDir) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectErrorStream(true); + + processBuilder.environment()["LD_LIBRARY_PATH"] = releaseBuildDir.absolutePath + + process = processBuilder.start() + + // Wait for readiness + repeat(30) { + try { + Socket("localhost", port).close() + return + } catch (_: Exception) { + Thread.sleep(1000) + } + } + throw RuntimeException("Server did not start in time") + } + } + + override fun close() { + process?.destroy() + process?.waitFor() + } +} diff --git a/lib-rust/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/lib-rust/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000..849fdf0a --- /dev/null +++ b/lib-rust/kson-lib-tests/src/jvmTest/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +ServerExtension diff --git a/lib-rust/kson-test-server/Cargo.lock b/lib-rust/kson-test-server/Cargo.lock new file mode 100644 index 00000000..dc4827f7 --- /dev/null +++ b/lib-rust/kson-test-server/Cargo.lock @@ -0,0 +1,1103 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "kson-rs" +version = "0.3.0-dev" +dependencies = [ + "kson-sys", +] + +[[package]] +name = "kson-sys" +version = "0.3.0-dev" +dependencies = [ + "anyhow", + "bindgen", + "flate2", + "tar", + "ureq", +] + +[[package]] +name = "kson-test-server" +version = "0.1.0" +dependencies = [ + "axum", + "kson-rs", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/lib-rust/kson-test-server/Cargo.toml b/lib-rust/kson-test-server/Cargo.toml new file mode 100644 index 00000000..89db7acc --- /dev/null +++ b/lib-rust/kson-test-server/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "kson-test-server" +version = "0.1.0" +edition = "2024" + +[profile.release] +rpath = true + +[dependencies] +kson = { package = "kson-rs", path = "../kson" } +axum = "0.8" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["rt-multi-thread", "macros"] } diff --git a/lib-rust/kson-test-server/src/main.rs b/lib-rust/kson-test-server/src/main.rs new file mode 100644 index 00000000..7b38fbf0 --- /dev/null +++ b/lib-rust/kson-test-server/src/main.rs @@ -0,0 +1,438 @@ +use axum::{Router, routing::post, Json}; +use serde::Deserialize; +use serde_json::Value; + +use kson::{ + EmbedRule, EmbedRuleResult, FormatOptions, FormattingStyle, IndentType, Kson, KsonValue, + indent_type, kson_value, transpile_options, +}; + +// --- Request types --- + +#[derive(Deserialize)] +#[serde(tag = "command")] +enum Request { + #[serde(rename = "format")] + Format { + kson: String, + #[serde(rename = "formatOptions")] + format_options: Option, + }, + #[serde(rename = "toJson")] + ToJson { + kson: String, + #[serde(rename = "retainEmbedTags", default = "default_true")] + retain_embed_tags: bool, + }, + #[serde(rename = "toYaml")] + ToYaml { + kson: String, + #[serde(rename = "retainEmbedTags", default = "default_true")] + retain_embed_tags: bool, + }, + #[serde(rename = "analyze")] + Analyze { + kson: String, + filepath: Option, + }, + #[serde(rename = "parseSchema")] + ParseSchema { + #[serde(rename = "schemaKson")] + schema_kson: String, + }, + #[serde(rename = "validate")] + Validate { + #[serde(rename = "schemaKson")] + schema_kson: String, + kson: String, + filepath: Option, + }, + #[serde(rename = "validateEmbedRule")] + ValidateEmbedRule { + #[serde(rename = "embedBlockRule")] + embed_block_rule: EmbedRuleDto, + }, +} + +fn default_true() -> bool { + true +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FormatOptionsDto { + indent_type: Option, + formatting_style: Option, + embed_block_rules: Option>, +} + +#[derive(Deserialize)] +struct IndentTypeDto { + r#type: String, + size: Option, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct EmbedRuleDto { + path_pattern: String, + tag: Option, +} + +// --- Response serialization helpers --- + +fn serialize_position(pos: kson::Position) -> Value { + serde_json::json!({ + "line": pos.line(), + "column": pos.column(), + }) +} + +fn serialize_message(msg: kson::Message) -> Value { + let severity = match msg.severity() { + kson::MessageSeverity::Error => "ERROR", + kson::MessageSeverity::Warning => "WARNING", + }; + serde_json::json!({ + "message": msg.message(), + "severity": severity, + "start": serialize_position(msg.start()), + "end": serialize_position(msg.end()), + }) +} + +fn serialize_token(token: kson::Token) -> Value { + let token_type = match token.token_type() { + kson::TokenType::CurlyBraceL => "CURLY_BRACE_L", + kson::TokenType::CurlyBraceR => "CURLY_BRACE_R", + kson::TokenType::SquareBracketL => "SQUARE_BRACKET_L", + kson::TokenType::SquareBracketR => "SQUARE_BRACKET_R", + kson::TokenType::AngleBracketL => "ANGLE_BRACKET_L", + kson::TokenType::AngleBracketR => "ANGLE_BRACKET_R", + kson::TokenType::Colon => "COLON", + kson::TokenType::Dot => "DOT", + kson::TokenType::EndDash => "END_DASH", + kson::TokenType::Comma => "COMMA", + kson::TokenType::Comment => "COMMENT", + kson::TokenType::EmbedOpenDelim => "EMBED_OPEN_DELIM", + kson::TokenType::EmbedCloseDelim => "EMBED_CLOSE_DELIM", + kson::TokenType::EmbedTag => "EMBED_TAG", + kson::TokenType::EmbedPreambleNewline => "EMBED_PREAMBLE_NEWLINE", + kson::TokenType::EmbedContent => "EMBED_CONTENT", + kson::TokenType::False => "FALSE", + kson::TokenType::UnquotedString => "UNQUOTED_STRING", + kson::TokenType::IllegalChar => "ILLEGAL_CHAR", + kson::TokenType::ListDash => "LIST_DASH", + kson::TokenType::Null => "NULL", + kson::TokenType::Number => "NUMBER", + kson::TokenType::StringOpenQuote => "STRING_OPEN_QUOTE", + kson::TokenType::StringCloseQuote => "STRING_CLOSE_QUOTE", + kson::TokenType::StringContent => "STRING_CONTENT", + kson::TokenType::True => "TRUE", + kson::TokenType::Whitespace => "WHITESPACE", + kson::TokenType::Eof => "EOF", + }; + serde_json::json!({ + "tokenType": token_type, + "text": token.text(), + "start": serialize_position(token.start()), + "end": serialize_position(token.end()), + }) +} + +fn serialize_kson_value(value: &KsonValue) -> Value { + match value { + KsonValue::KsonObject(obj) => { + let props = obj.properties(); + let property_keys = obj.property_keys(); + let mut serialized_props: serde_json::Map = serde_json::Map::new(); + for (key, val) in &props { + serialized_props.insert(key.clone(), serialize_kson_value(val)); + } + let mut serialized_keys: serde_json::Map = serde_json::Map::new(); + for (key, kson_str) in &property_keys { + serialized_keys.insert(key.clone(), serialize_kson_string(kson_str)); + } + serde_json::json!({ + "type": "OBJECT", + "properties": serialized_props, + "propertyKeys": serialized_keys, + "start": serialize_position(obj.start()), + "end": serialize_position(obj.end()), + }) + } + KsonValue::KsonArray(arr) => { + let elements: Vec = arr.elements().iter().map(serialize_kson_value).collect(); + serde_json::json!({ + "type": "ARRAY", + "elements": elements, + "start": serialize_position(arr.start()), + "end": serialize_position(arr.end()), + }) + } + KsonValue::KsonString(s) => serialize_kson_string(s), + KsonValue::KsonNumber(num) => match num { + kson_value::KsonNumber::Integer(i) => { + serde_json::json!({ + "type": "INTEGER", + "value": i.value(), + "start": serialize_position(i.start()), + "end": serialize_position(i.end()), + }) + } + kson_value::KsonNumber::Decimal(d) => { + serde_json::json!({ + "type": "DECIMAL", + "value": d.value(), + "start": serialize_position(d.start()), + "end": serialize_position(d.end()), + }) + } + }, + KsonValue::KsonBoolean(b) => { + serde_json::json!({ + "type": "BOOLEAN", + "value": b.value(), + "start": serialize_position(b.start()), + "end": serialize_position(b.end()), + }) + } + KsonValue::KsonNull(n) => { + serde_json::json!({ + "type": "NULL", + "start": serialize_position(n.start()), + "end": serialize_position(n.end()), + }) + } + KsonValue::KsonEmbed(e) => { + serde_json::json!({ + "type": "EMBED", + "tag": e.tag(), + "content": e.content(), + "start": serialize_position(e.start()), + "end": serialize_position(e.end()), + }) + } + } +} + +fn serialize_kson_string(s: &kson_value::KsonString) -> Value { + serde_json::json!({ + "type": "STRING", + "value": s.value(), + "start": serialize_position(s.start()), + "end": serialize_position(s.end()), + }) +} + +// --- Request handling --- + +fn convert_indent_type(dto: &IndentTypeDto) -> IndentType { + match dto.r#type.as_str() { + "tabs" => IndentType::Tabs(indent_type::Tabs::new()), + _ => IndentType::Spaces(indent_type::Spaces::new(dto.size.unwrap_or(2))), + } +} + +fn convert_formatting_style(s: &str) -> FormattingStyle { + match s { + "DELIMITED" => FormattingStyle::Delimited, + "COMPACT" => FormattingStyle::Compact, + "CLASSIC" => FormattingStyle::Classic, + _ => FormattingStyle::Plain, + } +} + +fn convert_format_options(dto: Option<&FormatOptionsDto>) -> FormatOptions { + let indent_type = dto + .and_then(|d| d.indent_type.as_ref()) + .map(convert_indent_type) + .unwrap_or_else(|| IndentType::Spaces(indent_type::Spaces::new(2))); + + let formatting_style = dto + .and_then(|d| d.formatting_style.as_deref()) + .map(convert_formatting_style) + .unwrap_or(FormattingStyle::Plain); + + let embed_rules: Vec = dto + .and_then(|d| d.embed_block_rules.as_ref()) + .map(|rules| { + rules + .iter() + .filter_map(|r| { + match EmbedRule::from_path_pattern( + &r.path_pattern, + r.tag.as_deref(), + ) { + EmbedRuleResult::Success(s) => Some(s.embed_rule()), + EmbedRuleResult::Failure(_) => None, + } + }) + .collect() + }) + .unwrap_or_default(); + + FormatOptions::new(indent_type, formatting_style, &embed_rules) +} + +fn handle_request(request: Request) -> Value { + match request { + Request::Format { + kson, + format_options, + } => { + let opts = convert_format_options(format_options.as_ref()); + let output = Kson::format(&kson, opts); + serde_json::json!({ + "command": "format", + "success": true, + "output": output, + }) + } + Request::ToJson { + kson, + retain_embed_tags, + } => { + let opts = transpile_options::Json::new(retain_embed_tags); + match Kson::to_json(&kson, opts) { + Ok(success) => serde_json::json!({ + "command": "toJson", + "success": true, + "output": success.output(), + }), + Err(failure) => { + let errors: Vec = + failure.errors().into_iter().map(serialize_message).collect(); + serde_json::json!({ + "command": "toJson", + "success": false, + "errors": errors, + }) + } + } + } + Request::ToYaml { + kson, + retain_embed_tags, + } => { + let opts = transpile_options::Yaml::new(retain_embed_tags); + match Kson::to_yaml(&kson, opts) { + Ok(success) => serde_json::json!({ + "command": "toYaml", + "success": true, + "output": success.output(), + }), + Err(failure) => { + let errors: Vec = + failure.errors().into_iter().map(serialize_message).collect(); + serde_json::json!({ + "command": "toYaml", + "success": false, + "errors": errors, + }) + } + } + } + Request::Analyze { kson, filepath } => { + let analysis = Kson::analyze(&kson, filepath.as_deref()); + let errors: Vec = analysis + .errors() + .into_iter() + .map(serialize_message) + .collect(); + let tokens: Vec = analysis + .tokens() + .into_iter() + .map(serialize_token) + .collect(); + let kson_value = analysis + .kson_value() + .as_ref() + .map(serialize_kson_value); + serde_json::json!({ + "command": "analyze", + "errors": errors, + "tokens": tokens, + "ksonValue": kson_value, + }) + } + Request::ParseSchema { schema_kson } => match Kson::parse_schema(&schema_kson) { + Ok(_) => serde_json::json!({ + "command": "parseSchema", + "success": true, + }), + Err(failure) => { + let errors: Vec = + failure.errors().into_iter().map(serialize_message).collect(); + serde_json::json!({ + "command": "parseSchema", + "success": false, + "errors": errors, + }) + } + }, + Request::Validate { + schema_kson, + kson, + filepath, + } => match Kson::parse_schema(&schema_kson) { + Ok(success) => { + let validator = success.schema_validator(); + let errors: Vec = validator + .validate(&kson, filepath.as_deref()) + .into_iter() + .map(serialize_message) + .collect(); + let is_success = errors.is_empty(); + serde_json::json!({ + "command": "validate", + "success": is_success, + "errors": errors, + }) + } + Err(failure) => { + let errors: Vec = + failure.errors().into_iter().map(serialize_message).collect(); + serde_json::json!({ + "command": "validate", + "success": false, + "errors": errors, + }) + } + }, + Request::ValidateEmbedRule { embed_block_rule } => { + match EmbedRule::from_path_pattern( + &embed_block_rule.path_pattern, + embed_block_rule.tag.as_deref(), + ) { + EmbedRuleResult::Success(_) => serde_json::json!({ + "command": "validateEmbedRule", + "success": true, + }), + EmbedRuleResult::Failure(failure) => serde_json::json!({ + "command": "validateEmbedRule", + "success": false, + "error": failure.message(), + }), + } + } + } +} + +async fn handler(Json(request): Json) -> Json { + Json(handle_request(request)) +} + +#[tokio::main] +async fn main() { + let port: u16 = std::env::args() + .nth(1) + .and_then(|s| s.parse().ok()) + .unwrap_or(3000); + + let app = Router::new().route("/", post(handler)); + let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port)); + println!("Listening on {addr}"); + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app).await.unwrap(); +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 37450566..65138bf9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ include("kson-tooling-lib") include("lib-python") include("lib-python:kson-lib-tests") include("lib-rust") +include("lib-rust:kson-lib-tests") include("tooling:jetbrains") include("tooling:language-server-protocol") include("tooling:lsp-clients") From 9d3b4f74850b97c039ead7c72c386fbf392b22df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Thu, 26 Feb 2026 13:16:17 -0300 Subject: [PATCH 5/6] Extract `kson-service-api` and `kson-service-tests` projects The previous commits implemented an alternative KSON implementation over HTTP and a mechanism to run the `kson-lib` tests against our bindings. However, the whole setup was somewhat hacky (duplicated class definitions, reusing source code files instead of using proper abstractions, etc). This commit is meant to straighten everything up: - The `kson-lib` project has been split. KSON's public interface (with no implementation) lives in `kson-service-api` while the implementation stays at `kson-lib` (which now depends on `kson-service-api`). - The `kson-lib-http` project has been renamed to `kson-http`. It now implements the interface from `kson-service-api` and hence has significantly less lines of code. - The `kson-lib` tests have been moved wholesale to an independent `kson-service-tests` project. There, they live now as an abstract class that can be inherited by other projects (currently `kson-lib`, `lib-rust/kson-lib-tests` and `lib-python/kson-lib-tests`). When extracting `kson-service-api` from `kson-lib`, I tried to avoid any changes to the public interface. Some things had to change anyway, though: - Some types used to have internal or private constructors, but those need to be public now the types live in `kson-service-api`. Otherwise consumers of the API (e.g., `kson-lib`) would be unable to build them. The types are: `Position`, `Message`, `EmbedRule`, `Token`, `Analysis`, `KsonObject`, `KsonArray`, `KsonString`, `KsonNumber.Integer`, `KsonNumber.Decimal`, `KsonBoolean`, `KsonNull`, `KsonEmbed`. - The `EmbedRule` type used to validate the json pointer upon construction and return an error in case it was invalid. This is no longer possible, because `kson-service-api` doesn't know how to validate json pointers. This means that `KsonService.format` no longer can assume `EmbedRule`s are valid. I have modified the `Kson.format` implementation from `kson-lib` to ignore embed blocks with invalid json pointers (the function's return type is `String` and we are avoiding exceptions, so this is the most sensible behavior I could come up with under those constraints). An alternative would be to make `EmbedRule` an interface, asking each implementor to ensure instances of the interface carry valid json pointers. - I had to introduce a `SchemaValidatorService`. I think the concept is sound, but the name is ugly. Ideas welcome! PS 1: while this commit is focused on getting the tests working, it lays the foundation for alternative KSON clients (e.g., KSON over KSON). All they have to do is implement the interfaces from `kson-services-api`. PS 2: we had to update the Krossover version to fix bugs we discovered along the way. --- {kson-lib-http => kson-http}/build.gradle.kts | 2 + .../kson-api-schema.json | 0 {kson-lib-http => kson-http}/readme.md | 2 +- .../src/commonMain/kotlin/org/kson/Kson.kt | 201 + .../src/commonMain/kotlin/org/kson/Kson.kt | 432 -- kson-lib/build.gradle.kts | 19 +- .../src/commonMain/kotlin/org/kson/Kson.kt | 423 +- .../kotlin/org/kson/KsonSmokeTest.kt | 398 +- kson-service-api/build.gradle.kts | 59 + kson-service-api/readme.md | 4 + .../kotlin/org/kson/api/FormatOptions.kt | 48 + .../kotlin/org/kson/api/KsonService.kt | 261 ++ .../kotlin/org/kson/api/TranspileOptions.kt | 22 + kson-service-tests/build.gradle.kts | 30 + kson-service-tests/readme.md | 4 + .../org/kson/api/KsonServiceSmokeTest.kt | 412 ++ lib-python/kson-lib-tests/build.gradle.kts | 27 +- .../src/jvmTest/kotlin/SmokeTest.kt | 9 + lib-python/src/kson/__init__.py | 1753 ++++---- lib-python/tests/api_server.py | 20 +- lib-rust/kson-lib-tests/build.gradle.kts | 27 +- .../src/jvmTest/kotlin/SmokeTest.kt | 9 + lib-rust/kson-test-server/src/main.rs | 33 +- lib-rust/kson/src/generated/mod.rs | 3935 +++++++++-------- lib-rust/kson/src/generated/util.rs | 34 + settings.gradle.kts | 4 +- 26 files changed, 4081 insertions(+), 4087 deletions(-) rename {kson-lib-http => kson-http}/build.gradle.kts (88%) rename {kson-lib-http => kson-http}/kson-api-schema.json (100%) rename {kson-lib-http => kson-http}/readme.md (98%) create mode 100644 kson-http/src/commonMain/kotlin/org/kson/Kson.kt delete mode 100644 kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt create mode 100644 kson-service-api/build.gradle.kts create mode 100644 kson-service-api/readme.md create mode 100644 kson-service-api/src/commonMain/kotlin/org/kson/api/FormatOptions.kt create mode 100644 kson-service-api/src/commonMain/kotlin/org/kson/api/KsonService.kt create mode 100644 kson-service-api/src/commonMain/kotlin/org/kson/api/TranspileOptions.kt create mode 100644 kson-service-tests/build.gradle.kts create mode 100644 kson-service-tests/readme.md create mode 100644 kson-service-tests/src/commonMain/kotlin/org/kson/api/KsonServiceSmokeTest.kt create mode 100644 lib-python/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt create mode 100644 lib-rust/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt diff --git a/kson-lib-http/build.gradle.kts b/kson-http/build.gradle.kts similarity index 88% rename from kson-lib-http/build.gradle.kts rename to kson-http/build.gradle.kts index c5e3f5b9..d33cb1f7 100644 --- a/kson-lib-http/build.gradle.kts +++ b/kson-http/build.gradle.kts @@ -16,6 +16,8 @@ kotlin { sourceSets { commonMain { dependencies { + implementation(project(":kson-service-api")) + implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1") diff --git a/kson-lib-http/kson-api-schema.json b/kson-http/kson-api-schema.json similarity index 100% rename from kson-lib-http/kson-api-schema.json rename to kson-http/kson-api-schema.json diff --git a/kson-lib-http/readme.md b/kson-http/readme.md similarity index 98% rename from kson-lib-http/readme.md rename to kson-http/readme.md index c39a244b..bc6f59a2 100644 --- a/kson-lib-http/readme.md +++ b/kson-http/readme.md @@ -1,4 +1,4 @@ -# kson-lib-http +# kson-http A reimplementation of KSON's public API (see [kson-lib](../kson-lib)) based on HTTP requests to a server that respects the [kson-api-schema](./kson-api-schema.json). diff --git a/kson-http/src/commonMain/kotlin/org/kson/Kson.kt b/kson-http/src/commonMain/kotlin/org/kson/Kson.kt new file mode 100644 index 00000000..82eed8a1 --- /dev/null +++ b/kson-http/src/commonMain/kotlin/org/kson/Kson.kt @@ -0,0 +1,201 @@ +package org.kson + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.* +import org.kson.api.* + +object Kson : KsonService { + private val client = HttpClient(CIO) + private val json = Json { ignoreUnknownKeys = true } + private var baseUrl = "http://localhost:8080" + + fun setPort(port: Int) { + baseUrl = "http://localhost:$port" + } + + override fun format(kson: String, formatOptions: FormatOptions): String { + val request = buildJsonObject { + put("command", "format") + put("kson", kson) + put("formatOptions", formatOptions.toJson()) + } + val response = post(request) + return response["output"]!!.jsonPrimitive.content + } + + + override fun toJson(kson: String, options: TranspileOptions.Json): Result { + val request = buildJsonObject { + put("command", "toJson") + put("kson", kson) + put("retainEmbedTags", options.retainEmbedTags) + } + return parseTranspileResult(post(request)) + } + + + override fun toYaml(kson: String, options: TranspileOptions.Yaml): Result { + val request = buildJsonObject { + put("command", "toYaml") + put("kson", kson) + put("retainEmbedTags", options.retainEmbedTags) + } + return parseTranspileResult(post(request)) + } + + + override fun analyze(kson: String, filepath: String?): Analysis { + val request = buildJsonObject { + put("command", "analyze") + put("kson", kson) + filepath?.let { put("filepath", it) } + } + val response = post(request) + val errors = parseMessages(response["errors"]!!.jsonArray) + val tokens = parseTokens(response["tokens"]!!.jsonArray) + val ksonValue = response["ksonValue"]?.let { + if (it is JsonNull) null else parseKsonValue(it.jsonObject) + } + return Analysis(errors, tokens, ksonValue) + } + + override fun parseSchema(schemaKson: String): SchemaResult { + val request = buildJsonObject { + put("command", "parseSchema") + put("schemaKson", schemaKson) + } + val response = post(request) + val success = response["success"]!!.jsonPrimitive.boolean + return if (success) { + SchemaResult.Success(SchemaValidator(schemaKson)) + } else { + SchemaResult.Failure(parseMessages(response["errors"]!!.jsonArray)) + } + } + + internal fun post(body: JsonObject): JsonObject = runBlocking { + val response = client.post(baseUrl) { + contentType(ContentType.Application.Json) + setBody(body.toString()) + } + json.parseToJsonElement(response.bodyAsText()).jsonObject + } + + private fun parseTranspileResult(response: JsonObject): Result { + val success = response["success"]!!.jsonPrimitive.boolean + return if (success) { + Result.Success(response["output"]!!.jsonPrimitive.content) + } else { + Result.Failure(parseMessages(response["errors"]!!.jsonArray)) + } + } +} + +class SchemaValidator internal constructor(private val schemaKson: String) : SchemaValidatorService { + + override fun validate(kson: String, filepath: String?): List { + val request = buildJsonObject { + put("command", "validate") + put("schemaKson", schemaKson) + put("kson", kson) + filepath?.let { put("filepath", it) } + } + val response = Kson.post(request) + return parseMessages(response["errors"]!!.jsonArray) + } +} + +internal fun FormatOptions.toJson(): JsonObject = buildJsonObject { + put("indentType", when (indentType) { + is IndentType.Spaces -> buildJsonObject { + put("type", "spaces") + put("size", (indentType as IndentType.Spaces).size) + } + is IndentType.Tabs -> buildJsonObject { + put("type", "tabs") + } + }) + put("formattingStyle", formattingStyle.name) + put("embedBlockRules", buildJsonArray { + for (rule in embedBlockRules) { + add(buildJsonObject { + put("pathPattern", rule.pathPattern) + rule.tag?.let { put("tag", it) } + }) + } + }) +} + +// --- JSON response parsing helpers --- + +internal fun parsePosition(obj: JsonObject): Position { + return Position( + line = obj["line"]!!.jsonPrimitive.int, + column = obj["column"]!!.jsonPrimitive.int + ) +} + +internal fun parseMessages(array: JsonArray): List { + return array.map { element -> + val obj = element.jsonObject + Message( + message = obj["message"]!!.jsonPrimitive.content, + severity = MessageSeverity.valueOf(obj["severity"]!!.jsonPrimitive.content), + start = parsePosition(obj["start"]!!.jsonObject), + end = parsePosition(obj["end"]!!.jsonObject) + ) + } +} + +internal fun parseTokens(array: JsonArray): List { + return array.map { element -> + val obj = element.jsonObject + Token( + tokenType = TokenType.valueOf(obj["tokenType"]!!.jsonPrimitive.content), + text = obj["text"]!!.jsonPrimitive.content, + start = parsePosition(obj["start"]!!.jsonObject), + end = parsePosition(obj["end"]!!.jsonObject) + ) + } +} + +internal fun parseKsonValue(obj: JsonObject): KsonValue { + val start = parsePosition(obj["start"]!!.jsonObject) + val end = parsePosition(obj["end"]!!.jsonObject) + + return when (val type = obj["type"]!!.jsonPrimitive.content) { + "OBJECT" -> { + val properties = obj["properties"]!!.jsonObject.mapValues { (_, v) -> parseKsonValue(v.jsonObject) } + val propertyKeys = obj["propertyKeys"]!!.jsonObject.mapValues { (_, v) -> + val keyObj = v.jsonObject + KsonValue.KsonString( + value = keyObj["value"]!!.jsonPrimitive.content, + internalStart = parsePosition(keyObj["start"]!!.jsonObject), + internalEnd = parsePosition(keyObj["end"]!!.jsonObject) + ) + } + KsonValue.KsonObject(properties, propertyKeys, start, end) + } + "ARRAY" -> { + val elements = obj["elements"]!!.jsonArray.map { parseKsonValue(it.jsonObject) } + KsonValue.KsonArray(elements, start, end) + } + "STRING" -> KsonValue.KsonString(obj["value"]!!.jsonPrimitive.content, start, end) + "INTEGER" -> KsonValue.KsonNumber.Integer(obj["value"]!!.jsonPrimitive.int, start, end) + "DECIMAL" -> KsonValue.KsonNumber.Decimal(obj["value"]!!.jsonPrimitive.double, start, end) + "BOOLEAN" -> KsonValue.KsonBoolean(obj["value"]!!.jsonPrimitive.boolean, start, end) + "NULL" -> KsonValue.KsonNull(start, end) + "EMBED" -> KsonValue.KsonEmbed( + tag = obj["tag"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }, + content = obj["content"]!!.jsonPrimitive.content, + internalStart = start, + internalEnd = end + ) + else -> throw IllegalArgumentException("Unknown KsonValue type: $type") + } +} diff --git a/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt b/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt deleted file mode 100644 index 365b2f44..00000000 --- a/kson-lib-http/src/commonMain/kotlin/org/kson/Kson.kt +++ /dev/null @@ -1,432 +0,0 @@ -package org.kson - -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.request.* -import io.ktor.client.statement.* -import io.ktor.http.* -import kotlinx.coroutines.runBlocking -import kotlinx.serialization.json.* - -object Kson { - private val client = HttpClient(CIO) - private val json = Json { ignoreUnknownKeys = true } - private var baseUrl = "http://localhost:8080" - - fun setPort(port: Int) { - baseUrl = "http://localhost:$port" - } - - fun format(kson: String, formatOptions: FormatOptions = FormatOptions()): String { - val request = buildJsonObject { - put("command", "format") - put("kson", kson) - put("formatOptions", formatOptions.toJson()) - } - val response = post(request) - return response["output"]!!.jsonPrimitive.content - } - - - fun toJson(kson: String, options: TranspileOptions.Json = TranspileOptions.Json()): Result { - val request = buildJsonObject { - put("command", "toJson") - put("kson", kson) - put("retainEmbedTags", options.retainEmbedTags) - } - return parseTranspileResult(post(request)) - } - - - fun toYaml(kson: String, options: TranspileOptions.Yaml = TranspileOptions.Yaml()): Result { - val request = buildJsonObject { - put("command", "toYaml") - put("kson", kson) - put("retainEmbedTags", options.retainEmbedTags) - } - return parseTranspileResult(post(request)) - } - - - fun analyze(kson: String, filepath: String? = null): Analysis { - val request = buildJsonObject { - put("command", "analyze") - put("kson", kson) - filepath?.let { put("filepath", it) } - } - val response = post(request) - val errors = parseMessages(response["errors"]!!.jsonArray) - val tokens = parseTokens(response["tokens"]!!.jsonArray) - val ksonValue = response["ksonValue"]?.let { - if (it is JsonNull) null else parseKsonValue(it.jsonObject) - } - return Analysis(errors, tokens, ksonValue) - } - - - fun parseSchema(schemaKson: String): SchemaResult { - val request = buildJsonObject { - put("command", "parseSchema") - put("schemaKson", schemaKson) - } - val response = post(request) - val success = response["success"]!!.jsonPrimitive.boolean - return if (success) { - SchemaResult.Success(SchemaValidator(schemaKson)) - } else { - SchemaResult.Failure(parseMessages(response["errors"]!!.jsonArray)) - } - } - - internal fun post(body: JsonObject): JsonObject = runBlocking { - val response = client.post(baseUrl) { - contentType(ContentType.Application.Json) - setBody(body.toString()) - } - json.parseToJsonElement(response.bodyAsText()).jsonObject - } - - private fun parseTranspileResult(response: JsonObject): Result { - val success = response["success"]!!.jsonPrimitive.boolean - return if (success) { - Result.Success(response["output"]!!.jsonPrimitive.content) - } else { - Result.Failure(parseMessages(response["errors"]!!.jsonArray)) - } - } -} - - -sealed class Result { - class Success(val output: String) : Result() - class Failure(val errors: List) : Result() -} - - -sealed class SchemaResult { - class Success(val schemaValidator: SchemaValidator) : SchemaResult() - class Failure(val errors: List) : SchemaResult() -} - -class SchemaValidator internal constructor(private val schemaKson: String) { - - fun validate(kson: String, filepath: String? = null): List { - val request = buildJsonObject { - put("command", "validate") - put("schemaKson", schemaKson) - put("kson", kson) - filepath?.let { put("filepath", it) } - } - val response = Kson.post(request) - return parseMessages(response["errors"]!!.jsonArray) - } -} - - -class EmbedRule private constructor( - val pathPattern: String, - val tag: String? = null -) { - companion object { - fun fromPathPattern(pathPattern: String, tag: String? = null): EmbedRuleResult { - val request = buildJsonObject { - put("command", "validateEmbedRule") - put("embedBlockRule", buildJsonObject { - put("pathPattern", pathPattern) - tag?.let { put("tag", it) } - }) - } - val response = Kson.post(request) - val success = response["success"]?.jsonPrimitive?.boolean == true - return if (success) { - EmbedRuleResult.Success(EmbedRule(pathPattern, tag)) - } else { - val error = response["error"]!!.jsonPrimitive.content - EmbedRuleResult.Failure(error) - } - } - } -} - -sealed class EmbedRuleResult { - data class Success(val embedRule: EmbedRule) : EmbedRuleResult() - data class Failure(val message: String) : EmbedRuleResult() -} - - -class FormatOptions( - val indentType: IndentType = IndentType.Spaces(2), - val formattingStyle: FormattingStyle = FormattingStyle.PLAIN, - val embedBlockRules: List = emptyList() -) { - internal fun toJson(): JsonObject = buildJsonObject { - put("indentType", when (indentType) { - is IndentType.Spaces -> buildJsonObject { - put("type", "spaces") - put("size", indentType.size) - } - is IndentType.Tabs -> buildJsonObject { - put("type", "tabs") - } - }) - put("formattingStyle", formattingStyle.name) - put("embedBlockRules", buildJsonArray { - for (rule in embedBlockRules) { - add(buildJsonObject { - put("pathPattern", rule.pathPattern) - rule.tag?.let { put("tag", it) } - }) - } - }) - } -} - - -sealed class TranspileOptions { - abstract val retainEmbedTags: Boolean - - - class Json( - override val retainEmbedTags: Boolean = true - ) : TranspileOptions() - - - class Yaml( - override val retainEmbedTags: Boolean = true - ) : TranspileOptions() -} - - -enum class FormattingStyle { - PLAIN, - DELIMITED, - COMPACT, - CLASSIC -} - -sealed class IndentType { - - class Spaces(val size: Int = 2) : IndentType() - - - data object Tabs : IndentType() -} - - -class Analysis internal constructor( - val errors: List, - val tokens: List, - val ksonValue: KsonValue? -) - - -class Token internal constructor( - val tokenType: TokenType, - val text: String, - val start: Position, - val end: Position) - -enum class TokenType { - CURLY_BRACE_L, - CURLY_BRACE_R, - SQUARE_BRACKET_L, - SQUARE_BRACKET_R, - ANGLE_BRACKET_L, - ANGLE_BRACKET_R, - COLON, - DOT, - END_DASH, - COMMA, - COMMENT, - EMBED_OPEN_DELIM, - EMBED_CLOSE_DELIM, - EMBED_TAG, - EMBED_PREAMBLE_NEWLINE, - EMBED_CONTENT, - FALSE, - UNQUOTED_STRING, - ILLEGAL_CHAR, - LIST_DASH, - NULL, - NUMBER, - STRING_OPEN_QUOTE, - STRING_CLOSE_QUOTE, - STRING_CONTENT, - TRUE, - WHITESPACE, - EOF -} - - -class Message internal constructor(val message: String, val severity: MessageSeverity, val start: Position, val end: Position) - - -enum class MessageSeverity { - ERROR, - WARNING, -} - - -class Position internal constructor(val line: Int, val column: Int) - - -enum class KsonValueType { - OBJECT, - ARRAY, - STRING, - INTEGER, - DECIMAL, - BOOLEAN, - NULL, - EMBED -} - -sealed class KsonValue(val start: Position, val end: Position) { - - abstract val type: KsonValueType - - - @ConsistentCopyVisibility - data class KsonObject internal constructor( - val properties: Map, - val propertyKeys: Map, - private val internalStart: Position, - private val internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.OBJECT - } - - - class KsonArray internal constructor( - val elements: List, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.ARRAY - } - - - class KsonString internal constructor( - val value: String, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.STRING - } - - - sealed class KsonNumber(start: Position, end: Position) : KsonValue(start, end) { - - class Integer internal constructor( - val value: Int, - val internalStart: Position, - val internalEnd: Position - ) : KsonNumber(internalStart, internalEnd) { - override val type = KsonValueType.INTEGER - } - - class Decimal internal constructor( - val value: Double, - internalStart: Position, - internalEnd: Position - ) : KsonNumber(internalStart, internalEnd) { - override val type = KsonValueType.DECIMAL - } - } - - - class KsonBoolean internal constructor( - val value: Boolean, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.BOOLEAN - } - - - class KsonNull internal constructor( - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.NULL - } - - - class KsonEmbed internal constructor( - val tag: String?, - val content: String, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.EMBED - } -} - -// --- JSON response parsing helpers --- - -internal fun parsePosition(obj: JsonObject): Position { - return Position( - line = obj["line"]!!.jsonPrimitive.int, - column = obj["column"]!!.jsonPrimitive.int - ) -} - -internal fun parseMessages(array: JsonArray): List { - return array.map { element -> - val obj = element.jsonObject - Message( - message = obj["message"]!!.jsonPrimitive.content, - severity = MessageSeverity.valueOf(obj["severity"]!!.jsonPrimitive.content), - start = parsePosition(obj["start"]!!.jsonObject), - end = parsePosition(obj["end"]!!.jsonObject) - ) - } -} - -internal fun parseTokens(array: JsonArray): List { - return array.map { element -> - val obj = element.jsonObject - Token( - tokenType = TokenType.valueOf(obj["tokenType"]!!.jsonPrimitive.content), - text = obj["text"]!!.jsonPrimitive.content, - start = parsePosition(obj["start"]!!.jsonObject), - end = parsePosition(obj["end"]!!.jsonObject) - ) - } -} - -internal fun parseKsonValue(obj: JsonObject): KsonValue { - val start = parsePosition(obj["start"]!!.jsonObject) - val end = parsePosition(obj["end"]!!.jsonObject) - - return when (val type = obj["type"]!!.jsonPrimitive.content) { - "OBJECT" -> { - val properties = obj["properties"]!!.jsonObject.mapValues { (_, v) -> parseKsonValue(v.jsonObject) } - val propertyKeys = obj["propertyKeys"]!!.jsonObject.mapValues { (_, v) -> - val keyObj = v.jsonObject - KsonValue.KsonString( - value = keyObj["value"]!!.jsonPrimitive.content, - internalStart = parsePosition(keyObj["start"]!!.jsonObject), - internalEnd = parsePosition(keyObj["end"]!!.jsonObject) - ) - } - KsonValue.KsonObject(properties, propertyKeys, start, end) - } - "ARRAY" -> { - val elements = obj["elements"]!!.jsonArray.map { parseKsonValue(it.jsonObject) } - KsonValue.KsonArray(elements, start, end) - } - "STRING" -> KsonValue.KsonString(obj["value"]!!.jsonPrimitive.content, start, end) - "INTEGER" -> KsonValue.KsonNumber.Integer(obj["value"]!!.jsonPrimitive.int, start, end) - "DECIMAL" -> KsonValue.KsonNumber.Decimal(obj["value"]!!.jsonPrimitive.double, start, end) - "BOOLEAN" -> KsonValue.KsonBoolean(obj["value"]!!.jsonPrimitive.boolean, start, end) - "NULL" -> KsonValue.KsonNull(start, end) - "EMBED" -> KsonValue.KsonEmbed( - tag = obj["tag"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }, - content = obj["content"]!!.jsonPrimitive.content, - internalStart = start, - internalEnd = end - ) - else -> throw IllegalArgumentException("Unknown KsonValue type: $type") - } -} diff --git a/kson-lib/build.gradle.kts b/kson-lib/build.gradle.kts index 6db44e4b..25efb263 100644 --- a/kson-lib/build.gradle.kts +++ b/kson-lib/build.gradle.kts @@ -10,7 +10,7 @@ plugins { kotlin("multiplatform") id("com.vanniktech.maven.publish") version "0.30.0" id("org.jetbrains.dokka") version "2.0.0" - id("nl.ochagavia.krossover") version "1.0.6" + id("nl.ochagavia.krossover") version "1.0.7" } repositories { @@ -40,13 +40,24 @@ kotlin { val commonMain by getting { dependencies { implementation(project(":")) + implementation(project(":kson-service-api")) } } val commonTest by getting { + dependencies { + implementation(project(":kson-service-tests")) + } + } + val jsTest by getting { dependencies { implementation(kotlin("test")) } } + val jvmTest by getting { + dependencies { + implementation(kotlin("test-junit5")) + } + } } } @@ -65,8 +76,8 @@ krossover { jniSysModule = "kson_sys" outputDir = Path("${rootProject.projectDir}/lib-rust/kson/src/generated") returnTypeMappings = listOf( - ReturnTypeMapping("org.kson.Result", "std::result::Result", "crate::kson_result_into_rust_result"), - ReturnTypeMapping("org.kson.SchemaResult", "std::result::Result", "crate::kson_schema_result_into_rust_result") + ReturnTypeMapping("org.kson.api.Result", "std::result::Result", "crate::kson_result_into_rust_result"), + ReturnTypeMapping("org.kson.api.SchemaResult", "std::result::Result", "crate::kson_schema_result_into_rust_result") ) } } @@ -114,7 +125,7 @@ tasks.register("copyNodeDistribution") { * * This task is used when you need to prepare the production library with its dependencies * installed. - * + * * This will: * 1. Build the production library (via jsNodeProductionLibraryDistribution) * 2. Run 'npm install' in build/dist/js/productionLibrary diff --git a/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt b/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt index 00d0cfcc..af29c108 100644 --- a/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt +++ b/kson-lib/src/commonMain/kotlin/org/kson/Kson.kt @@ -5,18 +5,19 @@ package org.kson import org.kson.Kson.parseSchema import org.kson.Kson.publishMessages +import org.kson.api.* import org.kson.parser.* import org.kson.parser.messages.MessageSeverity as InternalMessageSeverity import org.kson.schema.JsonSchema -import org.kson.tools.InternalEmbedRule -import org.kson.tools.FormattingStyle as InternalFormattingStyle import org.kson.tools.IndentType as InternalIndentType +import org.kson.tools.FormattingStyle as InternalFormattingStyle +import org.kson.tools.InternalEmbedRule import org.kson.tools.KsonFormatterConfig import org.kson.value.navigation.json_pointer.ExperimentalJsonPointerGlobLanguage import org.kson.value.navigation.json_pointer.JsonPointerGlob import org.kson.parser.TokenType as InternalTokenType import org.kson.parser.Token as InternalToken -import org.kson.validation.SourceContext +import org.kson.validation.SourceContext as InternalSourceContext import org.kson.value.KsonValue as InternalKsonValue import org.kson.value.KsonObject as InternalKsonObject import org.kson.value.KsonList as InternalKsonList @@ -30,7 +31,7 @@ import kotlin.js.JsExport /** * The [Kson](https://kson.org) language */ -object Kson { +object Kson : KsonService { /** * Formats Kson source with the specified formatting options. * @@ -38,7 +39,7 @@ object Kson { * @param formatOptions The formatting options to apply * @return The formatted Kson source */ - fun format(kson: String, formatOptions: FormatOptions = FormatOptions()): String { + override fun format(kson: String, formatOptions: FormatOptions): String { return org.kson.tools.format(kson, formatOptions.toInternal()) } @@ -49,7 +50,7 @@ object Kson { * @param options Options for the JSON transpilation * @return A Result containing either the Json output or error messages */ - fun toJson(kson: String, options: TranspileOptions.Json = TranspileOptions.Json()): Result { + override fun toJson(kson: String, options: TranspileOptions.Json): Result { val compileConfig = Json( retainEmbedTags = options.retainEmbedTags, ) @@ -68,7 +69,7 @@ object Kson { * @param options Options for the YAML transpilation * @return A Result containing either the Yaml output or error messages */ - fun toYaml(kson: String, options: TranspileOptions.Yaml = TranspileOptions.Yaml()): Result { + override fun toYaml(kson: String, options: TranspileOptions.Yaml): Result { val compileConfig = CompileTarget.Yaml( retainEmbedTags = options.retainEmbedTags, ) @@ -86,10 +87,10 @@ object Kson { * @param kson The Kson source to analyze * @param filepath Filepath of the document being analyzed */ - fun analyze(kson: String, filepath: String? = null) : Analysis { + override fun analyze(kson: String, filepath: String?) : Analysis { val parseResult = KsonCore.parseToAst( kson, - CoreCompileConfig(sourceContext = SourceContext(filepath)) + CoreCompileConfig(sourceContext = InternalSourceContext(filepath)) ) val tokens = convertTokens(parseResult.lexedTokens) val messages = publishMessages(parseResult.messages) @@ -103,7 +104,7 @@ object Kson { * @param schemaKson The Kson source defining a Json Schema * @return A SchemaValidator that can validate Kson documents against the schema */ - fun parseSchema(schemaKson: String): SchemaResult { + override fun parseSchema(schemaKson: String): SchemaResult { val schemaParseResult = KsonCore.parseSchema(schemaKson) val messages = publishMessages(schemaParseResult.messages) val jsonSchema = schemaParseResult.jsonSchema @@ -129,34 +130,17 @@ object Kson { Message( message = it.message.toString(), severity = severity, - start = Position(it.location.start), - end = Position(it.location.end) + start = it.location.start.toPosition(), + end = it.location.end.toPosition() ) } } } - -/** - * Result of a Kson conversion operation - */ -sealed class Result { - class Success(val output: String) : Result() - class Failure(val errors: List) : Result() -} - -/** - * A [parseSchema] result - */ -sealed class SchemaResult { - class Success(val schemaValidator: SchemaValidator) : SchemaResult() - class Failure(val errors: List) : SchemaResult() -} - /** * A validator that can check if Kson source conforms to a schema. */ -class SchemaValidator internal constructor(private val schema: JsonSchema) { +class SchemaValidator internal constructor(private val schema: JsonSchema) : SchemaValidatorService { /** * Validates the given Kson source against this validator's schema. * @param kson The Kson source to validate @@ -164,10 +148,10 @@ class SchemaValidator internal constructor(private val schema: JsonSchema) { * * @return A list of validation error messages, or empty list if valid */ - fun validate(kson: String, filepath: String? = null): List { + override fun validate(kson: String, filepath: String?): List { val astParseResult = KsonCore.parseToAst( kson, - CoreCompileConfig(sourceContext = SourceContext(filepath)) + CoreCompileConfig(sourceContext = InternalSourceContext(filepath)) ) if (astParseResult.hasErrors()) { return publishMessages(astParseResult.messages) @@ -176,7 +160,7 @@ class SchemaValidator internal constructor(private val schema: JsonSchema) { val messageSink = MessageSink() val ksonValue = astParseResult.ksonValue if (ksonValue != null) { - schema.validate(ksonValue, messageSink, SourceContext(filepath)) + schema.validate(ksonValue, messageSink, InternalSourceContext(filepath)) } return publishMessages(messageSink.loggedMessages()) @@ -184,211 +168,46 @@ class SchemaValidator internal constructor(private val schema: JsonSchema) { } /** - * A rule for formatting string values at specific paths as embed blocks. - * - * When formatting KSON, strings at paths matching [pathPattern] will be rendered - * as embed blocks instead of regular strings. + * Map [FormatOptions] to [org.kson.tools.KsonFormatterConfig] that is used internally to format a Kson document. * - * **Warning:** JsonPointerGlob syntax is experimental and may change in future versions. + * @throws IllegalArgumentException when one or more embed rules have an invalid [JsonPointerGlob] */ -class EmbedRule private constructor( - val pathPattern: String, - val tag: String? = null -) { - @OptIn(ExperimentalJsonPointerGlobLanguage::class) - internal val parsedPathPattern: JsonPointerGlob = JsonPointerGlob(pathPattern) - - companion object { - /** - * Builds a new [EmbedRule]. - * - * @param pathPattern A JsonPointerGlob pattern (e.g., "/scripts/ *", "/queries/ **") - * @param tag Optional embed tag to include (e.g., "yaml", "sql", "bash") - * @return [EmbedRuleResult.Success] if [pathPattern] is a valid JsonPointerGlob, otherwise [EmbedRuleResult.Failure] - * - * Example: - * ```kotlin - * EmbedRule.fromPathPattern("/scripts/ *", tag = "bash") // Match all values under "scripts" - * EmbedRule.fromPathPattern("/config/description") // Match exact path, no tag - * ``` - */ - fun fromPathPattern(pathPattern: String, tag: String? = null): EmbedRuleResult { - return try { - EmbedRuleResult.Success(EmbedRule(pathPattern, tag)) - } catch (e: IllegalArgumentException) { - EmbedRuleResult.Failure(e.message ?: "") - } - } +@OptIn(ExperimentalJsonPointerGlobLanguage::class) +internal fun FormatOptions.toInternal(): KsonFormatterConfig { + val indentType = when (indentType) { + is IndentType.Spaces -> InternalIndentType.Space((indentType as IndentType.Spaces).size) + is IndentType.Tabs -> InternalIndentType.Tab() } -} - -sealed class EmbedRuleResult { - data class Success(val embedRule: EmbedRule) : EmbedRuleResult() - data class Failure(val message: String) : EmbedRuleResult() -} - - -/** - * Options for formatting Kson output. - * - * @param indentType The type of indentation to use (spaces or tabs) - * @param formattingStyle The formatting style (PLAIN, DELIMITED, COMPACT, CLASSIC) - * @param embedBlockRules Rules for formatting specific paths as embed blocks - */ -class FormatOptions( - val indentType: IndentType = IndentType.Spaces(2), - val formattingStyle: FormattingStyle = FormattingStyle.PLAIN, - val embedBlockRules: List = emptyList() -) { - /** - * Map [FormatOptions] to [KsonFormatterConfig] that is used internally to format a Kson document. - */ - internal fun toInternal(): KsonFormatterConfig { - val indentType = when (indentType) { - is IndentType.Spaces -> InternalIndentType.Space(indentType.size) - is IndentType.Tabs -> InternalIndentType.Tab() - } - val formattingStyle = when (formattingStyle){ - FormattingStyle.PLAIN -> InternalFormattingStyle.PLAIN - FormattingStyle.DELIMITED -> InternalFormattingStyle.DELIMITED - FormattingStyle.COMPACT -> InternalFormattingStyle.COMPACT - FormattingStyle.CLASSIC -> InternalFormattingStyle.CLASSIC - } + val formattingStyle = when (formattingStyle){ + FormattingStyle.PLAIN -> InternalFormattingStyle.PLAIN + FormattingStyle.DELIMITED -> InternalFormattingStyle.DELIMITED + FormattingStyle.COMPACT -> InternalFormattingStyle.COMPACT + FormattingStyle.CLASSIC -> InternalFormattingStyle.CLASSIC + } - val internalEmbedRules = embedBlockRules.map { rule -> - InternalEmbedRule( - pathPattern = rule.parsedPathPattern, - tag = rule.tag - ) + val internalEmbedRules = embedBlockRules.mapNotNull { rule -> + val pathPattern = try { + JsonPointerGlob(rule.pathPattern) + } catch (e: IllegalArgumentException) { + // Discard invalid `jsonPointerGlob` + return@mapNotNull null } - - return KsonFormatterConfig( - indentType = indentType, - formattingStyle = formattingStyle, - embedBlockRules = internalEmbedRules + InternalEmbedRule( + pathPattern = pathPattern, + tag = rule.tag ) } -} -/** - * Core interface for transpilation options shared across all output formats. - */ -sealed class TranspileOptions { - abstract val retainEmbedTags: Boolean - - /** - * Options for transpiling Kson to JSON. - */ - class Json( - override val retainEmbedTags: Boolean = true - ) : TranspileOptions() - - /** - * Options for transpiling Kson to YAML. - */ - class Yaml( - override val retainEmbedTags: Boolean = true - ) : TranspileOptions() -} - -/** - * [FormattingStyle] options for Kson Output - */ -enum class FormattingStyle{ - /** - * These values map to [InternalFormattingStyle] - */ - PLAIN, - DELIMITED, - COMPACT, - CLASSIC -} - -/** - * Options for indenting Kson Output - */ -sealed class IndentType { - /** Use spaces for indentation with the specified count */ - class Spaces(val size: Int = 2) : IndentType() - - /** Use tabs for indentation */ - data object Tabs : IndentType() -} - -/** - * The result of statically analyzing a Kson document - */ -class Analysis internal constructor( - val errors: List, - val tokens: List, - val ksonValue: KsonValue? -) - -/** - * [Token] produced by the lexing phase of a Kson parse - */ -class Token internal constructor( - val tokenType: TokenType, - val text: String, - val start: Position, - val end: Position) - -enum class TokenType { - /** - * See [convertTokens] for the mapping from our [org.kson.parser.TokenType]/[InternalTokenType] tokens - */ - CURLY_BRACE_L, - CURLY_BRACE_R, - SQUARE_BRACKET_L, - SQUARE_BRACKET_R, - ANGLE_BRACKET_L, - ANGLE_BRACKET_R, - COLON, - DOT, - END_DASH, - COMMA, - COMMENT, - EMBED_OPEN_DELIM, - EMBED_CLOSE_DELIM, - EMBED_TAG, - EMBED_PREAMBLE_NEWLINE, - EMBED_CONTENT, - FALSE, - UNQUOTED_STRING, - ILLEGAL_CHAR, - LIST_DASH, - NULL, - NUMBER, - STRING_OPEN_QUOTE, - STRING_CLOSE_QUOTE, - STRING_CONTENT, - TRUE, - WHITESPACE, - EOF + return KsonFormatterConfig( + indentType = indentType, + formattingStyle = formattingStyle, + embedBlockRules = internalEmbedRules + ) } -/** - * Represents a message logged during Kson processing - */ -class Message internal constructor(val message: String, val severity: MessageSeverity, val start: Position, val end: Position) - -/** - * Represents the severity of a [Message] - */ -enum class MessageSeverity{ - ERROR, - WARNING, -} - -/** - * A zero-based line/column position in a document - * - * @param line The line number where the error occurred (0-based) - * @param column The column number where the error occurred (0-based) - */ -class Position internal constructor(val line: Int, val column: Int) { - internal constructor(coordinates: Coordinates) : this(coordinates.line, coordinates.column) +internal fun Coordinates.toPosition(): Position { + return Position(line, column) } /** @@ -505,8 +324,8 @@ private fun createPublicToken(publicTokenType: TokenType, internalToken: Interna return Token( publicTokenType, internalToken.lexeme.text, - Position(internalToken.lexeme.location.start), - Position(internalToken.lexeme.location.end) + internalToken.lexeme.location.start.toPosition(), + internalToken.lexeme.location.end.toPosition() ) } @@ -527,22 +346,22 @@ internal fun convertValue(ksonValue: InternalKsonValue): KsonValue { it.value.propName ) as KsonValue.KsonString) }.toMap(), - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } is InternalKsonList -> { KsonValue.KsonArray( elements = ksonValue.elements.map { convertValue(it) }, - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } is InternalKsonString -> { KsonValue.KsonString( value = ksonValue.value, - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } is InternalKsonNumber -> { @@ -550,151 +369,37 @@ internal fun convertValue(ksonValue: InternalKsonValue): KsonValue { if (isInteger) { KsonValue.KsonNumber.Integer( value = ksonValue.value.asString.toInt(), - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } else { KsonValue.KsonNumber.Decimal( value = ksonValue.value.asString.toDouble(), - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } } is InternalKsonBoolean -> { KsonValue.KsonBoolean( value = ksonValue.value, - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } is InternalKsonNull -> { KsonValue.KsonNull( - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } is InternalEmbedBlock -> { KsonValue.KsonEmbed( tag = ksonValue.embedTag?.value, content = ksonValue.embedContent.value, - internalStart = Position(ksonValue.location.start), - internalEnd = Position(ksonValue.location.end) + internalStart = ksonValue.location.start.toPosition(), + internalEnd = ksonValue.location.end.toPosition() ) } } } - -/** - * Type discriminator for KsonValue subclasses - */ -enum class KsonValueType { - OBJECT, - ARRAY, - STRING, - INTEGER, - DECIMAL, - BOOLEAN, - NULL, - EMBED -} - -/** - * Represents a parsed [InternalKsonValue] in the public API - */ -sealed class KsonValue(val start: Position, val end: Position) { - /** - * Type discriminator for easier type checking in TypeScript/JavaScript - */ - abstract val type: KsonValueType - /** - * A Kson object with key-value pairs - */ - @ConsistentCopyVisibility - data class KsonObject internal constructor( - val properties: Map, - val propertyKeys: Map, - private val internalStart: Position, - private val internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.OBJECT - } - - /** - * A Kson array with elements - */ - class KsonArray internal constructor( - val elements: List, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.ARRAY - } - - /** - * A Kson string value - */ - class KsonString internal constructor( - val value: String, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.STRING - } - - /** - * A Kson number value. - */ - sealed class KsonNumber(start: Position, end: Position) : KsonValue(start, end) { - - class Integer internal constructor( - val value: Int, - val internalStart: Position, - val internalEnd: Position - ) : KsonNumber(internalStart, internalEnd){ - override val type = KsonValueType.INTEGER - } - - class Decimal internal constructor( - val value: Double, - internalStart: Position, - internalEnd: Position - ) : KsonNumber(internalStart, internalEnd) { - override val type = KsonValueType.DECIMAL - } - } - - - /** - * A Kson boolean value - */ - class KsonBoolean internal constructor( - val value: Boolean, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.BOOLEAN - } - - /** - * A Kson null value - */ - class KsonNull internal constructor( - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.NULL - } - - /** - * A Kson embed block - */ - class KsonEmbed internal constructor( - val tag: String?, - val content: String, - internalStart: Position, - internalEnd: Position - ) : KsonValue(internalStart, internalEnd) { - override val type = KsonValueType.EMBED - } -} diff --git a/kson-lib/src/commonTest/kotlin/org/kson/KsonSmokeTest.kt b/kson-lib/src/commonTest/kotlin/org/kson/KsonSmokeTest.kt index d79f3b6f..c7c34384 100644 --- a/kson-lib/src/commonTest/kotlin/org/kson/KsonSmokeTest.kt +++ b/kson-lib/src/commonTest/kotlin/org/kson/KsonSmokeTest.kt @@ -1,401 +1,11 @@ package org.kson +import org.kson.api.* import kotlin.test.* -/** - * Tests for the public [Kson] interface. Note we explicitly call this out as a [KsonSmokeTest]: since the underlying - * code that [Kson] puts an interface on it well-tested, we only need to smoke test each [Kson] method to be - * confident in this code - */ -class KsonSmokeTest { - - @Test - fun testFormat_withDefaultOptions() { - val input = """{"name": "test", "value": 123}""" - val formatted = Kson.format(input) - assertEquals(""" - name: test - value: 123 - """.trimIndent(), - formatted) - } - - @Test - fun testFormat_withSpacesOption() { - val input = """{"name": "test", "value": 123}""" - val formatted = Kson.format(input, FormatOptions(IndentType.Spaces(6))) - assertEquals(""" - name: test - value: 123 - """.trimIndent(), - formatted) - } - - @Test - fun testFormat_withDelimitedOption() { - val input = """{"name": "test", "list": [1, 2, 3]}""" - val formatted = Kson.format(input, FormatOptions(formattingStyle = FormattingStyle.DELIMITED)) - assertEquals( - """ - { - name: test - list: < - - 1 - - 2 - - 3 - > - } - """.trimIndent(), - formatted - ) - } - - @Test - fun testFormat_withTabsOption() { - val input = """{"name": "test", "value": 123}""" - val result = Kson.format(input, FormatOptions(IndentType.Tabs)) - assertIs(result) - assertTrue(result.isNotEmpty()) - } - - @Test - fun testToJson_success() { - val input = """{"name": "test", "value": 123}""" - val result = Kson.toJson(input) - assertIs(result) - assertTrue(result.output.isNotEmpty()) - } - - @Test - fun testToJson_failure() { - val input = """{"invalid": }""" - val result = Kson.toJson(input) - assertIs(result) - assertTrue(result.errors.isNotEmpty()) - val error = result.errors.first() - assertIs(error.message) - assertIs(error.start) - assertIs(error.end) - assertTrue(error.start.line == 0) - assertTrue(error.start.column > 0) - } - - @Test - fun testToYaml_success() { - val input = """{"name": "test", "value": 123}""" - val result = Kson.toYaml(input) - assertIs(result) - assertTrue(result.output.isNotEmpty()) - } - - @Test - fun testToYaml_failure() { - val input = """{"invalid": }""" - val result = Kson.toYaml(input) - assertIs(result) - assertTrue(result.errors.isNotEmpty()) - } - - @Test - fun testAnalyze() { - val input = """{"name": "test", "value": 123}""" - val analysis = Kson.analyze(input) - assertIs(analysis) - assertIs>(analysis.errors) - assertIs>(analysis.tokens) - assertTrue(analysis.tokens.isNotEmpty()) - - val token = analysis.tokens.first() - assertIs(token.tokenType) - assertIs(token.text) - assertIs(token.start) - assertIs(token.end) - } - - @Test - fun testAnalysisUnclosedString() { - val analysis = Kson.analyze("'unclosed string") - assertIs(analysis) - assertIs>(analysis.errors) - assertIs>(analysis.tokens) - assertTrue(analysis.tokens.isNotEmpty()) - - val token = analysis.tokens.first() - assertIs(token.tokenType) - assertIs(token.text) - assertIs(token.start) - assertIs(token.end) +class KsonSmokeTest : KsonServiceSmokeTest() { + override fun createService(): KsonService { + return Kson } - @Test - fun testAnalyze_tokens() { - val input = """name: test, complexString: "this has legal \n and illegal \x escapes and \u3456 unicode"""" - val tokens = Kson.analyze(input).tokens - assertEquals( - listOf(TokenType.UNQUOTED_STRING, - TokenType.COLON, - TokenType.UNQUOTED_STRING, - TokenType.COMMA, - TokenType.UNQUOTED_STRING, - TokenType.COLON, - TokenType.STRING_OPEN_QUOTE, - TokenType.STRING_CONTENT, - TokenType.STRING_CLOSE_QUOTE, - TokenType.EOF), - tokens.map { it.tokenType }) - } - - @Test - fun testAnalyze_value() { - val input = """ - key: value - list: - - 1 - - 2.1 - - 3E5 - embed:%tag - %%""".trimIndent() - val value = Kson.analyze(input).ksonValue - assertNotNull(value) - assertTrue(value is KsonValue.KsonObject) - - assertEquals(3, value.properties.size) - - // Check root object location (should span entire document) - assertEquals(0, value.start.line) - assertEquals(0, value.start.column) - assertEquals(6, value.end.line) - assertEquals(2, value.end.column) - - // Check "key" property - val keyValue = value.properties.get("key") - assertTrue(keyValue is KsonValue.KsonString) - assertEquals("value", keyValue.value) - assertEquals(0, keyValue.start.line) - assertEquals(5, keyValue.start.column) - assertEquals(0, keyValue.end.line) - assertEquals(10, keyValue.end.column) - - // Check "list" property - val listValue = value.properties.get("list") - assertTrue(listValue is KsonValue.KsonArray) - assertEquals(3, listValue.elements.size) - assertEquals(2, listValue.start.line) - assertEquals(2, listValue.start.column) - assertEquals(4, listValue.end.line) - assertEquals(7, listValue.end.column) - - // Check list elements - val firstElement = listValue.elements[0] - assertTrue(firstElement is KsonValue.KsonNumber.Integer) - assertEquals(1, firstElement.value) - assertEquals(2, firstElement.start.line) - assertEquals(4, firstElement.start.column) - assertEquals(2, firstElement.end.line) - assertEquals(5, firstElement.end.column) - - val secondElement = listValue.elements[1] - assertTrue(secondElement is KsonValue.KsonNumber.Decimal) - assertEquals(2.1, secondElement.value) - assertEquals(3, secondElement.start.line) - assertEquals(4, secondElement.start.column) - assertEquals(3, secondElement.end.line) - assertEquals(7, secondElement.end.column) - - val thirdElement = listValue.elements[2] - assertTrue(thirdElement is KsonValue.KsonNumber.Decimal) - assertEquals(3e5, thirdElement.value) - assertEquals(4, thirdElement.start.line) - assertEquals(4, thirdElement.start.column) - assertEquals(4, thirdElement.end.line) - assertEquals(7, thirdElement.end.column) - - // Check "embed" property - val embedValue = value.properties.get("embed") - assertTrue(embedValue is KsonValue.KsonEmbed) - assertEquals("tag", embedValue.tag) - assertEquals("", embedValue.content) - assertEquals(5, embedValue.start.line) - assertEquals(6, embedValue.start.column) - assertEquals(6, embedValue.end.line) - assertEquals(2, embedValue.end.column) - } - - @Test - fun testParseSchema_success() { - val schemaKson = """{ - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "number"} - } - }""" - val result = Kson.parseSchema(schemaKson) - assertIs(result) - assertIs(result.schemaValidator) - } - - @Test - fun testParseSchema_failure() { - val invalidSchema = """{"type": }""" - val result = Kson.parseSchema(invalidSchema) - assertIs(result) - assertTrue(result.errors.isNotEmpty()) - } - - @Test - fun testSchemaValidator_validInput() { - val schemaKson = """{ - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "number"} - } - }""" - val schemaResult = Kson.parseSchema(schemaKson) - assertIs(schemaResult) - - val validator = schemaResult.schemaValidator - val validKson = """{"name": "John", "age": 30}""" - val errors = validator.validate(validKson) - assertTrue(errors.isEmpty()) - } - - @Test - fun testSchemaValidator_invalidInput() { - val schemaKson = """{ - "type": "object", - "properties": { - "name": {"type": "string"}, - "age": {"type": "number"} - }, - "required": ["name", "age"] - }""" - val schemaResult = Kson.parseSchema(schemaKson) - assertIs(schemaResult) - - val validator = schemaResult.schemaValidator - val invalidKson = """{"name": "John"}""" - val errors = validator.validate(invalidKson) - assertTrue(errors.isNotEmpty()) - } - - @Test - fun testSchemaValidator_validateWithParseErrors() { - val schemaKson = """{"type": "object"}""" - val schemaResult = Kson.parseSchema(schemaKson) - assertIs(schemaResult) - - val validator = schemaResult.schemaValidator - val invalidKson = """{"invalid": }""" - val errors = validator.validate(invalidKson) - assertTrue(errors.isNotEmpty()) - } - - @Test - fun testPropertyKeys_basicAccess() { - val input = """ - name: John - age: 30 - city: 'New York' - """.trimIndent() - val analysis = Kson.analyze(input) - val value = analysis.ksonValue - assertNotNull(value) - assertTrue(value is KsonValue.KsonObject) - - // Verify all keys are present in propertyKeys - assertEquals(3, value.propertyKeys.size) - assertTrue(value.propertyKeys.containsKey("name")) - assertTrue(value.propertyKeys.containsKey("age")) - assertTrue(value.propertyKeys.containsKey("city")) - - // Verify propertyKeys contains KsonString values - val nameKey = value.propertyKeys.get("name") - assertNotNull(nameKey) - assertEquals("name", nameKey.value) - - val ageKey = value.propertyKeys.get("age") - assertNotNull(ageKey) - assertEquals("age", ageKey.value) - } - - @Test - fun testPropertyKeys_withPositionInformation() { - val input = """ - name: John - age: 30 - """.trimIndent() - val analysis = Kson.analyze(input) - val value = analysis.ksonValue - assertNotNull(value) - assertTrue(value is KsonValue.KsonObject) - - // Verify position information for keys - val nameKey = value.propertyKeys.get("name") - assertNotNull(nameKey) - assertEquals(0, nameKey.start.line) - assertEquals(0, nameKey.start.column) - assertEquals(0, nameKey.end.line) - assertEquals(4, nameKey.end.column) - - val ageKey = value.propertyKeys.get("age") - assertNotNull(ageKey) - assertEquals(1, ageKey.start.line) - assertEquals(0, ageKey.start.column) - assertEquals(1, ageKey.end.line) - assertEquals(3, ageKey.end.column) - } - - @Test - fun testPropertyKeys_emptyObject() { - val input = "{}" - val analysis = Kson.analyze(input) - val value = analysis.ksonValue - assertNotNull(value) - assertTrue(value is KsonValue.KsonObject) - - // Empty object should have no propertyKeys - assertEquals(0, value.propertyKeys.size) - assertEquals(0, value.properties.size) - } - - @Test - fun testEmbedRule_validPattern() { - val ruleResult = EmbedRule.fromPathPattern("/scripts/*", tag = "bash") - val rule = assertIs(ruleResult).embedRule - assertEquals("/scripts/*", rule.pathPattern) - assertEquals("bash", rule.tag) - } - - @Test - fun testEmbedRule_invalidPattern() { - val ruleResult = EmbedRule.fromPathPattern("invalid[pattern") - assertIs(ruleResult) - } - - @Test - fun testFormat_withEmbedBlockRules() { - val input = """ - scripts: - build: "make all" - """.trimIndent() - - val rule = (EmbedRule.fromPathPattern("/scripts/build", tag = "bash") as EmbedRuleResult.Success).embedRule - val formatted = Kson.format( - input, - FormatOptions( - embedBlockRules = listOf(rule) - ) - ) - assertEquals( - """ - scripts: - build: %bash - make all - %% - """.trimIndent(), - formatted - ) - } } diff --git a/kson-service-api/build.gradle.kts b/kson-service-api/build.gradle.kts new file mode 100644 index 00000000..471cbb37 --- /dev/null +++ b/kson-service-api/build.gradle.kts @@ -0,0 +1,59 @@ +plugins { + kotlin("multiplatform") + id("com.vanniktech.maven.publish") version "0.30.0" + id("org.jetbrains.dokka") version "2.0.0" +} + +repositories { + mavenCentral() +} + +group = "org.kson" +// [[kson-version-num]] - base version defined in buildSrc/src/main/kotlin/org/kson/KsonVersion.kt +val isRelease = project.findProperty("release") == "true" +version = org.kson.KsonVersion.getVersion(isRelease = isRelease) + +kotlin { + jvm() + js(IR) { + browser() + nodejs() + binaries.library() + useEsModules() + generateTypeScriptDefinitions() + } +} + +mavenPublishing { + publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL, automaticRelease = false) + signAllPublications() + + coordinates("org.kson", "kson-service-api", org.kson.KsonVersion.getPublishVersion(rootProject.projectDir, isRelease = isRelease)) + + pom { + name.set("KSON API") + description.set("A 💌 to the humans maintaining computer configurations") + url.set("https://kson.org") + + licenses { + license { + name.set("Apache-2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + + developers { + developer { + id.set("dmarcotte") + name.set("Daniel Marcotte") + email.set("kson@kson.org") + } + } + + scm { + connection.set("scm:git:https://github.com/kson-org/kson.git") + developerConnection.set("scm:git:git@github.com:kson-org/kson.git") + url.set("https://github.com/kson-org/kson") + } + } +} diff --git a/kson-service-api/readme.md b/kson-service-api/readme.md new file mode 100644 index 00000000..99cb187f --- /dev/null +++ b/kson-service-api/readme.md @@ -0,0 +1,4 @@ +# kson-service-api + +KSON's public interface for Kotlin, decoupled from any specific implementation. See `kson-lib` for +the main implementation. diff --git a/kson-service-api/src/commonMain/kotlin/org/kson/api/FormatOptions.kt b/kson-service-api/src/commonMain/kotlin/org/kson/api/FormatOptions.kt new file mode 100644 index 00000000..56cddf5f --- /dev/null +++ b/kson-service-api/src/commonMain/kotlin/org/kson/api/FormatOptions.kt @@ -0,0 +1,48 @@ +package org.kson.api + +/** + * Options for formatting Kson output. + * + * @param indentType The type of indentation to use (spaces or tabs) + * @param formattingStyle The formatting style (PLAIN, DELIMITED, COMPACT, CLASSIC) + * @param embedBlockRules Rules for formatting specific paths as embed blocks + */ +class FormatOptions( + val indentType: IndentType = IndentType.Spaces(2), + val formattingStyle: FormattingStyle = FormattingStyle.PLAIN, + val embedBlockRules: List = emptyList() +) + +/** + * [FormattingStyle] options for Kson Output + */ +enum class FormattingStyle { + PLAIN, + DELIMITED, + COMPACT, + CLASSIC +} + +/** + * Options for indenting Kson Output + */ +sealed class IndentType { + /** Use spaces for indentation with the specified count */ + class Spaces(val size: Int = 2) : IndentType() + + /** Use tabs for indentation */ + data object Tabs : IndentType() +} + +/** + * A rule for formatting string values at specific paths as embed blocks. + * + * When formatting KSON, strings at paths matching [pathPattern] will be rendered + * as embed blocks instead of regular strings. + * + * **Warning:** JsonPointerGlob syntax is experimental and may change in future versions. + */ +class EmbedRule( + val pathPattern: String, + val tag: String? = null +) diff --git a/kson-service-api/src/commonMain/kotlin/org/kson/api/KsonService.kt b/kson-service-api/src/commonMain/kotlin/org/kson/api/KsonService.kt new file mode 100644 index 00000000..8beca85f --- /dev/null +++ b/kson-service-api/src/commonMain/kotlin/org/kson/api/KsonService.kt @@ -0,0 +1,261 @@ +package org.kson.api + +interface KsonService { + /** + * Formats Kson source with the specified formatting options. + * + * @param kson The Kson source to format + * @param formatOptions The formatting options to apply + * @return The formatted Kson source + */ + fun format(kson: String, formatOptions: FormatOptions = FormatOptions()): String + + /** + * Converts Kson to Json. + * + * @param kson The Kson source to convert + * @param options Options for the JSON transpilation + * @return A Result containing either the Json output or error messages + */ + fun toJson(kson: String, options: TranspileOptions.Json = TranspileOptions.Json()): Result + + /** + * Converts Kson to Yaml, preserving comments + * + * @param kson The Kson source to convert + * @param options Options for the YAML transpilation + * @return A Result containing either the Yaml output or error messages + */ + fun toYaml(kson: String, options: TranspileOptions.Yaml = TranspileOptions.Yaml()): Result + + /** + * Statically analyze the given Kson and return an [Analysis] object containing any messages generated along with a + * tokenized version of the source. Useful for tooling/editor support. + * @param kson The Kson source to analyze + * @param filepath Filepath of the document being analyzed + */ + fun analyze(kson: String, filepath: String? = null): Analysis + + /** + * Parses a Kson schema definition and returns a validator for that schema. + * + * @param schemaKson The Kson source defining a Json Schema + * @return A SchemaValidator that can validate Kson documents against the schema + */ + fun parseSchema(schemaKson: String): SchemaResult +} + +/** + * Result of a Kson conversion operation + */ +sealed class Result { + class Success(val output: String) : Result() + class Failure(val errors: List) : Result() +} + +/** + * A [KsonService.parseSchema] result + */ +sealed class SchemaResult { + class Success(val schemaValidator: SchemaValidatorService) : SchemaResult() + class Failure(val errors: List) : SchemaResult() +} + +/** + * A validator that can check if Kson source conforms to a schema. + */ +interface SchemaValidatorService { + /** + * Validates the given Kson source against this validator's schema. + * @param kson The Kson source to validate + * @param filepath Optional filepath of the document being validated, used by validators to determine which rules to apply + * + * @return A list of validation error messages, or empty list if valid + */ + fun validate(kson: String, filepath: String? = null): List +} + +/** + * Represents a message logged during Kson processing + */ +class Message(val message: String, val severity: MessageSeverity, val start: Position, val end: Position) + +/** + * Represents the severity of a [Message] + */ +enum class MessageSeverity { + ERROR, + WARNING, +} + +/** + * A zero-based line/column position in a document + * + * @param line The line number where the error occurred (0-based) + * @param column The column number where the error occurred (0-based) + */ +class Position(val line: Int, val column: Int) + +class SourceContext(val filepath: String?) + +/** + * The result of statically analyzing a Kson document + */ +class Analysis( + val errors: List, + val tokens: List, + val ksonValue: KsonValue? +) + +/** + * [Token] produced by the lexing phase of a Kson parse + */ +class Token( + val tokenType: TokenType, + val text: String, + val start: Position, + val end: Position) + +enum class TokenType { + CURLY_BRACE_L, + CURLY_BRACE_R, + SQUARE_BRACKET_L, + SQUARE_BRACKET_R, + ANGLE_BRACKET_L, + ANGLE_BRACKET_R, + COLON, + DOT, + END_DASH, + COMMA, + COMMENT, + EMBED_OPEN_DELIM, + EMBED_CLOSE_DELIM, + EMBED_TAG, + EMBED_PREAMBLE_NEWLINE, + EMBED_CONTENT, + FALSE, + UNQUOTED_STRING, + ILLEGAL_CHAR, + LIST_DASH, + NULL, + NUMBER, + STRING_OPEN_QUOTE, + STRING_CLOSE_QUOTE, + STRING_CONTENT, + TRUE, + WHITESPACE, + EOF +} + +/** + * Type discriminator for KsonValue subclasses + */ +enum class KsonValueType { + OBJECT, + ARRAY, + STRING, + INTEGER, + DECIMAL, + BOOLEAN, + NULL, + EMBED +} + +/** + * Represents a parsed KSON document + */ +sealed class KsonValue(val start: Position, val end: Position) { + /** + * Type discriminator for easier type checking in TypeScript/JavaScript + */ + abstract val type: KsonValueType + /** + * A Kson object with key-value pairs + */ + data class KsonObject( + val properties: Map, + val propertyKeys: Map, + private val internalStart: Position, + private val internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.OBJECT + } + + /** + * A Kson array with elements + */ + class KsonArray( + val elements: List, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.ARRAY + } + + /** + * A Kson string value + */ + class KsonString( + val value: String, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.STRING + } + + /** + * A Kson number value. + */ + sealed class KsonNumber(start: Position, end: Position) : KsonValue(start, end) { + + class Integer( + val value: Int, + val internalStart: Position, + val internalEnd: Position + ) : KsonNumber(internalStart, internalEnd){ + override val type = KsonValueType.INTEGER + } + + class Decimal( + val value: Double, + internalStart: Position, + internalEnd: Position + ) : KsonNumber(internalStart, internalEnd) { + override val type = KsonValueType.DECIMAL + } + } + + + /** + * A Kson boolean value + */ + class KsonBoolean( + val value: Boolean, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.BOOLEAN + } + + /** + * A Kson null value + */ + class KsonNull( + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.NULL + } + + /** + * A Kson embed block + */ + class KsonEmbed( + val tag: String?, + val content: String, + internalStart: Position, + internalEnd: Position + ) : KsonValue(internalStart, internalEnd) { + override val type = KsonValueType.EMBED + } +} diff --git a/kson-service-api/src/commonMain/kotlin/org/kson/api/TranspileOptions.kt b/kson-service-api/src/commonMain/kotlin/org/kson/api/TranspileOptions.kt new file mode 100644 index 00000000..6477fe5d --- /dev/null +++ b/kson-service-api/src/commonMain/kotlin/org/kson/api/TranspileOptions.kt @@ -0,0 +1,22 @@ +package org.kson.api + +/** + * Core interface for transpilation options shared across all output formats. + */ +sealed class TranspileOptions { + abstract val retainEmbedTags: Boolean + + /** + * Options for transpiling Kson to JSON. + */ + class Json( + override val retainEmbedTags: Boolean = true + ) : TranspileOptions() + + /** + * Options for transpiling Kson to YAML. + */ + class Yaml( + override val retainEmbedTags: Boolean = true + ) : TranspileOptions() +} diff --git a/kson-service-tests/build.gradle.kts b/kson-service-tests/build.gradle.kts new file mode 100644 index 00000000..3b04ff28 --- /dev/null +++ b/kson-service-tests/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("multiplatform") +} + +repositories { + mavenCentral() +} + +kotlin { + jvm() + js(IR) { + browser() + nodejs() + } + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":kson-service-api")) + api(kotlin("test")) + } + } + + val jvmMain by getting { + dependencies { + api(kotlin("test-junit5")) + } + } + } +} diff --git a/kson-service-tests/readme.md b/kson-service-tests/readme.md new file mode 100644 index 00000000..a8c8d3f8 --- /dev/null +++ b/kson-service-tests/readme.md @@ -0,0 +1,4 @@ +# kson-service-tests + +Tests for KSON's public Kotlin interface, decoupled from any specific implementation. The provided +test classes can be used through inheritance (see e.g., tests in `kson-lib`). diff --git a/kson-service-tests/src/commonMain/kotlin/org/kson/api/KsonServiceSmokeTest.kt b/kson-service-tests/src/commonMain/kotlin/org/kson/api/KsonServiceSmokeTest.kt new file mode 100644 index 00000000..c67dbbcd --- /dev/null +++ b/kson-service-tests/src/commonMain/kotlin/org/kson/api/KsonServiceSmokeTest.kt @@ -0,0 +1,412 @@ +package org.kson.api + +import kotlin.test.* + +/** + * Tests for the public [KsonService] interface. Note we explicitly call this out as a smoke test: since the underlying + * code that Kson puts an interface on is well-tested, we only need to smoke test each Kson method to be + * confident in this code. + * + * Implementations should subclass this and provide a [KsonService] instance via [createService]. + */ +abstract class KsonServiceSmokeTest { + abstract fun createService(): KsonService + + @Test + fun testFormat_withDefaultOptions() { + val input = """{"name": "test", "value": 123}""" + val formatted = createService().format(input) + assertEquals(""" + name: test + value: 123 + """.trimIndent(), + formatted) + } + + @Test + fun testFormat_withSpacesOption() { + val input = """{"name": "test", "value": 123}""" + val formatted = createService().format(input, FormatOptions(IndentType.Spaces(6))) + assertEquals(""" + name: test + value: 123 + """.trimIndent(), + formatted) + } + + @Test + fun testFormat_withDelimitedOption() { + val input = """{"name": "test", "list": [1, 2, 3]}""" + val formatted = createService().format(input, FormatOptions(formattingStyle = FormattingStyle.DELIMITED)) + assertEquals( + """ + { + name: test + list: < + - 1 + - 2 + - 3 + > + } + """.trimIndent(), + formatted + ) + } + + @Test + fun testFormat_withTabsOption() { + val input = """{"name": "test", "value": 123}""" + val result = createService().format(input, FormatOptions(IndentType.Tabs)) + assertIs(result) + assertTrue(result.isNotEmpty()) + } + + @Test + fun testToJson_success() { + val input = """{"name": "test", "value": 123}""" + val result = createService().toJson(input) + assertIs(result) + assertTrue(result.output.isNotEmpty()) + } + + @Test + fun testToJson_failure() { + val input = """{"invalid": }""" + val result = createService().toJson(input) + assertIs(result) + assertTrue(result.errors.isNotEmpty()) + val error = result.errors.first() + assertIs(error.message) + assertIs(error.start) + assertIs(error.end) + assertTrue(error.start.line == 0) + assertTrue(error.start.column > 0) + } + + @Test + fun testToYaml_success() { + val input = """{"name": "test", "value": 123}""" + val result = createService().toYaml(input) + assertIs(result) + assertTrue(result.output.isNotEmpty()) + } + + @Test + fun testToYaml_failure() { + val input = """{"invalid": }""" + val result = createService().toYaml(input) + assertIs(result) + assertTrue(result.errors.isNotEmpty()) + } + + @Test + fun testAnalyze() { + val input = """{"name": "test", "value": 123}""" + val analysis = createService().analyze(input) + assertIs(analysis) + assertIs>(analysis.errors) + assertIs>(analysis.tokens) + assertTrue(analysis.tokens.isNotEmpty()) + + val token = analysis.tokens.first() + assertIs(token.tokenType) + assertIs(token.text) + assertIs(token.start) + assertIs(token.end) + } + + @Test + fun testAnalysisUnclosedString() { + val analysis = createService().analyze("'unclosed string") + assertIs(analysis) + assertIs>(analysis.errors) + assertIs>(analysis.tokens) + assertTrue(analysis.tokens.isNotEmpty()) + + val token = analysis.tokens.first() + assertIs(token.tokenType) + assertIs(token.text) + assertIs(token.start) + assertIs(token.end) + } + + @Test + fun testAnalyze_tokens() { + val input = """name: test, complexString: "this has legal \n and illegal \x escapes and \u3456 unicode"""" + val tokens = createService().analyze(input).tokens + assertEquals( + listOf(TokenType.UNQUOTED_STRING, + TokenType.COLON, + TokenType.UNQUOTED_STRING, + TokenType.COMMA, + TokenType.UNQUOTED_STRING, + TokenType.COLON, + TokenType.STRING_OPEN_QUOTE, + TokenType.STRING_CONTENT, + TokenType.STRING_CLOSE_QUOTE, + TokenType.EOF), + tokens.map { it.tokenType }) + } + + @Test + fun testAnalyze_value() { + val input = """ + key: value + list: + - 1 + - 2.1 + - 3E5 + embed:%tag + %%""".trimIndent() + val value = createService().analyze(input).ksonValue + assertNotNull(value) + assertTrue(value is KsonValue.KsonObject) + + assertEquals(3, value.properties.size) + + // Check root object location (should span entire document) + assertEquals(0, value.start.line) + assertEquals(0, value.start.column) + assertEquals(6, value.end.line) + assertEquals(2, value.end.column) + + // Check "key" property + val keyValue = value.properties.get("key") + assertTrue(keyValue is KsonValue.KsonString) + assertEquals("value", keyValue.value) + assertEquals(0, keyValue.start.line) + assertEquals(5, keyValue.start.column) + assertEquals(0, keyValue.end.line) + assertEquals(10, keyValue.end.column) + + // Check "list" property + val listValue = value.properties.get("list") + assertTrue(listValue is KsonValue.KsonArray) + assertEquals(3, listValue.elements.size) + assertEquals(2, listValue.start.line) + assertEquals(2, listValue.start.column) + assertEquals(4, listValue.end.line) + assertEquals(7, listValue.end.column) + + // Check list elements + val firstElement = listValue.elements[0] + assertTrue(firstElement is KsonValue.KsonNumber.Integer) + assertEquals(1, firstElement.value) + assertEquals(2, firstElement.start.line) + assertEquals(4, firstElement.start.column) + assertEquals(2, firstElement.end.line) + assertEquals(5, firstElement.end.column) + + val secondElement = listValue.elements[1] + assertTrue(secondElement is KsonValue.KsonNumber.Decimal) + assertEquals(2.1, secondElement.value) + assertEquals(3, secondElement.start.line) + assertEquals(4, secondElement.start.column) + assertEquals(3, secondElement.end.line) + assertEquals(7, secondElement.end.column) + + val thirdElement = listValue.elements[2] + assertTrue(thirdElement is KsonValue.KsonNumber.Decimal) + assertEquals(3e5, thirdElement.value) + assertEquals(4, thirdElement.start.line) + assertEquals(4, thirdElement.start.column) + assertEquals(4, thirdElement.end.line) + assertEquals(7, thirdElement.end.column) + + // Check "embed" property + val embedValue = value.properties.get("embed") + assertTrue(embedValue is KsonValue.KsonEmbed) + assertEquals("tag", embedValue.tag) + assertEquals("", embedValue.content) + assertEquals(5, embedValue.start.line) + assertEquals(6, embedValue.start.column) + assertEquals(6, embedValue.end.line) + assertEquals(2, embedValue.end.column) + } + + @Test + fun testParseSchema_success() { + val schemaKson = """{ + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "number"} + } + }""" + val result = createService().parseSchema(schemaKson) + assertIs(result) + } + + @Test + fun testParseSchema_failure() { + val invalidSchema = """{"type": }""" + val result = createService().parseSchema(invalidSchema) + assertIs(result) + assertTrue(result.errors.isNotEmpty()) + } + + @Test + fun testSchemaValidator_validInput() { + val schemaKson = """{ + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "number"} + } + }""" + val schemaResult = createService().parseSchema(schemaKson) + assertIs(schemaResult) + + val validator = schemaResult.schemaValidator + val validKson = """{"name": "John", "age": 30}""" + val errors = validator.validate(validKson) + assertTrue(errors.isEmpty()) + } + + @Test + fun testSchemaValidator_invalidInput() { + val schemaKson = """{ + "type": "object", + "properties": { + "name": {"type": "string"}, + "age": {"type": "number"} + }, + "required": ["name", "age"] + }""" + val schemaResult = createService().parseSchema(schemaKson) + assertIs(schemaResult) + + val validator = schemaResult.schemaValidator + val invalidKson = """{"name": "John"}""" + val errors = validator.validate(invalidKson) + assertTrue(errors.isNotEmpty()) + } + + @Test + fun testSchemaValidator_validateWithParseErrors() { + val schemaKson = """{"type": "object"}""" + val schemaResult = createService().parseSchema(schemaKson) + assertIs(schemaResult) + + val validator = schemaResult.schemaValidator + val invalidKson = """{"invalid": }""" + val errors = validator.validate(invalidKson) + assertTrue(errors.isNotEmpty()) + } + + @Test + fun testPropertyKeys_basicAccess() { + val input = """ + name: John + age: 30 + city: 'New York' + """.trimIndent() + val analysis = createService().analyze(input) + val value = analysis.ksonValue + assertNotNull(value) + assertTrue(value is KsonValue.KsonObject) + + // Verify all keys are present in propertyKeys + assertEquals(3, value.propertyKeys.size) + assertTrue(value.propertyKeys.containsKey("name")) + assertTrue(value.propertyKeys.containsKey("age")) + assertTrue(value.propertyKeys.containsKey("city")) + + // Verify propertyKeys contains KsonString values + val nameKey = value.propertyKeys.get("name") + assertNotNull(nameKey) + assertEquals("name", nameKey.value) + + val ageKey = value.propertyKeys.get("age") + assertNotNull(ageKey) + assertEquals("age", ageKey.value) + } + + @Test + fun testPropertyKeys_withPositionInformation() { + val input = """ + name: John + age: 30 + """.trimIndent() + val analysis = createService().analyze(input) + val value = analysis.ksonValue + assertNotNull(value) + assertTrue(value is KsonValue.KsonObject) + + // Verify position information for keys + val nameKey = value.propertyKeys.get("name") + assertNotNull(nameKey) + assertEquals(0, nameKey.start.line) + assertEquals(0, nameKey.start.column) + assertEquals(0, nameKey.end.line) + assertEquals(4, nameKey.end.column) + + val ageKey = value.propertyKeys.get("age") + assertNotNull(ageKey) + assertEquals(1, ageKey.start.line) + assertEquals(0, ageKey.start.column) + assertEquals(1, ageKey.end.line) + assertEquals(3, ageKey.end.column) + } + + @Test + fun testPropertyKeys_emptyObject() { + val input = "{}" + val analysis = createService().analyze(input) + val value = analysis.ksonValue + assertNotNull(value) + assertTrue(value is KsonValue.KsonObject) + + // Empty object should have no propertyKeys + assertEquals(0, value.propertyKeys.size) + assertEquals(0, value.properties.size) + } + + @Test + fun testFormat_withValidEmbedBlockRules() { + val input = """ + scripts: + build: "make all" + """.trimIndent() + + val rule = EmbedRule("/scripts/build", tag = "bash") + val formatted = createService().format( + input, + FormatOptions( + embedBlockRules = listOf(rule) + ) + ) + assertEquals( + """ + scripts: + build: %bash + make all + %% + """.trimIndent(), + formatted + ) + } + + @Test + fun testFormat_withInvalidEmbedBlockRules() { + val input = """ + scripts: + build: "make all" + """.trimIndent() + + val rule = EmbedRule("invalid[pattern", tag = "bash") + val formatted = createService().format( + input, + FormatOptions( + embedBlockRules = listOf(rule) + ) + ) + assertEquals( + """ + scripts: + build: 'make all' + """.trimIndent(), + formatted + ) + } +} diff --git a/lib-python/kson-lib-tests/build.gradle.kts b/lib-python/kson-lib-tests/build.gradle.kts index c4f82005..f84c05fe 100644 --- a/lib-python/kson-lib-tests/build.gradle.kts +++ b/lib-python/kson-lib-tests/build.gradle.kts @@ -6,16 +6,6 @@ repositories { mavenCentral() } -val syncCommonTestSources by tasks.registering(Sync::class) { - from(project(":kson-lib").file("src/commonTest/kotlin")) - into(layout.buildDirectory.dir("commonTestSources")) -} - -val syncJvmTestSources by tasks.registering(Sync::class) { - from(project(":kson-lib").file("src/jvmTest/kotlin")) - into(layout.buildDirectory.dir("jvmTestSources")) -} - tasks.withType { dependsOn(":lib-python:build") @@ -31,22 +21,17 @@ kotlin { sourceSets { commonTest { dependencies { - implementation(project(":kson-lib-http")) - implementation(kotlin("test")) - } - - kotlin { - srcDir(syncCommonTestSources) + implementation(project(":kson-service-tests")) + implementation(project(":kson-http")) } } jvmTest { dependencies { - implementation("org.junit.jupiter:junit-jupiter-api:5.14.2") - runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.2") - } + implementation(kotlin("test-junit5")) - kotlin { - srcDir(syncJvmTestSources) + // Important: this ensures we have a recent-enough version of JUnit, supporting the `AutoCloseable` + // interface (otherwise test runs never finish because the HTTP server doesn't get closed) + implementation(project.dependencies.platform("org.junit:junit-bom:5.14.3")) } } } diff --git a/lib-python/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt b/lib-python/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt new file mode 100644 index 00000000..cbd81068 --- /dev/null +++ b/lib-python/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt @@ -0,0 +1,9 @@ +import org.kson.Kson +import org.kson.api.KsonService +import org.kson.api.KsonServiceSmokeTest + +class SmokeTest : KsonServiceSmokeTest() { + override fun createService(): KsonService { + return Kson + } +} \ No newline at end of file diff --git a/lib-python/src/kson/__init__.py b/lib-python/src/kson/__init__.py index a3c32d29..2b41bf07 100644 --- a/lib-python/src/kson/__init__.py +++ b/lib-python/src/kson/__init__.py @@ -8,7 +8,6 @@ from enum import Enum tls = threading.local() -tls.attached_jni_thread = None JNI_OK = 0 @@ -61,31 +60,38 @@ def _raise_if_null(env: Any, ptr: Any): # JNI Helpers # ############### -def _attach_jni_thread() -> Any: - if tls.attached_jni_thread: - return tls.attached_jni_thread +class AttachedJniThread: + """Automatically attaches/detaches the thread to the JNI.""" - env_ptr = ffi.new("JNIEnv **") - if jvm[0].AttachCurrentThread(jvm, ffi.cast("void **", env_ptr), ffi.NULL) != JNI_OK: - raise RuntimeError("failed to attach JNI thread") + should_detach: bool - tls.attached_jni_thread = env_ptr[0] - return env_ptr[0] + def __enter__(self): + self.should_detach = False + + if getattr(tls, 'attached_jni_thread', None): + return tls.attached_jni_thread + + env_ptr = ffi.new("JNIEnv **") + if jvm[0].AttachCurrentThread(jvm, ffi.cast("void **", env_ptr), ffi.NULL) != JNI_OK: + raise RuntimeError("failed to attach JNI thread") + + tls.attached_jni_thread = env_ptr[0] + self.should_detach = True + return env_ptr[0] + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.should_detach: + if jvm[0].DetachCurrentThread(jvm) != JNI_OK: + raise RuntimeError("failed to detach JNI thread") + tls.attached_jni_thread = None -def _detach_jni_thread(): - if jvm[0].DetachCurrentThread(jvm) != JNI_OK: - raise RuntimeError("failed to detach JNI thread") - tls.attached_jni_thread = None def _delete_local_ref(env, jni_ref: Any): env[0].DeleteLocalRef(env, ffi.cast("jobject", jni_ref)) def _delete_global_ref(jni_ref): - should_detach = tls.attached_jni_thread is None - env = _attach_jni_thread() - env[0].DeleteGlobalRef(env, ffi.cast("jobject", jni_ref)) - if should_detach: - _detach_jni_thread() + with AttachedJniThread() as env: + env[0].DeleteGlobalRef(env, ffi.cast("jobject", jni_ref)) def _to_gc_global_ref(env, jni_ref: Any) -> Any: global_jni_ref = env[0].NewGlobalRef(env, jni_ref) @@ -106,30 +112,27 @@ def _get_method(env, clazz: Any, method_name: bytes, method_signature: bytes) -> return method def _construct(class_name: bytes, constructor_signature: bytes, args: Any) -> Any: - env = _attach_jni_thread() - clazz = _get_class(env, class_name) - constructor = _get_method(env, clazz, b"", constructor_signature) - jni_ref = env[0].NewObject(env, clazz, constructor, *args) - _raise_exception_if_any(env) - jni_ref_global = _to_gc_global_ref(env, jni_ref) - _detach_jni_thread() - return jni_ref_global + with AttachedJniThread() as env: + clazz = _get_class(env, class_name) + constructor = _get_method(env, clazz, b"", constructor_signature) + jni_ref = env[0].NewObject(env, clazz, constructor, *args) + _raise_exception_if_any(env) + return _to_gc_global_ref(env, jni_ref) def _access_static_field(class_name: bytes, field_name: bytes, field_type: bytes) -> Any: - env = _attach_jni_thread() - c = _get_class(env, class_name) - signature_cstr = ffi.new("char[]", field_type) - field_name_cstr = ffi.new("char[]", field_name) + with AttachedJniThread() as env: + c = _get_class(env, class_name) + signature_cstr = ffi.new("char[]", field_type) + field_name_cstr = ffi.new("char[]", field_name) - # Get static id - field = env[0].GetStaticFieldID(env, c, field_name_cstr, signature_cstr) - _raise_if_null(env, field) + # Get static id + field = env[0].GetStaticFieldID(env, c, field_name_cstr, signature_cstr) + _raise_if_null(env, field) - # Access field - field_value = _to_gc_global_ref(env, env[0].GetStaticObjectField(env, c, field)) - _raise_if_null(env, field_value) - _detach_jni_thread() - return field_value + # Access field + field_value = _to_gc_global_ref(env, env[0].GetStaticObjectField(env, c, field)) + _raise_if_null(env, field_value) + return field_value def _call_method_raw(env: Any, class_name: bytes, jni_ref: Any, func_name: bytes, func_signature: bytes, jni_call_name: str, args: List[Any]) -> Any: clazz = _get_class(env, class_name) @@ -139,12 +142,11 @@ def _call_method_raw(env: Any, class_name: bytes, jni_ref: Any, func_name: bytes return result def _call_method(class_name: bytes, jni_ref: Any, func_name: bytes, func_signature: bytes, jni_call_name: str, args: List[Any]) -> Any: - env = _attach_jni_thread() - result = _call_method_raw(env, class_name, jni_ref, func_name, func_signature, jni_call_name, args) - if jni_call_name == "ObjectMethod": - result = _to_gc_global_ref(env, result) - _detach_jni_thread() - return result + with AttachedJniThread() as env: + result = _call_method_raw(env, class_name, jni_ref, func_name, func_signature, jni_call_name, args) + if jni_call_name == "ObjectMethod": + result = _to_gc_global_ref(env, result) + return result def _python_str_to_java_string(s: str) -> Any: utf16_bytes = s.encode("utf-16-le") @@ -155,41 +157,46 @@ def _python_str_to_java_string(s: str) -> Any: raise RuntimeError("entered unreachable code: raw string length was not divisible by 2") utf16_str = ffi.new("char[]", utf16_bytes) - env = _attach_jni_thread() - jni_ref = env[0].NewString(env, ffi.cast("jchar *", utf16_str), utf16_str_len) - _raise_if_null(env, jni_ref) - jni_ref = _to_gc_global_ref(env, jni_ref) - _detach_jni_thread() - return jni_ref + with AttachedJniThread() as env: + jni_ref = env[0].NewString(env, ffi.cast("jchar *", utf16_str), utf16_str_len) + _raise_if_null(env, jni_ref) + return _to_gc_global_ref(env, jni_ref) def _java_string_to_python_str(jni_ref: Any) -> str: - env = _attach_jni_thread() - native_chars = env[0].GetStringChars(env, jni_ref, ffi.NULL) - _raise_if_null(env, native_chars) - native_chars_byte_len = env[0].GetStringLength(env, jni_ref) * 2 - python_str = bytes(cast(Any, ffi.buffer(native_chars, native_chars_byte_len))).decode("utf-16-le", "strict") - env[0].ReleaseStringChars(env, jni_ref, native_chars) - _detach_jni_thread() - return python_str + with AttachedJniThread() as env: + native_chars = env[0].GetStringChars(env, jni_ref, ffi.NULL) + _raise_if_null(env, native_chars) + native_chars_byte_len = env[0].GetStringLength(env, jni_ref) * 2 + python_str = bytes(cast(Any, ffi.buffer(native_chars, native_chars_byte_len))).decode("utf-16-le", "strict") + env[0].ReleaseStringChars(env, jni_ref, native_chars) + return python_str def _jni_class_name(jni_ref: Any): if jni_ref == ffi.NULL: raise RuntimeError("entered unreachable code: attempted to obtain class name of null object") - env = _attach_jni_thread() - clazz = env[0].GetObjectClass(env, jni_ref) - _raise_if_null(env, clazz) - name_local = _call_method_raw(env, b"java/lang/Class", clazz, b"getName", b"()Ljava/lang/String;", "ObjectMethod", []) - name = _to_gc_global_ref(env, name_local) - _delete_local_ref(env, clazz) - _detach_jni_thread() - return _java_string_to_python_str(name) + with AttachedJniThread() as env: + clazz = env[0].GetObjectClass(env, jni_ref) + _raise_if_null(env, clazz) + name_local = _call_method_raw(env, b"java/lang/Class", clazz, b"getName", b"()Ljava/lang/String;", "ObjectMethod", []) + name = _to_gc_global_ref(env, name_local) + _delete_local_ref(env, clazz) + return _java_string_to_python_str(name) def _from_kotlin_object(python_class, jni_ref): obj = object.__new__(python_class) obj._jni_ref = jni_ref return obj +def _get_jni_ref(object) -> Any: + if hasattr(object, '_jni_ref'): + return object._jni_ref + + if isinstance(object, str): + return _python_str_to_java_string(object) + + raise TypeError(f"Cannot convert value to Kotlin: expected object with _jni_ref attribute or something convertible to a Kotlin type, got {type(object).__name__}") + def _from_kotlin_list( jni_ref: Any, wrap_item_fn: Callable[[Any], Any] ) -> List[Any]: @@ -207,28 +214,24 @@ def _from_kotlin_list( return python_list def _to_kotlin_list(list: List[Any]) -> Any: - env = _attach_jni_thread() + with AttachedJniThread() as env: - # Create a new ArrayList - array_list_class = _get_class(env, b"java/util/ArrayList") - constructor = _get_method(env, array_list_class, b"", b"()V") - array_list = env[0].NewObject(env, array_list_class, constructor) - _raise_exception_if_any(env) - array_list = _to_gc_global_ref(env, array_list) - - # Add each element to the list - for item in list: - if not hasattr(item, '_jni_ref'): - raise TypeError(f"Cannot convert item to Kotlin: expected object with _jni_ref attribute, got {type(item).__name__}") - item_ref = item._jni_ref - _call_method_raw(env, b"java/util/ArrayList", array_list, b"add", b"(Ljava/lang/Object;)Z", "BooleanMethod", [item_ref]) + # Create a new ArrayList + array_list_class = _get_class(env, b"java/util/ArrayList") + constructor = _get_method(env, array_list_class, b"", b"()V") + array_list = env[0].NewObject(env, array_list_class, constructor) _raise_exception_if_any(env) + array_list = _to_gc_global_ref(env, array_list) - _detach_jni_thread() - return array_list + # Add each element to the list + for item in list: + if not hasattr(item, '_jni_ref'): + raise TypeError(f"Cannot convert item to Kotlin: expected object with _jni_ref attribute, got {type(item).__name__}") + item_ref = item._jni_ref + _call_method_raw(env, b"java/util/ArrayList", array_list, b"add", b"(Ljava/lang/Object;)Z", "BooleanMethod", [item_ref]) + _raise_exception_if_any(env) -def _to_kotlin_map(list: Dict[Any, Any]) -> Any: - raise RuntimeError("not implemented") + return array_list def _from_kotlin_map( jni_ref: Any, @@ -253,41 +256,68 @@ def _from_kotlin_map( return python_dict +def _to_kotlin_map(items: Dict[Any, Any]) -> Any: + with AttachedJniThread() as env: + + # Create a new HashMap + map_class = _get_class(env, b"java/util/HashMap") + constructor = _get_method(env, map_class, b"", b"()V") + map = env[0].NewObject(env, map_class, constructor) + _raise_exception_if_any(env) + map = _to_gc_global_ref(env, map) + + # Add entries to it + for key, value in items.items(): + key_ref = _get_jni_ref(key) + value_ref = _get_jni_ref(value) + _call_method_raw(env, b"java/util/HashMap", map, b"put", b"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", "ObjectMethod", [key_ref, value_ref]) + _raise_exception_if_any(env) + + return map + ############ # Wrappers # ############ -class FormatOptions: - """Options for formatting Kson output. +class SchemaResult: + + _jni_ref: Any + + Success: TypeAlias + Failure: TypeAlias + def __eq__(self, other): + return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) + + def __hash__(self): + return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + + @staticmethod + def _downcast(jni_ref) -> Any: + match _jni_class_name(jni_ref): + + case "org.kson.api.SchemaResult$Success": + return _from_kotlin_object(_SchemaResult_Success, jni_ref) - @param indentType The type of indentation to use (spaces or tabs) - @param formattingStyle The formatting style (PLAIN, DELIMITED, COMPACT, CLASSIC) - @param embedBlockRules Rules for formatting specific paths as embed blocks - """ + case "org.kson.api.SchemaResult$Failure": + return _from_kotlin_object(_SchemaResult_Failure, jni_ref) + +class _SchemaResult_Success(SchemaResult): _jni_ref: Any def __init__( self, - indent_type: IndentType, - formatting_style: FormattingStyle, - embed_block_rules: List[EmbedRule], + schema_validator: SchemaValidatorService, ): - if indent_type is None: - raise ValueError("`indent_type` cannot be None") - if formatting_style is None: - raise ValueError("`formatting_style` cannot be None") - if embed_block_rules is None: - raise ValueError("`embed_block_rules` cannot be None") + if schema_validator is None: + raise ValueError("`schema_validator` cannot be None") self._jni_ref = _construct( - b"org/kson/FormatOptions", - b"(Lorg/kson/IndentType;Lorg/kson/FormattingStyle;Ljava/util/List;)V", + b"org/kson/api/SchemaResult$Success", + b"(Lorg/kson/api/SchemaValidatorService;)V", [ - indent_type._jni_ref, - formatting_style._to_kotlin_enum(), - _to_kotlin_list(embed_block_rules), + schema_validator._jni_ref, ] ) def __eq__(self, other): @@ -297,67 +327,92 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def indent_type( + def schema_validator( self, - ) -> IndentType: + ) -> SchemaValidatorService: jni_ref = self._jni_ref result = _call_method( - b"org/kson/FormatOptions", + b"org/kson/api/SchemaResult$Success", jni_ref, - b"getIndentType", - b"()Lorg/kson/IndentType;", + b"getSchemaValidator", + b"()Lorg/kson/api/SchemaValidatorService;", "ObjectMethod", [] ) - return cast(Any, (lambda x0: IndentType._downcast(x0))(result)) + return cast(Any, (lambda x0: _from_kotlin_object(SchemaValidatorService, x0))(result)) +SchemaResult.Success = _SchemaResult_Success - def formatting_style( - self, - ) -> FormattingStyle: +class _SchemaResult_Failure(SchemaResult): - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/FormatOptions", - jni_ref, - b"getFormattingStyle", - b"()Lorg/kson/FormattingStyle;", - "ObjectMethod", - [] + _jni_ref: Any + + def __init__( + self, + errors: List[Message], + ): + if errors is None: + raise ValueError("`errors` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/SchemaResult$Failure", + b"(Ljava/util/List;)V", + [ + + _to_kotlin_list(errors), + ] ) + def __eq__(self, other): + return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - return cast(Any, (lambda x0: FormattingStyle._from_kotlin_enum(x0))(result)) + def __hash__(self): + return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def embed_block_rules( + + def errors( self, - ) -> List[EmbedRule]: + ) -> List[Message]: jni_ref = self._jni_ref result = _call_method( - b"org/kson/FormatOptions", + b"org/kson/api/SchemaResult$Failure", jni_ref, - b"getEmbedBlockRules", + b"getErrors", b"()Ljava/util/List;", "ObjectMethod", [] ) - return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(EmbedRule, x1)))(result)) + return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Message, x1)))(result)) +SchemaResult.Failure = _SchemaResult_Failure -class Position: - """A zero-based line/column position in a document - @param line The line number where the error occurred (0-based) - @param column The column number where the error occurred (0-based) - """ +class Position: _jni_ref: Any + def __init__( + self, + line: int, + column: int, + ): + if line is None: + raise ValueError("`line` cannot be None") + if column is None: + raise ValueError("`column` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/Position", + b"(II)V", + [ + + ffi.cast('jint', line), + ffi.cast('jint', column), + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -372,7 +427,7 @@ def line( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Position", + b"org/kson/api/Position", jni_ref, b"getLine", b"()I", @@ -389,7 +444,7 @@ def column( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Position", + b"org/kson/api/Position", jni_ref, b"getColumn", b"()I", @@ -400,11 +455,135 @@ def column( return cast(Any, (lambda x0: x0)(result)) +class IndentType: + + _jni_ref: Any + + Spaces: TypeAlias + Tabs: TypeAlias + def __eq__(self, other): + return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) + + def __hash__(self): + return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + + @staticmethod + def _downcast(jni_ref) -> Any: + match _jni_class_name(jni_ref): + + case "org.kson.api.IndentType$Spaces": + return _from_kotlin_object(_IndentType_Spaces, jni_ref) + + case "org.kson.api.IndentType$Tabs": + return _from_kotlin_object(_IndentType_Tabs, jni_ref) + +class _IndentType_Spaces(IndentType): + + _jni_ref: Any + + def __init__( + self, + size: int, + ): + if size is None: + raise ValueError("`size` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/IndentType$Spaces", + b"(I)V", + [ + + ffi.cast('jint', size), + ] + ) + def __eq__(self, other): + return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) + + def __hash__(self): + return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + + + def size( + self, + ) -> int: + + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/IndentType$Spaces", + jni_ref, + b"getSize", + b"()I", + "IntMethod", + [] + ) + + return cast(Any, (lambda x0: x0)(result)) +IndentType.Spaces = _IndentType_Spaces + + +class _IndentType_Tabs(IndentType): + + _jni_ref: Any + + def __init__(self): + self._jni_ref = _access_static_field(b"org/kson/api/IndentType$Tabs", b"INSTANCE", b"Lorg/kson/api/IndentType$Tabs;") + def __eq__(self, other): + return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) + + def __hash__(self): + return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + + + @staticmethod + def to_string( + ) -> str: + + + jni_ref = _access_static_field(b"org/kson/api/IndentType$Tabs", b"INSTANCE", b"Lorg/kson/api/IndentType$Tabs;") + result = _call_method( + b"org/kson/api/IndentType$Tabs", + jni_ref, + b"toString", + b"()Ljava/lang/String;", + "ObjectMethod", + [] + ) + + return cast(Any, (_java_string_to_python_str)(result)) +IndentType.Tabs = _IndentType_Tabs + + + class Message: - """Represents a message logged during Kson processing""" _jni_ref: Any + def __init__( + self, + message: str, + severity: MessageSeverity, + start: Position, + end: Position, + ): + if message is None: + raise ValueError("`message` cannot be None") + if severity is None: + raise ValueError("`severity` cannot be None") + if start is None: + raise ValueError("`start` cannot be None") + if end is None: + raise ValueError("`end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/Message", + b"(Ljava/lang/String;Lorg/kson/api/MessageSeverity;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + _python_str_to_java_string(message), + severity._to_kotlin_enum(), + start._jni_ref, + end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -419,7 +598,7 @@ def message( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Message", + b"org/kson/api/Message", jni_ref, b"getMessage", b"()Ljava/lang/String;", @@ -436,10 +615,10 @@ def severity( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Message", + b"org/kson/api/Message", jni_ref, b"getSeverity", - b"()Lorg/kson/MessageSeverity;", + b"()Lorg/kson/api/MessageSeverity;", "ObjectMethod", [] ) @@ -453,10 +632,10 @@ def start( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Message", + b"org/kson/api/Message", jni_ref, b"getStart", - b"()Lorg/kson/Position;", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) @@ -470,10 +649,10 @@ def end( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Message", + b"org/kson/api/Message", jni_ref, b"getEnd", - b"()Lorg/kson/Position;", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) @@ -481,11 +660,19 @@ def end( return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) -class Token: - """[Token] produced by the lexing phase of a Kson parse""" +class KsonValue: _jni_ref: Any + KsonArray: TypeAlias + KsonNull: TypeAlias + KsonNumber: TypeAlias + KsonObject: TypeAlias + KsonString: TypeAlias + KsonBoolean: TypeAlias + KsonEmbed: TypeAlias + Decimal: TypeAlias + Integer: TypeAlias def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -493,160 +680,51 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def token_type( + def start( self, - ) -> TokenType: + ) -> Position: jni_ref = self._jni_ref result = _call_method( - b"org/kson/Token", + b"org/kson/api/KsonValue", jni_ref, - b"getTokenType", - b"()Lorg/kson/TokenType;", + b"getStart", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) - return cast(Any, (lambda x0: TokenType._from_kotlin_enum(x0))(result)) + return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - def text( + def end( self, - ) -> str: + ) -> Position: jni_ref = self._jni_ref result = _call_method( - b"org/kson/Token", + b"org/kson/api/KsonValue", jni_ref, - b"getText", - b"()Ljava/lang/String;", + b"getEnd", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) - return cast(Any, (_java_string_to_python_str)(result)) + return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - def start( + def type( self, - ) -> Position: + ) -> KsonValueType: jni_ref = self._jni_ref result = _call_method( - b"org/kson/Token", + b"org/kson/api/KsonValue", jni_ref, - b"getStart", - b"()Lorg/kson/Position;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - - def end( - self, - ) -> Position: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/Token", - jni_ref, - b"getEnd", - b"()Lorg/kson/Position;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - - -class KsonValue: - """Represents a parsed [InternalKsonValue] in the public API""" - - _jni_ref: Any - - KsonNull: TypeAlias - KsonArray: TypeAlias - KsonString: TypeAlias - KsonEmbed: TypeAlias - KsonBoolean: TypeAlias - KsonObject: TypeAlias - KsonNumber: TypeAlias - Decimal: TypeAlias - Integer: TypeAlias - def __init__( - self, - start: Position, - end: Position, - ): - if start is None: - raise ValueError("`start` cannot be None") - if end is None: - raise ValueError("`end` cannot be None") - self._jni_ref = _construct( - b"org/kson/KsonValue", - b"(Lorg/kson/Position;Lorg/kson/Position;)V", - [ - - start._jni_ref, - end._jni_ref, - ] - ) - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - - - def start( - self, - ) -> Position: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/KsonValue", - jni_ref, - b"getStart", - b"()Lorg/kson/Position;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - - def end( - self, - ) -> Position: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/KsonValue", - jni_ref, - b"getEnd", - b"()Lorg/kson/Position;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) - - def type( - self, - ) -> KsonValueType: - """Type discriminator for easier type checking in TypeScript/JavaScript""" - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/KsonValue", - jni_ref, - b"getType", - b"()Lorg/kson/KsonValueType;", + b"getType", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -656,38 +734,63 @@ def type( def _downcast(jni_ref) -> Any: match _jni_class_name(jni_ref): - case "org.kson.KsonValue$KsonNull": + case "org.kson.api.KsonValue$KsonArray": + return _from_kotlin_object(_KsonValue_KsonArray, jni_ref) + + case "org.kson.api.KsonValue$KsonNull": return _from_kotlin_object(_KsonValue_KsonNull, jni_ref) - case "org.kson.KsonValue$KsonArray": - return _from_kotlin_object(_KsonValue_KsonArray, jni_ref) + case "org.kson.api.KsonValue$KsonNumber": + return _from_kotlin_object(_KsonValue_KsonNumber, jni_ref) - case "org.kson.KsonValue$KsonString": - return _from_kotlin_object(_KsonValue_KsonString, jni_ref) + case "org.kson.api.KsonValue$KsonObject": + return _from_kotlin_object(_KsonValue_KsonObject, jni_ref) - case "org.kson.KsonValue$KsonEmbed": - return _from_kotlin_object(_KsonValue_KsonEmbed, jni_ref) + case "org.kson.api.KsonValue$KsonString": + return _from_kotlin_object(_KsonValue_KsonString, jni_ref) - case "org.kson.KsonValue$KsonBoolean": + case "org.kson.api.KsonValue$KsonBoolean": return _from_kotlin_object(_KsonValue_KsonBoolean, jni_ref) - case "org.kson.KsonValue$KsonObject": - return _from_kotlin_object(_KsonValue_KsonObject, jni_ref) - - case "org.kson.KsonValue$KsonNumber": - return _from_kotlin_object(_KsonValue_KsonNumber, jni_ref) + case "org.kson.api.KsonValue$KsonEmbed": + return _from_kotlin_object(_KsonValue_KsonEmbed, jni_ref) - case "org.kson.KsonValue$KsonNumber$Decimal": + case "org.kson.api.KsonValue$KsonNumber$Decimal": return _from_kotlin_object(_KsonValue_KsonNumber_Decimal, jni_ref) - case "org.kson.KsonValue$KsonNumber$Integer": + case "org.kson.api.KsonValue$KsonNumber$Integer": return _from_kotlin_object(_KsonValue_KsonNumber_Integer, jni_ref) class _KsonValue_KsonObject(KsonValue): - """A Kson object with key-value pairs""" _jni_ref: Any + def __init__( + self, + properties: Dict[str, KsonValue], + property_keys: Dict[str, _KsonValue_KsonString], + internal_start: Position, + internal_end: Position, + ): + if properties is None: + raise ValueError("`properties` cannot be None") + if property_keys is None: + raise ValueError("`property_keys` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonObject", + b"(Ljava/util/Map;Ljava/util/Map;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + _to_kotlin_map(properties), + _to_kotlin_map(property_keys), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -702,7 +805,7 @@ def properties( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonObject", + b"org/kson/api/KsonValue$KsonObject", jni_ref, b"getProperties", b"()Ljava/util/Map;", @@ -719,7 +822,7 @@ def property_keys( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonObject", + b"org/kson/api/KsonValue$KsonObject", jni_ref, b"getPropertyKeys", b"()Ljava/util/Map;", @@ -736,23 +839,61 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonObject", + b"org/kson/api/KsonValue$KsonObject", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) return cast(Any, (lambda x0: KsonValueType._from_kotlin_enum(x0))(result)) + + def to_string( + self, + ) -> str: + + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/KsonValue$KsonObject", + jni_ref, + b"toString", + b"()Ljava/lang/String;", + "ObjectMethod", + [] + ) + + return cast(Any, (_java_string_to_python_str)(result)) KsonValue.KsonObject = _KsonValue_KsonObject class _KsonValue_KsonArray(KsonValue): - """A Kson array with elements""" _jni_ref: Any + def __init__( + self, + elements: List[KsonValue], + internal_start: Position, + internal_end: Position, + ): + if elements is None: + raise ValueError("`elements` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonArray", + b"(Ljava/util/List;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + _to_kotlin_list(elements), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -767,7 +908,7 @@ def elements( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonArray", + b"org/kson/api/KsonValue$KsonArray", jni_ref, b"getElements", b"()Ljava/util/List;", @@ -784,10 +925,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonArray", + b"org/kson/api/KsonValue$KsonArray", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -797,10 +938,31 @@ def type( class _KsonValue_KsonString(KsonValue): - """A Kson string value""" _jni_ref: Any + def __init__( + self, + value: str, + internal_start: Position, + internal_end: Position, + ): + if value is None: + raise ValueError("`value` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonString", + b"(Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + _python_str_to_java_string(value), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -815,7 +977,7 @@ def value( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonString", + b"org/kson/api/KsonValue$KsonString", jni_ref, b"getValue", b"()Ljava/lang/String;", @@ -832,10 +994,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonString", + b"org/kson/api/KsonValue$KsonString", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -845,30 +1007,11 @@ def type( class _KsonValue_KsonNumber(KsonValue): - """A Kson number value.""" _jni_ref: Any Decimal: TypeAlias Integer: TypeAlias - def __init__( - self, - start: Position, - end: Position, - ): - if start is None: - raise ValueError("`start` cannot be None") - if end is None: - raise ValueError("`end` cannot be None") - self._jni_ref = _construct( - b"org/kson/KsonValue$KsonNumber", - b"(Lorg/kson/Position;Lorg/kson/Position;)V", - [ - - start._jni_ref, - end._jni_ref, - ] - ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -879,10 +1022,10 @@ def __hash__(self): def _downcast(jni_ref) -> Any: match _jni_class_name(jni_ref): - case "org.kson.KsonValue$KsonNumber$Decimal": + case "org.kson.api.KsonValue$KsonNumber$Decimal": return _from_kotlin_object(_KsonValue_KsonNumber_Decimal, jni_ref) - case "org.kson.KsonValue$KsonNumber$Integer": + case "org.kson.api.KsonValue$KsonNumber$Integer": return _from_kotlin_object(_KsonValue_KsonNumber_Integer, jni_ref) KsonValue.KsonNumber = _KsonValue_KsonNumber @@ -890,6 +1033,28 @@ class _KsonValue_KsonNumber_Integer(KsonValue.KsonNumber): _jni_ref: Any + def __init__( + self, + value: int, + internal_start: Position, + internal_end: Position, + ): + if value is None: + raise ValueError("`value` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonNumber$Integer", + b"(ILorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + ffi.cast('jint', value), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -904,7 +1069,7 @@ def value( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Integer", + b"org/kson/api/KsonValue$KsonNumber$Integer", jni_ref, b"getValue", b"()I", @@ -921,10 +1086,10 @@ def internal_start( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Integer", + b"org/kson/api/KsonValue$KsonNumber$Integer", jni_ref, b"getInternalStart", - b"()Lorg/kson/Position;", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) @@ -938,10 +1103,10 @@ def internal_end( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Integer", + b"org/kson/api/KsonValue$KsonNumber$Integer", jni_ref, b"getInternalEnd", - b"()Lorg/kson/Position;", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) @@ -955,10 +1120,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Integer", + b"org/kson/api/KsonValue$KsonNumber$Integer", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -971,6 +1136,28 @@ class _KsonValue_KsonNumber_Decimal(KsonValue.KsonNumber): _jni_ref: Any + def __init__( + self, + value: float, + internal_start: Position, + internal_end: Position, + ): + if value is None: + raise ValueError("`value` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonNumber$Decimal", + b"(DLorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + ffi.cast('jdouble', value), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -985,7 +1172,7 @@ def value( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Decimal", + b"org/kson/api/KsonValue$KsonNumber$Decimal", jni_ref, b"getValue", b"()D", @@ -1002,10 +1189,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNumber$Decimal", + b"org/kson/api/KsonValue$KsonNumber$Decimal", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -1016,10 +1203,31 @@ def type( class _KsonValue_KsonBoolean(KsonValue): - """A Kson boolean value""" _jni_ref: Any + def __init__( + self, + value: bool, + internal_start: Position, + internal_end: Position, + ): + if value is None: + raise ValueError("`value` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonBoolean", + b"(ZLorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + ffi.cast('jboolean', value), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -1034,7 +1242,7 @@ def value( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonBoolean", + b"org/kson/api/KsonValue$KsonBoolean", jni_ref, b"getValue", b"()Z", @@ -1051,10 +1259,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonBoolean", + b"org/kson/api/KsonValue$KsonBoolean", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -1064,10 +1272,27 @@ def type( class _KsonValue_KsonNull(KsonValue): - """A Kson null value""" _jni_ref: Any + def __init__( + self, + internal_start: Position, + internal_end: Position, + ): + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonNull", + b"(Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -1082,10 +1307,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonNull", + b"org/kson/api/KsonValue$KsonNull", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -1095,10 +1320,33 @@ def type( class _KsonValue_KsonEmbed(KsonValue): - """A Kson embed block""" _jni_ref: Any + def __init__( + self, + tag: Optional[str], + content: str, + internal_start: Position, + internal_end: Position, + ): + if content is None: + raise ValueError("`content` cannot be None") + if internal_start is None: + raise ValueError("`internal_start` cannot be None") + if internal_end is None: + raise ValueError("`internal_end` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/KsonValue$KsonEmbed", + b"(Ljava/lang/String;Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + _python_str_to_java_string(tag) if tag is not None else ffi.NULL, + _python_str_to_java_string(content), + internal_start._jni_ref, + internal_end._jni_ref, + ] + ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -1113,7 +1361,7 @@ def tag( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonEmbed", + b"org/kson/api/KsonValue$KsonEmbed", jni_ref, b"getTag", b"()Ljava/lang/String;", @@ -1130,7 +1378,7 @@ def content( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonEmbed", + b"org/kson/api/KsonValue$KsonEmbed", jni_ref, b"getContent", b"()Ljava/lang/String;", @@ -1147,10 +1395,10 @@ def type( jni_ref = self._jni_ref result = _call_method( - b"org/kson/KsonValue$KsonEmbed", + b"org/kson/api/KsonValue$KsonEmbed", jni_ref, b"getType", - b"()Lorg/kson/KsonValueType;", + b"()Lorg/kson/api/KsonValueType;", "ObjectMethod", [] ) @@ -1160,8 +1408,7 @@ def type( -class SchemaValidator: - """A validator that can check if Kson source conforms to a schema.""" +class SchemaValidatorService: _jni_ref: Any @@ -1178,18 +1425,12 @@ def validate( filepath: Optional[str], ) -> List[Message]: - """Validates the given Kson source against this validator's schema. - @param kson The Kson source to validate - @param filepath Optional filepath of the document being validated, used by validators to determine which rules to apply - - @return A list of validation error messages, or empty list if valid - """ if kson is None: raise ValueError("`kson` cannot be None") jni_ref = self._jni_ref result = _call_method( - b"org/kson/SchemaValidator", + b"org/kson/api/SchemaValidatorService", jni_ref, b"validate", b"(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;", @@ -1204,54 +1445,61 @@ def validate( return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Message, x1)))(result)) -class EmbedRuleResult: +class TranspileOptions: _jni_ref: Any - Success: TypeAlias - Failure: TypeAlias - def __init__( - self, - - ): - - self._jni_ref = _construct( - b"org/kson/EmbedRuleResult", - b"()V", - [] - ) + Yaml: TypeAlias + Json: TypeAlias def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + + def retain_embed_tags( + self, + ) -> bool: + + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/TranspileOptions", + jni_ref, + b"getRetainEmbedTags", + b"()Z", + "BooleanMethod", + [] + ) + + return cast(Any, (lambda x0: x0)(result)) @staticmethod def _downcast(jni_ref) -> Any: match _jni_class_name(jni_ref): - case "org.kson.EmbedRuleResult$Success": - return _from_kotlin_object(_EmbedRuleResult_Success, jni_ref) + case "org.kson.api.TranspileOptions$Yaml": + return _from_kotlin_object(_TranspileOptions_Yaml, jni_ref) - case "org.kson.EmbedRuleResult$Failure": - return _from_kotlin_object(_EmbedRuleResult_Failure, jni_ref) + case "org.kson.api.TranspileOptions$Json": + return _from_kotlin_object(_TranspileOptions_Json, jni_ref) -class _EmbedRuleResult_Success(EmbedRuleResult): +class _TranspileOptions_Json(TranspileOptions): _jni_ref: Any def __init__( self, - embed_rule: EmbedRule, + retain_embed_tags: bool, ): - if embed_rule is None: - raise ValueError("`embed_rule` cannot be None") + if retain_embed_tags is None: + raise ValueError("`retain_embed_tags` cannot be None") self._jni_ref = _construct( - b"org/kson/EmbedRuleResult$Success", - b"(Lorg/kson/EmbedRule;)V", + b"org/kson/api/TranspileOptions$Json", + b"(Z)V", [ - embed_rule._jni_ref, + ffi.cast('jboolean', retain_embed_tags), ] ) def __eq__(self, other): @@ -1261,41 +1509,41 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def embed_rule( + def retain_embed_tags( self, - ) -> EmbedRule: + ) -> bool: jni_ref = self._jni_ref result = _call_method( - b"org/kson/EmbedRuleResult$Success", + b"org/kson/api/TranspileOptions$Json", jni_ref, - b"getEmbedRule", - b"()Lorg/kson/EmbedRule;", - "ObjectMethod", + b"getRetainEmbedTags", + b"()Z", + "BooleanMethod", [] ) - return cast(Any, (lambda x0: _from_kotlin_object(EmbedRule, x0))(result)) -EmbedRuleResult.Success = _EmbedRuleResult_Success + return cast(Any, (lambda x0: x0)(result)) +TranspileOptions.Json = _TranspileOptions_Json -class _EmbedRuleResult_Failure(EmbedRuleResult): +class _TranspileOptions_Yaml(TranspileOptions): _jni_ref: Any def __init__( self, - message: str, + retain_embed_tags: bool, ): - if message is None: - raise ValueError("`message` cannot be None") - self._jni_ref = _construct( - b"org/kson/EmbedRuleResult$Failure", - b"(Ljava/lang/String;)V", + if retain_embed_tags is None: + raise ValueError("`retain_embed_tags` cannot be None") + self._jni_ref = _construct( + b"org/kson/api/TranspileOptions$Yaml", + b"(Z)V", [ - _python_str_to_java_string(message), + ffi.cast('jboolean', retain_embed_tags), ] ) def __eq__(self, other): @@ -1305,107 +1553,32 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def message( - self, - ) -> str: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/EmbedRuleResult$Failure", - jni_ref, - b"getMessage", - b"()Ljava/lang/String;", - "ObjectMethod", - [] - ) - - return cast(Any, (_java_string_to_python_str)(result)) -EmbedRuleResult.Failure = _EmbedRuleResult_Failure - - - -class Analysis: - """The result of statically analyzing a Kson document""" - - _jni_ref: Any - - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - - - def errors( - self, - ) -> List[Message]: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/Analysis", - jni_ref, - b"getErrors", - b"()Ljava/util/List;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Message, x1)))(result)) - - def tokens( + def retain_embed_tags( self, - ) -> List[Token]: + ) -> bool: jni_ref = self._jni_ref result = _call_method( - b"org/kson/Analysis", + b"org/kson/api/TranspileOptions$Yaml", jni_ref, - b"getTokens", - b"()Ljava/util/List;", - "ObjectMethod", + b"getRetainEmbedTags", + b"()Z", + "BooleanMethod", [] ) - return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Token, x1)))(result)) - - def kson_value( - self, - ) -> Optional[KsonValue]: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/Analysis", - jni_ref, - b"getKsonValue", - b"()Lorg/kson/KsonValue;", - "ObjectMethod", - [] - ) + return cast(Any, (lambda x0: x0)(result)) +TranspileOptions.Yaml = _TranspileOptions_Yaml - return cast(Any, (lambda x0: None if x0 == ffi.NULL else (lambda x0: KsonValue._downcast(x0))(x0))(result)) class Result: - """Result of a Kson conversion operation""" _jni_ref: Any Failure: TypeAlias Success: TypeAlias - def __init__( - self, - - ): - - self._jni_ref = _construct( - b"org/kson/Result", - b"()V", - [] - ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -1416,10 +1589,10 @@ def __hash__(self): def _downcast(jni_ref) -> Any: match _jni_class_name(jni_ref): - case "org.kson.Result$Failure": + case "org.kson.api.Result$Failure": return _from_kotlin_object(_Result_Failure, jni_ref) - case "org.kson.Result$Success": + case "org.kson.api.Result$Success": return _from_kotlin_object(_Result_Success, jni_ref) class _Result_Success(Result): @@ -1433,7 +1606,7 @@ def __init__( if output is None: raise ValueError("`output` cannot be None") self._jni_ref = _construct( - b"org/kson/Result$Success", + b"org/kson/api/Result$Success", b"(Ljava/lang/String;)V", [ @@ -1454,7 +1627,7 @@ def output( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Result$Success", + b"org/kson/api/Result$Success", jni_ref, b"getOutput", b"()Ljava/lang/String;", @@ -1477,7 +1650,7 @@ def __init__( if errors is None: raise ValueError("`errors` cannot be None") self._jni_ref = _construct( - b"org/kson/Result$Failure", + b"org/kson/api/Result$Failure", b"(Ljava/util/List;)V", [ @@ -1498,7 +1671,7 @@ def errors( jni_ref = self._jni_ref result = _call_method( - b"org/kson/Result$Failure", + b"org/kson/api/Result$Failure", jni_ref, b"getErrors", b"()Ljava/util/List;", @@ -1511,22 +1684,35 @@ def errors( -class SchemaResult: - """A [parseSchema] result""" +class Token: _jni_ref: Any - Failure: TypeAlias - Success: TypeAlias def __init__( self, - + token_type: TokenType, + text: str, + start: Position, + end: Position, ): - + if token_type is None: + raise ValueError("`token_type` cannot be None") + if text is None: + raise ValueError("`text` cannot be None") + if start is None: + raise ValueError("`start` cannot be None") + if end is None: + raise ValueError("`end` cannot be None") self._jni_ref = _construct( - b"org/kson/SchemaResult", - b"()V", - [] + b"org/kson/api/Token", + b"(Lorg/kson/api/TokenType;Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V", + [ + + token_type._to_kotlin_enum(), + _python_str_to_java_string(text), + start._jni_ref, + end._jni_ref, + ] ) def __eq__(self, other): return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) @@ -1534,76 +1720,98 @@ def __eq__(self, other): def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - @staticmethod - def _downcast(jni_ref) -> Any: - match _jni_class_name(jni_ref): - case "org.kson.SchemaResult$Failure": - return _from_kotlin_object(_SchemaResult_Failure, jni_ref) + def token_type( + self, + ) -> TokenType: - case "org.kson.SchemaResult$Success": - return _from_kotlin_object(_SchemaResult_Success, jni_ref) -class _SchemaResult_Success(SchemaResult): + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/Token", + jni_ref, + b"getTokenType", + b"()Lorg/kson/api/TokenType;", + "ObjectMethod", + [] + ) - _jni_ref: Any + return cast(Any, (lambda x0: TokenType._from_kotlin_enum(x0))(result)) - def __init__( + def text( self, - schema_validator: SchemaValidator, - ): - if schema_validator is None: - raise ValueError("`schema_validator` cannot be None") - self._jni_ref = _construct( - b"org/kson/SchemaResult$Success", - b"(Lorg/kson/SchemaValidator;)V", - [ + ) -> str: - schema_validator._jni_ref, - ] + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/Token", + jni_ref, + b"getText", + b"()Ljava/lang/String;", + "ObjectMethod", + [] ) - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + return cast(Any, (_java_string_to_python_str)(result)) + + def start( + self, + ) -> Position: - def schema_validator( + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/Token", + jni_ref, + b"getStart", + b"()Lorg/kson/api/Position;", + "ObjectMethod", + [] + ) + + return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) + + def end( self, - ) -> SchemaValidator: + ) -> Position: jni_ref = self._jni_ref result = _call_method( - b"org/kson/SchemaResult$Success", + b"org/kson/api/Token", jni_ref, - b"getSchemaValidator", - b"()Lorg/kson/SchemaValidator;", + b"getEnd", + b"()Lorg/kson/api/Position;", "ObjectMethod", [] ) - return cast(Any, (lambda x0: _from_kotlin_object(SchemaValidator, x0))(result)) -SchemaResult.Success = _SchemaResult_Success + return cast(Any, (lambda x0: _from_kotlin_object(Position, x0))(result)) -class _SchemaResult_Failure(SchemaResult): +class Analysis: _jni_ref: Any def __init__( self, errors: List[Message], + tokens: List[Token], + kson_value: Optional[KsonValue], ): if errors is None: raise ValueError("`errors` cannot be None") + if tokens is None: + raise ValueError("`tokens` cannot be None") self._jni_ref = _construct( - b"org/kson/SchemaResult$Failure", - b"(Ljava/util/List;)V", + b"org/kson/api/Analysis", + b"(Ljava/util/List;Ljava/util/List;Lorg/kson/api/KsonValue;)V", [ _to_kotlin_list(errors), + _to_kotlin_list(tokens), + kson_value._jni_ref if kson_value is not None else ffi.NULL, ] ) def __eq__(self, other): @@ -1620,7 +1828,7 @@ def errors( jni_ref = self._jni_ref result = _call_method( - b"org/kson/SchemaResult$Failure", + b"org/kson/api/Analysis", jni_ref, b"getErrors", b"()Ljava/util/List;", @@ -1629,77 +1837,60 @@ def errors( ) return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Message, x1)))(result)) -SchemaResult.Failure = _SchemaResult_Failure - - - -class TranspileOptions: - """Core interface for transpilation options shared across all output formats.""" - _jni_ref: Any - - Json: TypeAlias - Yaml: TypeAlias - def __init__( + def tokens( self, + ) -> List[Token]: - ): - self._jni_ref = _construct( - b"org/kson/TranspileOptions", - b"()V", + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/Analysis", + jni_ref, + b"getTokens", + b"()Ljava/util/List;", + "ObjectMethod", [] ) - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) + return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(Token, x1)))(result)) - def retain_embed_tags( + def kson_value( self, - ) -> bool: + ) -> Optional[KsonValue]: jni_ref = self._jni_ref result = _call_method( - b"org/kson/TranspileOptions", + b"org/kson/api/Analysis", jni_ref, - b"getRetainEmbedTags", - b"()Z", - "BooleanMethod", + b"getKsonValue", + b"()Lorg/kson/api/KsonValue;", + "ObjectMethod", [] ) - return cast(Any, (lambda x0: x0)(result)) - @staticmethod - def _downcast(jni_ref) -> Any: - match _jni_class_name(jni_ref): - - case "org.kson.TranspileOptions$Json": - return _from_kotlin_object(_TranspileOptions_Json, jni_ref) + return cast(Any, (lambda x0: None if x0 == ffi.NULL else (lambda x0: KsonValue._downcast(x0))(x0))(result)) - case "org.kson.TranspileOptions$Yaml": - return _from_kotlin_object(_TranspileOptions_Yaml, jni_ref) -class _TranspileOptions_Json(TranspileOptions): - """Options for transpiling Kson to JSON.""" +class EmbedRule: _jni_ref: Any def __init__( self, - retain_embed_tags: bool, + path_pattern: str, + tag: Optional[str], ): - if retain_embed_tags is None: - raise ValueError("`retain_embed_tags` cannot be None") + if path_pattern is None: + raise ValueError("`path_pattern` cannot be None") self._jni_ref = _construct( - b"org/kson/TranspileOptions$Json", - b"(Z)V", + b"org/kson/api/EmbedRule", + b"(Ljava/lang/String;Ljava/lang/String;)V", [ - ffi.cast('jboolean', retain_embed_tags), + _python_str_to_java_string(path_pattern), + _python_str_to_java_string(tag) if tag is not None else ffi.NULL, ] ) def __eq__(self, other): @@ -1709,42 +1900,65 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def retain_embed_tags( + def path_pattern( self, - ) -> bool: + ) -> str: jni_ref = self._jni_ref result = _call_method( - b"org/kson/TranspileOptions$Json", + b"org/kson/api/EmbedRule", jni_ref, - b"getRetainEmbedTags", - b"()Z", - "BooleanMethod", + b"getPathPattern", + b"()Ljava/lang/String;", + "ObjectMethod", [] ) - return cast(Any, (lambda x0: x0)(result)) -TranspileOptions.Json = _TranspileOptions_Json + return cast(Any, (_java_string_to_python_str)(result)) + def tag( + self, + ) -> Optional[str]: -class _TranspileOptions_Yaml(TranspileOptions): - """Options for transpiling Kson to YAML.""" + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/EmbedRule", + jni_ref, + b"getTag", + b"()Ljava/lang/String;", + "ObjectMethod", + [] + ) + + return cast(Any, (lambda x0: None if x0 == ffi.NULL else (_java_string_to_python_str)(x0))(result)) + + +class FormatOptions: _jni_ref: Any def __init__( self, - retain_embed_tags: bool, + indent_type: IndentType, + formatting_style: FormattingStyle, + embed_block_rules: List[EmbedRule], ): - if retain_embed_tags is None: - raise ValueError("`retain_embed_tags` cannot be None") + if indent_type is None: + raise ValueError("`indent_type` cannot be None") + if formatting_style is None: + raise ValueError("`formatting_style` cannot be None") + if embed_block_rules is None: + raise ValueError("`embed_block_rules` cannot be None") self._jni_ref = _construct( - b"org/kson/TranspileOptions$Yaml", - b"(Z)V", + b"org/kson/api/FormatOptions", + b"(Lorg/kson/api/IndentType;Lorg/kson/api/FormattingStyle;Ljava/util/List;)V", [ - ffi.cast('jboolean', retain_embed_tags), + indent_type._jni_ref, + formatting_style._to_kotlin_enum(), + _to_kotlin_list(embed_block_rules), ] ) def __eq__(self, other): @@ -1754,24 +1968,56 @@ def __hash__(self): return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - def retain_embed_tags( + def indent_type( self, - ) -> bool: + ) -> IndentType: jni_ref = self._jni_ref result = _call_method( - b"org/kson/TranspileOptions$Yaml", + b"org/kson/api/FormatOptions", jni_ref, - b"getRetainEmbedTags", - b"()Z", - "BooleanMethod", + b"getIndentType", + b"()Lorg/kson/api/IndentType;", + "ObjectMethod", [] ) - return cast(Any, (lambda x0: x0)(result)) -TranspileOptions.Yaml = _TranspileOptions_Yaml + return cast(Any, (lambda x0: IndentType._downcast(x0))(result)) + + def formatting_style( + self, + ) -> FormattingStyle: + + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/FormatOptions", + jni_ref, + b"getFormattingStyle", + b"()Lorg/kson/api/FormattingStyle;", + "ObjectMethod", + [] + ) + + return cast(Any, (lambda x0: FormattingStyle._from_kotlin_enum(x0))(result)) + def embed_block_rules( + self, + ) -> List[EmbedRule]: + + + jni_ref = self._jni_ref + result = _call_method( + b"org/kson/api/FormatOptions", + jni_ref, + b"getEmbedBlockRules", + b"()Ljava/util/List;", + "ObjectMethod", + [] + ) + + return cast(Any, (lambda x0: _from_kotlin_list(x0, lambda x1: _from_kotlin_object(EmbedRule, x1)))(result)) class Kson: @@ -1808,7 +2054,7 @@ def format( b"org/kson/Kson", jni_ref, b"format", - b"(Ljava/lang/String;Lorg/kson/FormatOptions;)Ljava/lang/String;", + b"(Ljava/lang/String;Lorg/kson/api/FormatOptions;)Ljava/lang/String;", "ObjectMethod", [ @@ -1841,7 +2087,7 @@ def to_json( b"org/kson/Kson", jni_ref, b"toJson", - b"(Ljava/lang/String;Lorg/kson/TranspileOptions$Json;)Lorg/kson/Result;", + b"(Ljava/lang/String;Lorg/kson/api/TranspileOptions$Json;)Lorg/kson/api/Result;", "ObjectMethod", [ @@ -1874,7 +2120,7 @@ def to_yaml( b"org/kson/Kson", jni_ref, b"toYaml", - b"(Ljava/lang/String;Lorg/kson/TranspileOptions$Yaml;)Lorg/kson/Result;", + b"(Ljava/lang/String;Lorg/kson/api/TranspileOptions$Yaml;)Lorg/kson/api/Result;", "ObjectMethod", [ @@ -1904,7 +2150,7 @@ def analyze( b"org/kson/Kson", jni_ref, b"analyze", - b"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/Analysis;", + b"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/api/Analysis;", "ObjectMethod", [ @@ -1933,7 +2179,7 @@ def parse_schema( b"org/kson/Kson", jni_ref, b"parseSchema", - b"(Ljava/lang/String;)Lorg/kson/SchemaResult;", + b"(Ljava/lang/String;)Lorg/kson/api/SchemaResult;", "ObjectMethod", [ @@ -1944,233 +2190,66 @@ def parse_schema( return cast(Any, (lambda x0: SchemaResult._downcast(x0))(result)) -class EmbedRule: - """A rule for formatting string values at specific paths as embed blocks. - - When formatting KSON, strings at paths matching [pathPattern] will be rendered - as embed blocks instead of regular strings. - - **Warning:** JsonPointerGlob syntax is experimental and may change in future versions. - """ - - _jni_ref: Any - - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - - - def path_pattern( - self, - ) -> str: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/EmbedRule", - jni_ref, - b"getPathPattern", - b"()Ljava/lang/String;", - "ObjectMethod", - [] - ) - - return cast(Any, (_java_string_to_python_str)(result)) - - def tag( - self, - ) -> Optional[str]: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/EmbedRule", - jni_ref, - b"getTag", - b"()Ljava/lang/String;", - "ObjectMethod", - [] - ) - - return cast(Any, (lambda x0: None if x0 == ffi.NULL else (_java_string_to_python_str)(x0))(result)) - - @staticmethod - def from_path_pattern( - path_pattern: str, - tag: Optional[str], - - ) -> EmbedRuleResult: - """Builds a new [EmbedRule]. - - @param pathPattern A JsonPointerGlob pattern (e.g., "/scripts/ *", "/queries/ **") - @param tag Optional embed tag to include (e.g., "yaml", "sql", "bash") - @return [EmbedRuleResult.Success] if [pathPattern] is a valid JsonPointerGlob, otherwise [EmbedRuleResult.Failure] - - Example: - ```kotlin - EmbedRule.fromPathPattern("/scripts/ *", tag = "bash") // Match all values under "scripts" - EmbedRule.fromPathPattern("/config/description") // Match exact path, no tag - ``` - """ - - if path_pattern is None: - raise ValueError("`path_pattern` cannot be None") - jni_ref = _access_static_field(b"org/kson/EmbedRule", b"Companion", b"Lorg/kson/EmbedRule$Companion;") - result = _call_method( - b"org/kson/EmbedRule$Companion", - jni_ref, - b"fromPathPattern", - b"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/EmbedRuleResult;", - "ObjectMethod", - [ - - _python_str_to_java_string(path_pattern), - _python_str_to_java_string(tag) if tag is not None else ffi.NULL, - ] - ) - - return cast(Any, (lambda x0: EmbedRuleResult._downcast(x0))(result)) - - -class IndentType: - """Options for indenting Kson Output""" - - _jni_ref: Any - - Tabs: TypeAlias - Spaces: TypeAlias - def __init__( - self, - - ): - - self._jni_ref = _construct( - b"org/kson/IndentType", - b"()V", - [] - ) - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - - @staticmethod - def _downcast(jni_ref) -> Any: - match _jni_class_name(jni_ref): - - case "org.kson.IndentType$Tabs": - return _from_kotlin_object(_IndentType_Tabs, jni_ref) - - case "org.kson.IndentType$Spaces": - return _from_kotlin_object(_IndentType_Spaces, jni_ref) - -class _IndentType_Spaces(IndentType): - """Use spaces for indentation with the specified count""" - - _jni_ref: Any - - def __init__( - self, - size: int, - ): - if size is None: - raise ValueError("`size` cannot be None") - self._jni_ref = _construct( - b"org/kson/IndentType$Spaces", - b"(I)V", - [ - - ffi.cast('jint', size), - ] - ) - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - - - def size( - self, - ) -> int: - - - jni_ref = self._jni_ref - result = _call_method( - b"org/kson/IndentType$Spaces", - jni_ref, - b"getSize", - b"()I", - "IntMethod", - [] - ) - - return cast(Any, (lambda x0: x0)(result)) -IndentType.Spaces = _IndentType_Spaces - - -class _IndentType_Tabs(IndentType): - """Use tabs for indentation""" - - _jni_ref: Any - - def __init__(self): - self._jni_ref = _access_static_field(b"org/kson/IndentType$Tabs", b"INSTANCE", b"Lorg/kson/IndentType$Tabs;") - def __eq__(self, other): - return _call_method(b"java/lang/Object", self._jni_ref, b"equals", b"(Ljava/lang/Object;)Z", "BooleanMethod", [other._jni_ref]) - - def __hash__(self): - return _call_method(b"java/lang/Object", self._jni_ref, b"hashCode", b"()I", "IntMethod", []) - -IndentType.Tabs = _IndentType_Tabs - - - class MessageSeverity(Enum): - """Represents the severity of a [Message]""" - def _to_kotlin_enum(self): match self: case MessageSeverity.ERROR: - return _access_static_field(b"org/kson/MessageSeverity", b"ERROR", b"Lorg/kson/MessageSeverity;") + return _access_static_field(b"org/kson/api/MessageSeverity", b"ERROR", b"Lorg/kson/api/MessageSeverity;") case MessageSeverity.WARNING: - return _access_static_field(b"org/kson/MessageSeverity", b"WARNING", b"Lorg/kson/MessageSeverity;") + return _access_static_field(b"org/kson/api/MessageSeverity", b"WARNING", b"Lorg/kson/api/MessageSeverity;") @staticmethod def _from_kotlin_enum(jni_ref): - index = _call_method(b"org/kson/MessageSeverity", jni_ref, b"ordinal", b"()I", "IntMethod", []) + index = _call_method(b"org/kson/api/MessageSeverity", jni_ref, b"ordinal", b"()I", "IntMethod", []) return MessageSeverity(index) ERROR = 0 WARNING = 1 -class KsonValueType(Enum): - """Type discriminator for KsonValue subclasses""" +class FormattingStyle(Enum): + def _to_kotlin_enum(self): + match self: + case FormattingStyle.PLAIN: + return _access_static_field(b"org/kson/api/FormattingStyle", b"PLAIN", b"Lorg/kson/api/FormattingStyle;") + case FormattingStyle.DELIMITED: + return _access_static_field(b"org/kson/api/FormattingStyle", b"DELIMITED", b"Lorg/kson/api/FormattingStyle;") + case FormattingStyle.COMPACT: + return _access_static_field(b"org/kson/api/FormattingStyle", b"COMPACT", b"Lorg/kson/api/FormattingStyle;") + case FormattingStyle.CLASSIC: + return _access_static_field(b"org/kson/api/FormattingStyle", b"CLASSIC", b"Lorg/kson/api/FormattingStyle;") + @staticmethod + def _from_kotlin_enum(jni_ref): + index = _call_method(b"org/kson/api/FormattingStyle", jni_ref, b"ordinal", b"()I", "IntMethod", []) + return FormattingStyle(index) + + PLAIN = 0 + DELIMITED = 1 + COMPACT = 2 + CLASSIC = 3 + +class KsonValueType(Enum): def _to_kotlin_enum(self): match self: case KsonValueType.OBJECT: - return _access_static_field(b"org/kson/KsonValueType", b"OBJECT", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"OBJECT", b"Lorg/kson/api/KsonValueType;") case KsonValueType.ARRAY: - return _access_static_field(b"org/kson/KsonValueType", b"ARRAY", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"ARRAY", b"Lorg/kson/api/KsonValueType;") case KsonValueType.STRING: - return _access_static_field(b"org/kson/KsonValueType", b"STRING", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"STRING", b"Lorg/kson/api/KsonValueType;") case KsonValueType.INTEGER: - return _access_static_field(b"org/kson/KsonValueType", b"INTEGER", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"INTEGER", b"Lorg/kson/api/KsonValueType;") case KsonValueType.DECIMAL: - return _access_static_field(b"org/kson/KsonValueType", b"DECIMAL", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"DECIMAL", b"Lorg/kson/api/KsonValueType;") case KsonValueType.BOOLEAN: - return _access_static_field(b"org/kson/KsonValueType", b"BOOLEAN", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"BOOLEAN", b"Lorg/kson/api/KsonValueType;") case KsonValueType.NULL: - return _access_static_field(b"org/kson/KsonValueType", b"NULL", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"NULL", b"Lorg/kson/api/KsonValueType;") case KsonValueType.EMBED: - return _access_static_field(b"org/kson/KsonValueType", b"EMBED", b"Lorg/kson/KsonValueType;") + return _access_static_field(b"org/kson/api/KsonValueType", b"EMBED", b"Lorg/kson/api/KsonValueType;") @staticmethod def _from_kotlin_enum(jni_ref): - index = _call_method(b"org/kson/KsonValueType", jni_ref, b"ordinal", b"()I", "IntMethod", []) + index = _call_method(b"org/kson/api/KsonValueType", jni_ref, b"ordinal", b"()I", "IntMethod", []) return KsonValueType(index) OBJECT = 0 @@ -2183,92 +2262,68 @@ def _from_kotlin_enum(jni_ref): EMBED = 7 -class FormattingStyle(Enum): - """[FormattingStyle] options for Kson Output""" - - def _to_kotlin_enum(self): - match self: - case FormattingStyle.PLAIN: - return _access_static_field(b"org/kson/FormattingStyle", b"PLAIN", b"Lorg/kson/FormattingStyle;") - case FormattingStyle.DELIMITED: - return _access_static_field(b"org/kson/FormattingStyle", b"DELIMITED", b"Lorg/kson/FormattingStyle;") - case FormattingStyle.COMPACT: - return _access_static_field(b"org/kson/FormattingStyle", b"COMPACT", b"Lorg/kson/FormattingStyle;") - case FormattingStyle.CLASSIC: - return _access_static_field(b"org/kson/FormattingStyle", b"CLASSIC", b"Lorg/kson/FormattingStyle;") - @staticmethod - def _from_kotlin_enum(jni_ref): - index = _call_method(b"org/kson/FormattingStyle", jni_ref, b"ordinal", b"()I", "IntMethod", []) - return FormattingStyle(index) - - PLAIN = 0 - DELIMITED = 1 - COMPACT = 2 - CLASSIC = 3 - - class TokenType(Enum): def _to_kotlin_enum(self): match self: case TokenType.CURLY_BRACE_L: - return _access_static_field(b"org/kson/TokenType", b"CURLY_BRACE_L", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"CURLY_BRACE_L", b"Lorg/kson/api/TokenType;") case TokenType.CURLY_BRACE_R: - return _access_static_field(b"org/kson/TokenType", b"CURLY_BRACE_R", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"CURLY_BRACE_R", b"Lorg/kson/api/TokenType;") case TokenType.SQUARE_BRACKET_L: - return _access_static_field(b"org/kson/TokenType", b"SQUARE_BRACKET_L", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"SQUARE_BRACKET_L", b"Lorg/kson/api/TokenType;") case TokenType.SQUARE_BRACKET_R: - return _access_static_field(b"org/kson/TokenType", b"SQUARE_BRACKET_R", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"SQUARE_BRACKET_R", b"Lorg/kson/api/TokenType;") case TokenType.ANGLE_BRACKET_L: - return _access_static_field(b"org/kson/TokenType", b"ANGLE_BRACKET_L", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"ANGLE_BRACKET_L", b"Lorg/kson/api/TokenType;") case TokenType.ANGLE_BRACKET_R: - return _access_static_field(b"org/kson/TokenType", b"ANGLE_BRACKET_R", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"ANGLE_BRACKET_R", b"Lorg/kson/api/TokenType;") case TokenType.COLON: - return _access_static_field(b"org/kson/TokenType", b"COLON", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"COLON", b"Lorg/kson/api/TokenType;") case TokenType.DOT: - return _access_static_field(b"org/kson/TokenType", b"DOT", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"DOT", b"Lorg/kson/api/TokenType;") case TokenType.END_DASH: - return _access_static_field(b"org/kson/TokenType", b"END_DASH", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"END_DASH", b"Lorg/kson/api/TokenType;") case TokenType.COMMA: - return _access_static_field(b"org/kson/TokenType", b"COMMA", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"COMMA", b"Lorg/kson/api/TokenType;") case TokenType.COMMENT: - return _access_static_field(b"org/kson/TokenType", b"COMMENT", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"COMMENT", b"Lorg/kson/api/TokenType;") case TokenType.EMBED_OPEN_DELIM: - return _access_static_field(b"org/kson/TokenType", b"EMBED_OPEN_DELIM", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EMBED_OPEN_DELIM", b"Lorg/kson/api/TokenType;") case TokenType.EMBED_CLOSE_DELIM: - return _access_static_field(b"org/kson/TokenType", b"EMBED_CLOSE_DELIM", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EMBED_CLOSE_DELIM", b"Lorg/kson/api/TokenType;") case TokenType.EMBED_TAG: - return _access_static_field(b"org/kson/TokenType", b"EMBED_TAG", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EMBED_TAG", b"Lorg/kson/api/TokenType;") case TokenType.EMBED_PREAMBLE_NEWLINE: - return _access_static_field(b"org/kson/TokenType", b"EMBED_PREAMBLE_NEWLINE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EMBED_PREAMBLE_NEWLINE", b"Lorg/kson/api/TokenType;") case TokenType.EMBED_CONTENT: - return _access_static_field(b"org/kson/TokenType", b"EMBED_CONTENT", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EMBED_CONTENT", b"Lorg/kson/api/TokenType;") case TokenType.FALSE: - return _access_static_field(b"org/kson/TokenType", b"FALSE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"FALSE", b"Lorg/kson/api/TokenType;") case TokenType.UNQUOTED_STRING: - return _access_static_field(b"org/kson/TokenType", b"UNQUOTED_STRING", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"UNQUOTED_STRING", b"Lorg/kson/api/TokenType;") case TokenType.ILLEGAL_CHAR: - return _access_static_field(b"org/kson/TokenType", b"ILLEGAL_CHAR", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"ILLEGAL_CHAR", b"Lorg/kson/api/TokenType;") case TokenType.LIST_DASH: - return _access_static_field(b"org/kson/TokenType", b"LIST_DASH", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"LIST_DASH", b"Lorg/kson/api/TokenType;") case TokenType.NULL: - return _access_static_field(b"org/kson/TokenType", b"NULL", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"NULL", b"Lorg/kson/api/TokenType;") case TokenType.NUMBER: - return _access_static_field(b"org/kson/TokenType", b"NUMBER", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"NUMBER", b"Lorg/kson/api/TokenType;") case TokenType.STRING_OPEN_QUOTE: - return _access_static_field(b"org/kson/TokenType", b"STRING_OPEN_QUOTE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"STRING_OPEN_QUOTE", b"Lorg/kson/api/TokenType;") case TokenType.STRING_CLOSE_QUOTE: - return _access_static_field(b"org/kson/TokenType", b"STRING_CLOSE_QUOTE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"STRING_CLOSE_QUOTE", b"Lorg/kson/api/TokenType;") case TokenType.STRING_CONTENT: - return _access_static_field(b"org/kson/TokenType", b"STRING_CONTENT", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"STRING_CONTENT", b"Lorg/kson/api/TokenType;") case TokenType.TRUE: - return _access_static_field(b"org/kson/TokenType", b"TRUE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"TRUE", b"Lorg/kson/api/TokenType;") case TokenType.WHITESPACE: - return _access_static_field(b"org/kson/TokenType", b"WHITESPACE", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"WHITESPACE", b"Lorg/kson/api/TokenType;") case TokenType.EOF: - return _access_static_field(b"org/kson/TokenType", b"EOF", b"Lorg/kson/TokenType;") + return _access_static_field(b"org/kson/api/TokenType", b"EOF", b"Lorg/kson/api/TokenType;") @staticmethod def _from_kotlin_enum(jni_ref): - index = _call_method(b"org/kson/TokenType", jni_ref, b"ordinal", b"()I", "IntMethod", []) + index = _call_method(b"org/kson/api/TokenType", jni_ref, b"ordinal", b"()I", "IntMethod", []) return TokenType(index) CURLY_BRACE_L = 0 diff --git a/lib-python/tests/api_server.py b/lib-python/tests/api_server.py index 38914f23..676ec6c8 100644 --- a/lib-python/tests/api_server.py +++ b/lib-python/tests/api_server.py @@ -2,9 +2,10 @@ import json import sys +import traceback from http.server import HTTPServer, BaseHTTPRequestHandler from kson import ( - Kson, FormatOptions, IndentType, FormattingStyle, EmbedRule, EmbedRuleResult, + Kson, FormatOptions, IndentType, FormattingStyle, EmbedRule, TranspileOptions, Result, SchemaResult, KsonValue, KsonValueType, MessageSeverity, ) @@ -114,9 +115,7 @@ def _parse_formatting_style(name): def _parse_embed_rule(obj): - result = EmbedRule.from_path_pattern(obj["pathPattern"], obj.get("tag")) - assert isinstance(result, EmbedRuleResult.Success) - return result.embed_rule() + return EmbedRule(obj["pathPattern"], obj.get("tag")) def _parse_format_options(obj): @@ -183,15 +182,6 @@ def handle_parse_schema(req): } -def handle_validate_embed_rule(req): - rule_obj = req["embedBlockRule"] - result = EmbedRule.from_path_pattern(rule_obj["pathPattern"], rule_obj.get("tag")) - if isinstance(result, EmbedRuleResult.Success): - return {"command": "validateEmbedRule", "success": True} - else: - return {"command": "validateEmbedRule", "success": False, "error": result.message()} - - def handle_validate(req): schema_result = Kson.parse_schema(req["schemaKson"]) if isinstance(schema_result, SchemaResult.Failure): @@ -217,7 +207,6 @@ def handle_validate(req): "analyze": handle_analyze, "parseSchema": handle_parse_schema, "validate": handle_validate, - "validateEmbedRule": handle_validate_embed_rule, } @@ -241,7 +230,8 @@ def do_POST(self): try: response = handler(req) except Exception as e: - self._send_error(500, str(e)) + stack_trace = traceback.format_exc() + self._send_error(500, stack_trace) return self._send_json(200, response) diff --git a/lib-rust/kson-lib-tests/build.gradle.kts b/lib-rust/kson-lib-tests/build.gradle.kts index 4841c624..71516170 100644 --- a/lib-rust/kson-lib-tests/build.gradle.kts +++ b/lib-rust/kson-lib-tests/build.gradle.kts @@ -8,16 +8,6 @@ repositories { val releaseBuildDir: String = project(":lib-rust").projectDir.resolve("kson-test-server/target/release").absolutePath -val syncCommonTestSources by tasks.registering(Sync::class) { - from(project(":kson-lib").file("src/commonTest/kotlin")) - into(layout.buildDirectory.dir("commonTestSources")) -} - -val syncJvmTestSources by tasks.registering(Sync::class) { - from(project(":kson-lib").file("src/jvmTest/kotlin")) - into(layout.buildDirectory.dir("jvmTestSources")) -} - val buildTestServer by tasks.registering(Exec::class) { dependsOn(":kson-lib:buildWithGraalVmNativeImage") @@ -53,22 +43,17 @@ kotlin { sourceSets { commonTest { dependencies { - implementation(project(":kson-lib-http")) - implementation(kotlin("test")) - } - - kotlin { - srcDir(syncCommonTestSources) + implementation(project(":kson-service-tests")) + implementation(project(":kson-http")) } } jvmTest { dependencies { - implementation("org.junit.jupiter:junit-jupiter-api:5.14.2") - runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.14.2") - } + implementation(kotlin("test-junit5")) - kotlin { - srcDir(syncJvmTestSources) + // Important: this ensures we have a recent-enough version of JUnit, supporting the `AutoCloseable` + // interface (otherwise test runs never finish because the HTTP server doesn't get closed) + implementation(project.dependencies.platform("org.junit:junit-bom:5.14.3")) } } } diff --git a/lib-rust/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt b/lib-rust/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt new file mode 100644 index 00000000..cbd81068 --- /dev/null +++ b/lib-rust/kson-lib-tests/src/jvmTest/kotlin/SmokeTest.kt @@ -0,0 +1,9 @@ +import org.kson.Kson +import org.kson.api.KsonService +import org.kson.api.KsonServiceSmokeTest + +class SmokeTest : KsonServiceSmokeTest() { + override fun createService(): KsonService { + return Kson + } +} \ No newline at end of file diff --git a/lib-rust/kson-test-server/src/main.rs b/lib-rust/kson-test-server/src/main.rs index 7b38fbf0..466ed72e 100644 --- a/lib-rust/kson-test-server/src/main.rs +++ b/lib-rust/kson-test-server/src/main.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use serde_json::Value; use kson::{ - EmbedRule, EmbedRuleResult, FormatOptions, FormattingStyle, IndentType, Kson, KsonValue, + EmbedRule, FormatOptions, FormattingStyle, IndentType, Kson, KsonValue, indent_type, kson_value, transpile_options, }; @@ -47,11 +47,6 @@ enum Request { kson: String, filepath: Option, }, - #[serde(rename = "validateEmbedRule")] - ValidateEmbedRule { - #[serde(rename = "embedBlockRule")] - embed_block_rule: EmbedRuleDto, - }, } fn default_true() -> bool { @@ -259,15 +254,7 @@ fn convert_format_options(dto: Option<&FormatOptionsDto>) -> FormatOptions { .map(|rules| { rules .iter() - .filter_map(|r| { - match EmbedRule::from_path_pattern( - &r.path_pattern, - r.tag.as_deref(), - ) { - EmbedRuleResult::Success(s) => Some(s.embed_rule()), - EmbedRuleResult::Failure(_) => None, - } - }) + .map(|r| EmbedRule::new(&r.path_pattern, r.tag.as_deref())) .collect() }) .unwrap_or_default(); @@ -399,22 +386,6 @@ fn handle_request(request: Request) -> Value { "errors": errors, }) } - }, - Request::ValidateEmbedRule { embed_block_rule } => { - match EmbedRule::from_path_pattern( - &embed_block_rule.path_pattern, - embed_block_rule.tag.as_deref(), - ) { - EmbedRuleResult::Success(_) => serde_json::json!({ - "command": "validateEmbedRule", - "success": true, - }), - EmbedRuleResult::Failure(failure) => serde_json::json!({ - "command": "validateEmbedRule", - "success": false, - "error": failure.message(), - }), - } } } } diff --git a/lib-rust/kson/src/generated/mod.rs b/lib-rust/kson/src/generated/mod.rs index dabaec17..0c89ad11 100644 --- a/lib-rust/kson/src/generated/mod.rs +++ b/lib-rust/kson/src/generated/mod.rs @@ -11,149 +11,250 @@ use self::sys::jobject; use self::util::{AsKotlinObject, FromKotlinObject, KotlinPtr, ToKotlinObject}; -/// Options for formatting Kson output. -/// -/// @param indentType The type of indentation to use (spaces or tabs) -/// @param formattingStyle The formatting style (PLAIN, DELIMITED, COMPACT, CLASSIC) -/// @param embedBlockRules Rules for formatting specific paths as embed blocks #[derive(Clone)] -pub struct FormatOptions { - kotlin_ptr: KotlinPtr, +pub enum SchemaResult { + Failure(schema_result::Failure), + Success(schema_result::Success), } -impl FromKotlinObject for FormatOptions { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } +pub mod schema_result { + use super::*; + + + + #[derive(Clone)] + pub struct Failure { + kotlin_ptr: KotlinPtr, } -} -impl ToKotlinObject for FormatOptions { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() + impl FromKotlinObject for Failure { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } } -} -impl AsKotlinObject for FormatOptions { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner + impl ToKotlinObject for Failure { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } } -} -impl FormatOptions { - pub fn new( - indent_type: IndentType, - formatting_style: FormattingStyle, - embed_block_rules: &[EmbedRule], - ) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/FormatOptions"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/IndentType;Lorg/kson/FormattingStyle;Ljava/util/List;)V"); + impl AsKotlinObject for Failure { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } + } - let indent_type_ptr = indent_type.to_kotlin_object(); - let indent_type = indent_type_ptr.as_kotlin_object(); - let formatting_style_ptr = formatting_style.to_kotlin_object(); - let formatting_style = formatting_style_ptr.as_kotlin_object(); - let embed_block_rules_ptr = util::to_kotlin_list(embed_block_rules); - let embed_block_rules = embed_block_rules_ptr.as_kotlin_object(); + impl Failure { + pub fn new( + errors: &[Message], + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/SchemaResult$Failure"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/List;)V"); - let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - indent_type, - formatting_style, - embed_block_rules, - )}; - util::panic_upon_exception(env); - Self { - kotlin_ptr: util::to_gc_global_ref(env, jobject) + let errors_ptr = util::to_kotlin_list(errors); + let errors = errors_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + errors, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } } } -} -impl FormatOptions { + impl Failure { - pub fn indent_type( - &self, - ) -> IndentType { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + pub fn errors( + &self, + ) -> Vec { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/FormatOptions", - c"getIndentType", - c"()Lorg/kson/IndentType;", - CallObjectMethod, - self_obj, + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/SchemaResult$Failure", + c"getErrors", + c"()Ljava/util/List;", + CallObjectMethod, + self_obj, - ); + ); - FromKotlinObject::from_kotlin_object(result) + util::from_kotlin_list(result) + } } + impl std::fmt::Debug for Failure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/SchemaResult$Failure", &obj)) + } + } - pub fn formatting_style( - &self, - ) -> FormattingStyle { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + impl Eq for Failure {} + impl PartialEq for Failure { + fn eq(&self, other: &Failure) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } + } + impl std::hash::Hash for Failure { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } + } - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/FormatOptions", - c"getFormattingStyle", - c"()Lorg/kson/FormattingStyle;", - CallObjectMethod, - self_obj, + #[derive(Clone)] + pub struct Success { + kotlin_ptr: KotlinPtr, + } - ); + impl FromKotlinObject for Success { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } + } - FromKotlinObject::from_kotlin_object(result) + impl ToKotlinObject for Success { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } } + impl AsKotlinObject for Success { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } + } - pub fn embed_block_rules( - &self, - ) -> Vec { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + impl Success { + pub fn new( + schema_validator: SchemaValidatorService, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/SchemaResult$Success"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/api/SchemaValidatorService;)V"); + let schema_validator_ptr = schema_validator.to_kotlin_object(); + let schema_validator = schema_validator_ptr.as_kotlin_object(); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/FormatOptions", - c"getEmbedBlockRules", - c"()Ljava/util/List;", - CallObjectMethod, - self_obj, + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + schema_validator, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } + } - ); - util::from_kotlin_list(result) + impl Success { + + + pub fn schema_validator( + &self, + ) -> SchemaValidatorService { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/SchemaResult$Success", + c"getSchemaValidator", + c"()Lorg/kson/api/SchemaValidatorService;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + } + + impl std::fmt::Debug for Success { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/SchemaResult$Success", &obj)) + } + } + + impl Eq for Success {} + impl PartialEq for Success { + fn eq(&self, other: &Success) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } + } + impl std::hash::Hash for Success { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } + } +} +impl FromKotlinObject for SchemaResult { + fn from_kotlin_object(obj: jobject) -> Self { + match util::class_name(obj).as_str() { + "org.kson.api.SchemaResult$Failure" => SchemaResult::Failure(schema_result::Failure::from_kotlin_object(obj)), + "org.kson.api.SchemaResult$Success" => SchemaResult::Success(schema_result::Success::from_kotlin_object(obj)), + _ => unreachable!(), + } } } -impl std::fmt::Debug for FormatOptions { +impl ToKotlinObject for SchemaResult { + fn to_kotlin_object(&self) -> KotlinPtr { + match self { + Self::Failure(inner) => inner.to_kotlin_object(), + Self::Success(inner) => inner.to_kotlin_object(), + } + } +} + +impl SchemaResult { + pub fn name(self) -> String { + let obj = self.to_kotlin_object(); + util::enum_name(&obj) + } +} + + +impl SchemaResult { +} + +impl std::fmt::Debug for SchemaResult { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/FormatOptions", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/SchemaResult", &obj)) } } -impl Eq for FormatOptions {} -impl PartialEq for FormatOptions { - fn eq(&self, other: &FormatOptions) -> bool { +impl Eq for SchemaResult {} +impl PartialEq for SchemaResult { + fn eq(&self, other: &SchemaResult) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for FormatOptions { +impl std::hash::Hash for SchemaResult { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -163,10 +264,7 @@ impl std::hash::Hash for FormatOptions { } -/// A zero-based line/column position in a document -/// -/// @param line The line number where the error occurred (0-based) -/// @param column The column number where the error occurred (0-based) + #[derive(Clone)] pub struct Position { kotlin_ptr: KotlinPtr, @@ -193,6 +291,25 @@ impl AsKotlinObject for Position { } impl Position { + pub fn new( + line: i32, + column: i32, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/Position"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(II)V"); + + + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + line, + column, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } @@ -209,7 +326,7 @@ impl Position { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Position", + c"org/kson/api/Position", c"getLine", c"()I", CallIntMethod, @@ -225,94 +342,38 @@ impl Position { &self, ) -> i32 { let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Position", - c"getColumn", - c"()I", - CallIntMethod, - self_obj, - - ); - - result - } -} - -impl std::fmt::Debug for Position { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Position", &obj)) - } -} - -impl Eq for Position {} -impl PartialEq for Position { - fn eq(&self, other: &Position) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } -} -impl std::hash::Hash for Position { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } -} - - - -#[derive(Clone)] -pub struct Companion { - kotlin_ptr: KotlinPtr, -} - -impl FromKotlinObject for Companion { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } -} - -impl ToKotlinObject for Companion { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } -} - -impl AsKotlinObject for Companion { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } -} + let self_obj = self_ptr.as_kotlin_object(); -impl Companion { -} + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/Position", + c"getColumn", + c"()I", + CallIntMethod, + self_obj, + + ); -impl Companion { + result + } } -impl std::fmt::Debug for Companion { +impl std::fmt::Debug for Position { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/EmbedRule$Companion", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Position", &obj)) } } -impl Eq for Companion {} -impl PartialEq for Companion { - fn eq(&self, other: &Companion) -> bool { +impl Eq for Position {} +impl PartialEq for Position { + fn eq(&self, other: &Position) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Companion { +impl std::hash::Hash for Position { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -322,141 +383,234 @@ impl std::hash::Hash for Companion { } -/// Represents a message logged during Kson processing #[derive(Clone)] -pub struct Message { - kotlin_ptr: KotlinPtr, +pub enum IndentType { + Spaces(indent_type::Spaces), + Tabs(indent_type::Tabs), } -impl FromKotlinObject for Message { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } +pub mod indent_type { + use super::*; + + + + #[derive(Clone)] + pub struct Spaces { + kotlin_ptr: KotlinPtr, } -} -impl ToKotlinObject for Message { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() + impl FromKotlinObject for Spaces { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } } -} -impl AsKotlinObject for Message { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner + impl ToKotlinObject for Spaces { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } } -} -impl Message { -} + impl AsKotlinObject for Spaces { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } + } + impl Spaces { + pub fn new( + size: i32, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/IndentType$Spaces"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(I)V"); -impl Message { - pub fn message( - &self, - ) -> String { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + size, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } + } - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Message", - c"getMessage", - c"()Ljava/lang/String;", - CallObjectMethod, - self_obj, + impl Spaces { - ); - FromKotlinObject::from_kotlin_object(result) - } + pub fn size( + &self, + ) -> i32 { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn severity( - &self, - ) -> MessageSeverity { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/IndentType$Spaces", + c"getSize", + c"()I", + CallIntMethod, + self_obj, + ); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Message", - c"getSeverity", - c"()Lorg/kson/MessageSeverity;", - CallObjectMethod, - self_obj, + result + } + } - ); + impl std::fmt::Debug for Spaces { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/IndentType$Spaces", &obj)) + } + } - FromKotlinObject::from_kotlin_object(result) + impl Eq for Spaces {} + impl PartialEq for Spaces { + fn eq(&self, other: &Spaces) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } + } + impl std::hash::Hash for Spaces { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } } - pub fn start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + #[derive(Clone)] + pub struct Tabs { + kotlin_ptr: KotlinPtr, + } + impl FromKotlinObject for Tabs { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } + } - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Message", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + impl ToKotlinObject for Tabs { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } + } - ); + impl AsKotlinObject for Tabs { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } + } - FromKotlinObject::from_kotlin_object(result) + impl Tabs { + pub fn new() -> Self { + let kotlin_ptr = util::access_static_field(c"org/kson/api/IndentType$Tabs", c"INSTANCE", c"Lorg/kson/api/IndentType$Tabs;"); + Self { kotlin_ptr } + } } - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + impl Tabs { - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Message", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + pub fn to_string( + ) -> String { + let self_ptr = util::access_static_field(c"org/kson/api/IndentType$Tabs", c"INSTANCE", c"Lorg/kson/api/IndentType$Tabs;"); + let self_obj = self_ptr.as_kotlin_object(); - ); - FromKotlinObject::from_kotlin_object(result) + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/IndentType$Tabs", + c"toString", + c"()Ljava/lang/String;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + } + + impl std::fmt::Debug for Tabs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/IndentType$Tabs", &obj)) + } + } + + impl Eq for Tabs {} + impl PartialEq for Tabs { + fn eq(&self, other: &Tabs) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } + } + impl std::hash::Hash for Tabs { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } + } +} +impl FromKotlinObject for IndentType { + fn from_kotlin_object(obj: jobject) -> Self { + match util::class_name(obj).as_str() { + "org.kson.api.IndentType$Spaces" => IndentType::Spaces(indent_type::Spaces::from_kotlin_object(obj)), + "org.kson.api.IndentType$Tabs" => IndentType::Tabs(indent_type::Tabs::from_kotlin_object(obj)), + _ => unreachable!(), + } } } -impl std::fmt::Debug for Message { +impl ToKotlinObject for IndentType { + fn to_kotlin_object(&self) -> KotlinPtr { + match self { + Self::Spaces(inner) => inner.to_kotlin_object(), + Self::Tabs(inner) => inner.to_kotlin_object(), + } + } +} + +impl IndentType { + pub fn name(self) -> String { + let obj = self.to_kotlin_object(); + util::enum_name(&obj) + } +} + + +impl IndentType { +} + +impl std::fmt::Debug for IndentType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Message", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/IndentType", &obj)) } } -impl Eq for Message {} -impl PartialEq for Message { - fn eq(&self, other: &Message) -> bool { +impl Eq for IndentType {} +impl PartialEq for IndentType { + fn eq(&self, other: &IndentType) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Message { +impl std::hash::Hash for IndentType { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -466,13 +620,13 @@ impl std::hash::Hash for Message { } -/// [Token] produced by the lexing phase of a Kson parse + #[derive(Clone)] -pub struct Token { +pub struct Message { kotlin_ptr: KotlinPtr, } -impl FromKotlinObject for Token { +impl FromKotlinObject for Message { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -480,28 +634,58 @@ impl FromKotlinObject for Token { } } -impl ToKotlinObject for Token { +impl ToKotlinObject for Message { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } -impl AsKotlinObject for Token { +impl AsKotlinObject for Message { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } -impl Token { +impl Message { + pub fn new( + message: &str, + severity: MessageSeverity, + start: Position, + end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/Message"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;Lorg/kson/api/MessageSeverity;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let message_ptr = message.to_kotlin_object(); + let message = message_ptr.as_kotlin_object(); + let severity_ptr = severity.to_kotlin_object(); + let severity = severity_ptr.as_kotlin_object(); + let start_ptr = start.to_kotlin_object(); + let start = start_ptr.as_kotlin_object(); + let end_ptr = end.to_kotlin_object(); + let end = end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + message, + severity, + start, + end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } -impl Token { +impl Message { - pub fn token_type( + pub fn message( &self, - ) -> TokenType { + ) -> String { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -509,9 +693,9 @@ impl Token { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Token", - c"getTokenType", - c"()Lorg/kson/TokenType;", + c"org/kson/api/Message", + c"getMessage", + c"()Ljava/lang/String;", CallObjectMethod, self_obj, @@ -521,9 +705,9 @@ impl Token { } - pub fn text( + pub fn severity( &self, - ) -> String { + ) -> MessageSeverity { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -531,9 +715,9 @@ impl Token { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Token", - c"getText", - c"()Ljava/lang/String;", + c"org/kson/api/Message", + c"getSeverity", + c"()Lorg/kson/api/MessageSeverity;", CallObjectMethod, self_obj, @@ -553,9 +737,9 @@ impl Token { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Token", + c"org/kson/api/Message", c"getStart", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -575,9 +759,9 @@ impl Token { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Token", + c"org/kson/api/Message", c"getEnd", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -587,20 +771,20 @@ impl Token { } } -impl std::fmt::Debug for Token { +impl std::fmt::Debug for Message { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Token", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Message", &obj)) } } -impl Eq for Token {} -impl PartialEq for Token { - fn eq(&self, other: &Token) -> bool { +impl Eq for Message {} +impl PartialEq for Message { + fn eq(&self, other: &Message) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Token { +impl std::hash::Hash for Message { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -609,7 +793,7 @@ impl std::hash::Hash for Token { } } -/// Represents a parsed [InternalKsonValue] in the public API + #[derive(Clone)] pub enum KsonValue { KsonArray(kson_value::KsonArray), @@ -625,7 +809,7 @@ pub mod kson_value { use super::*; - /// A Kson array with elements + #[derive(Clone)] pub struct KsonArray { kotlin_ptr: KotlinPtr, @@ -652,6 +836,32 @@ pub mod kson_value { } impl KsonArray { + pub fn new( + elements: &[KsonValue], + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonArray"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/List;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let elements_ptr = util::to_kotlin_list(elements); + let elements = elements_ptr.as_kotlin_object(); + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + elements, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } @@ -668,7 +878,7 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonArray", + c"org/kson/api/KsonValue$KsonArray", c"getElements", c"()Ljava/util/List;", CallObjectMethod, @@ -690,9 +900,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonArray", + c"org/kson/api/KsonValue$KsonArray", c"getType", - c"()Lorg/kson/KsonValueType;", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -712,9 +922,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getStart", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -734,9 +944,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getEnd", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -749,7 +959,7 @@ pub mod kson_value { impl std::fmt::Debug for KsonArray { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonArray", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonArray", &obj)) } } @@ -768,7 +978,7 @@ pub mod kson_value { } } - /// A Kson boolean value + #[derive(Clone)] pub struct KsonBoolean { kotlin_ptr: KotlinPtr, @@ -795,6 +1005,31 @@ pub mod kson_value { } impl KsonBoolean { + pub fn new( + value: bool, + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonBoolean"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(ZLorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let value = value as c_int; + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + value, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } @@ -811,7 +1046,7 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonBoolean", + c"org/kson/api/KsonValue$KsonBoolean", c"getValue", c"()Z", CallBooleanMethod, @@ -833,9 +1068,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonBoolean", + c"org/kson/api/KsonValue$KsonBoolean", c"getType", - c"()Lorg/kson/KsonValueType;", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -855,9 +1090,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getStart", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -877,9 +1112,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getEnd", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -892,7 +1127,7 @@ pub mod kson_value { impl std::fmt::Debug for KsonBoolean { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonBoolean", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonBoolean", &obj)) } } @@ -911,7 +1146,7 @@ pub mod kson_value { } } - /// A Kson embed block + #[derive(Clone)] pub struct KsonEmbed { kotlin_ptr: KotlinPtr, @@ -938,6 +1173,36 @@ pub mod kson_value { } impl KsonEmbed { + pub fn new( + tag: Option<&str>, + content: &str, + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonEmbed"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let tag_ptr = tag.to_kotlin_object(); + let tag = tag_ptr.as_kotlin_object(); + let content_ptr = content.to_kotlin_object(); + let content = content_ptr.as_kotlin_object(); + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + tag, + content, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } @@ -954,7 +1219,7 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonEmbed", + c"org/kson/api/KsonValue$KsonEmbed", c"getTag", c"()Ljava/lang/String;", CallObjectMethod, @@ -976,7 +1241,7 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonEmbed", + c"org/kson/api/KsonValue$KsonEmbed", c"getContent", c"()Ljava/lang/String;", CallObjectMethod, @@ -998,9 +1263,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonEmbed", + c"org/kson/api/KsonValue$KsonEmbed", c"getType", - c"()Lorg/kson/KsonValueType;", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -1020,9 +1285,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getStart", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1042,9 +1307,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getEnd", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1057,7 +1322,7 @@ pub mod kson_value { impl std::fmt::Debug for KsonEmbed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonEmbed", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonEmbed", &obj)) } } @@ -1076,7 +1341,7 @@ pub mod kson_value { } } - /// A Kson null value + #[derive(Clone)] pub struct KsonNull { kotlin_ptr: KotlinPtr, @@ -1103,6 +1368,28 @@ pub mod kson_value { } impl KsonNull { + pub fn new( + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonNull"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } @@ -1119,9 +1406,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonNull", + c"org/kson/api/KsonValue$KsonNull", c"getType", - c"()Lorg/kson/KsonValueType;", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -1140,223 +1427,80 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - } - - impl std::fmt::Debug for KsonNull { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonNull", &obj)) - } - } - - impl Eq for KsonNull {} - impl PartialEq for KsonNull { - fn eq(&self, other: &KsonNull) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for KsonNull { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } - } - /// A Kson number value. - #[derive(Clone)] - pub enum KsonNumber { - Decimal(kson_value::kson_number::Decimal), - Integer(kson_value::kson_number::Integer), - } - - pub mod kson_number { - use super::*; - - - - #[derive(Clone)] - pub struct Decimal { - kotlin_ptr: KotlinPtr, - } - - impl FromKotlinObject for Decimal { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } - } - - impl ToKotlinObject for Decimal { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } - } - - impl AsKotlinObject for Decimal { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } - } - - impl Decimal { - } - - - impl Decimal { - - - pub fn value( - &self, - ) -> f64 { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonNumber$Decimal", - c"getValue", - c"()D", - CallDoubleMethod, - self_obj, - - ); - - result - } - - - pub fn type_( - &self, - ) -> KsonValueType { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonNumber$Decimal", - c"getType", - c"()Lorg/kson/KsonValueType;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - - pub fn start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + util, + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, - ); + ); - FromKotlinObject::from_kotlin_object(result) - } + FromKotlinObject::from_kotlin_object(result) + } - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + pub fn end( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, - ); + ); - FromKotlinObject::from_kotlin_object(result) - } + FromKotlinObject::from_kotlin_object(result) } + } - impl std::fmt::Debug for Decimal { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonNumber$Decimal", &obj)) - } + impl std::fmt::Debug for KsonNull { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonNull", &obj)) } + } - impl Eq for Decimal {} - impl PartialEq for Decimal { - fn eq(&self, other: &Decimal) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } + impl Eq for KsonNull {} + impl PartialEq for KsonNull { + fn eq(&self, other: &KsonNull) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } - impl std::hash::Hash for Decimal { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } + } + impl std::hash::Hash for KsonNull { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) } + } + + #[derive(Clone)] + pub enum KsonNumber { + Decimal(kson_value::kson_number::Decimal), + Integer(kson_value::kson_number::Integer), + } + + pub mod kson_number { + use super::*; + #[derive(Clone)] - pub struct Integer { + pub struct Decimal { kotlin_ptr: KotlinPtr, } - impl FromKotlinObject for Integer { + impl FromKotlinObject for Decimal { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -1364,28 +1508,53 @@ pub mod kson_value { } } - impl ToKotlinObject for Integer { + impl ToKotlinObject for Decimal { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } - impl AsKotlinObject for Integer { + impl AsKotlinObject for Decimal { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } - impl Integer { + impl Decimal { + pub fn new( + value: f64, + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonNumber$Decimal"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(DLorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + value, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } - impl Integer { + impl Decimal { pub fn value( &self, - ) -> i32 { + ) -> f64 { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -1393,10 +1562,10 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonNumber$Integer", + c"org/kson/api/KsonValue$KsonNumber$Decimal", c"getValue", - c"()I", - CallIntMethod, + c"()D", + CallDoubleMethod, self_obj, ); @@ -1405,50 +1574,6 @@ pub mod kson_value { } - pub fn internal_start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonNumber$Integer", - c"getInternalStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - - pub fn internal_end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonNumber$Integer", - c"getInternalEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - pub fn type_( &self, ) -> KsonValueType { @@ -1459,9 +1584,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonNumber$Integer", + c"org/kson/api/KsonValue$KsonNumber$Decimal", c"getType", - c"()Lorg/kson/KsonValueType;", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -1481,9 +1606,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getStart", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1503,9 +1628,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", + c"org/kson/api/KsonValue", c"getEnd", - c"()Lorg/kson/Position;", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1515,20 +1640,20 @@ pub mod kson_value { } } - impl std::fmt::Debug for Integer { + impl std::fmt::Debug for Decimal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonNumber$Integer", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonNumber$Decimal", &obj)) } } - impl Eq for Integer {} - impl PartialEq for Integer { - fn eq(&self, other: &Integer) -> bool { + impl Eq for Decimal {} + impl PartialEq for Decimal { + fn eq(&self, other: &Decimal) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for Integer { + impl std::hash::Hash for Decimal { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -1536,348 +1661,252 @@ pub mod kson_value { util::apply_hash_code(self.to_kotlin_object(), state) } } - } - impl FromKotlinObject for KsonNumber { - fn from_kotlin_object(obj: jobject) -> Self { - match util::class_name(obj).as_str() { - "org.kson.KsonValue$KsonNumber$Decimal" => kson_value::KsonNumber::Decimal(kson_value::kson_number::Decimal::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonNumber$Integer" => kson_value::KsonNumber::Integer(kson_value::kson_number::Integer::from_kotlin_object(obj)), - _ => unreachable!(), - } - } - } - - impl ToKotlinObject for KsonNumber { - fn to_kotlin_object(&self) -> KotlinPtr { - match self { - Self::Decimal(inner) => inner.to_kotlin_object(), - Self::Integer(inner) => inner.to_kotlin_object(), - } - } - } - - impl KsonNumber { - pub fn name(self) -> String { - let obj = self.to_kotlin_object(); - util::enum_name(&obj) - } - } - - - impl KsonNumber { - - - pub fn start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - - /// Type discriminator for easier type checking in TypeScript/JavaScript - pub fn type_( - &self, - ) -> KsonValueType { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getType", - c"()Lorg/kson/KsonValueType;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } - } - impl std::fmt::Debug for KsonNumber { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonNumber", &obj)) - } - } - impl Eq for KsonNumber {} - impl PartialEq for KsonNumber { - fn eq(&self, other: &KsonNumber) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for KsonNumber { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) + #[derive(Clone)] + pub struct Integer { + kotlin_ptr: KotlinPtr, } - } - /// A Kson object with key-value pairs - #[derive(Clone)] - pub struct KsonObject { - kotlin_ptr: KotlinPtr, - } + impl FromKotlinObject for Integer { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } + } - impl FromKotlinObject for KsonObject { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } + impl ToKotlinObject for Integer { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } } - } - impl ToKotlinObject for KsonObject { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() + impl AsKotlinObject for Integer { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } } - } - impl AsKotlinObject for KsonObject { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner + impl Integer { + pub fn new( + value: i32, + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonNumber$Integer"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(ILorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + value, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } - } - impl KsonObject { - } + impl Integer { - impl KsonObject { + pub fn value( + &self, + ) -> i32 { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn properties( - &self, - ) -> std::collections::HashMap { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonNumber$Integer", + c"getValue", + c"()I", + CallIntMethod, + self_obj, - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonObject", - c"getProperties", - c"()Ljava/util/Map;", - CallObjectMethod, - self_obj, + ); - ); + result + } - util::from_kotlin_value_map(result) - } + pub fn internal_start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn property_keys( - &self, - ) -> std::collections::HashMap { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonNumber$Integer", + c"getInternalStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonObject", - c"getPropertyKeys", - c"()Ljava/util/Map;", - CallObjectMethod, - self_obj, + ); - ); + FromKotlinObject::from_kotlin_object(result) + } - util::from_kotlin_value_map(result) - } + pub fn internal_end( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn type_( - &self, - ) -> KsonValueType { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonNumber$Integer", + c"getInternalEnd", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonObject", - c"getType", - c"()Lorg/kson/KsonValueType;", - CallObjectMethod, - self_obj, + ); - ); + FromKotlinObject::from_kotlin_object(result) + } - FromKotlinObject::from_kotlin_object(result) - } + pub fn type_( + &self, + ) -> KsonValueType { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonNumber$Integer", + c"getType", + c"()Lorg/kson/api/KsonValueType;", + CallObjectMethod, + self_obj, + + ); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + FromKotlinObject::from_kotlin_object(result) + } - ); - FromKotlinObject::from_kotlin_object(result) - } + pub fn start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + ); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, + FromKotlinObject::from_kotlin_object(result) + } - ); - FromKotlinObject::from_kotlin_object(result) + pub fn end( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } } - } - impl std::fmt::Debug for KsonObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonObject", &obj)) + impl std::fmt::Debug for Integer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonNumber$Integer", &obj)) + } } - } - impl Eq for KsonObject {} - impl PartialEq for KsonObject { - fn eq(&self, other: &KsonObject) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + impl Eq for Integer {} + impl PartialEq for Integer { + fn eq(&self, other: &Integer) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } } - } - impl std::hash::Hash for KsonObject { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) + impl std::hash::Hash for Integer { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } } } - - /// A Kson string value - #[derive(Clone)] - pub struct KsonString { - kotlin_ptr: KotlinPtr, - } - - impl FromKotlinObject for KsonString { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } + impl FromKotlinObject for KsonNumber { + fn from_kotlin_object(obj: jobject) -> Self { + match util::class_name(obj).as_str() { + "org.kson.api.KsonValue$KsonNumber$Decimal" => kson_value::KsonNumber::Decimal(kson_value::kson_number::Decimal::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonNumber$Integer" => kson_value::KsonNumber::Integer(kson_value::kson_number::Integer::from_kotlin_object(obj)), + _ => unreachable!(), + } } } - impl ToKotlinObject for KsonString { + impl ToKotlinObject for KsonNumber { fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() + match self { + Self::Decimal(inner) => inner.to_kotlin_object(), + Self::Integer(inner) => inner.to_kotlin_object(), + } } } - impl AsKotlinObject for KsonString { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner + impl KsonNumber { + pub fn name(self) -> String { + let obj = self.to_kotlin_object(); + util::enum_name(&obj) } } - impl KsonString { - } - - - impl KsonString { - - - pub fn value( - &self, - ) -> String { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue$KsonString", - c"getValue", - c"()Ljava/lang/String;", - CallObjectMethod, - self_obj, - - ); - FromKotlinObject::from_kotlin_object(result) - } + impl KsonNumber { - pub fn type_( + pub fn start( &self, - ) -> KsonValueType { + ) -> Position { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -1885,9 +1914,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue$KsonString", - c"getType", - c"()Lorg/kson/KsonValueType;", + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1897,7 +1926,7 @@ pub mod kson_value { } - pub fn start( + pub fn end( &self, ) -> Position { let self_ptr = self.to_kotlin_object(); @@ -1907,9 +1936,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -1919,9 +1948,9 @@ pub mod kson_value { } - pub fn end( + pub fn type_( &self, - ) -> Position { + ) -> KsonValueType { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -1929,9 +1958,9 @@ pub mod kson_value { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", + c"org/kson/api/KsonValue", + c"getType", + c"()Lorg/kson/api/KsonValueType;", CallObjectMethod, self_obj, @@ -1941,20 +1970,20 @@ pub mod kson_value { } } - impl std::fmt::Debug for KsonString { + impl std::fmt::Debug for KsonNumber { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue$KsonString", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonNumber", &obj)) } } - impl Eq for KsonString {} - impl PartialEq for KsonString { - fn eq(&self, other: &KsonString) -> bool { + impl Eq for KsonNumber {} + impl PartialEq for KsonNumber { + fn eq(&self, other: &KsonNumber) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for KsonString { + impl std::hash::Hash for KsonNumber { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -1962,290 +1991,183 @@ pub mod kson_value { util::apply_hash_code(self.to_kotlin_object(), state) } } -} -impl FromKotlinObject for KsonValue { - fn from_kotlin_object(obj: jobject) -> Self { - match util::class_name(obj).as_str() { - "org.kson.KsonValue$KsonArray" => KsonValue::KsonArray(kson_value::KsonArray::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonBoolean" => KsonValue::KsonBoolean(kson_value::KsonBoolean::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonEmbed" => KsonValue::KsonEmbed(kson_value::KsonEmbed::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonNull" => KsonValue::KsonNull(kson_value::KsonNull::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonNumber" => KsonValue::KsonNumber(kson_value::KsonNumber::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonObject" => KsonValue::KsonObject(kson_value::KsonObject::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonString" => KsonValue::KsonString(kson_value::KsonString::from_kotlin_object(obj)), - "org.kson.KsonValue$KsonNumber$Decimal" => KsonValue::KsonNumber(kson_value::KsonNumber::Decimal(kson_value::kson_number::Decimal::from_kotlin_object(obj))), - "org.kson.KsonValue$KsonNumber$Integer" => KsonValue::KsonNumber(kson_value::KsonNumber::Integer(kson_value::kson_number::Integer::from_kotlin_object(obj))), - _ => unreachable!(), - } - } -} -impl ToKotlinObject for KsonValue { - fn to_kotlin_object(&self) -> KotlinPtr { - match self { - Self::KsonArray(inner) => inner.to_kotlin_object(), - Self::KsonBoolean(inner) => inner.to_kotlin_object(), - Self::KsonEmbed(inner) => inner.to_kotlin_object(), - Self::KsonNull(inner) => inner.to_kotlin_object(), - Self::KsonNumber(inner) => inner.to_kotlin_object(), - Self::KsonObject(inner) => inner.to_kotlin_object(), - Self::KsonString(inner) => inner.to_kotlin_object(), - } - } -} -impl KsonValue { - pub fn name(self) -> String { - let obj = self.to_kotlin_object(); - util::enum_name(&obj) + #[derive(Clone)] + pub struct KsonObject { + kotlin_ptr: KotlinPtr, } -} - - -impl KsonValue { - - - pub fn start( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getStart", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - ); - - FromKotlinObject::from_kotlin_object(result) + impl FromKotlinObject for KsonObject { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } } - - pub fn end( - &self, - ) -> Position { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getEnd", - c"()Lorg/kson/Position;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) + impl ToKotlinObject for KsonObject { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } } - /// Type discriminator for easier type checking in TypeScript/JavaScript - pub fn type_( - &self, - ) -> KsonValueType { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/KsonValue", - c"getType", - c"()Lorg/kson/KsonValueType;", - CallObjectMethod, - self_obj, + impl AsKotlinObject for KsonObject { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } + } - ); + impl KsonObject { + pub fn new( + properties: &std::collections::HashMap<&str, KsonValue>, + property_keys: &std::collections::HashMap<&str, kson_value::KsonString>, + internal_start: Position, + internal_end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonObject"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/Map;Ljava/util/Map;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let properties_ptr = util::to_kotlin_map(properties); + let properties = properties_ptr.as_kotlin_object(); + let property_keys_ptr = util::to_kotlin_map(property_keys); + let property_keys = property_keys_ptr.as_kotlin_object(); + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); - FromKotlinObject::from_kotlin_object(result) + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + properties, + property_keys, + internal_start, + internal_end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } -} -impl std::fmt::Debug for KsonValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/KsonValue", &obj)) - } -} -impl Eq for KsonValue {} -impl PartialEq for KsonValue { - fn eq(&self, other: &KsonValue) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } -} -impl std::hash::Hash for KsonValue { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } -} + impl KsonObject { -/// A validator that can check if Kson source conforms to a schema. -#[derive(Clone)] -pub struct SchemaValidator { - kotlin_ptr: KotlinPtr, -} + pub fn properties( + &self, + ) -> std::collections::HashMap { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); -impl FromKotlinObject for SchemaValidator { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } -} -impl ToKotlinObject for SchemaValidator { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } -} + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonObject", + c"getProperties", + c"()Ljava/util/Map;", + CallObjectMethod, + self_obj, -impl AsKotlinObject for SchemaValidator { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } -} + ); -impl SchemaValidator { -} + util::from_kotlin_value_map(result) + } -impl SchemaValidator { + pub fn property_keys( + &self, + ) -> std::collections::HashMap { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - /// Validates the given Kson source against this validator's schema. - /// @param kson The Kson source to validate - /// @param filepath Optional filepath of the document being validated, used by validators to determine which rules to apply - /// - /// @return A list of validation error messages, or empty list if valid - pub fn validate( - &self, - kson: &str, - filepath: Option<&str>, - ) -> Vec { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - let kson_ptr = kson.to_kotlin_object(); - let kson = kson_ptr.as_kotlin_object(); - let filepath_ptr = filepath.to_kotlin_object(); - let filepath = filepath_ptr.as_kotlin_object(); - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/SchemaValidator", - c"validate", - c"(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;", - CallObjectMethod, - self_obj, - kson, - filepath, - ); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonObject", + c"getPropertyKeys", + c"()Ljava/util/Map;", + CallObjectMethod, + self_obj, - util::from_kotlin_list(result) - } -} + ); -impl std::fmt::Debug for SchemaValidator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/SchemaValidator", &obj)) - } -} + util::from_kotlin_value_map(result) + } -impl Eq for SchemaValidator {} -impl PartialEq for SchemaValidator { - fn eq(&self, other: &SchemaValidator) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } -} -impl std::hash::Hash for SchemaValidator { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } -} + pub fn type_( + &self, + ) -> KsonValueType { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); -#[derive(Clone)] -pub enum EmbedRuleResult { - Failure(embed_rule_result::Failure), - Success(embed_rule_result::Success), -} -pub mod embed_rule_result { - use super::*; + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonObject", + c"getType", + c"()Lorg/kson/api/KsonValueType;", + CallObjectMethod, + self_obj, + ); + FromKotlinObject::from_kotlin_object(result) + } - #[derive(Clone)] - pub struct Failure { - kotlin_ptr: KotlinPtr, - } - impl FromKotlinObject for Failure { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } - } + pub fn to_string( + &self, + ) -> String { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - impl ToKotlinObject for Failure { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } - } - impl AsKotlinObject for Failure { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonObject", + c"toString", + c"()Ljava/lang/String;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) } - } - impl Failure { - pub fn new( - message: &str, - ) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/EmbedRuleResult$Failure"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;)V"); - let message_ptr = message.to_kotlin_object(); - let message = message_ptr.as_kotlin_object(); + pub fn start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + - let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - message, - )}; - util::panic_upon_exception(env); - Self { - kotlin_ptr: util::to_gc_global_ref(env, jobject) - } - } - } + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + ); - impl Failure { + FromKotlinObject::from_kotlin_object(result) + } - pub fn message( + pub fn end( &self, - ) -> String { + ) -> Position { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -2253,9 +2175,9 @@ pub mod embed_rule_result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRuleResult$Failure", - c"getMessage", - c"()Ljava/lang/String;", + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -2265,20 +2187,20 @@ pub mod embed_rule_result { } } - impl std::fmt::Debug for Failure { + impl std::fmt::Debug for KsonObject { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/EmbedRuleResult$Failure", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonObject", &obj)) } } - impl Eq for Failure {} - impl PartialEq for Failure { - fn eq(&self, other: &Failure) -> bool { + impl Eq for KsonObject {} + impl PartialEq for KsonObject { + fn eq(&self, other: &KsonObject) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for Failure { + impl std::hash::Hash for KsonObject { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2289,11 +2211,11 @@ pub mod embed_rule_result { #[derive(Clone)] - pub struct Success { + pub struct KsonString { kotlin_ptr: KotlinPtr, } - impl FromKotlinObject for Success { + impl FromKotlinObject for KsonString { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -2301,31 +2223,39 @@ pub mod embed_rule_result { } } - impl ToKotlinObject for Success { + impl ToKotlinObject for KsonString { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } - impl AsKotlinObject for Success { + impl AsKotlinObject for KsonString { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } - impl Success { + impl KsonString { pub fn new( - embed_rule: EmbedRule, + value: &str, + internal_start: Position, + internal_end: Position, ) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/EmbedRuleResult$Success"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/EmbedRule;)V"); + let class = util::get_class(env, c"org/kson/api/KsonValue$KsonString"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); - let embed_rule_ptr = embed_rule.to_kotlin_object(); - let embed_rule = embed_rule_ptr.as_kotlin_object(); + let value_ptr = value.to_kotlin_object(); + let value = value_ptr.as_kotlin_object(); + let internal_start_ptr = internal_start.to_kotlin_object(); + let internal_start = internal_start_ptr.as_kotlin_object(); + let internal_end_ptr = internal_end.to_kotlin_object(); + let internal_end = internal_end_ptr.as_kotlin_object(); let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - embed_rule, + value, + internal_start, + internal_end, )}; util::panic_upon_exception(env); Self { @@ -2335,12 +2265,78 @@ pub mod embed_rule_result { } - impl Success { + impl KsonString { + + + pub fn value( + &self, + ) -> String { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonString", + c"getValue", + c"()Ljava/lang/String;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + + + pub fn type_( + &self, + ) -> KsonValueType { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue$KsonString", + c"getType", + c"()Lorg/kson/api/KsonValueType;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + + + pub fn start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } - pub fn embed_rule( + pub fn end( &self, - ) -> EmbedRule { + ) -> Position { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -2348,9 +2344,9 @@ pub mod embed_rule_result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRuleResult$Success", - c"getEmbedRule", - c"()Lorg/kson/EmbedRule;", + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", CallObjectMethod, self_obj, @@ -2360,20 +2356,20 @@ pub mod embed_rule_result { } } - impl std::fmt::Debug for Success { + impl std::fmt::Debug for KsonString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/EmbedRuleResult$Success", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue$KsonString", &obj)) } } - impl Eq for Success {} - impl PartialEq for Success { - fn eq(&self, other: &Success) -> bool { + impl Eq for KsonString {} + impl PartialEq for KsonString { + fn eq(&self, other: &KsonString) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for Success { + impl std::hash::Hash for KsonString { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2382,50 +2378,128 @@ pub mod embed_rule_result { } } } -impl FromKotlinObject for EmbedRuleResult { +impl FromKotlinObject for KsonValue { fn from_kotlin_object(obj: jobject) -> Self { match util::class_name(obj).as_str() { - "org.kson.EmbedRuleResult$Failure" => EmbedRuleResult::Failure(embed_rule_result::Failure::from_kotlin_object(obj)), - "org.kson.EmbedRuleResult$Success" => EmbedRuleResult::Success(embed_rule_result::Success::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonArray" => KsonValue::KsonArray(kson_value::KsonArray::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonBoolean" => KsonValue::KsonBoolean(kson_value::KsonBoolean::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonEmbed" => KsonValue::KsonEmbed(kson_value::KsonEmbed::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonNull" => KsonValue::KsonNull(kson_value::KsonNull::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonNumber" => KsonValue::KsonNumber(kson_value::KsonNumber::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonObject" => KsonValue::KsonObject(kson_value::KsonObject::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonString" => KsonValue::KsonString(kson_value::KsonString::from_kotlin_object(obj)), + "org.kson.api.KsonValue$KsonNumber$Decimal" => KsonValue::KsonNumber(kson_value::KsonNumber::Decimal(kson_value::kson_number::Decimal::from_kotlin_object(obj))), + "org.kson.api.KsonValue$KsonNumber$Integer" => KsonValue::KsonNumber(kson_value::KsonNumber::Integer(kson_value::kson_number::Integer::from_kotlin_object(obj))), _ => unreachable!(), } } } -impl ToKotlinObject for EmbedRuleResult { - fn to_kotlin_object(&self) -> KotlinPtr { - match self { - Self::Failure(inner) => inner.to_kotlin_object(), - Self::Success(inner) => inner.to_kotlin_object(), - } - } -} +impl ToKotlinObject for KsonValue { + fn to_kotlin_object(&self) -> KotlinPtr { + match self { + Self::KsonArray(inner) => inner.to_kotlin_object(), + Self::KsonBoolean(inner) => inner.to_kotlin_object(), + Self::KsonEmbed(inner) => inner.to_kotlin_object(), + Self::KsonNull(inner) => inner.to_kotlin_object(), + Self::KsonNumber(inner) => inner.to_kotlin_object(), + Self::KsonObject(inner) => inner.to_kotlin_object(), + Self::KsonString(inner) => inner.to_kotlin_object(), + } + } +} + +impl KsonValue { + pub fn name(self) -> String { + let obj = self.to_kotlin_object(); + util::enum_name(&obj) + } +} + + +impl KsonValue { + + + pub fn start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + + + pub fn end( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getEnd", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, -impl EmbedRuleResult { - pub fn name(self) -> String { - let obj = self.to_kotlin_object(); - util::enum_name(&obj) + ); + + FromKotlinObject::from_kotlin_object(result) } -} -impl EmbedRuleResult { + pub fn type_( + &self, + ) -> KsonValueType { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/KsonValue", + c"getType", + c"()Lorg/kson/api/KsonValueType;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } } -impl std::fmt::Debug for EmbedRuleResult { +impl std::fmt::Debug for KsonValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/EmbedRuleResult", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/KsonValue", &obj)) } } -impl Eq for EmbedRuleResult {} -impl PartialEq for EmbedRuleResult { - fn eq(&self, other: &EmbedRuleResult) -> bool { +impl Eq for KsonValue {} +impl PartialEq for KsonValue { + fn eq(&self, other: &KsonValue) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for EmbedRuleResult { +impl std::hash::Hash for KsonValue { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2435,13 +2509,13 @@ impl std::hash::Hash for EmbedRuleResult { } -/// The result of statically analyzing a Kson document + #[derive(Clone)] -pub struct Analysis { +pub struct SchemaValidatorService { kotlin_ptr: KotlinPtr, } -impl FromKotlinObject for Analysis { +impl FromKotlinObject for SchemaValidatorService { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -2449,105 +2523,67 @@ impl FromKotlinObject for Analysis { } } -impl ToKotlinObject for Analysis { +impl ToKotlinObject for SchemaValidatorService { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } -impl AsKotlinObject for Analysis { +impl AsKotlinObject for SchemaValidatorService { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } -impl Analysis { +impl SchemaValidatorService { } -impl Analysis { +impl SchemaValidatorService { - pub fn errors( + pub fn validate( &self, + kson: &str, + filepath: Option<&str>, ) -> Vec { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Analysis", - c"getErrors", - c"()Ljava/util/List;", - CallObjectMethod, - self_obj, - - ); - - util::from_kotlin_list(result) - } - - - pub fn tokens( - &self, - ) -> Vec { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - + let kson_ptr = kson.to_kotlin_object(); + let kson = kson_ptr.as_kotlin_object(); + let filepath_ptr = filepath.to_kotlin_object(); + let filepath = filepath_ptr.as_kotlin_object(); let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Analysis", - c"getTokens", - c"()Ljava/util/List;", + c"org/kson/api/SchemaValidatorService", + c"validate", + c"(Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;", CallObjectMethod, self_obj, - + kson, + filepath, ); util::from_kotlin_list(result) } - - - pub fn kson_value( - &self, - ) -> Option { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/Analysis", - c"getKsonValue", - c"()Lorg/kson/KsonValue;", - CallObjectMethod, - self_obj, - - ); - - FromKotlinObject::from_kotlin_object(result) - } } -impl std::fmt::Debug for Analysis { +impl std::fmt::Debug for SchemaValidatorService { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Analysis", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/SchemaValidatorService", &obj)) } } -impl Eq for Analysis {} -impl PartialEq for Analysis { - fn eq(&self, other: &Analysis) -> bool { +impl Eq for SchemaValidatorService {} +impl PartialEq for SchemaValidatorService { + fn eq(&self, other: &SchemaValidatorService) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Analysis { +impl std::hash::Hash for SchemaValidatorService { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2556,24 +2592,24 @@ impl std::hash::Hash for Analysis { } } -/// Result of a Kson conversion operation + #[derive(Clone)] -pub enum Result { - Failure(result::Failure), - Success(result::Success), +pub enum TranspileOptions { + Json(transpile_options::Json), + Yaml(transpile_options::Yaml), } -pub mod result { +pub mod transpile_options { use super::*; #[derive(Clone)] - pub struct Failure { + pub struct Json { kotlin_ptr: KotlinPtr, } - impl FromKotlinObject for Failure { + impl FromKotlinObject for Json { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -2581,31 +2617,30 @@ pub mod result { } } - impl ToKotlinObject for Failure { + impl ToKotlinObject for Json { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } - impl AsKotlinObject for Failure { + impl AsKotlinObject for Json { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } - impl Failure { + impl Json { pub fn new( - errors: &[Message], + retain_embed_tags: bool, ) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/Result$Failure"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/List;)V"); + let class = util::get_class(env, c"org/kson/api/TranspileOptions$Json"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Z)V"); - let errors_ptr = util::to_kotlin_list(errors); - let errors = errors_ptr.as_kotlin_object(); + let retain_embed_tags = retain_embed_tags as c_int; let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - errors, + retain_embed_tags, )}; util::panic_upon_exception(env); Self { @@ -2615,12 +2650,12 @@ pub mod result { } - impl Failure { + impl Json { - pub fn errors( + pub fn retain_embed_tags( &self, - ) -> Vec { + ) -> bool { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -2628,32 +2663,32 @@ pub mod result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Result$Failure", - c"getErrors", - c"()Ljava/util/List;", - CallObjectMethod, + c"org/kson/api/TranspileOptions$Json", + c"getRetainEmbedTags", + c"()Z", + CallBooleanMethod, self_obj, ); - util::from_kotlin_list(result) + result != 0 } } - impl std::fmt::Debug for Failure { + impl std::fmt::Debug for Json { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Result$Failure", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/TranspileOptions$Json", &obj)) } } - impl Eq for Failure {} - impl PartialEq for Failure { - fn eq(&self, other: &Failure) -> bool { + impl Eq for Json {} + impl PartialEq for Json { + fn eq(&self, other: &Json) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for Failure { + impl std::hash::Hash for Json { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2664,11 +2699,11 @@ pub mod result { #[derive(Clone)] - pub struct Success { + pub struct Yaml { kotlin_ptr: KotlinPtr, } - impl FromKotlinObject for Success { + impl FromKotlinObject for Yaml { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -2676,31 +2711,30 @@ pub mod result { } } - impl ToKotlinObject for Success { + impl ToKotlinObject for Yaml { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } - impl AsKotlinObject for Success { + impl AsKotlinObject for Yaml { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } - impl Success { + impl Yaml { pub fn new( - output: &str, + retain_embed_tags: bool, ) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/Result$Success"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;)V"); + let class = util::get_class(env, c"org/kson/api/TranspileOptions$Yaml"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Z)V"); - let output_ptr = output.to_kotlin_object(); - let output = output_ptr.as_kotlin_object(); + let retain_embed_tags = retain_embed_tags as c_int; let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - output, + retain_embed_tags, )}; util::panic_upon_exception(env); Self { @@ -2710,12 +2744,12 @@ pub mod result { } - impl Success { + impl Yaml { - pub fn output( + pub fn retain_embed_tags( &self, - ) -> String { + ) -> bool { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -2723,32 +2757,32 @@ pub mod result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Result$Success", - c"getOutput", - c"()Ljava/lang/String;", - CallObjectMethod, + c"org/kson/api/TranspileOptions$Yaml", + c"getRetainEmbedTags", + c"()Z", + CallBooleanMethod, self_obj, ); - FromKotlinObject::from_kotlin_object(result) + result != 0 } } - impl std::fmt::Debug for Success { + impl std::fmt::Debug for Yaml { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Result$Success", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/TranspileOptions$Yaml", &obj)) } } - impl Eq for Success {} - impl PartialEq for Success { - fn eq(&self, other: &Success) -> bool { + impl Eq for Yaml {} + impl PartialEq for Yaml { + fn eq(&self, other: &Yaml) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } - impl std::hash::Hash for Success { + impl std::hash::Hash for Yaml { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2757,26 +2791,26 @@ pub mod result { } } } -impl FromKotlinObject for Result { +impl FromKotlinObject for TranspileOptions { fn from_kotlin_object(obj: jobject) -> Self { match util::class_name(obj).as_str() { - "org.kson.Result$Failure" => Result::Failure(result::Failure::from_kotlin_object(obj)), - "org.kson.Result$Success" => Result::Success(result::Success::from_kotlin_object(obj)), + "org.kson.api.TranspileOptions$Json" => TranspileOptions::Json(transpile_options::Json::from_kotlin_object(obj)), + "org.kson.api.TranspileOptions$Yaml" => TranspileOptions::Yaml(transpile_options::Yaml::from_kotlin_object(obj)), _ => unreachable!(), } } } -impl ToKotlinObject for Result { +impl ToKotlinObject for TranspileOptions { fn to_kotlin_object(&self) -> KotlinPtr { match self { - Self::Failure(inner) => inner.to_kotlin_object(), - Self::Success(inner) => inner.to_kotlin_object(), + Self::Json(inner) => inner.to_kotlin_object(), + Self::Yaml(inner) => inner.to_kotlin_object(), } } } -impl Result { +impl TranspileOptions { pub fn name(self) -> String { let obj = self.to_kotlin_object(); util::enum_name(&obj) @@ -2784,23 +2818,45 @@ impl Result { } -impl Result { +impl TranspileOptions { + + + pub fn retain_embed_tags( + &self, + ) -> bool { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); + + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/TranspileOptions", + c"getRetainEmbedTags", + c"()Z", + CallBooleanMethod, + self_obj, + + ); + + result != 0 + } } -impl std::fmt::Debug for Result { +impl std::fmt::Debug for TranspileOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Result", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/TranspileOptions", &obj)) } } -impl Eq for Result {} -impl PartialEq for Result { - fn eq(&self, other: &Result) -> bool { +impl Eq for TranspileOptions {} +impl PartialEq for TranspileOptions { + fn eq(&self, other: &TranspileOptions) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Result { +impl std::hash::Hash for TranspileOptions { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -2809,14 +2865,14 @@ impl std::hash::Hash for Result { } } -/// A [parseSchema] result + #[derive(Clone)] -pub enum SchemaResult { - Failure(schema_result::Failure), - Success(schema_result::Success), +pub enum Result { + Failure(result::Failure), + Success(result::Success), } -pub mod schema_result { +pub mod result { use super::*; @@ -2851,7 +2907,7 @@ pub mod schema_result { errors: &[Message], ) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/SchemaResult$Failure"); + let class = util::get_class(env, c"org/kson/api/Result$Failure"); let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/List;)V"); let errors_ptr = util::to_kotlin_list(errors); @@ -2881,7 +2937,7 @@ pub mod schema_result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/SchemaResult$Failure", + c"org/kson/api/Result$Failure", c"getErrors", c"()Ljava/util/List;", CallObjectMethod, @@ -2896,7 +2952,7 @@ pub mod schema_result { impl std::fmt::Debug for Failure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/SchemaResult$Failure", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Result$Failure", &obj)) } } @@ -2943,17 +2999,17 @@ pub mod schema_result { impl Success { pub fn new( - schema_validator: SchemaValidator, + output: &str, ) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/SchemaResult$Success"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/SchemaValidator;)V"); + let class = util::get_class(env, c"org/kson/api/Result$Success"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;)V"); - let schema_validator_ptr = schema_validator.to_kotlin_object(); - let schema_validator = schema_validator_ptr.as_kotlin_object(); + let output_ptr = output.to_kotlin_object(); + let output = output_ptr.as_kotlin_object(); let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - schema_validator, + output, )}; util::panic_upon_exception(env); Self { @@ -2966,9 +3022,9 @@ pub mod schema_result { impl Success { - pub fn schema_validator( + pub fn output( &self, - ) -> SchemaValidator { + ) -> String { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -2976,9 +3032,9 @@ pub mod schema_result { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/SchemaResult$Success", - c"getSchemaValidator", - c"()Lorg/kson/SchemaValidator;", + c"org/kson/api/Result$Success", + c"getOutput", + c"()Ljava/lang/String;", CallObjectMethod, self_obj, @@ -2991,7 +3047,7 @@ pub mod schema_result { impl std::fmt::Debug for Success { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/SchemaResult$Success", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Result$Success", &obj)) } } @@ -3010,17 +3066,17 @@ pub mod schema_result { } } } -impl FromKotlinObject for SchemaResult { +impl FromKotlinObject for Result { fn from_kotlin_object(obj: jobject) -> Self { match util::class_name(obj).as_str() { - "org.kson.SchemaResult$Failure" => SchemaResult::Failure(schema_result::Failure::from_kotlin_object(obj)), - "org.kson.SchemaResult$Success" => SchemaResult::Success(schema_result::Success::from_kotlin_object(obj)), + "org.kson.api.Result$Failure" => Result::Failure(result::Failure::from_kotlin_object(obj)), + "org.kson.api.Result$Success" => Result::Success(result::Success::from_kotlin_object(obj)), _ => unreachable!(), } } } -impl ToKotlinObject for SchemaResult { +impl ToKotlinObject for Result { fn to_kotlin_object(&self) -> KotlinPtr { match self { Self::Failure(inner) => inner.to_kotlin_object(), @@ -3029,7 +3085,7 @@ impl ToKotlinObject for SchemaResult { } } -impl SchemaResult { +impl Result { pub fn name(self) -> String { let obj = self.to_kotlin_object(); util::enum_name(&obj) @@ -3037,23 +3093,23 @@ impl SchemaResult { } -impl SchemaResult { +impl Result { } -impl std::fmt::Debug for SchemaResult { +impl std::fmt::Debug for Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/SchemaResult", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Result", &obj)) } } -impl Eq for SchemaResult {} -impl PartialEq for SchemaResult { - fn eq(&self, other: &SchemaResult) -> bool { +impl Eq for Result {} +impl PartialEq for Result { + fn eq(&self, other: &Result) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for SchemaResult { +impl std::hash::Hash for Result { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -3062,238 +3118,139 @@ impl std::hash::Hash for SchemaResult { } } -/// Core interface for transpilation options shared across all output formats. -#[derive(Clone)] -pub enum TranspileOptions { - Json(transpile_options::Json), - Yaml(transpile_options::Yaml), -} - -pub mod transpile_options { - use super::*; - - /// Options for transpiling Kson to JSON. - #[derive(Clone)] - pub struct Json { - kotlin_ptr: KotlinPtr, - } - impl FromKotlinObject for Json { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } - } +#[derive(Clone)] +pub struct Token { + kotlin_ptr: KotlinPtr, +} - impl ToKotlinObject for Json { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } +impl FromKotlinObject for Token { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } } +} - impl AsKotlinObject for Json { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } +impl ToKotlinObject for Token { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() } +} - impl Json { - pub fn new( - retain_embed_tags: bool, - ) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/TranspileOptions$Json"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Z)V"); - - let retain_embed_tags = retain_embed_tags as c_int; - - let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - retain_embed_tags, - )}; - util::panic_upon_exception(env); - Self { - kotlin_ptr: util::to_gc_global_ref(env, jobject) - } - } +impl AsKotlinObject for Token { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner } +} +impl Token { + pub fn new( + token_type: TokenType, + text: &str, + start: Position, + end: Position, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/Token"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/api/TokenType;Ljava/lang/String;Lorg/kson/api/Position;Lorg/kson/api/Position;)V"); + + let token_type_ptr = token_type.to_kotlin_object(); + let token_type = token_type_ptr.as_kotlin_object(); + let text_ptr = text.to_kotlin_object(); + let text = text_ptr.as_kotlin_object(); + let start_ptr = start.to_kotlin_object(); + let start = start_ptr.as_kotlin_object(); + let end_ptr = end.to_kotlin_object(); + let end = end_ptr.as_kotlin_object(); - impl Json { - - - pub fn retain_embed_tags( - &self, - ) -> bool { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/TranspileOptions$Json", - c"getRetainEmbedTags", - c"()Z", - CallBooleanMethod, - self_obj, - - ); - - result != 0 - } - } - - impl std::fmt::Debug for Json { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/TranspileOptions$Json", &obj)) + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + token_type, + text, + start, + end, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) } } +} - impl Eq for Json {} - impl PartialEq for Json { - fn eq(&self, other: &Json) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for Json { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } - } - /// Options for transpiling Kson to YAML. - #[derive(Clone)] - pub struct Yaml { - kotlin_ptr: KotlinPtr, - } +impl Token { - impl FromKotlinObject for Yaml { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } - } - impl ToKotlinObject for Yaml { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } - } + pub fn token_type( + &self, + ) -> TokenType { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - impl AsKotlinObject for Yaml { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } - } - impl Yaml { - pub fn new( - retain_embed_tags: bool, - ) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/TranspileOptions$Yaml"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Z)V"); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/Token", + c"getTokenType", + c"()Lorg/kson/api/TokenType;", + CallObjectMethod, + self_obj, - let retain_embed_tags = retain_embed_tags as c_int; + ); - let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - retain_embed_tags, - )}; - util::panic_upon_exception(env); - Self { - kotlin_ptr: util::to_gc_global_ref(env, jobject) - } - } + FromKotlinObject::from_kotlin_object(result) } - impl Yaml { - - - pub fn retain_embed_tags( - &self, - ) -> bool { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/TranspileOptions$Yaml", - c"getRetainEmbedTags", - c"()Z", - CallBooleanMethod, - self_obj, - - ); - - result != 0 - } - } + pub fn text( + &self, + ) -> String { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - impl std::fmt::Debug for Yaml { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/TranspileOptions$Yaml", &obj)) - } - } - impl Eq for Yaml {} - impl PartialEq for Yaml { - fn eq(&self, other: &Yaml) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for Yaml { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } - } -} -impl FromKotlinObject for TranspileOptions { - fn from_kotlin_object(obj: jobject) -> Self { - match util::class_name(obj).as_str() { - "org.kson.TranspileOptions$Json" => TranspileOptions::Json(transpile_options::Json::from_kotlin_object(obj)), - "org.kson.TranspileOptions$Yaml" => TranspileOptions::Yaml(transpile_options::Yaml::from_kotlin_object(obj)), - _ => unreachable!(), - } - } -} + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/Token", + c"getText", + c"()Ljava/lang/String;", + CallObjectMethod, + self_obj, -impl ToKotlinObject for TranspileOptions { - fn to_kotlin_object(&self) -> KotlinPtr { - match self { - Self::Json(inner) => inner.to_kotlin_object(), - Self::Yaml(inner) => inner.to_kotlin_object(), - } - } -} + ); -impl TranspileOptions { - pub fn name(self) -> String { - let obj = self.to_kotlin_object(); - util::enum_name(&obj) + FromKotlinObject::from_kotlin_object(result) } -} -impl TranspileOptions { + pub fn start( + &self, + ) -> Position { + let self_ptr = self.to_kotlin_object(); + let self_obj = self_ptr.as_kotlin_object(); - pub fn retain_embed_tags( + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/api/Token", + c"getStart", + c"()Lorg/kson/api/Position;", + CallObjectMethod, + self_obj, + + ); + + FromKotlinObject::from_kotlin_object(result) + } + + + pub fn end( &self, - ) -> bool { + ) -> Position { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -3301,32 +3258,32 @@ impl TranspileOptions { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/TranspileOptions", - c"getRetainEmbedTags", - c"()Z", - CallBooleanMethod, + c"org/kson/api/Token", + c"getEnd", + c"()Lorg/kson/api/Position;", + CallObjectMethod, self_obj, ); - result != 0 + FromKotlinObject::from_kotlin_object(result) } } -impl std::fmt::Debug for TranspileOptions { +impl std::fmt::Debug for Token { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/TranspileOptions", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/Token", &obj)) } } -impl Eq for TranspileOptions {} -impl PartialEq for TranspileOptions { - fn eq(&self, other: &TranspileOptions) -> bool { +impl Eq for Token {} +impl PartialEq for Token { + fn eq(&self, other: &Token) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for TranspileOptions { +impl std::hash::Hash for Token { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -3336,13 +3293,13 @@ impl std::hash::Hash for TranspileOptions { } -/// The [Kson](https://kson.org) language + #[derive(Clone)] -pub struct Kson { +pub struct Analysis { kotlin_ptr: KotlinPtr, } -impl FromKotlinObject for Kson { +impl FromKotlinObject for Analysis { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -3350,188 +3307,253 @@ impl FromKotlinObject for Kson { } } -impl ToKotlinObject for Kson { +impl ToKotlinObject for Analysis { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } -impl AsKotlinObject for Kson { +impl AsKotlinObject for Analysis { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } -impl Kson { +impl Analysis { + pub fn new( + errors: &[Message], + tokens: &[Token], + kson_value: Option, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/Analysis"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/util/List;Ljava/util/List;Lorg/kson/api/KsonValue;)V"); + + let errors_ptr = util::to_kotlin_list(errors); + let errors = errors_ptr.as_kotlin_object(); + let tokens_ptr = util::to_kotlin_list(tokens); + let tokens = tokens_ptr.as_kotlin_object(); + let kson_value_ptr = kson_value.to_kotlin_object(); + let kson_value = kson_value_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + errors, + tokens, + kson_value, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } -impl Kson { +impl Analysis { - /// Formats Kson source with the specified formatting options. - /// - /// @param kson The Kson source to format - /// @param formatOptions The formatting options to apply - /// @return The formatted Kson source - pub fn format( - kson: &str, - format_options: FormatOptions, - ) -> String { - let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + + pub fn errors( + &self, + ) -> Vec { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let kson_ptr = kson.to_kotlin_object(); - let kson = kson_ptr.as_kotlin_object(); - let format_options_ptr = format_options.to_kotlin_object(); - let format_options = format_options_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Kson", - c"format", - c"(Ljava/lang/String;Lorg/kson/FormatOptions;)Ljava/lang/String;", + c"org/kson/api/Analysis", + c"getErrors", + c"()Ljava/util/List;", CallObjectMethod, self_obj, - kson, - format_options, + ); - FromKotlinObject::from_kotlin_object(result) + util::from_kotlin_list(result) } - /// Converts Kson to Json. - /// - /// @param kson The Kson source to convert - /// @param options Options for the JSON transpilation - /// @return A Result containing either the Json output or error messages - pub fn to_json( - kson: &str, - options: transpile_options::Json, - ) -> std::result::Result { - let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + + pub fn tokens( + &self, + ) -> Vec { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let kson_ptr = kson.to_kotlin_object(); - let kson = kson_ptr.as_kotlin_object(); - let options_ptr = options.to_kotlin_object(); - let options = options_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Kson", - c"toJson", - c"(Ljava/lang/String;Lorg/kson/TranspileOptions$Json;)Lorg/kson/Result;", + c"org/kson/api/Analysis", + c"getTokens", + c"()Ljava/util/List;", CallObjectMethod, self_obj, - kson, - options, + ); - crate::kson_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) + util::from_kotlin_list(result) } - /// Converts Kson to Yaml, preserving comments - /// - /// @param kson The Kson source to convert - /// @param options Options for the YAML transpilation - /// @return A Result containing either the Yaml output or error messages - pub fn to_yaml( - kson: &str, - options: transpile_options::Yaml, - ) -> std::result::Result { - let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + + pub fn kson_value( + &self, + ) -> Option { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let kson_ptr = kson.to_kotlin_object(); - let kson = kson_ptr.as_kotlin_object(); - let options_ptr = options.to_kotlin_object(); - let options = options_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Kson", - c"toYaml", - c"(Ljava/lang/String;Lorg/kson/TranspileOptions$Yaml;)Lorg/kson/Result;", + c"org/kson/api/Analysis", + c"getKsonValue", + c"()Lorg/kson/api/KsonValue;", CallObjectMethod, self_obj, - kson, - options, + ); - crate::kson_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) + FromKotlinObject::from_kotlin_object(result) } +} - /// Statically analyze the given Kson and return an [Analysis] object containing any messages generated along with a - /// tokenized version of the source. Useful for tooling/editor support. - /// @param kson The Kson source to analyze - /// @param filepath Filepath of the document being analyzed - pub fn analyze( - kson: &str, - filepath: Option<&str>, - ) -> Analysis { - let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); +impl std::fmt::Debug for Analysis { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let obj = self.to_kotlin_object(); + write!(f, "{}", util::call_to_string(c"org/kson/api/Analysis", &obj)) + } +} + +impl Eq for Analysis {} +impl PartialEq for Analysis { + fn eq(&self, other: &Analysis) -> bool { + util::equals(self.to_kotlin_object(), other.to_kotlin_object()) + } +} +impl std::hash::Hash for Analysis { + fn hash(&self, state: &mut H) + where + H: std::hash::Hasher, + { + util::apply_hash_code(self.to_kotlin_object(), state) + } +} + + + +#[derive(Clone)] +pub struct EmbedRule { + kotlin_ptr: KotlinPtr, +} + +impl FromKotlinObject for EmbedRule { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } + } +} + +impl ToKotlinObject for EmbedRule { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() + } +} + +impl AsKotlinObject for EmbedRule { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner + } +} + +impl EmbedRule { + pub fn new( + path_pattern: &str, + tag: Option<&str>, + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/EmbedRule"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Ljava/lang/String;Ljava/lang/String;)V"); + + let path_pattern_ptr = path_pattern.to_kotlin_object(); + let path_pattern = path_pattern_ptr.as_kotlin_object(); + let tag_ptr = tag.to_kotlin_object(); + let tag = tag_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + path_pattern, + tag, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } +} + + +impl EmbedRule { + + + pub fn path_pattern( + &self, + ) -> String { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let kson_ptr = kson.to_kotlin_object(); - let kson = kson_ptr.as_kotlin_object(); - let filepath_ptr = filepath.to_kotlin_object(); - let filepath = filepath_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Kson", - c"analyze", - c"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/Analysis;", + c"org/kson/api/EmbedRule", + c"getPathPattern", + c"()Ljava/lang/String;", CallObjectMethod, self_obj, - kson, - filepath, + ); FromKotlinObject::from_kotlin_object(result) } - /// Parses a Kson schema definition and returns a validator for that schema. - /// - /// @param schemaKson The Kson source defining a Json Schema - /// @return A SchemaValidator that can validate Kson documents against the schema - pub fn parse_schema( - schema_kson: &str, - ) -> std::result::Result { - let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + + pub fn tag( + &self, + ) -> Option { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let schema_kson_ptr = schema_kson.to_kotlin_object(); - let schema_kson = schema_kson_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/Kson", - c"parseSchema", - c"(Ljava/lang/String;)Lorg/kson/SchemaResult;", + c"org/kson/api/EmbedRule", + c"getTag", + c"()Ljava/lang/String;", CallObjectMethod, self_obj, - schema_kson, + ); - crate::kson_schema_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) + FromKotlinObject::from_kotlin_object(result) } } -impl std::fmt::Debug for Kson { +impl std::fmt::Debug for EmbedRule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/Kson", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/EmbedRule", &obj)) } } -impl Eq for Kson {} -impl PartialEq for Kson { - fn eq(&self, other: &Kson) -> bool { +impl Eq for EmbedRule {} +impl PartialEq for EmbedRule { + fn eq(&self, other: &EmbedRule) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for Kson { +impl std::hash::Hash for EmbedRule { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -3541,18 +3563,13 @@ impl std::hash::Hash for Kson { } -/// A rule for formatting string values at specific paths as embed blocks. -/// -/// When formatting KSON, strings at paths matching [pathPattern] will be rendered -/// as embed blocks instead of regular strings. -/// -/// **Warning:** JsonPointerGlob syntax is experimental and may change in future versions. + #[derive(Clone)] -pub struct EmbedRule { +pub struct FormatOptions { kotlin_ptr: KotlinPtr, } -impl FromKotlinObject for EmbedRule { +impl FromKotlinObject for FormatOptions { fn from_kotlin_object(obj: self::sys::jobject) -> Self { let (env, _detach_guard) = util::attach_thread_to_java_vm(); let kotlin_ptr = util::to_gc_global_ref(env, obj); @@ -3560,28 +3577,54 @@ impl FromKotlinObject for EmbedRule { } } -impl ToKotlinObject for EmbedRule { +impl ToKotlinObject for FormatOptions { fn to_kotlin_object(&self) -> KotlinPtr { self.kotlin_ptr.clone() } } -impl AsKotlinObject for EmbedRule { +impl AsKotlinObject for FormatOptions { fn as_kotlin_object(&self) -> self::sys::jobject { self.kotlin_ptr.inner.inner } } -impl EmbedRule { +impl FormatOptions { + pub fn new( + indent_type: IndentType, + formatting_style: FormattingStyle, + embed_block_rules: &[EmbedRule], + ) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let class = util::get_class(env, c"org/kson/api/FormatOptions"); + let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(Lorg/kson/api/IndentType;Lorg/kson/api/FormattingStyle;Ljava/util/List;)V"); + + let indent_type_ptr = indent_type.to_kotlin_object(); + let indent_type = indent_type_ptr.as_kotlin_object(); + let formatting_style_ptr = formatting_style.to_kotlin_object(); + let formatting_style = formatting_style_ptr.as_kotlin_object(); + let embed_block_rules_ptr = util::to_kotlin_list(embed_block_rules); + let embed_block_rules = embed_block_rules_ptr.as_kotlin_object(); + + let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, + indent_type, + formatting_style, + embed_block_rules, + )}; + util::panic_upon_exception(env); + Self { + kotlin_ptr: util::to_gc_global_ref(env, jobject) + } + } } -impl EmbedRule { +impl FormatOptions { - pub fn path_pattern( + pub fn indent_type( &self, - ) -> String { + ) -> IndentType { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -3589,9 +3632,9 @@ impl EmbedRule { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRule", - c"getPathPattern", - c"()Ljava/lang/String;", + c"org/kson/api/FormatOptions", + c"getIndentType", + c"()Lorg/kson/api/IndentType;", CallObjectMethod, self_obj, @@ -3601,9 +3644,9 @@ impl EmbedRule { } - pub fn tag( + pub fn formatting_style( &self, - ) -> Option { + ) -> FormattingStyle { let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); @@ -3611,9 +3654,9 @@ impl EmbedRule { let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRule", - c"getTag", - c"()Ljava/lang/String;", + c"org/kson/api/FormatOptions", + c"getFormattingStyle", + c"()Lorg/kson/api/FormattingStyle;", CallObjectMethod, self_obj, @@ -3622,58 +3665,43 @@ impl EmbedRule { FromKotlinObject::from_kotlin_object(result) } - /// Builds a new [EmbedRule]. - /// - /// @param pathPattern A JsonPointerGlob pattern (e.g., "/scripts/ *", "/queries/ **") - /// @param tag Optional embed tag to include (e.g., "yaml", "sql", "bash") - /// @return [EmbedRuleResult.Success] if [pathPattern] is a valid JsonPointerGlob, otherwise [EmbedRuleResult.Failure] - /// - /// Example: - /// ```kotlin - /// EmbedRule.fromPathPattern("/scripts/ *", tag = "bash") // Match all values under "scripts" - /// EmbedRule.fromPathPattern("/config/description") // Match exact path, no tag - /// ``` - pub fn from_path_pattern( - path_pattern: &str, - tag: Option<&str>, - ) -> EmbedRuleResult { - let self_ptr = util::access_static_field(c"org/kson/EmbedRule", c"Companion", c"Lorg/kson/EmbedRule$Companion;"); + + pub fn embed_block_rules( + &self, + ) -> Vec { + let self_ptr = self.to_kotlin_object(); let self_obj = self_ptr.as_kotlin_object(); - let path_pattern_ptr = path_pattern.to_kotlin_object(); - let path_pattern = path_pattern_ptr.as_kotlin_object(); - let tag_ptr = tag.to_kotlin_object(); - let tag = tag_ptr.as_kotlin_object(); + let (_, _detach_guard) = util::attach_thread_to_java_vm(); let result = call_jvm_function!( util, - c"org/kson/EmbedRule$Companion", - c"fromPathPattern", - c"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/EmbedRuleResult;", + c"org/kson/api/FormatOptions", + c"getEmbedBlockRules", + c"()Ljava/util/List;", CallObjectMethod, self_obj, - path_pattern, - tag, + ); - FromKotlinObject::from_kotlin_object(result) + util::from_kotlin_list(result) } } -impl std::fmt::Debug for EmbedRule { +impl std::fmt::Debug for FormatOptions { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/EmbedRule", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/api/FormatOptions", &obj)) } } -impl Eq for EmbedRule {} -impl PartialEq for EmbedRule { - fn eq(&self, other: &EmbedRule) -> bool { +impl Eq for FormatOptions {} +impl PartialEq for FormatOptions { + fn eq(&self, other: &FormatOptions) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for EmbedRule { +impl std::hash::Hash for FormatOptions { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -3682,214 +3710,203 @@ impl std::hash::Hash for EmbedRule { } } -/// Options for indenting Kson Output + +/// The [Kson](https://kson.org) language #[derive(Clone)] -pub enum IndentType { - Spaces(indent_type::Spaces), - Tabs(indent_type::Tabs), +pub struct Kson { + kotlin_ptr: KotlinPtr, } -pub mod indent_type { - use super::*; - - - /// Use spaces for indentation with the specified count - #[derive(Clone)] - pub struct Spaces { - kotlin_ptr: KotlinPtr, - } - - impl FromKotlinObject for Spaces { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } - } - - impl ToKotlinObject for Spaces { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } +impl FromKotlinObject for Kson { + fn from_kotlin_object(obj: self::sys::jobject) -> Self { + let (env, _detach_guard) = util::attach_thread_to_java_vm(); + let kotlin_ptr = util::to_gc_global_ref(env, obj); + Self { kotlin_ptr } } +} - impl AsKotlinObject for Spaces { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } +impl ToKotlinObject for Kson { + fn to_kotlin_object(&self) -> KotlinPtr { + self.kotlin_ptr.clone() } +} - impl Spaces { - pub fn new( - size: i32, - ) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let class = util::get_class(env, c"org/kson/IndentType$Spaces"); - let constructor = util::get_method(env, class.as_kotlin_object(), c"", c"(I)V"); - - - - let jobject = unsafe { (**env).NewObject.unwrap()(env, class.as_kotlin_object(), constructor, - size, - )}; - util::panic_upon_exception(env); - Self { - kotlin_ptr: util::to_gc_global_ref(env, jobject) - } - } +impl AsKotlinObject for Kson { + fn as_kotlin_object(&self) -> self::sys::jobject { + self.kotlin_ptr.inner.inner } +} +impl Kson { +} - impl Spaces { - - - pub fn size( - &self, - ) -> i32 { - let self_ptr = self.to_kotlin_object(); - let self_obj = self_ptr.as_kotlin_object(); - - - let (_, _detach_guard) = util::attach_thread_to_java_vm(); - let result = call_jvm_function!( - util, - c"org/kson/IndentType$Spaces", - c"getSize", - c"()I", - CallIntMethod, - self_obj, - - ); - - result - } - } - impl std::fmt::Debug for Spaces { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/IndentType$Spaces", &obj)) - } - } +impl Kson { - impl Eq for Spaces {} - impl PartialEq for Spaces { - fn eq(&self, other: &Spaces) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for Spaces { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } - } + /// Formats Kson source with the specified formatting options. + /// + /// @param kson The Kson source to format + /// @param formatOptions The formatting options to apply + /// @return The formatted Kson source + pub fn format( + kson: &str, + format_options: FormatOptions, + ) -> String { + let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + let self_obj = self_ptr.as_kotlin_object(); + let kson_ptr = kson.to_kotlin_object(); + let kson = kson_ptr.as_kotlin_object(); + let format_options_ptr = format_options.to_kotlin_object(); + let format_options = format_options_ptr.as_kotlin_object(); - /// Use tabs for indentation - #[derive(Clone)] - pub struct Tabs { - kotlin_ptr: KotlinPtr, - } + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/Kson", + c"format", + c"(Ljava/lang/String;Lorg/kson/api/FormatOptions;)Ljava/lang/String;", + CallObjectMethod, + self_obj, + kson, + format_options, + ); - impl FromKotlinObject for Tabs { - fn from_kotlin_object(obj: self::sys::jobject) -> Self { - let (env, _detach_guard) = util::attach_thread_to_java_vm(); - let kotlin_ptr = util::to_gc_global_ref(env, obj); - Self { kotlin_ptr } - } + FromKotlinObject::from_kotlin_object(result) } - impl ToKotlinObject for Tabs { - fn to_kotlin_object(&self) -> KotlinPtr { - self.kotlin_ptr.clone() - } - } + /// Converts Kson to Json. + /// + /// @param kson The Kson source to convert + /// @param options Options for the JSON transpilation + /// @return A Result containing either the Json output or error messages + pub fn to_json( + kson: &str, + options: transpile_options::Json, + ) -> std::result::Result { + let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + let self_obj = self_ptr.as_kotlin_object(); + let kson_ptr = kson.to_kotlin_object(); + let kson = kson_ptr.as_kotlin_object(); + let options_ptr = options.to_kotlin_object(); + let options = options_ptr.as_kotlin_object(); - impl AsKotlinObject for Tabs { - fn as_kotlin_object(&self) -> self::sys::jobject { - self.kotlin_ptr.inner.inner - } - } + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/Kson", + c"toJson", + c"(Ljava/lang/String;Lorg/kson/api/TranspileOptions$Json;)Lorg/kson/api/Result;", + CallObjectMethod, + self_obj, + kson, + options, + ); - impl Tabs { - pub fn new() -> Self { - let kotlin_ptr = util::access_static_field(c"org/kson/IndentType$Tabs", c"INSTANCE", c"Lorg/kson/IndentType$Tabs;"); - Self { kotlin_ptr } - } + crate::kson_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) } + /// Converts Kson to Yaml, preserving comments + /// + /// @param kson The Kson source to convert + /// @param options Options for the YAML transpilation + /// @return A Result containing either the Yaml output or error messages + pub fn to_yaml( + kson: &str, + options: transpile_options::Yaml, + ) -> std::result::Result { + let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + let self_obj = self_ptr.as_kotlin_object(); + let kson_ptr = kson.to_kotlin_object(); + let kson = kson_ptr.as_kotlin_object(); + let options_ptr = options.to_kotlin_object(); + let options = options_ptr.as_kotlin_object(); - impl Tabs { - } + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/Kson", + c"toYaml", + c"(Ljava/lang/String;Lorg/kson/api/TranspileOptions$Yaml;)Lorg/kson/api/Result;", + CallObjectMethod, + self_obj, + kson, + options, + ); - impl std::fmt::Debug for Tabs { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/IndentType$Tabs", &obj)) - } + crate::kson_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) } - impl Eq for Tabs {} - impl PartialEq for Tabs { - fn eq(&self, other: &Tabs) -> bool { - util::equals(self.to_kotlin_object(), other.to_kotlin_object()) - } - } - impl std::hash::Hash for Tabs { - fn hash(&self, state: &mut H) - where - H: std::hash::Hasher, - { - util::apply_hash_code(self.to_kotlin_object(), state) - } - } -} -impl FromKotlinObject for IndentType { - fn from_kotlin_object(obj: jobject) -> Self { - match util::class_name(obj).as_str() { - "org.kson.IndentType$Spaces" => IndentType::Spaces(indent_type::Spaces::from_kotlin_object(obj)), - "org.kson.IndentType$Tabs" => IndentType::Tabs(indent_type::Tabs::from_kotlin_object(obj)), - _ => unreachable!(), - } - } -} + /// Statically analyze the given Kson and return an [Analysis] object containing any messages generated along with a + /// tokenized version of the source. Useful for tooling/editor support. + /// @param kson The Kson source to analyze + /// @param filepath Filepath of the document being analyzed + pub fn analyze( + kson: &str, + filepath: Option<&str>, + ) -> Analysis { + let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + let self_obj = self_ptr.as_kotlin_object(); + let kson_ptr = kson.to_kotlin_object(); + let kson = kson_ptr.as_kotlin_object(); + let filepath_ptr = filepath.to_kotlin_object(); + let filepath = filepath_ptr.as_kotlin_object(); -impl ToKotlinObject for IndentType { - fn to_kotlin_object(&self) -> KotlinPtr { - match self { - Self::Spaces(inner) => inner.to_kotlin_object(), - Self::Tabs(inner) => inner.to_kotlin_object(), - } - } -} + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/Kson", + c"analyze", + c"(Ljava/lang/String;Ljava/lang/String;)Lorg/kson/api/Analysis;", + CallObjectMethod, + self_obj, + kson, + filepath, + ); -impl IndentType { - pub fn name(self) -> String { - let obj = self.to_kotlin_object(); - util::enum_name(&obj) + FromKotlinObject::from_kotlin_object(result) } -} + /// Parses a Kson schema definition and returns a validator for that schema. + /// + /// @param schemaKson The Kson source defining a Json Schema + /// @return A SchemaValidator that can validate Kson documents against the schema + pub fn parse_schema( + schema_kson: &str, + ) -> std::result::Result { + let self_ptr = util::access_static_field(c"org/kson/Kson", c"INSTANCE", c"Lorg/kson/Kson;"); + let self_obj = self_ptr.as_kotlin_object(); + let schema_kson_ptr = schema_kson.to_kotlin_object(); + let schema_kson = schema_kson_ptr.as_kotlin_object(); + + let (_, _detach_guard) = util::attach_thread_to_java_vm(); + let result = call_jvm_function!( + util, + c"org/kson/Kson", + c"parseSchema", + c"(Ljava/lang/String;)Lorg/kson/api/SchemaResult;", + CallObjectMethod, + self_obj, + schema_kson, + ); -impl IndentType { + crate::kson_schema_result_into_rust_result(FromKotlinObject::from_kotlin_object(result)) + } } -impl std::fmt::Debug for IndentType { +impl std::fmt::Debug for Kson { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let obj = self.to_kotlin_object(); - write!(f, "{}", util::call_to_string(c"org/kson/IndentType", &obj)) + write!(f, "{}", util::call_to_string(c"org/kson/Kson", &obj)) } } -impl Eq for IndentType {} -impl PartialEq for IndentType { - fn eq(&self, other: &IndentType) -> bool { +impl Eq for Kson {} +impl PartialEq for Kson { + fn eq(&self, other: &Kson) -> bool { util::equals(self.to_kotlin_object(), other.to_kotlin_object()) } } -impl std::hash::Hash for IndentType { +impl std::hash::Hash for Kson { fn hash(&self, state: &mut H) where H: std::hash::Hasher, @@ -3898,7 +3915,7 @@ impl std::hash::Hash for IndentType { } } -/// Represents the severity of a [Message] + #[derive(Copy, Clone)] pub enum MessageSeverity { Error, @@ -3907,7 +3924,7 @@ pub enum MessageSeverity { impl FromKotlinObject for MessageSeverity { fn from_kotlin_object(obj: jobject) -> Self { - match util::enum_ordinal(c"org/kson/MessageSeverity", obj) { + match util::enum_ordinal(c"org/kson/api/MessageSeverity", obj) { 0 => MessageSeverity::Error, 1 => MessageSeverity::Warning, _ => unreachable!(), @@ -3918,8 +3935,8 @@ impl FromKotlinObject for MessageSeverity { impl ToKotlinObject for MessageSeverity { fn to_kotlin_object(&self) -> KotlinPtr { match self { - MessageSeverity::Error => util::access_static_field(c"org/kson/MessageSeverity", c"ERROR", c"Lorg/kson/MessageSeverity;"), - MessageSeverity::Warning => util::access_static_field(c"org/kson/MessageSeverity", c"WARNING", c"Lorg/kson/MessageSeverity;"), + MessageSeverity::Error => util::access_static_field(c"org/kson/api/MessageSeverity", c"ERROR", c"Lorg/kson/api/MessageSeverity;"), + MessageSeverity::Warning => util::access_static_field(c"org/kson/api/MessageSeverity", c"WARNING", c"Lorg/kson/api/MessageSeverity;"), } } } @@ -3930,89 +3947,89 @@ impl MessageSeverity { util::enum_name(&obj) } } -/// Type discriminator for KsonValue subclasses + #[derive(Copy, Clone)] -pub enum KsonValueType { - Object, - Array, - String, - Integer, - Decimal, - Boolean, - Null, - Embed, +pub enum FormattingStyle { + Plain, + Delimited, + Compact, + Classic, } -impl FromKotlinObject for KsonValueType { +impl FromKotlinObject for FormattingStyle { fn from_kotlin_object(obj: jobject) -> Self { - match util::enum_ordinal(c"org/kson/KsonValueType", obj) { - 0 => KsonValueType::Object, - 1 => KsonValueType::Array, - 2 => KsonValueType::String, - 3 => KsonValueType::Integer, - 4 => KsonValueType::Decimal, - 5 => KsonValueType::Boolean, - 6 => KsonValueType::Null, - 7 => KsonValueType::Embed, + match util::enum_ordinal(c"org/kson/api/FormattingStyle", obj) { + 0 => FormattingStyle::Plain, + 1 => FormattingStyle::Delimited, + 2 => FormattingStyle::Compact, + 3 => FormattingStyle::Classic, _ => unreachable!(), } } } -impl ToKotlinObject for KsonValueType { +impl ToKotlinObject for FormattingStyle { fn to_kotlin_object(&self) -> KotlinPtr { match self { - KsonValueType::Object => util::access_static_field(c"org/kson/KsonValueType", c"OBJECT", c"Lorg/kson/KsonValueType;"), - KsonValueType::Array => util::access_static_field(c"org/kson/KsonValueType", c"ARRAY", c"Lorg/kson/KsonValueType;"), - KsonValueType::String => util::access_static_field(c"org/kson/KsonValueType", c"STRING", c"Lorg/kson/KsonValueType;"), - KsonValueType::Integer => util::access_static_field(c"org/kson/KsonValueType", c"INTEGER", c"Lorg/kson/KsonValueType;"), - KsonValueType::Decimal => util::access_static_field(c"org/kson/KsonValueType", c"DECIMAL", c"Lorg/kson/KsonValueType;"), - KsonValueType::Boolean => util::access_static_field(c"org/kson/KsonValueType", c"BOOLEAN", c"Lorg/kson/KsonValueType;"), - KsonValueType::Null => util::access_static_field(c"org/kson/KsonValueType", c"NULL", c"Lorg/kson/KsonValueType;"), - KsonValueType::Embed => util::access_static_field(c"org/kson/KsonValueType", c"EMBED", c"Lorg/kson/KsonValueType;"), + FormattingStyle::Plain => util::access_static_field(c"org/kson/api/FormattingStyle", c"PLAIN", c"Lorg/kson/api/FormattingStyle;"), + FormattingStyle::Delimited => util::access_static_field(c"org/kson/api/FormattingStyle", c"DELIMITED", c"Lorg/kson/api/FormattingStyle;"), + FormattingStyle::Compact => util::access_static_field(c"org/kson/api/FormattingStyle", c"COMPACT", c"Lorg/kson/api/FormattingStyle;"), + FormattingStyle::Classic => util::access_static_field(c"org/kson/api/FormattingStyle", c"CLASSIC", c"Lorg/kson/api/FormattingStyle;"), } } } -impl KsonValueType { +impl FormattingStyle { pub fn name(self) -> String { let obj = self.to_kotlin_object(); util::enum_name(&obj) } } -/// [FormattingStyle] options for Kson Output + #[derive(Copy, Clone)] -pub enum FormattingStyle { - Plain, - Delimited, - Compact, - Classic, +pub enum KsonValueType { + Object, + Array, + String, + Integer, + Decimal, + Boolean, + Null, + Embed, } -impl FromKotlinObject for FormattingStyle { +impl FromKotlinObject for KsonValueType { fn from_kotlin_object(obj: jobject) -> Self { - match util::enum_ordinal(c"org/kson/FormattingStyle", obj) { - 0 => FormattingStyle::Plain, - 1 => FormattingStyle::Delimited, - 2 => FormattingStyle::Compact, - 3 => FormattingStyle::Classic, + match util::enum_ordinal(c"org/kson/api/KsonValueType", obj) { + 0 => KsonValueType::Object, + 1 => KsonValueType::Array, + 2 => KsonValueType::String, + 3 => KsonValueType::Integer, + 4 => KsonValueType::Decimal, + 5 => KsonValueType::Boolean, + 6 => KsonValueType::Null, + 7 => KsonValueType::Embed, _ => unreachable!(), } } } -impl ToKotlinObject for FormattingStyle { +impl ToKotlinObject for KsonValueType { fn to_kotlin_object(&self) -> KotlinPtr { match self { - FormattingStyle::Plain => util::access_static_field(c"org/kson/FormattingStyle", c"PLAIN", c"Lorg/kson/FormattingStyle;"), - FormattingStyle::Delimited => util::access_static_field(c"org/kson/FormattingStyle", c"DELIMITED", c"Lorg/kson/FormattingStyle;"), - FormattingStyle::Compact => util::access_static_field(c"org/kson/FormattingStyle", c"COMPACT", c"Lorg/kson/FormattingStyle;"), - FormattingStyle::Classic => util::access_static_field(c"org/kson/FormattingStyle", c"CLASSIC", c"Lorg/kson/FormattingStyle;"), + KsonValueType::Object => util::access_static_field(c"org/kson/api/KsonValueType", c"OBJECT", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Array => util::access_static_field(c"org/kson/api/KsonValueType", c"ARRAY", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::String => util::access_static_field(c"org/kson/api/KsonValueType", c"STRING", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Integer => util::access_static_field(c"org/kson/api/KsonValueType", c"INTEGER", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Decimal => util::access_static_field(c"org/kson/api/KsonValueType", c"DECIMAL", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Boolean => util::access_static_field(c"org/kson/api/KsonValueType", c"BOOLEAN", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Null => util::access_static_field(c"org/kson/api/KsonValueType", c"NULL", c"Lorg/kson/api/KsonValueType;"), + KsonValueType::Embed => util::access_static_field(c"org/kson/api/KsonValueType", c"EMBED", c"Lorg/kson/api/KsonValueType;"), } } } -impl FormattingStyle { +impl KsonValueType { pub fn name(self) -> String { let obj = self.to_kotlin_object(); util::enum_name(&obj) @@ -4053,7 +4070,7 @@ pub enum TokenType { impl FromKotlinObject for TokenType { fn from_kotlin_object(obj: jobject) -> Self { - match util::enum_ordinal(c"org/kson/TokenType", obj) { + match util::enum_ordinal(c"org/kson/api/TokenType", obj) { 0 => TokenType::CurlyBraceL, 1 => TokenType::CurlyBraceR, 2 => TokenType::SquareBracketL, @@ -4090,34 +4107,34 @@ impl FromKotlinObject for TokenType { impl ToKotlinObject for TokenType { fn to_kotlin_object(&self) -> KotlinPtr { match self { - TokenType::CurlyBraceL => util::access_static_field(c"org/kson/TokenType", c"CURLY_BRACE_L", c"Lorg/kson/TokenType;"), - TokenType::CurlyBraceR => util::access_static_field(c"org/kson/TokenType", c"CURLY_BRACE_R", c"Lorg/kson/TokenType;"), - TokenType::SquareBracketL => util::access_static_field(c"org/kson/TokenType", c"SQUARE_BRACKET_L", c"Lorg/kson/TokenType;"), - TokenType::SquareBracketR => util::access_static_field(c"org/kson/TokenType", c"SQUARE_BRACKET_R", c"Lorg/kson/TokenType;"), - TokenType::AngleBracketL => util::access_static_field(c"org/kson/TokenType", c"ANGLE_BRACKET_L", c"Lorg/kson/TokenType;"), - TokenType::AngleBracketR => util::access_static_field(c"org/kson/TokenType", c"ANGLE_BRACKET_R", c"Lorg/kson/TokenType;"), - TokenType::Colon => util::access_static_field(c"org/kson/TokenType", c"COLON", c"Lorg/kson/TokenType;"), - TokenType::Dot => util::access_static_field(c"org/kson/TokenType", c"DOT", c"Lorg/kson/TokenType;"), - TokenType::EndDash => util::access_static_field(c"org/kson/TokenType", c"END_DASH", c"Lorg/kson/TokenType;"), - TokenType::Comma => util::access_static_field(c"org/kson/TokenType", c"COMMA", c"Lorg/kson/TokenType;"), - TokenType::Comment => util::access_static_field(c"org/kson/TokenType", c"COMMENT", c"Lorg/kson/TokenType;"), - TokenType::EmbedOpenDelim => util::access_static_field(c"org/kson/TokenType", c"EMBED_OPEN_DELIM", c"Lorg/kson/TokenType;"), - TokenType::EmbedCloseDelim => util::access_static_field(c"org/kson/TokenType", c"EMBED_CLOSE_DELIM", c"Lorg/kson/TokenType;"), - TokenType::EmbedTag => util::access_static_field(c"org/kson/TokenType", c"EMBED_TAG", c"Lorg/kson/TokenType;"), - TokenType::EmbedPreambleNewline => util::access_static_field(c"org/kson/TokenType", c"EMBED_PREAMBLE_NEWLINE", c"Lorg/kson/TokenType;"), - TokenType::EmbedContent => util::access_static_field(c"org/kson/TokenType", c"EMBED_CONTENT", c"Lorg/kson/TokenType;"), - TokenType::False => util::access_static_field(c"org/kson/TokenType", c"FALSE", c"Lorg/kson/TokenType;"), - TokenType::UnquotedString => util::access_static_field(c"org/kson/TokenType", c"UNQUOTED_STRING", c"Lorg/kson/TokenType;"), - TokenType::IllegalChar => util::access_static_field(c"org/kson/TokenType", c"ILLEGAL_CHAR", c"Lorg/kson/TokenType;"), - TokenType::ListDash => util::access_static_field(c"org/kson/TokenType", c"LIST_DASH", c"Lorg/kson/TokenType;"), - TokenType::Null => util::access_static_field(c"org/kson/TokenType", c"NULL", c"Lorg/kson/TokenType;"), - TokenType::Number => util::access_static_field(c"org/kson/TokenType", c"NUMBER", c"Lorg/kson/TokenType;"), - TokenType::StringOpenQuote => util::access_static_field(c"org/kson/TokenType", c"STRING_OPEN_QUOTE", c"Lorg/kson/TokenType;"), - TokenType::StringCloseQuote => util::access_static_field(c"org/kson/TokenType", c"STRING_CLOSE_QUOTE", c"Lorg/kson/TokenType;"), - TokenType::StringContent => util::access_static_field(c"org/kson/TokenType", c"STRING_CONTENT", c"Lorg/kson/TokenType;"), - TokenType::True => util::access_static_field(c"org/kson/TokenType", c"TRUE", c"Lorg/kson/TokenType;"), - TokenType::Whitespace => util::access_static_field(c"org/kson/TokenType", c"WHITESPACE", c"Lorg/kson/TokenType;"), - TokenType::Eof => util::access_static_field(c"org/kson/TokenType", c"EOF", c"Lorg/kson/TokenType;"), + TokenType::CurlyBraceL => util::access_static_field(c"org/kson/api/TokenType", c"CURLY_BRACE_L", c"Lorg/kson/api/TokenType;"), + TokenType::CurlyBraceR => util::access_static_field(c"org/kson/api/TokenType", c"CURLY_BRACE_R", c"Lorg/kson/api/TokenType;"), + TokenType::SquareBracketL => util::access_static_field(c"org/kson/api/TokenType", c"SQUARE_BRACKET_L", c"Lorg/kson/api/TokenType;"), + TokenType::SquareBracketR => util::access_static_field(c"org/kson/api/TokenType", c"SQUARE_BRACKET_R", c"Lorg/kson/api/TokenType;"), + TokenType::AngleBracketL => util::access_static_field(c"org/kson/api/TokenType", c"ANGLE_BRACKET_L", c"Lorg/kson/api/TokenType;"), + TokenType::AngleBracketR => util::access_static_field(c"org/kson/api/TokenType", c"ANGLE_BRACKET_R", c"Lorg/kson/api/TokenType;"), + TokenType::Colon => util::access_static_field(c"org/kson/api/TokenType", c"COLON", c"Lorg/kson/api/TokenType;"), + TokenType::Dot => util::access_static_field(c"org/kson/api/TokenType", c"DOT", c"Lorg/kson/api/TokenType;"), + TokenType::EndDash => util::access_static_field(c"org/kson/api/TokenType", c"END_DASH", c"Lorg/kson/api/TokenType;"), + TokenType::Comma => util::access_static_field(c"org/kson/api/TokenType", c"COMMA", c"Lorg/kson/api/TokenType;"), + TokenType::Comment => util::access_static_field(c"org/kson/api/TokenType", c"COMMENT", c"Lorg/kson/api/TokenType;"), + TokenType::EmbedOpenDelim => util::access_static_field(c"org/kson/api/TokenType", c"EMBED_OPEN_DELIM", c"Lorg/kson/api/TokenType;"), + TokenType::EmbedCloseDelim => util::access_static_field(c"org/kson/api/TokenType", c"EMBED_CLOSE_DELIM", c"Lorg/kson/api/TokenType;"), + TokenType::EmbedTag => util::access_static_field(c"org/kson/api/TokenType", c"EMBED_TAG", c"Lorg/kson/api/TokenType;"), + TokenType::EmbedPreambleNewline => util::access_static_field(c"org/kson/api/TokenType", c"EMBED_PREAMBLE_NEWLINE", c"Lorg/kson/api/TokenType;"), + TokenType::EmbedContent => util::access_static_field(c"org/kson/api/TokenType", c"EMBED_CONTENT", c"Lorg/kson/api/TokenType;"), + TokenType::False => util::access_static_field(c"org/kson/api/TokenType", c"FALSE", c"Lorg/kson/api/TokenType;"), + TokenType::UnquotedString => util::access_static_field(c"org/kson/api/TokenType", c"UNQUOTED_STRING", c"Lorg/kson/api/TokenType;"), + TokenType::IllegalChar => util::access_static_field(c"org/kson/api/TokenType", c"ILLEGAL_CHAR", c"Lorg/kson/api/TokenType;"), + TokenType::ListDash => util::access_static_field(c"org/kson/api/TokenType", c"LIST_DASH", c"Lorg/kson/api/TokenType;"), + TokenType::Null => util::access_static_field(c"org/kson/api/TokenType", c"NULL", c"Lorg/kson/api/TokenType;"), + TokenType::Number => util::access_static_field(c"org/kson/api/TokenType", c"NUMBER", c"Lorg/kson/api/TokenType;"), + TokenType::StringOpenQuote => util::access_static_field(c"org/kson/api/TokenType", c"STRING_OPEN_QUOTE", c"Lorg/kson/api/TokenType;"), + TokenType::StringCloseQuote => util::access_static_field(c"org/kson/api/TokenType", c"STRING_CLOSE_QUOTE", c"Lorg/kson/api/TokenType;"), + TokenType::StringContent => util::access_static_field(c"org/kson/api/TokenType", c"STRING_CONTENT", c"Lorg/kson/api/TokenType;"), + TokenType::True => util::access_static_field(c"org/kson/api/TokenType", c"TRUE", c"Lorg/kson/api/TokenType;"), + TokenType::Whitespace => util::access_static_field(c"org/kson/api/TokenType", c"WHITESPACE", c"Lorg/kson/api/TokenType;"), + TokenType::Eof => util::access_static_field(c"org/kson/api/TokenType", c"EOF", c"Lorg/kson/api/TokenType;"), } } } diff --git a/lib-rust/kson/src/generated/util.rs b/lib-rust/kson/src/generated/util.rs index e21b2d22..d57f51e6 100644 --- a/lib-rust/kson/src/generated/util.rs +++ b/lib-rust/kson/src/generated/util.rs @@ -401,6 +401,40 @@ pub(super) fn from_kotlin_list(list: jobject) -> Vec { elements } +pub(super) fn to_kotlin_map< + K: ToKotlinObject + Eq + std::hash::Hash, + V: ToKotlinObject, +>( + items: &std::collections::HashMap +) -> KotlinPtr { + let (env, _detach_guard) = attach_thread_to_java_vm(); + + // Create a new map + let map_class = get_class(env, c"java/util/HashMap"); + let constructor = get_method(env, map_class.as_kotlin_object(), c"", c"()V"); + let map = unsafe { + let new_object = (**env).NewObjectA.unwrap(); + new_object(env, map_class.as_kotlin_object(), constructor, std::ptr::null()) + }; + panic_upon_exception(env); + let map = to_gc_global_ref(env, map); + + // Add entries to it + let put_method = get_method(env, map_class.as_kotlin_object(), c"put", c"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + for (key, value) in items { + let key = key.to_kotlin_object(); + let value = value.to_kotlin_object(); + let args = &[jvalue { l: key.as_kotlin_object() }, jvalue { l: value.as_kotlin_object() }]; + unsafe { + let call_object = (**env).CallObjectMethodA.unwrap(); + call_object(env, map.as_kotlin_object(), put_method, args.as_ptr()); + } + panic_upon_exception(env); + } + + map +} + pub(super) fn from_kotlin_value_map< K: FromKotlinObject + Eq + std::hash::Hash, V: FromKotlinObject, diff --git a/settings.gradle.kts b/settings.gradle.kts index 65138bf9..ef7b68b5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,8 +13,10 @@ pluginManagement { rootProject.name = "kson" include("native-tests") +include("kson-service-api") +include("kson-service-tests") include("kson-lib") -include("kson-lib-http") +include("kson-http") include("kson-tooling-lib") include("lib-python") include("lib-python:kson-lib-tests") From b7153ffb9d510a3a73edb9f81155e71b50982ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20Ochagav=C3=ADa?= Date: Fri, 6 Mar 2026 08:51:17 -0300 Subject: [PATCH 6/6] Temporary workaround for pyright bug See the modified files for details. A fix is ready, but it will not be available until the next pyright release. --- lib-python/pyproject.toml | 3 +++ lib-python/stubs/cffi/__init__.pyi | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 lib-python/stubs/cffi/__init__.pyi diff --git a/lib-python/pyproject.toml b/lib-python/pyproject.toml index 6fceaa8b..792d1c85 100644 --- a/lib-python/pyproject.toml +++ b/lib-python/pyproject.toml @@ -48,6 +48,9 @@ package-dir = {"" = "src"} [tool.setuptools.package-data] kson = ["kson_api.h", "kson.dll", "libkson.dylib", "libkson.so", "jni_simplified.h"] +[tool.pyright] +stubPath = "stubs" + [dependency-groups] dev = [ "pyright>=1.1.403", diff --git a/lib-python/stubs/cffi/__init__.pyi b/lib-python/stubs/cffi/__init__.pyi new file mode 100644 index 00000000..60dac9ca --- /dev/null +++ b/lib-python/stubs/cffi/__init__.pyi @@ -0,0 +1,8 @@ +# This stub exists because otherwise type checking fails. The root cause is that `pyright` ships +# with a buggy stub for the `cffi` library. A fix is ready, but it will not be available until the +# next release. +# +# This code can be deleted once we upgrade `pyright` to a version higher than `1.1.408`. + +from typing import Any +def __getattr__(name: str) -> Any: ...