-
Notifications
You must be signed in to change notification settings - Fork 5
Split OpenAPI.json into digest-able and editable YML files. Including OpenAPI lint and build workflow #145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Document OpenAPI workflow and add CI linting for modular API specs and examples
📍Where to StartStart with the OpenAPI root document at index.yaml to see paths and components, then review the bundling and validation logic in openapi_bundle.py and the CI workflow in openapi-validation.yml. Changes since #145 opened
📊 Macroscope summarized a180701. 4 files reviewed, 21 issues evaluated, 18 issues filtered, 1 comment posted🗂️ Filtered Issuesscripts/pre-commit.sh — 0 comments posted, 3 evaluated, 3 filtered
scripts/split_openapi.py — 0 comments posted, 5 evaluated, 5 filtered
tools/openapi_bundle.py — 1 comment posted, 7 evaluated, 5 filtered
tools/openapi_yaml.py — 0 comments posted, 6 evaluated, 5 filtered
|
| fragment = fragment.lstrip("/") | ||
| for part in fragment.split("/"): | ||
| if part: | ||
| if isinstance(resolved, dict) and part in resolved: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fragment resolution in _resolve and _resolve doesn’t follow JSON Pointer rules: tokens aren’t unescaped (~1→/, ~0→~), numeric path segments aren’t supported as list indexes, and empty tokens are skipped. Update _resolve to decode tokens per RFC 6901, treat numeric segments as array indices with bounds checking, and preserve empty keys so valid $ref fragments don’t fail.
- if isinstance(resolved, dict) and part in resolved:
- resolved = resolved[part]
- else:
- raise BundleError(f"Fragment '{fragment}' not found in {ref}")
+ if isinstance(resolved, dict) and part in resolved:
+ resolved = resolved[part]
+ elif isinstance(resolved, list):
+ try:
+ idx = int(part)
+ except ValueError:
+ raise BundleError(f"Fragment '{fragment}' not found in {ref}")
+ if 0 <= idx < len(resolved):
+ resolved = resolved[idx]
+ else:
+ raise BundleError(f"Fragment '{fragment}' not found in {ref}")
+ else:
+ raise BundleError(f"Fragment '{fragment}' not found in {ref}")🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| return True | ||
| if value.lower() in {"null", "true", "false"}: | ||
| return True | ||
| if value and value[0] in "-?:[]{}#&,*!|>'\"%@`": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Numeric-like strings (e.g., "200", "0", "-1", "1.0", "1e3") are not quoted by _needs_quotes. In YAML 1.2, these plain scalars are parsed as numbers, so emitting them unquoted as mapping keys (like OpenAPI status codes) can change their type or break JSON-compatibility expectations.
Consider detecting number-like strings and forcing quotes to preserve string semantics, especially for mapping keys that must remain strings.
+ try:
+ float(value)
+ return True
+ except ValueError:
+ pass🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| if token == "{}": | ||
| return {} | ||
| if token.startswith('"') and token.endswith('"'): | ||
| return json.loads(token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quoted strings are parsed via json.loads without error handling, so invalid escapes raise JSONDecodeError instead of the parser’s YamlError. This breaks the parser’s error contract in both _parse_value and quoted map keys in _parse_block.
Consider catching json.JSONDecodeError at both sites and re-raising as YamlError with a concise message so invalid quoted strings consistently surface as YAML parse errors.
- return json.loads(token)
+ try:
+ return json.loads(token)
+ except json.JSONDecodeError as e:
+ raise YamlError(f"Invalid quoted string: {e}") from e
- key = json.loads(key)
+ try:
+ key = json.loads(key)
+ except json.JSONDecodeError as e:
+ raise YamlError(f"Invalid quoted key: {e}") from e🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| if ch == '\\': | ||
| escape = True | ||
| continue | ||
| if ch == '"': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_split_mapping_line treats colons inside single-quoted scalars as separators because in_quotes only toggles on double quotes. This breaks cases like "'user:name': value" and values like "key: 'http://example.com:8080'".
Consider toggling in_quotes for both single and double quotes so colons within either quoted scalar are ignored.
- if ch == '"':
- in_quotes = not in_quotes
- continue
+ if ch == '"' or ch == "'":
+ in_quotes = not in_quotes
+ continue🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| if mode == "map": | ||
| raise YamlError("Cannot mix list and map entries at the same level") | ||
| mode = "list" | ||
| if content == "-": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bare list item ('-') without an inline value or nested block is parsed as {} instead of None. In _parse_block, the recursive call returns an empty mapping when there are no indented lines (mode is None), so the list item becomes {}. YAML typically treats a bare item as null, leading to incorrect data types.
Consider detecting the absence of a nested block before recursing: if the next line is not more indented, set the list item value to None instead of recursing into _parse_block.
- if content == "-":
- value, index = _parse_block(lines, index, indent + 1)
- else:
- value = _parse_value(content[2:].strip())
- items.append(value)
+ if content == "-":
+ if index < len(lines) and lines[index].indent > indent:
+ value, index = _parse_block(lines, index, indent + 1)
+ else:
+ value = None
+ else:
+ value = _parse_value(content[2:].strip())
+ items.append(value)🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
Fixed all 13 Spectral validation errors in OpenAPI specification: - Added missing schema types to webhook_notifications included array (shipment, container, port, terminal, vessel, metro_area, rail_terminal) - Converted relative URIs to absolute format (https://api.terminal49.com/v2/...) - URL-encoded pagination parameters (page[size] → page%5Bsize%5D) - Added nullable: true to optional relationship fields (shipment.data, location_name) - Fixed ref_numbers array example (removed null value) - Added "Shipping Lines" to global tags Setup automated development workflow: - Added justfile task runner with comprehensive commands (bundle, lint, watch, preview) - Configured bun/npm scripts for bundling, linting, and watch mode - Updated Python bundler to use standard yaml.safe_load (custom parser was too strict) - Added enhanced validation with detailed error messages - Setup chokidar file watcher for auto-bundling on YAML changes - Configured Mintlify with explicit openapi.json reference - Fixed pre-commit hook line endings (CRLF → LF) Added comprehensive documentation: - CLAUDE.md with complete project overview and workflows - docs/openapi/DEVELOPER_GUIDE.md with detailed development instructions - Enhanced README.md with modular OpenAPI workflow documentation - Added inline validation error context and helpful tips Tooling improvements: - Added .spectral.yaml linter config (replaced .spectral.mjs) - Added .redocly.yaml config for Redocly CLI - Added .watchmanconfig for file watching - Added bun.lock and package.json with dev dependencies - Fixed containers-id.yaml indentation for custom parser compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| "": | ||
| type: "string" | ||
| x-stoplight: | ||
| id: "kwcjunrtu3r5o" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| echo -e "${GREEN}✅ Bundle regenerated successfully${NC}" | ||
|
|
||
| # Check if the bundle changed | ||
| if git diff --name-only docs/openapi.json | grep -q "openapi.json"; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs/openapi.json won’t be staged if it’s newly created or previously deleted, because git diff --name-only ignores untracked files. On first bundle generation, the file is created but not added, so the commit misses the artifact and the repo falls out of sync.
Consider updating the staging condition to also detect untracked files (e.g., via git ls-files --error-unmatch), or simply always git add the bundled file after a successful generation so new files are captured.
- if git diff --name-only docs/openapi.json | grep -q "openapi.json"; then
+ if git diff --name-only -- docs/openapi.json | grep -q "openapi.json" || ! git ls-files --error-unmatch docs/openapi.json >/dev/null 2>&1; then🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| exit 1 | ||
| } | ||
|
|
||
| # Compare the staged version with the freshly bundled version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The verification step checks the working tree docs/openapi.json instead of the staged blob. This can produce false positives/negatives when the index differs from the working copy, even though the hook is triggered by staged changes.
Consider comparing the staged blob by reading :docs/openapi.json into a temp file and diffing that against the freshly bundled output. Also ensure both temp files are cleaned up in both success and failure paths.
+ STAGED_FILE=$(mktemp)
+ git show :docs/openapi.json > "$STAGED_FILE" || {
+ echo -e "${RED}❌ Failed to read staged openapi.json${NC}"
+ rm -f "$TEMP_BUNDLE" "$STAGED_FILE"
+ exit 1
+ }
- if ! diff -q docs/openapi.json "$TEMP_BUNDLE" > /dev/null 2>&1; then
+ if ! diff -q "$STAGED_FILE" "$TEMP_BUNDLE" > /dev/null 2>&1; then
echo -e "${RED}❌ openapi.json is out of sync with YAML sources!${NC}"
echo -e "${YELLOW}Run: python -m tools.openapi_bundle docs/openapi/index.yaml docs/openapi.json${NC}"
- rm -f "$TEMP_BUNDLE"
+ rm -f "$TEMP_BUNDLE" "$STAGED_FILE"
exit 1
fi
- rm -f "$TEMP_BUNDLE"
+ rm -f "$TEMP_BUNDLE" "$STAGED_FILE"🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
scripts/pre-commit.sh
Outdated
| echo -e "${YELLOW}🔍 Verifying openapi.json is in sync...${NC}" | ||
|
|
||
| # Create a temp file with the freshly bundled version | ||
| TEMP_BUNDLE=$(mktemp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mktemp failure isn’t handled. If mktemp fails, TEMP_BUNDLE is empty and later commands operate on an empty path. This can lead to confusing errors and an unsafe rm -f "$TEMP_BUNDLE".
Consider handling mktemp failure immediately and aborting with a clear message before using TEMP_BUNDLE anywhere.
- TEMP_BUNDLE=$(mktemp)
+ TEMP_BUNDLE=$(mktemp) || {
+ echo -e "${RED}❌ Failed to create temporary file for verification${NC}"
+ exit 1
+ }🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
| f"Schema file appears to be missing expected fields: {path}", | ||
| f"", | ||
| f"Expected at least one of: {', '.join(sorted(expected_fields))}", | ||
| f"Found keys: {', '.join(sorted(data.keys()))}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
', '.join(sorted(data.keys())) can raise TypeError when schema files have mixed-type or non-string keys, masking the intended ValidationError.
Consider converting keys to strings before sorting and joining so the error message is robust (e.g., use sorted(map(str, data.keys()))).
- f"Found keys: {', '.join(sorted(data.keys()))}",
+ f"Found keys: {', '.join(sorted(map(str, data.keys())))}",
🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
mattyturner
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just setup is assuming I already have bun installed.
Ignore Vercel deployment artifacts and Claude Code configuration files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
maurycy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| import json | ||
|
|
||
| bundled = bundle_openapi(source, validate_schemas=validate_schemas) | ||
| destination.write_text(json.dumps(bundled, indent=2, sort_keys=False) + "\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yaml.safe_load can parse timestamp-like scalars (e.g., ISO dates) into datetime/date objects, which will cause json.dumps to fail with TypeError: Object of type date is not JSON serializable during write_bundle.
Consider either preventing implicit timestamp parsing in the YAML loader (e.g., a custom loader that treats timestamps as strings) or making JSON serialization tolerant of such objects by providing a default handler (e.g., default=str) so the bundle always serializes successfully.
- destination.write_text(json.dumps(bundled, indent=2, sort_keys=False) + "\n")
+ destination.write_text(json.dumps(bundled, indent=2, sort_keys=False, default=str) + "\n")🚀 Reply to ask Macroscope to explain or update this suggestion.
👍 Helpful? React to give us feedback.
Summary
Testing
https://chatgpt.com/codex/tasks/task_b_68f863d34bfc8322a40d3a48c68b8962