jentic-openapi-validator-redocly 1.0.0a20__py3-none-any.whl → 1.0.0a22__py3-none-any.whl
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.
- jentic/apitools/openapi/validator/backends/redocly/__init__.py +125 -30
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/METADATA +3 -3
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/RECORD +7 -7
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/WHEEL +0 -0
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/entry_points.txt +0 -0
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/licenses/LICENSE +0 -0
- {jentic_openapi_validator_redocly-1.0.0a20.dist-info → jentic_openapi_validator_redocly-1.0.0a22.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
import shlex
|
|
3
4
|
import tempfile
|
|
4
5
|
from collections.abc import Sequence
|
|
@@ -23,6 +24,8 @@ from jentic.apitools.openapi.validator.core import JenticDiagnostic, ValidationR
|
|
|
23
24
|
__all__ = ["RedoclyValidatorBackend"]
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
26
29
|
rulesets_files_dir = files("jentic.apitools.openapi.validator.backends.redocly.rulesets")
|
|
27
30
|
ruleset_file = rulesets_files_dir.joinpath("redocly.yaml")
|
|
28
31
|
|
|
@@ -34,6 +37,7 @@ class RedoclyValidatorBackend(BaseValidatorBackend):
|
|
|
34
37
|
ruleset_path: str | None = None,
|
|
35
38
|
timeout: float = 600.0,
|
|
36
39
|
allowed_base_dir: str | Path | None = None,
|
|
40
|
+
max_problems: int = 1000000,
|
|
37
41
|
):
|
|
38
42
|
"""
|
|
39
43
|
Initialize the RedoclyValidatorBackend.
|
|
@@ -50,11 +54,15 @@ class RedoclyValidatorBackend(BaseValidatorBackend):
|
|
|
50
54
|
If None (default), only file extension validation is performed (no base directory
|
|
51
55
|
containment check). Extension validation ensures only .yaml, .yml, and .json files
|
|
52
56
|
are processed.
|
|
57
|
+
max_problems: Maximum number of validation problems to report (default: 1000000).
|
|
58
|
+
This limits the number of issues returned by Redocly to prevent memory/performance
|
|
59
|
+
issues when validating large documents with many errors.
|
|
53
60
|
"""
|
|
54
61
|
self.redocly_path = redocly_path
|
|
55
62
|
self.ruleset_path = ruleset_path if isinstance(ruleset_path, str) else None
|
|
56
63
|
self.timeout = timeout
|
|
57
64
|
self.allowed_base_dir = allowed_base_dir
|
|
65
|
+
self.max_problems = max_problems
|
|
58
66
|
|
|
59
67
|
@staticmethod
|
|
60
68
|
def accepts() -> Sequence[Literal["uri", "dict"]]:
|
|
@@ -133,41 +141,81 @@ class RedoclyValidatorBackend(BaseValidatorBackend):
|
|
|
133
141
|
else self.ruleset_path
|
|
134
142
|
)
|
|
135
143
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
144
|
+
# Create a temporary file to capture Redocly output
|
|
145
|
+
# This avoids stdout buffer size limitations (65536 bytes)
|
|
146
|
+
with tempfile.NamedTemporaryFile(
|
|
147
|
+
mode="w", suffix=".json", delete=False, encoding="utf-8"
|
|
148
|
+
) as tmp_output:
|
|
149
|
+
output_path = tmp_output.name
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
with as_file(ruleset_file) as default_ruleset_path:
|
|
153
|
+
# Build redocly command
|
|
154
|
+
cmd = [
|
|
155
|
+
*shlex.split(self.redocly_path),
|
|
156
|
+
"lint",
|
|
157
|
+
"--config",
|
|
158
|
+
validated_ruleset_path or default_ruleset_path,
|
|
159
|
+
"--format",
|
|
160
|
+
"json",
|
|
161
|
+
"--max-problems",
|
|
162
|
+
str(self.max_problems),
|
|
163
|
+
validated_doc_path,
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
# Open the temp output file for writing and redirect stdout to it
|
|
167
|
+
with open(output_path, "w", encoding="utf-8") as output_file:
|
|
168
|
+
result = run_subprocess(cmd, timeout=self.timeout, stdout=output_file)
|
|
169
|
+
|
|
170
|
+
if result is None:
|
|
171
|
+
raise RuntimeError("Redocly validation failed - no result returned")
|
|
172
|
+
|
|
173
|
+
# Check for execution errors
|
|
174
|
+
if result.returncode not in (0, 1):
|
|
175
|
+
# Redocly returns 0 (no errors) or 1 (validation errors found).
|
|
176
|
+
# Exit code 2 or higher indicates command-line/configuration errors.
|
|
177
|
+
stderr_msg = result.stderr.strip()
|
|
178
|
+
|
|
179
|
+
# Try custom error handling (can be overridden in subclasses)
|
|
180
|
+
custom_diagnostics = self._handle_error(
|
|
181
|
+
stderr_msg, result, validated_doc_path, target
|
|
182
|
+
)
|
|
183
|
+
if custom_diagnostics is not None:
|
|
184
|
+
return custom_diagnostics
|
|
185
|
+
|
|
186
|
+
# Default error handling
|
|
187
|
+
msg = stderr_msg or f"Redocly exited with code {result.returncode}"
|
|
188
|
+
raise RuntimeError(msg)
|
|
189
|
+
|
|
190
|
+
# Read and parse JSON output
|
|
191
|
+
try:
|
|
192
|
+
with open(output_path, mode="r", encoding="utf-8") as f:
|
|
193
|
+
output_data = json.load(f)
|
|
194
|
+
problems: list[dict] = output_data.get("problems", [])
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
if result.stderr:
|
|
197
|
+
raise RuntimeError(
|
|
198
|
+
f"Redocly did not create output file: {result.stderr.strip()}"
|
|
199
|
+
)
|
|
200
|
+
logger.warning("Redocly output file not found, returning empty diagnostics")
|
|
201
|
+
return ValidationResult(diagnostics=[])
|
|
202
|
+
except json.JSONDecodeError as e:
|
|
203
|
+
if result.stderr:
|
|
204
|
+
raise RuntimeError(
|
|
205
|
+
f"Redocly output is not valid JSON: {result.stderr.strip()}"
|
|
206
|
+
)
|
|
207
|
+
logger.warning(
|
|
208
|
+
f"Redocly output is not valid JSON: {e}, returning empty diagnostics"
|
|
209
|
+
)
|
|
210
|
+
return ValidationResult(diagnostics=[])
|
|
211
|
+
finally:
|
|
212
|
+
# Clean up temp output file
|
|
213
|
+
Path(output_path).unlink(missing_ok=True)
|
|
148
214
|
|
|
149
215
|
except SubprocessExecutionError as e:
|
|
150
216
|
# only timeout and OS errors, as run_subprocess has a default `fail_on_error = False`
|
|
151
217
|
raise e
|
|
152
218
|
|
|
153
|
-
if result is None:
|
|
154
|
-
raise RuntimeError("Redocly validation failed - no result returned")
|
|
155
|
-
|
|
156
|
-
if result.returncode not in (0, 1) or (result.stderr and not result.stdout):
|
|
157
|
-
# Redocly returns 0 (no errors) or 1 (validation errors found).
|
|
158
|
-
# Exit code 2 or higher indicates command-line/configuration errors.
|
|
159
|
-
err = result.stderr.strip() or result.stdout.strip()
|
|
160
|
-
msg = err or f"Redocly exited with code {result.returncode}"
|
|
161
|
-
raise RuntimeError(msg)
|
|
162
|
-
|
|
163
|
-
output = result.stdout
|
|
164
|
-
|
|
165
|
-
try:
|
|
166
|
-
problems: list[dict] = json.loads(output).get("problems", [])
|
|
167
|
-
except json.JSONDecodeError:
|
|
168
|
-
# If output isn't JSON (maybe redocly error format), handle gracefully
|
|
169
|
-
return ValidationResult(diagnostics=[])
|
|
170
|
-
|
|
171
219
|
diagnostics: list[JenticDiagnostic] = []
|
|
172
220
|
for problem in problems:
|
|
173
221
|
locations = []
|
|
@@ -219,3 +267,50 @@ class RedoclyValidatorBackend(BaseValidatorBackend):
|
|
|
219
267
|
return self._validate_uri(
|
|
220
268
|
Path(temp_file.name).as_uri(), base_url=base_url, target=target
|
|
221
269
|
)
|
|
270
|
+
|
|
271
|
+
def _handle_error(
|
|
272
|
+
self,
|
|
273
|
+
stderr_msg: str,
|
|
274
|
+
result: SubprocessExecutionResult,
|
|
275
|
+
document_path: str,
|
|
276
|
+
target: str | None = None,
|
|
277
|
+
) -> ValidationResult | None:
|
|
278
|
+
"""Handle custom error cases from Redocly execution.
|
|
279
|
+
|
|
280
|
+
This is an extension point for subclasses to provide custom error handling.
|
|
281
|
+
By default, returns None to proceed with standard error handling (raising RuntimeError).
|
|
282
|
+
|
|
283
|
+
If this method returns a ValidationResult, that result will be returned to the caller.
|
|
284
|
+
If this method returns None, the default error handling will proceed (raising RuntimeError).
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
stderr_msg: The stderr output from Redocly
|
|
288
|
+
result: The subprocess execution result from Redocly
|
|
289
|
+
document_path: The path or URL being validated
|
|
290
|
+
target: Optional target identifier for validation context
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
ValidationResult if the error was handled, None to proceed with default handling
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
Override this method to handle specific errors gracefully:
|
|
297
|
+
|
|
298
|
+
def _handle_error(self, stderr_msg, result, document_path, target):
|
|
299
|
+
# Handle fetch errors (403, 404, etc.) by returning diagnostics
|
|
300
|
+
if "ENOTFOUND" in stderr_msg or "ETIMEDOUT" in stderr_msg:
|
|
301
|
+
diagnostic = JenticDiagnostic(
|
|
302
|
+
range=Range(start=Position(line=0, character=0),
|
|
303
|
+
end=Position(line=0, character=0)),
|
|
304
|
+
message=f"Could not fetch document: {document_path}",
|
|
305
|
+
severity=DiagnosticSeverity.Error,
|
|
306
|
+
code="document-fetch-error",
|
|
307
|
+
source="redocly-validator",
|
|
308
|
+
)
|
|
309
|
+
diagnostic.set_target(target)
|
|
310
|
+
return ValidationResult(diagnostics=[diagnostic])
|
|
311
|
+
|
|
312
|
+
# Fall back to default behavior
|
|
313
|
+
return super()._handle_error(stderr_msg, result, document_path, target)
|
|
314
|
+
"""
|
|
315
|
+
# Return None to proceed with default error handling (raising RuntimeError)
|
|
316
|
+
return None
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jentic-openapi-validator-redocly
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a22
|
|
4
4
|
Summary: Jentic OpenAPI Redocly Validator Backend
|
|
5
5
|
Author: Jentic
|
|
6
6
|
Author-email: Jentic <hello@jentic.com>
|
|
7
7
|
License-Expression: Apache-2.0
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
License-File: NOTICE
|
|
10
|
-
Requires-Dist: jentic-openapi-common~=1.0.
|
|
11
|
-
Requires-Dist: jentic-openapi-validator~=1.0.
|
|
10
|
+
Requires-Dist: jentic-openapi-common~=1.0.0a22
|
|
11
|
+
Requires-Dist: jentic-openapi-validator~=1.0.0a22
|
|
12
12
|
Requires-Dist: lsprotocol~=2025.0.0
|
|
13
13
|
Requires-Dist: jsonpointer~=3.0.0
|
|
14
14
|
Requires-Python: >=3.11
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
jentic/apitools/openapi/validator/backends/redocly/__init__.py,sha256=
|
|
1
|
+
jentic/apitools/openapi/validator/backends/redocly/__init__.py,sha256=HJWMFUCDf5YDWz1zFLoHLg4Hn2mL36E7FmSgYboVYSk,14053
|
|
2
2
|
jentic/apitools/openapi/validator/backends/redocly/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
jentic/apitools/openapi/validator/backends/redocly/rulesets/redocly.yaml,sha256=4YWOtk4-uXmH7Im58adZJr-VR1mJG2bFaOR1DdF1cM8,30
|
|
4
|
-
jentic_openapi_validator_redocly-1.0.
|
|
5
|
-
jentic_openapi_validator_redocly-1.0.
|
|
6
|
-
jentic_openapi_validator_redocly-1.0.
|
|
7
|
-
jentic_openapi_validator_redocly-1.0.
|
|
8
|
-
jentic_openapi_validator_redocly-1.0.
|
|
9
|
-
jentic_openapi_validator_redocly-1.0.
|
|
4
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
|
|
5
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/licenses/NOTICE,sha256=pAOGW-rGw9KNc2cuuLWZkfx0GSTV4TicbgBKZSLPMIs,168
|
|
6
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
7
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/entry_points.txt,sha256=wOFM-VNEwo-czDoLgOTePKT_bi0WXB9rDsb204dcFjs,131
|
|
8
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/METADATA,sha256=QJXE0y1Rif5AkunoLooX-IO-NyamXs0H02thm1oKObI,9109
|
|
9
|
+
jentic_openapi_validator_redocly-1.0.0a22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|