Skip to content

Commit b8721b0

Browse files
committed
✅ add qs.js comparison
1 parent e2b3270 commit b8721b0

File tree

10 files changed

+721
-1
lines changed

10 files changed

+721
-1
lines changed

.swiftlint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ excluded:
99
- Bench
1010
- ObjCE2ETests
1111
- Tests
12+
- Tools
1213

1314
disabled_rules:
1415
- trailing_whitespace

.vscode/launch.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
11
{
22
"version": "0.2.0",
3-
"configurations": []
3+
"configurations": [
4+
{
5+
"type": "swift",
6+
"request": "launch",
7+
"args": [],
8+
"cwd": "${workspaceFolder:QsSwift}",
9+
"name": "Debug QsSwiftComparison",
10+
"program": "${workspaceFolder:QsSwift}/.build/debug/QsSwiftComparison",
11+
"preLaunchTask": "swift: Build Debug QsSwiftComparison"
12+
},
13+
{
14+
"type": "swift",
15+
"request": "launch",
16+
"args": [],
17+
"cwd": "${workspaceFolder:QsSwift}",
18+
"name": "Release QsSwiftComparison",
19+
"program": "${workspaceFolder:QsSwift}/.build/release/QsSwiftComparison",
20+
"preLaunchTask": "swift: Build Release QsSwiftComparison"
21+
}
22+
]
423
}

Package.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ let package = Package(
4848
],
4949
path: "Tests/QsObjCTests"
5050
),
51+
.executableTarget(
52+
name: "QsSwiftComparison",
53+
dependencies: ["QsSwift"],
54+
path: "Tools/QsSwiftComparison"
55+
),
5156
]
5257
)

Package@swift-5.10.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,10 @@ let package = Package(
6161
],
6262
path: "Tests/QsObjCTests"
6363
),
64+
.executableTarget(
65+
name: "QsSwiftComparison",
66+
dependencies: ["QsSwift"],
67+
path: "Tools/QsSwiftComparison"
68+
),
6469
]
6570
)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
export SWIFT_DETERMINISTIC_HASHING=1
5+
6+
script_dir="$(cd "$(dirname "$0")" && pwd)"
7+
8+
# Canonicalize query strings by sorting key-value pairs so order differences
9+
# don't cause spurious diffs. This mirrors how we compare semantics, not order.
10+
canonicalize_query() {
11+
local qs="$1"
12+
local IFS='&'
13+
# Split on '&' into an array (bash 3-compatible). Predeclare to avoid
14+
# "unbound variable" under `set -u` when the input is empty.
15+
local -a parts=()
16+
# Only split when the string is non-empty; otherwise leave as empty array.
17+
if [[ -n "$qs" ]]; then
18+
read -r -a parts <<< "$qs"
19+
fi
20+
# If there are no parts, return an empty string so callers print `Encoded: `
21+
# without tripping `set -u` on ${parts[@]}.
22+
if ((${#parts[@]} == 0)); then
23+
echo ""
24+
return
25+
fi
26+
# Sort pairs lexicographically in a C locale and join back with '&'
27+
LC_ALL=C printf '%s\n' "${parts[@]}" | sort | paste -sd '&' -
28+
}
29+
30+
# For lines like `Decoded: { ... }`, recursively sort object keys so
31+
# JSON key ordering differences don't cause spurious diffs.
32+
# Uses Node (already a dependency for the comparison).
33+
canonicalize_json_with_node() {
34+
node - <<'NODE'
35+
let s = "";
36+
process.stdin.on("data", c => (s += c));
37+
process.stdin.on("end", () => {
38+
function sort(v) {
39+
if (Array.isArray(v)) return v.map(sort);
40+
if (v && typeof v === "object") {
41+
const keys = Object.keys(v).sort();
42+
const o = {};
43+
for (const k of keys) o[k] = sort(v[k]);
44+
return o;
45+
}
46+
return v;
47+
}
48+
try {
49+
const v = JSON.parse(s);
50+
process.stdout.write(JSON.stringify(sort(v)));
51+
} catch {
52+
process.stdout.write(s);
53+
}
54+
});
55+
NODE
56+
}
57+
58+
normalize_stream() {
59+
local line
60+
while IFS= read -r line; do
61+
if [[ "$line" == Encoded:* ]]; then
62+
local qs="${line#Encoded: }"
63+
echo "Encoded: $(canonicalize_query "$qs")"
64+
elif [[ "$line" == Decoded:* ]]; then
65+
local json="${line#Decoded: }"
66+
local canon
67+
canon="$(printf '%s' "$json" | canonicalize_json_with_node)"
68+
echo "Decoded: $canon"
69+
else
70+
echo "$line"
71+
fi
72+
done
73+
}
74+
75+
node_output_raw="$(node "$script_dir/js/qs.js")"
76+
swift_output_raw="$(swift run -q -c release QsSwiftComparison)"
77+
78+
# Normalize both outputs to remove ordering noise.
79+
node_output="$(printf '%s\n' "$node_output_raw" | normalize_stream)"
80+
swift_output="$(printf '%s\n' "$swift_output_raw" | normalize_stream)"
81+
82+
if [ "$node_output" == "$swift_output" ]; then
83+
echo "The outputs are identical."
84+
exit 0
85+
else
86+
echo "The outputs are different."
87+
diff -u <(echo "$node_output") <(echo "$swift_output") || true
88+
exit 1
89+
fi
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "qs_comparison",
3+
"version": "1.0.0",
4+
"description": "A comparison of query string parsing libraries",
5+
"author": "Klemen Tusar",
6+
"license": "BSD-3-Clause",
7+
"dependencies": {
8+
"qs": "^6.14.0"
9+
}
10+
}

Tools/QsSwiftComparison/js/qs.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const qs = require('qs');
4+
5+
// Use path.join to combine __dirname with the relative path to test_cases.json
6+
const filePath = path.join(__dirname, 'test_cases.json');
7+
const e2eTestCases = JSON.parse(fs.readFileSync(filePath).toString());
8+
9+
e2eTestCases.forEach(testCase => {
10+
console.log('Encoded:', qs.stringify(testCase.data));
11+
console.log('Decoded:', JSON.stringify(qs.parse(testCase.encoded)));
12+
});

0 commit comments

Comments
 (0)