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.
@@ -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
- with as_file(ruleset_file) as default_ruleset_path:
137
- # Build redocly command
138
- cmd = [
139
- *shlex.split(self.redocly_path),
140
- "lint",
141
- "--config",
142
- validated_ruleset_path or default_ruleset_path,
143
- "--format",
144
- "json",
145
- validated_doc_path,
146
- ]
147
- result = run_subprocess(cmd, timeout=self.timeout)
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.0a20
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.0a20
11
- Requires-Dist: jentic-openapi-validator~=1.0.0a20
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=dJl82sdUd92NvTdfTj9QcUc9IrSE1IauWsI2p73m4XM,9395
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.0a20.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
5
- jentic_openapi_validator_redocly-1.0.0a20.dist-info/licenses/NOTICE,sha256=pAOGW-rGw9KNc2cuuLWZkfx0GSTV4TicbgBKZSLPMIs,168
6
- jentic_openapi_validator_redocly-1.0.0a20.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
7
- jentic_openapi_validator_redocly-1.0.0a20.dist-info/entry_points.txt,sha256=wOFM-VNEwo-czDoLgOTePKT_bi0WXB9rDsb204dcFjs,131
8
- jentic_openapi_validator_redocly-1.0.0a20.dist-info/METADATA,sha256=4vnOiNYObqqwtCTRLfiJMMZZmBdf1r9I76KIHHK3x6w,9109
9
- jentic_openapi_validator_redocly-1.0.0a20.dist-info/RECORD,,
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,,