Skip to content

Commit 27f7fdc

Browse files
committed
improve msg for validation
1 parent 6e55a28 commit 27f7fdc

3 files changed

Lines changed: 108 additions & 28 deletions

File tree

src/sphinx_codelinks/cmd.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import deque
2+
import json
23
from os import linesep
34
from pathlib import Path
45
import tomllib
@@ -14,7 +15,7 @@
1415
generate_project_configs,
1516
)
1617
from sphinx_codelinks.logger import logger
17-
from sphinx_codelinks.needextend_write import convert_marked_content
18+
from sphinx_codelinks.needextend_write import MarkedObjType, convert_marked_content
1819
from sphinx_codelinks.source_discover.config import (
1920
CommentType,
2021
SourceDiscoverConfig,
@@ -268,7 +269,24 @@ def write_rst( # noqa: PLR0913 # for CLI, so it takes as many as it requires
268269
) -> None:
269270
"""Generate needextend.rst from the extracted obj in JSON."""
270271
logger.configure(verbose, quiet)
271-
convert_marked_content(jsonpath, outpath, remote_url_field, title)
272+
try:
273+
with jsonpath.open("r") as f:
274+
marked_content = json.load(f)
275+
except Exception as e:
276+
raise typer.BadParameter(
277+
f"Failed to load marked content from {jsonpath}: {e}"
278+
) from e
279+
280+
marked_objs: list[MarkedObjType] = [
281+
obj for objs in marked_content.values() for obj in objs
282+
]
283+
284+
errors = convert_marked_content(marked_objs, outpath, remote_url_field, title)
285+
if errors:
286+
raise typer.BadParameter(
287+
f"Errors occurred during conversion: {linesep.join(errors)}"
288+
)
289+
typer.echo(f"Generated {outpath}")
272290

273291

274292
def load_config_from_toml(toml_file: Path) -> CodeLinksConfigType:

src/sphinx_codelinks/needextend_write.py

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from collections import deque
44
from dataclasses import MISSING, dataclass, field, fields
5-
import json
65
from os import linesep
76
from pathlib import Path
87
from string import Template
@@ -11,7 +10,6 @@
1110
from jsonschema import ValidationError, validate
1211

1312
from sphinx_codelinks.analyse.models import MarkedContentType, SourceMap
14-
from sphinx_codelinks.logger import logger
1513

1614
NEEDEXTEND_TEMPLATE = Template(""".. needextend:: $need_id
1715
:$remote_url_field: $remote_url
@@ -164,21 +162,13 @@ class MarkedObjType(TypedDict):
164162

165163

166164
def convert_marked_content(
167-
jsonpath: Path,
165+
marked_objs: list[MarkedObjType],
168166
outpath: Path,
169167
remote_url_field: str = "remote-url",
170168
title: str | None = None,
171-
) -> None:
169+
) -> list[str] | None:
172170
"""Convert marked objects extracted by anaylse CLI to needextend in RST"""
173-
with jsonpath.open("r") as f:
174-
marked_content = json.load(f)
175-
176-
warnings = []
177-
178-
# flatten objects
179-
marked_objs: list[MarkedObjType] = [
180-
obj for objs in marked_content.values() for obj in objs
181-
]
171+
errors = []
182172

183173
intersted_objs = [
184174
obj
@@ -187,18 +177,20 @@ def convert_marked_content(
187177
]
188178

189179
for obj in intersted_objs:
190-
schema = MarkedContentSchema(**obj)
191-
obj_warnings: deque[str] = deque()
192-
obj_warnings.extend(schema.check_schema())
193-
obj_warnings.extend(schema.check_conditional_required_fields())
194-
if obj_warnings:
195-
obj_warnings.appendleft(f"{obj} has the following warnings:")
196-
warnings.extend(list(obj_warnings))
197-
198-
if warnings:
199-
logger.warning(
200-
f"Marked objects have the following warnings: {linesep.join(warnings)}"
201-
)
180+
try:
181+
schema = MarkedContentSchema(**obj)
182+
except TypeError as e:
183+
errors.append(str(e))
184+
continue
185+
obj_errors: deque[str] = deque()
186+
obj_errors.extend(schema.check_schema())
187+
obj_errors.extend(schema.check_conditional_required_fields())
188+
if obj_errors:
189+
obj_errors.appendleft(f"{obj} has the following errors:")
190+
errors.extend(list(obj_errors))
191+
192+
if errors:
193+
return errors
202194

203195
needextend_texts: list[str] = []
204196
if title:
@@ -217,4 +209,4 @@ def convert_marked_content(
217209
with outpath.open("w") as f:
218210
f.writelines(needextend_texts)
219211

220-
logger.info(f"Generated {outpath}")
212+
return None

tests/test_cmd.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,73 @@ def test_analyse_project_negative(projects, output_lines, tmp_path: Path) -> Non
212212
assert result.exit_code != 0
213213
for line in output_lines:
214214
assert line in result.stdout
215+
216+
217+
def test_write_rst_invalid_json(tmp_path: Path) -> None:
218+
json_obj = {
219+
"whatever": "json",
220+
"not": "expected",
221+
}
222+
json_text = json.dumps(json_obj)
223+
json_text = json_text.replace(",", "")
224+
jsonpath = tmp_path / "invalid_json.json"
225+
with jsonpath.open("w") as f:
226+
f.write(json_text)
227+
228+
options = [
229+
"write",
230+
"rst",
231+
str(jsonpath),
232+
"--outpath",
233+
str(tmp_path / "out.rst"),
234+
]
235+
result = runner.invoke(app, options)
236+
237+
assert result.exit_code != 0
238+
assert "Expecting ',' delimiter: line 1 column 21 (char 20)" in result.stdout
239+
240+
241+
@pytest.mark.parametrize(
242+
("json_objs", "output_lines"),
243+
[
244+
(
245+
[
246+
{
247+
"filepath": 123,
248+
"remote_url": "https://github.com/useblocks/sphinx-codelinks/blob/951e40e7845f06d5cfc4ca20ebb984308fdaf985/tests/data/marked_rst/dummy_1.cpp#L4",
249+
"source_map": {
250+
"start": {"row": 3, "column": 8},
251+
"end": {"row": 3, "column": 61},
252+
},
253+
"tagged_scope": "void dummy_func1(){\n //...\n }",
254+
"rst": ".. impl:: implement dummy function 1\n :id: IMPL_71\n",
255+
"type": "need-id-refs",
256+
}
257+
],
258+
[
259+
"Invalid value: Errors occurred",
260+
"Schema validation error in field 'filepath': 123 is not of type 'string'",
261+
"Marker is required for marked content of type 'need_id_refs' ",
262+
"Need id refs are required for marked content of type 'need_id_refs'",
263+
],
264+
),
265+
],
266+
)
267+
def test_write_rst_negative(json_objs: list[dict], output_lines, tmp_path) -> None:
268+
to_dump = {"project_1": json_objs}
269+
jsonpath = Path("invalid_objs.json")
270+
with jsonpath.open("w") as f:
271+
json.dump(to_dump, f)
272+
outpath = tmp_path / "needextend.rst"
273+
options = [
274+
"write",
275+
"rst",
276+
str(jsonpath),
277+
"--outpath",
278+
str(outpath),
279+
]
280+
result = runner.invoke(app, options)
281+
282+
assert result.exit_code != 0
283+
for line in output_lines:
284+
assert line in result.stdout

0 commit comments

Comments
 (0)