snowflake-code-unit-registry 0.4.3__tar.gz → 0.4.10__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/Cargo.lock +56 -12
  2. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/Cargo.toml +1 -1
  3. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/PKG-INFO +1 -1
  4. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/Cargo.toml +3 -2
  5. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/build.rs +210 -161
  6. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/examples/code-unit.example.json +1 -1
  7. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/schemas/code-unit.schema.json +11 -10
  8. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/checksum.rs +15 -29
  9. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/error.rs +11 -22
  10. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/filter.rs +13 -14
  11. snowflake_code_unit_registry-0.4.10/crates/scai-state-core/src/generated/file_path_fields.rs +9 -0
  12. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/generated/mod.rs +1 -0
  13. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/generated/query_reference.rs +5 -5
  14. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/lib.rs +6 -0
  15. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/loader.rs +1 -6
  16. snowflake_code_unit_registry-0.4.10/crates/scai-state-core/src/registry/paths.rs +208 -0
  17. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/tests.rs +350 -12
  18. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry.rs +72 -35
  19. snowflake_code_unit_registry-0.4.10/crates/scai-state-core/src/validation.rs +328 -0
  20. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/src/lib.rs +25 -0
  21. snowflake_code_unit_registry-0.4.10/crates/scai-state-python/tests/test_validation.py +90 -0
  22. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/pyproject.toml +1 -1
  23. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/python/snowflake_code_unit_registry/__init__.py +68 -1
  24. snowflake_code_unit_registry-0.4.10/python/snowflake_code_unit_registry/types.py +375 -0
  25. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/README.md +0 -0
  26. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/schemas/query-reference.rs.tmpl +0 -0
  27. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/schemas/query-reference.tmpl +0 -0
  28. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/generated/updated_at_paths.rs +0 -0
  29. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/identity.rs +0 -0
  30. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/README.md +0 -0
  31. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/graph.rs +0 -0
  32. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/in_memory.rs +0 -0
  33. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/query.rs +0 -0
  34. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-core/src/registry/test_helpers.rs +0 -0
  35. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/Cargo.toml +0 -0
  36. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/README.md +0 -0
  37. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/conftest.py +0 -0
  38. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_batch.py +0 -0
  39. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_checksum.py +0 -0
  40. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_crud.py +0 -0
  41. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_errors.py +0 -0
  42. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_query.py +0 -0
  43. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_refresh.py +0 -0
  44. {snowflake_code_unit_registry-0.4.3 → snowflake_code_unit_registry-0.4.10}/crates/scai-state-python/tests/test_serialization.py +0 -0
@@ -1192,9 +1192,9 @@ dependencies = [
1192
1192
 
1193
1193
  [[package]]
1194
1194
  name = "jsonschema"
1195
- version = "0.44.1"
1195
+ version = "0.45.0"
1196
1196
  source = "registry+https://github.com/rust-lang/crates.io-index"
1197
- checksum = "f98b64a413c93a1b413dfbaf973b78d271648b9cae50b10302ad88af78991672"
1197
+ checksum = "6f29616f6e19415398eb186964fb7cbbeef572c79bede3622a8277667924bbe3"
1198
1198
  dependencies = [
1199
1199
  "ahash",
1200
1200
  "bytecount",
@@ -1852,9 +1852,9 @@ dependencies = [
1852
1852
 
1853
1853
  [[package]]
1854
1854
  name = "referencing"
1855
- version = "0.44.1"
1855
+ version = "0.45.0"
1856
1856
  source = "registry+https://github.com/rust-lang/crates.io-index"
1857
- checksum = "22952642836711d7a730d23a4dfb0d732e75a85e4c4f5704266d9c8fac278ff1"
1857
+ checksum = "b8a618c14f8ba29d8193bb55e2bf13e4fb2b1115313ecb7ae94b43100c7ac7d5"
1858
1858
  dependencies = [
1859
1859
  "ahash",
1860
1860
  "fluent-uri",
@@ -1904,6 +1904,16 @@ dependencies = [
1904
1904
  "memchr",
1905
1905
  ]
1906
1906
 
1907
+ [[package]]
1908
+ name = "regress"
1909
+ version = "0.11.0"
1910
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1911
+ checksum = "07948de9abc2e83adbeb7543c061a5ddaf7d944afcafbdd6e6b39aeacd40504b"
1912
+ dependencies = [
1913
+ "hashbrown 0.16.1",
1914
+ "memchr",
1915
+ ]
1916
+
1907
1917
  [[package]]
1908
1918
  name = "reqwest"
1909
1919
  version = "0.13.2"
@@ -2067,7 +2077,7 @@ dependencies = [
2067
2077
 
2068
2078
  [[package]]
2069
2079
  name = "scai-state-core"
2070
- version = "0.4.3"
2080
+ version = "0.4.10"
2071
2081
  dependencies = [
2072
2082
  "chrono",
2073
2083
  "err_code",
@@ -2075,7 +2085,7 @@ dependencies = [
2075
2085
  "jsonschema",
2076
2086
  "prettyplease",
2077
2087
  "rayon",
2078
- "regress",
2088
+ "regress 0.11.0",
2079
2089
  "schemars",
2080
2090
  "serde",
2081
2091
  "serde_json",
@@ -2083,6 +2093,7 @@ dependencies = [
2083
2093
  "strum",
2084
2094
  "syn",
2085
2095
  "tempfile",
2096
+ "test-case",
2086
2097
  "thiserror 2.0.18",
2087
2098
  "typify",
2088
2099
  "uuid",
@@ -2091,7 +2102,7 @@ dependencies = [
2091
2102
 
2092
2103
  [[package]]
2093
2104
  name = "scai-state-csharp"
2094
- version = "0.4.3"
2105
+ version = "0.4.10"
2095
2106
  dependencies = [
2096
2107
  "interoptopus",
2097
2108
  "interoptopus_backend_csharp",
@@ -2102,7 +2113,7 @@ dependencies = [
2102
2113
 
2103
2114
  [[package]]
2104
2115
  name = "scai-state-node"
2105
- version = "0.4.3"
2116
+ version = "0.4.10"
2106
2117
  dependencies = [
2107
2118
  "napi",
2108
2119
  "napi-build",
@@ -2114,7 +2125,7 @@ dependencies = [
2114
2125
 
2115
2126
  [[package]]
2116
2127
  name = "scai-state-python"
2117
- version = "0.4.3"
2128
+ version = "0.4.10"
2118
2129
  dependencies = [
2119
2130
  "pyo3",
2120
2131
  "pythonize",
@@ -2417,9 +2428,9 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
2417
2428
 
2418
2429
  [[package]]
2419
2430
  name = "tempfile"
2420
- version = "3.26.0"
2431
+ version = "3.27.0"
2421
2432
  source = "registry+https://github.com/rust-lang/crates.io-index"
2422
- checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
2433
+ checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
2423
2434
  dependencies = [
2424
2435
  "fastrand",
2425
2436
  "getrandom 0.4.2",
@@ -2450,6 +2461,39 @@ dependencies = [
2450
2461
  "unicode-segmentation",
2451
2462
  ]
2452
2463
 
2464
+ [[package]]
2465
+ name = "test-case"
2466
+ version = "3.3.1"
2467
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2468
+ checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8"
2469
+ dependencies = [
2470
+ "test-case-macros",
2471
+ ]
2472
+
2473
+ [[package]]
2474
+ name = "test-case-core"
2475
+ version = "3.3.1"
2476
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2477
+ checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f"
2478
+ dependencies = [
2479
+ "cfg-if",
2480
+ "proc-macro2",
2481
+ "quote",
2482
+ "syn",
2483
+ ]
2484
+
2485
+ [[package]]
2486
+ name = "test-case-macros"
2487
+ version = "3.3.1"
2488
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2489
+ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb"
2490
+ dependencies = [
2491
+ "proc-macro2",
2492
+ "quote",
2493
+ "syn",
2494
+ "test-case-core",
2495
+ ]
2496
+
2453
2497
  [[package]]
2454
2498
  name = "thiserror"
2455
2499
  version = "1.0.69"
@@ -2633,7 +2677,7 @@ dependencies = [
2633
2677
  "log",
2634
2678
  "proc-macro2",
2635
2679
  "quote",
2636
- "regress",
2680
+ "regress 0.10.5",
2637
2681
  "schemars",
2638
2682
  "semver",
2639
2683
  "serde",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/scai-state-core", "crates/scai-state-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.4.3"
6
+ version = "0.4.10"
7
7
  edition = "2021"
8
8
  license = "SEE LICENSE IN LICENSE"
9
9
  repository = "https://github.com/snowflake-eng/scai-state"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-code-unit-registry
3
- Version: 0.4.3
3
+ Version: 0.4.10
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -14,10 +14,11 @@ strum.workspace = true
14
14
  uuid.workspace = true
15
15
  fs2.workspace = true
16
16
  sqlparser.workspace = true
17
- regress = "0.10"
17
+ regress = "0.11"
18
18
  chrono = { version = "0.4", features = ["serde"] }
19
19
  xxhash-rust = { version = "0.8", features = ["xxh3"] }
20
20
  rayon = "1.10"
21
+ jsonschema = "0.45"
21
22
 
22
23
  [build-dependencies]
23
24
  typify = "0.6"
@@ -28,4 +29,4 @@ prettyplease = "0.2"
28
29
 
29
30
  [dev-dependencies]
30
31
  tempfile = "3"
31
- jsonschema = "0.44"
32
+ test-case = "3"
@@ -1,6 +1,104 @@
1
1
  use std::fs;
2
2
  use std::path::Path;
3
3
 
4
+ const DEFINITION_REF_PREFIX: &str = "#/definitions/";
5
+
6
+ // ── Schema Document ──────────────────────────────────────────────────────
7
+
8
+ type JsonObject = serde_json::Map<String, serde_json::Value>;
9
+
10
+ /// Parsed JSON Schema with centralized navigation helpers.
11
+ /// Created once and shared across all generators so the schema is parsed
12
+ /// and its `definitions` map resolved in a single place.
13
+ struct SchemaDoc {
14
+ root: serde_json::Value,
15
+ }
16
+
17
+ impl SchemaDoc {
18
+ fn from_str(text: &str) -> Self {
19
+ let root: serde_json::Value =
20
+ serde_json::from_str(text).expect("Failed to parse schema JSON");
21
+ SchemaDoc { root }
22
+ }
23
+
24
+ fn definitions(&self) -> Option<&JsonObject> {
25
+ self.root.get("definitions").and_then(|d| d.as_object())
26
+ }
27
+
28
+ /// Resolve a `$ref` node to its target definition.
29
+ /// Non-ref nodes pass through unchanged.
30
+ /// Panics on malformed or unresolvable refs — those are schema-authoring
31
+ /// errors that should fail the build loudly.
32
+ fn resolve<'a>(&'a self, node: &'a serde_json::Value) -> &'a serde_json::Value {
33
+ let Some(ref_path) = node.get("$ref").and_then(|r| r.as_str()) else {
34
+ return node;
35
+ };
36
+ let def_name = ref_path
37
+ .strip_prefix(DEFINITION_REF_PREFIX)
38
+ .unwrap_or_else(|| panic!("Unsupported $ref format: {ref_path}"));
39
+ self.definitions()
40
+ .and_then(|d| d.get(def_name))
41
+ .unwrap_or_else(|| panic!("Unresolved $ref: {ref_path}"))
42
+ }
43
+
44
+ /// Sorted property entries of a JSON Schema object node.
45
+ /// Returns an empty vec for non-objects or objects without a `properties` key.
46
+ fn sorted_properties<'a>(
47
+ &'a self,
48
+ node: &'a serde_json::Value,
49
+ ) -> Vec<(&'a str, &'a serde_json::Value)> {
50
+ let Some(properties) = node.get("properties").and_then(|p| p.as_object()) else {
51
+ return Vec::new();
52
+ };
53
+ let mut entries: Vec<(&str, &serde_json::Value)> =
54
+ properties.iter().map(|(k, v)| (k.as_str(), v)).collect();
55
+ entries.sort_by(|(a, _), (b, _)| a.cmp(b));
56
+ entries
57
+ }
58
+
59
+ /// Schema `type` keyword, defaulting to `"object"` when absent.
60
+ fn node_type<'a>(&self, node: &'a serde_json::Value) -> &'a str {
61
+ node.get("type")
62
+ .and_then(|t| t.as_str())
63
+ .unwrap_or("object")
64
+ }
65
+
66
+ fn root_properties(&self) -> Vec<(&str, &serde_json::Value)> {
67
+ self.sorted_properties(&self.root)
68
+ }
69
+ }
70
+
71
+ // ── Shared Object Tree Walker ────────────────────────────────────────────
72
+
73
+ /// Recursively visit every property of every reachable object node in the
74
+ /// schema, resolving `$ref` along the way. Arrays and scalars are
75
+ /// intentionally skipped — the generated path constants are consumed by
76
+ /// object-only JSON navigation helpers that do not model array indices.
77
+ ///
78
+ /// The `visit` callback receives the full segment path and the *resolved*
79
+ /// property value for every property of every reachable object.
80
+ fn walk_object_tree(
81
+ doc: &SchemaDoc,
82
+ current_path: &[String],
83
+ node: &serde_json::Value,
84
+ visit: &mut impl FnMut(&[String], &serde_json::Value),
85
+ ) {
86
+ let node = doc.resolve(node);
87
+ if doc.node_type(node) != "object" {
88
+ return;
89
+ }
90
+
91
+ for (name, raw_child) in doc.sorted_properties(node) {
92
+ let child = doc.resolve(raw_child);
93
+ let mut child_path = current_path.to_vec();
94
+ child_path.push(name.to_string());
95
+ visit(&child_path, child);
96
+ walk_object_tree(doc, &child_path, child, visit);
97
+ }
98
+ }
99
+
100
+ // ── Main ─────────────────────────────────────────────────────────────────
101
+
4
102
  fn main() {
5
103
  println!("cargo:rerun-if-changed=./schemas/code-unit.schema.json");
6
104
  println!("cargo:rerun-if-changed=./schemas/query-reference.tmpl");
@@ -23,29 +121,37 @@ fn main() {
23
121
  let schema_content =
24
122
  fs::read_to_string(schema_path).expect("Failed to read code-unit.schema.json");
25
123
 
124
+ // Typify needs its own parse (schemars::schema::RootSchema), so it
125
+ // still receives the raw string.
26
126
  let generated_code = generate_with_typify(&schema_content);
127
+ write_generated(out_dir, "types.rs", &generated_code, "types");
27
128
 
28
- let output_path = out_dir.join("types.rs");
29
- fs::write(&output_path, &generated_code).expect("Failed to write generated types");
30
-
31
- println!("cargo:warning=Generated types to {:?}", output_path);
129
+ let doc = SchemaDoc::from_str(&schema_content);
32
130
 
33
- let query_ref_code = generate_query_reference(&schema_content);
34
- let query_ref_path = out_dir.join("query_reference.rs");
35
- fs::write(&query_ref_path, &query_ref_code).expect("Failed to write query reference");
131
+ let query_template = fs::read_to_string("./schemas/query-reference.tmpl")
132
+ .expect("Failed to read schemas/query-reference.tmpl");
133
+ let query_rs_template = fs::read_to_string("./schemas/query-reference.rs.tmpl")
134
+ .expect("Failed to read schemas/query-reference.rs.tmpl");
36
135
 
37
- println!(
38
- "cargo:warning=Generated query reference to {:?}",
39
- query_ref_path
136
+ write_generated(
137
+ out_dir,
138
+ "query_reference.rs",
139
+ &generate_query_reference(&doc, &query_template, &query_rs_template),
140
+ "query reference",
40
141
  );
41
142
 
42
- let updated_at_code = generate_updated_at_paths(&schema_content);
43
- let updated_at_path = out_dir.join("updated_at_paths.rs");
44
- fs::write(&updated_at_path, &updated_at_code).expect("Failed to write updated_at_paths");
143
+ write_generated(
144
+ out_dir,
145
+ "updated_at_paths.rs",
146
+ &generate_updated_at_paths(&doc),
147
+ "updated_at_paths",
148
+ );
45
149
 
46
- println!(
47
- "cargo:warning=Generated updated_at_paths to {:?}",
48
- updated_at_path
150
+ write_generated(
151
+ out_dir,
152
+ "file_path_fields.rs",
153
+ &generate_file_path_fields(&doc),
154
+ "file_path_fields",
49
155
  );
50
156
 
51
157
  check_sibling_generated_files();
@@ -72,6 +178,25 @@ fn check_sibling_generated_files() {
72
178
  }
73
179
  }
74
180
 
181
+ fn write_generated(out_dir: &Path, filename: &str, code: &str, label: &str) {
182
+ let output_path = out_dir.join(filename);
183
+ fs::write(&output_path, code).unwrap_or_else(|_| panic!("Failed to write {}", label));
184
+ println!("cargo:warning=Generated {} to {:?}", label, output_path);
185
+ }
186
+
187
+ fn render_path_constant(const_name: &str, comment: &str, paths: &[Vec<String>]) -> String {
188
+ let mut code = String::from("// AUTO-GENERATED FROM JSON SCHEMA - DO NOT EDIT MANUALLY\n");
189
+ code.push_str(comment);
190
+ code.push_str("\n\n");
191
+ code.push_str(&format!("pub const {const_name}: &[&[&str]] = &[\n"));
192
+ for path in paths {
193
+ let segments: Vec<String> = path.iter().map(|s| format!("\"{}\"", s)).collect();
194
+ code.push_str(&format!(" &[{}],\n", segments.join(", ")));
195
+ }
196
+ code.push_str("];\n");
197
+ code
198
+ }
199
+
75
200
  // ── Query Reference Generator ────────────────────────────────────────────
76
201
 
77
202
  /// A flattened field descriptor extracted from the JSON Schema.
@@ -84,30 +209,19 @@ struct FieldInfo {
84
209
 
85
210
  /// Walk the JSON Schema and produce a Rust source file containing
86
211
  /// `pub const QUERY_REFERENCE: &str` with the full flattened field reference.
87
- fn generate_query_reference(schema_content: &str) -> String {
88
- let schema: serde_json::Value =
89
- serde_json::from_str(schema_content).expect("Failed to parse schema JSON");
90
- let definitions = schema.get("definitions").and_then(|d| d.as_object());
91
-
212
+ ///
213
+ /// This generator has its own recursive traversal (`collect_fields`) because
214
+ /// it handles arrays, enums, and `x-query-help` exclusion — none of which
215
+ /// apply to the path-oriented generators that share `walk_object_tree`.
216
+ fn generate_query_reference(doc: &SchemaDoc, template: &str, rs_template: &str) -> String {
92
217
  let mut fields = Vec::new();
93
- if let Some(properties) = schema.get("properties").and_then(|p| p.as_object()) {
94
- let mut sorted_keys: Vec<&String> = properties.keys().collect();
95
- sorted_keys.sort();
96
- for name in sorted_keys {
97
- let prop = &properties[name];
98
- collect_fields(&mut fields, name, prop, definitions);
99
- }
218
+ for (name, prop) in doc.root_properties() {
219
+ collect_fields(&mut fields, name, prop, doc);
100
220
  }
101
221
 
102
222
  let fields_table = format_fields_table(&fields);
103
-
104
- let template = fs::read_to_string("./schemas/query-reference.tmpl")
105
- .expect("Failed to read schemas/query-reference.tmpl");
106
223
  let reference = template.replace("{fields_table}", &fields_table);
107
-
108
224
  let ref_literal = format!("r#\"{}\"#", reference);
109
- let rs_template = fs::read_to_string("./schemas/query-reference.rs.tmpl")
110
- .expect("Failed to read schemas/query-reference.rs.tmpl");
111
225
  rs_template.replace("{ref_literal}", &ref_literal)
112
226
  }
113
227
 
@@ -124,26 +238,15 @@ fn collect_fields(
124
238
  fields: &mut Vec<FieldInfo>,
125
239
  prefix: &str,
126
240
  prop: &serde_json::Value,
127
- definitions: Option<&serde_json::Map<String, serde_json::Value>>,
241
+ doc: &SchemaDoc,
128
242
  ) {
129
243
  if is_excluded(prop) {
130
244
  return;
131
245
  }
132
246
 
133
- // Resolve $ref
134
- if let Some(ref_path) = prop.get("$ref").and_then(|r| r.as_str()) {
135
- if let Some(def_name) = ref_path.strip_prefix("#/definitions/") {
136
- if let Some(resolved) = definitions.and_then(|d| d.get(def_name)) {
137
- collect_fields(fields, prefix, resolved, definitions);
138
- }
139
- }
140
- return;
141
- }
247
+ let prop = doc.resolve(prop);
142
248
 
143
- let type_str = prop
144
- .get("type")
145
- .and_then(|t| t.as_str())
146
- .unwrap_or("object");
249
+ let type_str = doc.node_type(prop);
147
250
  let description = prop
148
251
  .get("description")
149
252
  .and_then(|d| d.as_str())
@@ -152,34 +255,21 @@ fn collect_fields(
152
255
 
153
256
  match type_str {
154
257
  "object" => {
155
- if let Some(sub_props) = prop.get("properties").and_then(|p| p.as_object()) {
156
- // Emit the object node itself (useful for IS NULL checks)
157
- fields.push(FieldInfo {
158
- path: prefix.to_string(),
159
- type_name: "object".to_string(),
160
- enum_values: None,
161
- description: description.clone(),
162
- });
163
- let mut sorted_keys: Vec<&String> = sub_props.keys().collect();
164
- sorted_keys.sort();
165
- for name in sorted_keys {
166
- let sub_prop = &sub_props[name];
167
- let child_path = format!("{}.{}", prefix, name);
168
- collect_fields(fields, &child_path, sub_prop, definitions);
169
- }
170
- } else {
171
- fields.push(FieldInfo {
172
- path: prefix.to_string(),
173
- type_name: "object".to_string(),
174
- enum_values: None,
175
- description,
176
- });
258
+ fields.push(FieldInfo {
259
+ path: prefix.to_string(),
260
+ type_name: "object".to_string(),
261
+ enum_values: None,
262
+ description,
263
+ });
264
+ for (name, sub_prop) in doc.sorted_properties(prop) {
265
+ let child_path = format!("{}.{}", prefix, name);
266
+ collect_fields(fields, &child_path, sub_prop, doc);
177
267
  }
178
268
  }
179
269
  "array" => {
180
270
  let item_desc = prop
181
271
  .get("items")
182
- .and_then(|i| describe_array_items(i, definitions))
272
+ .map(|i| describe_array_items(i, doc))
183
273
  .unwrap_or_default();
184
274
  let full_desc = if description.is_empty() {
185
275
  item_desc
@@ -220,32 +310,22 @@ fn collect_fields(
220
310
  }
221
311
 
222
312
  /// Produce a short description of array item shapes (for display only).
223
- fn describe_array_items(
224
- items: &serde_json::Value,
225
- definitions: Option<&serde_json::Map<String, serde_json::Value>>,
226
- ) -> Option<String> {
227
- // Resolve $ref on items
228
- let resolved = if let Some(ref_path) = items.get("$ref").and_then(|r| r.as_str()) {
229
- ref_path
230
- .strip_prefix("#/definitions/")
231
- .and_then(|name| definitions.and_then(|d| d.get(name)))
232
- .unwrap_or(items)
233
- } else {
234
- items
235
- };
313
+ fn describe_array_items(items: &serde_json::Value, doc: &SchemaDoc) -> String {
314
+ let resolved = doc.resolve(items);
236
315
 
237
316
  match resolved.get("type").and_then(|t| t.as_str()) {
238
- Some("string") => Some("string".to_string()),
317
+ Some("string") => "string".to_string(),
239
318
  Some("object") => {
240
- if let Some(props) = resolved.get("properties").and_then(|p| p.as_object()) {
241
- let keys: Vec<&str> = props.keys().map(|k| k.as_str()).collect();
242
- Some(format!("{{{}}}", keys.join(", ")))
319
+ let props = doc.sorted_properties(resolved);
320
+ if props.is_empty() {
321
+ "object".to_string()
243
322
  } else {
244
- Some("object".to_string())
323
+ let keys: Vec<&str> = props.iter().map(|(k, _)| *k).collect();
324
+ format!("{{{}}}", keys.join(", "))
245
325
  }
246
326
  }
247
- Some(t) => Some(t.to_string()),
248
- None => None,
327
+ Some(t) => t.to_string(),
328
+ None => String::new(),
249
329
  }
250
330
  }
251
331
 
@@ -286,87 +366,56 @@ fn format_fields_table(fields: &[FieldInfo]) -> String {
286
366
 
287
367
  /// Walk the JSON Schema and find all parent paths of `updatedAt` properties.
288
368
  /// Generates a Rust source file with a constant array of those paths.
289
- fn generate_updated_at_paths(schema_content: &str) -> String {
290
- let schema: serde_json::Value =
291
- serde_json::from_str(schema_content).expect("Failed to parse schema JSON");
292
- let definitions = schema.get("definitions").and_then(|d| d.as_object());
293
-
369
+ ///
370
+ /// Uses `walk_object_tree` and simply records the parent path (all segments
371
+ /// except the last) whenever the walker visits a property named `updatedAt`.
372
+ fn generate_updated_at_paths(doc: &SchemaDoc) -> String {
294
373
  let mut parent_paths: Vec<Vec<String>> = Vec::new();
295
- if let Some(properties) = schema.get("properties").and_then(|p| p.as_object()) {
296
- if properties.contains_key("updatedAt") {
297
- parent_paths.push(vec![]);
298
- }
299
- for (name, prop) in properties {
300
- if name == "updatedAt" {
301
- continue;
302
- }
303
- collect_updated_at_parents(
304
- &mut parent_paths,
305
- std::slice::from_ref(name),
306
- prop,
307
- definitions,
308
- );
374
+
375
+ walk_object_tree(doc, &[], &doc.root, &mut |path, _node| {
376
+ if path.last().map(|s| s.as_str()) == Some("updatedAt") {
377
+ parent_paths.push(path[..path.len() - 1].to_vec());
309
378
  }
310
- }
379
+ });
311
380
 
312
381
  parent_paths.sort();
313
-
314
- let mut code = String::from(
315
- "// AUTO-GENERATED FROM JSON SCHEMA - DO NOT EDIT MANUALLY\n\
316
- // Parent paths of all `updatedAt` properties in code-unit.schema.json\n\n",
317
- );
318
-
319
- code.push_str("pub const UPDATED_AT_PARENT_PATHS: &[&[&str]] = &[\n");
320
- for path in &parent_paths {
321
- let segments: Vec<String> = path.iter().map(|s| format!("\"{}\"", s)).collect();
322
- code.push_str(&format!(" &[{}],\n", segments.join(", ")));
323
- }
324
- code.push_str("];\n");
325
-
326
- code
382
+ render_path_constant(
383
+ "UPDATED_AT_PARENT_PATHS",
384
+ "// Parent paths of all `updatedAt` properties in code-unit.schema.json",
385
+ &parent_paths,
386
+ )
327
387
  }
328
388
 
329
- /// Recursively walk schema properties. When an `updatedAt` property is found,
330
- /// record the path to its parent object (all segments except the last).
331
- fn collect_updated_at_parents(
332
- parents: &mut Vec<Vec<String>>,
333
- current_path: &[String],
334
- prop: &serde_json::Value,
335
- definitions: Option<&serde_json::Map<String, serde_json::Value>>,
336
- ) {
337
- // Resolve $ref
338
- if let Some(ref_path) = prop.get("$ref").and_then(|r| r.as_str()) {
339
- if let Some(def_name) = ref_path.strip_prefix("#/definitions/") {
340
- if let Some(resolved) = definitions.and_then(|d| d.get(def_name)) {
341
- collect_updated_at_parents(parents, current_path, resolved, definitions);
342
- }
343
- }
344
- return;
345
- }
346
-
347
- let type_str = prop
348
- .get("type")
349
- .and_then(|t| t.as_str())
350
- .unwrap_or("object");
351
-
352
- if type_str == "object" {
353
- if let Some(sub_props) = prop.get("properties").and_then(|p| p.as_object()) {
354
- if sub_props.contains_key("updatedAt") {
355
- // current_path points to the parent object that contains updatedAt
356
- parents.push(current_path.to_vec());
357
- }
358
- for (name, sub_prop) in sub_props {
359
- if name == "updatedAt" {
360
- continue;
389
+ // ── File Path Field Extraction ─────────────────────────────────────────────
390
+
391
+ /// Walk the JSON Schema and find all string properties whose description
392
+ /// starts with "Repo-root-relative". Generates a constant array of
393
+ /// segment paths.
394
+ ///
395
+ /// Uses `walk_object_tree` and inspects each visited node.
396
+ fn generate_file_path_fields(doc: &SchemaDoc) -> String {
397
+ let mut paths: Vec<Vec<String>> = Vec::new();
398
+
399
+ walk_object_tree(doc, &[], &doc.root, &mut |path, node| {
400
+ if doc.node_type(node) == "string" {
401
+ if let Some(desc) = node.get("description").and_then(|d| d.as_str()) {
402
+ if desc.starts_with("Repo-root-relative") {
403
+ paths.push(path.to_vec());
361
404
  }
362
- let mut child_path = current_path.to_vec();
363
- child_path.push(name.clone());
364
- collect_updated_at_parents(parents, &child_path, sub_prop, definitions);
365
405
  }
366
406
  }
367
- }
407
+ });
408
+
409
+ paths.sort();
410
+ render_path_constant(
411
+ "FILE_PATH_FIELDS",
412
+ "// Segment paths of all file path properties in code-unit.schema.json",
413
+ &paths,
414
+ )
368
415
  }
369
416
 
417
+ // ── Typify Code Generation ───────────────────────────────────────────────
418
+
370
419
  fn generate_with_typify(schema: &str) -> String {
371
420
  use typify::{TypeSpace, TypeSpaceSettings};
372
421
 
@@ -46,7 +46,7 @@
46
46
  "converterVersion": "3.1.0"
47
47
  },
48
48
  "aiVerification": {
49
- "status": "reviewed",
49
+ "status": "completed",
50
50
  "convertedChecksum": "defa8dbc2f2c5915579ade9fdc6aef24"
51
51
  },
52
52
  "assessment": {