Skip to content

Commit

Permalink
Move origin_type from OpenAPI to AtomSlice, jmespath improvements (#28)
Browse files Browse the repository at this point in the history
* Standardize jmespath projections to snake case, filter calls.

Signed-off-by: Caroline Russell <[email protected]>

* Move origin_type to AtomSlice class for reuse.

Signed-off-by: Caroline Russell <[email protected]>

---------

Signed-off-by: Caroline Russell <[email protected]>
  • Loading branch information
cerrussell authored Mar 2, 2024
1 parent 44d1591 commit ce46fa7
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 40 deletions.
27 changes: 13 additions & 14 deletions atom_tools/lib/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class OpenAPI:
Attributes:
usages (AtomSlice): The usage slice.
origin_type (str): The origin type.
openapi_version (str): The OpenAPI version.
title (str): The title for the OpenAPI document
file_endpoint_map (dict): Stores the originating filename for endpoints
Expand Down Expand Up @@ -64,8 +63,7 @@ def __init__(
origin_type: str,
usages: str,
) -> None:
self.usages: AtomSlice = AtomSlice(usages)
self.origin_type = origin_type
self.usages: AtomSlice = AtomSlice(usages, origin_type)
self.openapi_version = dest_format.replace('openapi', '')
self.title = f'OpenAPI Specification for {Path(usages).parent.stem}'
self.file_endpoint_map: Dict = {}
Expand Down Expand Up @@ -144,14 +142,14 @@ def process_methods(self) -> Dict[str, List[str]]:
Create a dictionary of full names and their corresponding methods.
"""
method_map = self._process_methods_helper(
'objectSlices[].{fullName: fullName, resolvedMethods: usages[].*.resolvedMethod[]}')
'objectSlices[].{full_name: fullName, resolved_methods: usages[].*.resolvedMethod[]}')

calls = self._process_methods_helper(
'objectSlices[].{fullName: fullName, resolvedMethods: usages[].*[][?resolvedMethod].'
'resolvedMethod[]}')
'objectSlices[].{full_name: fullName, resolved_methods: usages[].*[?resolvedMethod][]'
'[].resolvedMethod[]}')

user_defined_types = self._process_methods_helper(
'userDefinedTypes[].{fullName: name, resolvedMethods: fields[].name}')
'userDefinedTypes[].{full_name: name, resolved_methods: fields[].name}')

for key, value in calls.items():
if method_map.get(key):
Expand Down Expand Up @@ -199,7 +197,7 @@ def _query_calls_helper(self, full_name: str) -> List[Dict]:
Returns:
list: The result of searching for the calls pattern in the usages.
"""
pattern = f'objectSlices[?fullName==`{json.dumps(full_name)}`].usages[].*[][]'
pattern = f'objectSlices[?fullName==`{json.dumps(full_name)}`].usages[].*[?callName][][]'
compiled_pattern = jmespath.compile(pattern)
return compiled_pattern.search(self.usages.content)

Expand Down Expand Up @@ -297,13 +295,13 @@ def _process_methods_helper(self, pattern: str) -> Dict[str, Any]:
dict_resolved_pattern = jmespath.compile(pattern)
result = [
i for i in dict_resolved_pattern.search(self.usages.content)
if i.get('resolvedMethods')
if i.get('resolved_methods')
]

resolved: Dict = {}
for r in result:
full_name = r['fullName']
methods = r['resolvedMethods']
full_name = r['full_name']
methods = r['resolved_methods']
resolved.setdefault(full_name, {'resolved_methods': []})[
'resolved_methods'].extend(methods)

Expand Down Expand Up @@ -406,9 +404,9 @@ def _paths_object_helper(
def _extract_params(self, ep: str) -> Tuple[str, bool, List]:
tmp_params: List = []
py_special_case = False
if self.origin_type in ('js', 'ts', 'javascript', 'typescript'):
if self.usages.origin_type in ('js', 'ts', 'javascript', 'typescript'):
ep = js_helper(ep)
elif self.origin_type in ('py', 'python'):
elif self.usages.origin_type in ('py', 'python'):
ep, tmp_params = py_helper(ep)
py_special_case = True
return ep, py_special_case, tmp_params
Expand Down Expand Up @@ -473,6 +471,7 @@ def calls_to_params(self, ep: str, orig_ep: str, call: Dict | None) -> Dict[str,
Args:
call (dict): The call object
ep (str): The endpoint
orig_ep (str): The original endpoint
Returns:
dict: The operation object
"""
Expand Down Expand Up @@ -542,7 +541,7 @@ def _filter_matches(self, matches: List[str], code: str) -> List[str]:
"""
filtered_matches: List[str] = []

match self.origin_type:
match self.usages.origin_type:
case 'java' | 'jar':
if not (
code.startswith('@')
Expand Down
28 changes: 15 additions & 13 deletions atom_tools/lib/slices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import json
import logging
import re

logger = logging.getLogger(__name__)

Expand All @@ -18,14 +17,16 @@ class AtomSlice:
Attributes:
content (dict): The dictionary loaded from the usages JSON file.
slice_type (str): The type of slice.
origin_type (str): The originating language.
Methods:
import_slice: Imports a slice from a JSON file.
"""

def __init__(self, filename: str) -> None:
self.content = self.import_slice(filename)
self.endpoints_regex = re.compile(r'[\'"](\S*?)[\'"]', re.IGNORECASE)
def __init__(self, filename: str, origin_type: str) -> None:
self.content, self.slice_type = self.import_slice(filename)
self.origin_type = origin_type

@staticmethod
def import_slice(filename):
Expand All @@ -47,20 +48,21 @@ def import_slice(filename):
If the JSON file is not a valid usage slice, a warning is logged.
"""
if not filename:
logger.warning('No filename specified.')
return {}
try:
with open(filename, 'r', encoding='utf-8') as f:
content = json.load(f)
if content.get('objectSlices'):
return content
return content, 'usages'
if content.get('reachables'):
return content, 'reachables'
except (json.decoder.JSONDecodeError, UnicodeDecodeError):
logger.warning(
f'Failed to load usages slice: {filename}\nPlease check '
f'that you specified a valid json file.')
f'Failed to load usages slice: {filename}\nPlease check that you specified a valid'
f' json file.'
)
except FileNotFoundError:
logger.warning(
f'Failed to locate the usages slice file in the location '
f'specified: {filename}')

logger.warning('Failed to load usages slice.')
return {}
logger.warning(f'Failed to locate the following slice file: {filename}')
logger.warning('Slice type not recognized.')
return {}, 'unknown'
23 changes: 10 additions & 13 deletions test/test_slices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,48 @@

@fixture
def java_usages_1():
return AtomSlice('test/data/java-piggymetrics-usages.json')
return AtomSlice('test/data/java-piggymetrics-usages.json', 'java')


@fixture
def java_usages_2():
return AtomSlice('test/data/java-sec-code-usages.json')
return AtomSlice('test/data/java-sec-code-usages.json', 'java')


@fixture
def js_usages_1():
return AtomSlice('test/data/js-juiceshop-usages.json')
return AtomSlice('test/data/js-juiceshop-usages.json', 'js')


@fixture
def js_usages_2():
return AtomSlice('test/data/js-nodegoat-usages.json')
return AtomSlice('test/data/js-nodegoat-usages.json', 'js')


@fixture
def py_usages_1():
return AtomSlice('test/data/py-depscan-usages.json')
return AtomSlice('test/data/py-depscan-usages.json', 'js')


@fixture
def py_usages_2():
return AtomSlice('test/data/py-tornado-usages.json')
return AtomSlice('test/data/py-tornado-usages.json', 'js')


def test_usages_class(
java_usages_1,
java_usages_2,
):
usages = AtomSlice('test/data/java-piggymetrics-usages.json')
usages = AtomSlice('test/data/java-piggymetrics-usages.json', 'java')
assert usages.content is not None

usages = AtomSlice('test/data/java-sec-code-usages.json')
usages = AtomSlice('test/data/java-sec-code-usages.json', 'java')
assert usages.content is not None


def test_import_slices(js_usages_1):
# Test nonexistent file
assert AtomSlice('test/data/js-tornado-usages.json').content == {}
assert AtomSlice('test/data/js-tornado-usages.json', 'js').content == {}

# Test invalid JSON file
assert AtomSlice('test/data/invalid.json').content == {}

# Valid JSON but reachables not usages
assert AtomSlice('test/data/js-juiceshop-reachables.json').content == {}
assert AtomSlice('test/data/invalid.json', 'js').content == {}

0 comments on commit ce46fa7

Please sign in to comment.