atom-tools 0.8.2__tar.gz → 0.8.4__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 (39) hide show
  1. {atom_tools-0.8.2/atom_tools.egg-info → atom_tools-0.8.4}/PKG-INFO +1 -1
  2. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/__init__.py +1 -1
  3. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/converter.py +100 -1
  4. {atom_tools-0.8.2 → atom_tools-0.8.4/atom_tools.egg-info}/PKG-INFO +1 -1
  5. {atom_tools-0.8.2 → atom_tools-0.8.4}/pyproject.toml +1 -1
  6. {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_converter.py +42 -6
  7. {atom_tools-0.8.2 → atom_tools-0.8.4}/LICENSE +0 -0
  8. {atom_tools-0.8.2 → atom_tools-0.8.4}/MANIFEST.in +0 -0
  9. {atom_tools-0.8.2 → atom_tools-0.8.4}/README.md +0 -0
  10. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/__init__.py +0 -0
  11. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/application.py +0 -0
  12. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/command_loader.py +0 -0
  13. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/__init__.py +0 -0
  14. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/check_reachable.py +0 -0
  15. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/command.py +0 -0
  16. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/convert.py +0 -0
  17. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/filter.py +0 -0
  18. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/query_endpoints.py +0 -0
  19. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/commands/validate_lines.py +0 -0
  20. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/cli/logging_config.py +0 -0
  21. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/__init__.py +0 -0
  22. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/filtering.py +0 -0
  23. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/regex_utils.py +0 -0
  24. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/ruby_converter.py +0 -0
  25. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/ruby_semantics.py +0 -0
  26. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/scala_converter.py +0 -0
  27. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/slices.py +0 -0
  28. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/utils.py +0 -0
  29. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools/lib/validator.py +0 -0
  30. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/SOURCES.txt +0 -0
  31. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/dependency_links.txt +0 -0
  32. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/entry_points.txt +0 -0
  33. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/requires.txt +0 -0
  34. {atom_tools-0.8.2 → atom_tools-0.8.4}/atom_tools.egg-info/top_level.txt +0 -0
  35. {atom_tools-0.8.2 → atom_tools-0.8.4}/setup.cfg +0 -0
  36. {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_filtering.py +0 -0
  37. {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_ruby_semantics.py +0 -0
  38. {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_slices.py +0 -0
  39. {atom_tools-0.8.2 → atom_tools-0.8.4}/test/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atom-tools
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Collection of tools for use with AppThreat/atom.
5
5
  Author-email: Caroline Russell <caroline@appthreat.dev>
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
1
  """
2
2
  A cli, classes and functions for converting an atom slice to a different format
3
3
  """
4
- __version__ = '0.8.2'
4
+ __version__ = '0.8.4'
@@ -71,7 +71,11 @@ class OpenAPI:
71
71
  self.target_line_nums = self._identify_target_line_nums(methods)
72
72
  self.file_endpoint_map = self.create_file_to_method_dict(methods)
73
73
  methods = self._process_calls(methods)
74
- return self.populate_endpoints(methods)
74
+ paths = self.populate_endpoints(methods)
75
+ udt_methods = self._extract_methods_from_udt()
76
+ if udt_methods:
77
+ paths = merge_path_objects(paths, udt_methods)
78
+ return paths
75
79
 
76
80
  def create_file_to_method_dict(self, method_map: Dict[str, Any]) -> Dict[str, List]:
77
81
  """
@@ -149,8 +153,16 @@ class OpenAPI:
149
153
  dict: A new method map containing endpoints.
150
154
  """
151
155
  new_method_map: Dict = {'file_names': {}}
156
+ class_prefixes = self._get_java_class_prefixes()
152
157
  for file_name, resolved_methods in method_map.items():
153
158
  if new_resolved := self._process_resolved_methods(resolved_methods):
159
+ prefix = class_prefixes.get(file_name, '')
160
+ if prefix:
161
+ for method_key in new_resolved:
162
+ new_resolved[method_key]['endpoints'] = [
163
+ prefix.rstrip('/') + ep
164
+ for ep in new_resolved[method_key]['endpoints']
165
+ ]
154
166
  new_method_map['file_names'][file_name] = {'resolved_methods': new_resolved}
155
167
  return new_method_map
156
168
 
@@ -437,6 +449,93 @@ class OpenAPI:
437
449
  self.params[new_endpoint] = params
438
450
  return new_endpoint.replace("/{}", "/")
439
451
 
452
+ def _extract_methods_from_udt(self) -> Dict[str, Any]:
453
+ """
454
+ Extract HTTP method and endpoint pairings from userDefinedTypes fields.
455
+
456
+ Handles custom router patterns where HTTP method and path are both
457
+ fields in the same userDefinedType entry (e.g., registerRoute('get', '/')).
458
+
459
+ Returns:
460
+ dict: A paths object mapping endpoints to their HTTP method operations.
461
+ """
462
+ ops = {'get', 'put', 'post', 'delete', 'options', 'head', 'patch'}
463
+ pattern = jmespath.compile(
464
+ 'userDefinedTypes[].{file_name: fileName, line_number: lineNumber,'
465
+ ' fields: fields[].name}'
466
+ )
467
+ result = pattern.search(self.usages.content)
468
+ if not result:
469
+ return {}
470
+ paths_object: Dict = {}
471
+ for entry in result:
472
+ fields = entry.get('fields') or []
473
+ file_name = entry.get('file_name', '')
474
+ line_number = entry.get('line_number')
475
+ found_ops = [f.strip('"').strip("'") for f in fields
476
+ if f and f.strip('"').strip("'").lower() in ops]
477
+ found_paths = [f.strip('"').strip("'") for f in fields
478
+ if f and '/' in f]
479
+ if not found_ops or not found_paths:
480
+ continue
481
+ for ep in found_paths:
482
+ ep = self._parse_path_regexes(ep)
483
+ path_item: Dict = {}
484
+ for op in found_ops:
485
+ path_item[op.lower()] = {'responses': {}}
486
+ if line_number and file_name:
487
+ ln_entry = create_ln_entries(
488
+ file_name, [line_number], None
489
+ )
490
+ path_item.update(ln_entry)
491
+ if paths_object.get(ep):
492
+ paths_object[ep] = merge_path_objects(
493
+ paths_object[ep], path_item
494
+ )
495
+ else:
496
+ paths_object[ep] = path_item
497
+ return paths_object
498
+
499
+ def _get_java_class_prefixes(self) -> Dict[str, str]:
500
+ """
501
+ Scans objectSlices for class-level mapping annotations in
502
+ Java/Spring Boot and returns a map of {fileName: prefix_path}.
503
+
504
+ In Spring Boot, a @RequestMapping (or any mapping variant like
505
+ @GetMapping, @PostMapping, etc.) on the class itself acts as a URL
506
+ prefix for all methods in that class. These entries appear in
507
+ objectSlices with an empty usages list and the annotation text in
508
+ the 'code' field.
509
+
510
+ Returns:
511
+ dict: A mapping of file name to its class-level URL path prefix.
512
+ Empty dict for non-Java origins.
513
+ """
514
+ if self.usages.origin_type not in ('java', 'jar'):
515
+ return {}
516
+ class_mapping_annotations = (
517
+ '@RequestMapping', '@GetMapping', '@PostMapping',
518
+ '@PutMapping', '@DeleteMapping', '@PatchMapping',
519
+ )
520
+ prefixes: Dict[str, str] = {}
521
+ object_slices = self.usages.content.get('objectSlices', [])
522
+ for entry in object_slices:
523
+ code = entry.get('code', '') or ''
524
+ file_name = entry.get('fileName', '') or ''
525
+ usages = entry.get('usages', [])
526
+ # Class-level annotations appear with empty usages and the
527
+ # annotation text in 'code'. We check for all Spring Boot
528
+ # mapping variants that carry a URL path (contains '/').
529
+ if (usages == []
530
+ and code.startswith(class_mapping_annotations)
531
+ and '/' in code
532
+ and file_name
533
+ and file_name not in prefixes):
534
+ extracted = self._extract_endpoints(code)
535
+ if extracted:
536
+ prefixes[file_name] = extracted[0]
537
+ return prefixes
538
+
440
539
  def _process_calls(self, method_map: Dict) -> Dict[str, Any]:
441
540
  """
442
541
  Process calls and return a new method map.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atom-tools
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Collection of tools for use with AppThreat/atom.
5
5
  Author-email: Caroline Russell <caroline@appthreat.dev>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atom-tools"
3
- version = "0.8.2"
3
+ version = "0.8.4"
4
4
  description = "Collection of tools for use with AppThreat/atom."
5
5
  authors = [
6
6
  { name = "Caroline Russell", email = "caroline@appthreat.dev" },
@@ -135,15 +135,15 @@ def test_endpoints_to_openapi(java_usages_1):
135
135
  'put': {'responses': {}},
136
136
  'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [25,
137
137
  30],
138
- 'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22],
139
- 'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [21,
140
- 26],
141
138
  'statistics-service/src/main/java/com/piggymetrics/statistics/controller/StatisticsController.java': [20]},
142
- 'target': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [30],
143
- 'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22],
144
- 'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [26]}}},
139
+ 'target': {'account-service/src/main/java/com/piggymetrics/account/controller/AccountController.java': [30]}}},
145
140
  '/latest': {'get': {'responses': {}},
146
141
  'x-atom-usages': {'call': {'statistics-service/src/main/java/com/piggymetrics/statistics/client/ExchangeRatesClient.java': [13]}}},
142
+ '/recipients/current': {'get': {'responses': {}},
143
+ 'put': {'responses': {}},
144
+ 'x-atom-usages': {'call': {'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [21,
145
+ 26]},
146
+ 'target': {'notification-service/src/main/java/com/piggymetrics/notification/controller/RecipientController.java': [26]}}},
147
147
  '/statistics/{accountName}': {'parameters': [{'in': 'path',
148
148
  'name': 'accountName',
149
149
  'required': True}],
@@ -151,6 +151,9 @@ def test_endpoints_to_openapi(java_usages_1):
151
151
  'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/client/StatisticsServiceClient.java': [13]}}},
152
152
  '/uaa/users': {'post': {'responses': {}},
153
153
  'x-atom-usages': {'call': {'account-service/src/main/java/com/piggymetrics/account/client/AuthServiceClient.java': [12]}}},
154
+ '/users/current': {'get': {'responses': {}},
155
+ 'x-atom-usages': {'call': {'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22]},
156
+ 'target': {'auth-service/src/main/java/com/piggymetrics/auth/controller/UserController.java': [22]}}},
154
157
  '/{accountName}': {'get': {'responses': {}},
155
158
  'parameters': [{'in': 'path',
156
159
  'name': 'accountName',
@@ -1356,3 +1359,36 @@ def test_rb(rb_usages_1):
1356
1359
  def test_rb_with_endpoints(rb_usages_2):
1357
1360
  result = ruby_convert(rb_usages_2.usages)
1358
1361
  assert result
1362
+
1363
+
1364
+ @pytest.fixture
1365
+ def ts_usages_1():
1366
+ return OpenAPI('openapi3.0.1', 'typescript',
1367
+ 'test/data/ts-custom-router-usages.json')
1368
+
1369
+
1370
+ def test_ts_custom_router(ts_usages_1):
1371
+ result = ts_usages_1.convert_usages()
1372
+ result = sort_openapi_result(result)
1373
+ assert result
1374
+ # Verify HTTP methods are present for endpoints with registerRoute pattern
1375
+ assert '/' in result
1376
+ assert 'get' in result['/']
1377
+ assert 'post' in result['/']
1378
+ assert '/complex' in result
1379
+ assert 'get' in result['/complex']
1380
+
1381
+
1382
+ def test_ts_custom_router_openapi(ts_usages_1):
1383
+ result = ts_usages_1.endpoints_to_openapi()
1384
+ result = sort_openapi_result(result)
1385
+ paths = result['paths']
1386
+ # Check that methods from registerRoute are populated
1387
+ assert 'get' in paths['/']
1388
+ assert 'post' in paths['/']
1389
+ assert 'get' in paths['/complex']
1390
+ # Verify the standard paths also exist
1391
+ assert '/api' in paths
1392
+ assert '/items' in paths
1393
+ assert '/users' in paths
1394
+ assert '/render' in paths
File without changes
File without changes
File without changes
File without changes