From a2f910fd23039d8a74a9be976b5c22c69162dc4d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 6 Nov 2025 20:21:46 +0400 Subject: [PATCH 1/2] test: Add a test case --- tests/Unit/TomlEncodeTest.php | 36 +++++++++++++++++++++++++++++++++++ tests/Unit/fixtures/rr.toml | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 tests/Unit/fixtures/rr.toml diff --git a/tests/Unit/TomlEncodeTest.php b/tests/Unit/TomlEncodeTest.php index 3e2cff1..06f5fa6 100644 --- a/tests/Unit/TomlEncodeTest.php +++ b/tests/Unit/TomlEncodeTest.php @@ -40,6 +40,11 @@ public static function provideRoundTripData(): \Generator ['str' => 'text', 'int' => 42, 'float' => 3.14, 'bool' => true], ]; } + + public static function provideFixtureFiles(): \Generator + { + yield 'roadrunner config' => ['rr.toml']; + } // ============================================ // Basic Encoding Tests // ============================================ @@ -345,4 +350,35 @@ public function testEncodeStringWithNewlinesProducesMultilineString(): void $lines = \explode("\n", $toml); self::assertGreaterThan(3, \count($lines), 'Should have multiple lines'); } + + // ============================================ + // Fixture-based Round-Trip Tests + // ============================================ + + #[DataProvider('provideFixtureFiles')] + public function testEncodeFixtureFileRoundTrip(string $filename): void + { + // Arrange + $fixturePath = __DIR__ . '/fixtures/' . $filename; + $tomlContent = \file_get_contents($fixturePath); + self::assertNotFalse($tomlContent, "Failed to read fixture file: {$filename}"); + + // Parse original TOML to array + $originalArray = Toml::parseToArray($tomlContent); + + // Act + // Encode array back to TOML string + $encodedToml = (string) Toml::encode($originalArray); + + // Parse the encoded TOML back to array + $reEncodedArray = Toml::parseToArray($encodedToml); + + // Assert + // The re-encoded array should match the original parsed array + self::assertEquals( + $originalArray, + $reEncodedArray, + "Round-trip encoding/decoding changed the data structure for {$filename}" + ); + } } diff --git a/tests/Unit/fixtures/rr.toml b/tests/Unit/fixtures/rr.toml new file mode 100644 index 0000000..bfc5922 --- /dev/null +++ b/tests/Unit/fixtures/rr.toml @@ -0,0 +1,34 @@ +[roadrunner] +ref = "v2025.1.1" + +[log] +level = "info" +mode = "production" + +[github.token] +token = "123adryen7y123" + +[github.plugins.temporal] +ref = "v5.7.0" +owner = "temporalio" +repository = "roadrunner-temporal" + +[github.plugins.kv] +ref = "v5.2.8" +owner = "roadrunner-server" +repository = "kv" + +[github.plugins.logger] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "logger" + +[github.plugins.server] +ref = "v5.2.9" +owner = "roadrunner-server" +repository = "server" + +[github.plugins.rpc] +ref = "v5.1.8" +owner = "roadrunner-server" +repository = "rpc" \ No newline at end of file From f2ea717bcad373f4a1a300dcee26519dca9ac1df Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Thu, 6 Nov 2025 20:25:59 +0400 Subject: [PATCH 2/2] fix: Handle nested tables in TOML encoder --- src/Encoder/ArrayToDocumentConverter.php | 80 ++++++++++++++++++++---- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/src/Encoder/ArrayToDocumentConverter.php b/src/Encoder/ArrayToDocumentConverter.php index 414c37a..72d9b02 100644 --- a/src/Encoder/ArrayToDocumentConverter.php +++ b/src/Encoder/ArrayToDocumentConverter.php @@ -26,8 +26,11 @@ public function convert(array $data): Document $nodes = []; $position = new Position(1, 1, 0); - // Categorize data into root entries, tables, and table arrays - [$rootEntries, $tables, $tableArrays] = $this->categorizeData($data); + // Flatten the data structure to handle nested tables + $flatData = $this->flattenData($data); + + // Categorize flattened data into root entries, tables, and table arrays + [$rootEntries, $tables, $tableArrays] = $this->categorizeData($flatData); // Root key-value pairs first foreach ($rootEntries as $key => $value) { @@ -49,6 +52,65 @@ public function convert(array $data): Document return new Document($nodes, $position); } + /** + * Flattens nested array structure into dotted keys. + * + * Example: + * ['github' => ['token' => ['key' => 'val']]] + * becomes: + * ['github.token' => ['key' => 'val']] + * + * @param array $data + * @param string $prefix + * @return array + */ + private function flattenData(array $data, string $prefix = ''): array + { + $result = []; + + foreach ($data as $key => $value) { + if (!\is_string($key)) { + throw new \InvalidArgumentException('TOML keys must be strings, got: ' . \get_debug_type($key)); + } + + $fullKey = $prefix === '' ? $key : $prefix . '.' . $key; + + if (!\is_array($value)) { + // Scalar value + $result[$fullKey] = $value; + } elseif ($this->isTableArray($value)) { + // Table array - keep as is + $result[$fullKey] = $value; + } elseif ($this->isAssociativeArray($value)) { + // Check if this table has only scalar/array values (leaf table) + // or if it has nested tables + $hasNestedTables = false; + foreach ($value as $subValue) { + if (\is_array($subValue) && $this->isAssociativeArray($subValue) && !$this->isTableArray($subValue)) { + $hasNestedTables = true; + break; + } + } + + if ($hasNestedTables) { + // Recursively flatten nested tables + $flattened = $this->flattenData($value, $fullKey); + foreach ($flattened as $flatKey => $flatValue) { + $result[$flatKey] = $flatValue; + } + } else { + // Leaf table - keep as is + $result[$fullKey] = $value; + } + } else { + // Simple array (list of scalars) + $result[$fullKey] = $value; + } + } + + return $result; + } + /** * Categorizes data into root entries, tables, and table arrays. * @@ -102,18 +164,14 @@ private function createTable(string $name, array $data): Table $entries = []; $position = new Position(0, 0, 0); - // Categorize table data - [$rootEntries, $nestedTables, $nestedTableArrays] = $this->categorizeData($data); - - // Add root entries - foreach ($rootEntries as $key => $value) { + // Add all entries (data is already flattened, so no nested tables here) + foreach ($data as $key => $value) { + if (!\is_string($key)) { + throw new \InvalidArgumentException('TOML keys must be strings, got: ' . \get_debug_type($key)); + } $entries[] = $this->createEntry($key, $value); } - // Nested tables and table arrays will be handled at document level - // For now, we only handle simple table entries - // TODO: Handle nested structures properly - return new Table($keyNode, $entries, null, $position); }