Skip to content

fix: resolve syntax error in generated openapi json#253

Merged
PENEKhun merged 4 commits intodevelopfrom
fix/oas-syntax
Oct 5, 2025
Merged

fix: resolve syntax error in generated openapi json#253
PENEKhun merged 4 commits intodevelopfrom
fix/oas-syntax

Conversation

@PENEKhun
Copy link
Copy Markdown
Member

@PENEKhun PENEKhun commented Oct 4, 2025

ko

생성된 oas.json에 syntax 에러가 발생해서 이를 해결합니다.

en

A syntax error occurred in the generated oas.json, and this commit resolves it.

result

AS-IS

syntax error was displayed

image

TO-BE

syntax error was not displaying

image

closed #252

Summary by CodeRabbit

  • Bug Fixes

    • OpenAPI paths now use standard {param} syntax instead of :param, improving spec compliance and tooling compatibility.
    • Path parameter handling normalized to prevent mismatches between declared paths and parameters.
    • Request builder now detects and skips duplicate headers (case-insensitive) and emits a warning.
  • Documentation

    • Updated generated OpenAPI example to reflect corrected path templates.
  • Tests

    • Added unit tests verifying path template normalization.

@PENEKhun PENEKhun self-assigned this Oct 4, 2025
@PENEKhun PENEKhun added the bug bugbugbug label Oct 4, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Oct 4, 2025

Walkthrough

The PR normalizes Express-style path templates to OpenAPI format and improves header handling. examples/express/expected/oas.json path keys changed from "/users/:userId" → "/users/{userId}" and "/users/:userId/friends/:friendName" → "/users/{userId}/friends/{friendName}". lib/dsl/generator/OpenAPIGenerator.ts adds normalizePathTemplate and extractPathParameterNames, uses normalized paths when generating operations, and updates parameter validation. lib/dsl/test-builders/RequestBuilder.ts deduplicates headers case-insensitively, logs a warning for duplicates, and stores lowercased unique headers. Unit tests for normalizePathTemplate were added.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • json-choi

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The addition of duplicate header detection in RequestBuilder.ts introduces functionality beyond resolving the syntax error in the generated oas.json as described in issue #252, representing an unrelated enhancement that falls outside the stated objectives of this bug fix. The duplicate header detection feature should be extracted into a separate pull request focused on header management enhancements, allowing this PR to remain narrowly scoped to fixing the OpenAPI syntax error.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately describes the primary objective of the pull request, which is to fix a syntax error in the generated OpenAPI JSON, and directly reflects the intent described in the PR summary without unnecessary detail or noise.
Linked Issues Check ✅ Passed The changes implement the core fix outlined in issue #252 by converting colon-style path parameters to OpenAPI brace syntax, normalizing header names, updating the generated oas.json and adding tests to verify the conversions, thereby resolving the invalid syntax in the generated OpenAPI specification.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/oas-syntax

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cae1f6 and f1dd5c8.

📒 Files selected for processing (1)
  • lib/__tests__/unit/dsl/OpenAPIGenerator.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/tests/unit/dsl/OpenAPIGenerator.test.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/dsl/generator/OpenAPIGenerator.ts (1)

578-600: Path normalization and extraction methods look good.

The implementation correctly converts Express-style parameters to OpenAPI format and provides defensive fallback logic for extraction.

One minor note for future consideration: The normalization regex /:([A-Za-z0-9_]+)/g is more restrictive than the extraction regex /:([^/]+)/g. If parameter names ever include hyphens (e.g., :user-id), the normalization won't convert them but extraction might still detect them. For consistency, consider aligning these patterns:

 private normalizePathTemplate(path: string): string {
-    return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
+    return path.replace(/:([A-Za-z0-9_-]+)/g, "{$1}")
 }

However, since the current examples follow standard naming conventions (alphanumeric + underscore), this is not a pressing concern.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0acfa8f and 23f03d5.

📒 Files selected for processing (2)
  • examples/express/expected/oas.json (2 hunks)
  • lib/dsl/generator/OpenAPIGenerator.ts (3 hunks)
🔇 Additional comments (3)
lib/dsl/generator/OpenAPIGenerator.ts (2)

133-141: LGTM! Path normalization correctly applied.

The normalization ensures all paths use OpenAPI-compliant {param} syntax before generating operations, which directly addresses the syntax error mentioned in the PR.


541-541: LGTM! Parameter extraction properly updated.

Using the new extraction helper ensures path parameters are correctly identified from normalized paths.

examples/express/expected/oas.json (1)

92-92: LGTM! Path syntax now conforms to OpenAPI spec.

The path templates correctly use curly-brace notation ({userId}, {friendName}) instead of colon-prefixed notation, which is the standard format required by OpenAPI 3.0.0. This directly resolves the syntax error mentioned in issue #252.

Also applies to: 386-386

@jaesong-blip
Copy link
Copy Markdown

코드 리뷰: PR #253 (fix/oas-syntax)

📋 전체 요약

목적: OpenAPI 스펙 준수를 위한 구문 오류 수정

  • Path 파라미터 형식: :userId{userId} (OpenAPI 표준)
  • HTTP 헤더 정규화: 대소문자 혼용 → 소문자 통일

변경 범위: 4개 파일, +48줄, -14줄

평가: ✅ 전반적으로 잘 구현됨. 몇 가지 개선 사항 존재


📁 파일별 상세 분석

1. lib/dsl/generator/OpenAPIGenerator.ts

✅ 잘된 점

새로운 메서드 추가 (578-600줄)

private normalizePathTemplate(path: string): string {
    return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
}

private extractPathParameterNames(path: string): string[] {
    const braceMatches = path.match(/\{([^}]+)\}/g) || []
    if (braceMatches.length > 0) {
        return braceMatches.map((param) => param.slice(1, -1))
    }
    const colonMatches = path.match(/:([^/]+)/g) || []
    return colonMatches.map((param) => param.slice(1))
}
  • SRP 준수: 단일 책임 원칙에 따라 기능별로 메서드 분리
  • 후방 호환성: 두 가지 형식(:param, {param}) 모두 지원
  • 명확한 네이밍: 메서드 이름이 기능을 정확히 표현

통합 지점 (133-141줄)

for (const [path, methods] of groupedResults) {
    const normalizedPath = this.normalizePathTemplate(path)
    paths[normalizedPath] = {}

    for (const [method, statusCodes] of methods) {
        paths[normalizedPath][method] = this.generateOperationObject(
            normalizedPath, method, statusCodes
        )
    }
}
  • 올바른 위치: path 처리 초기 단계에서 정규화
  • 일관성 유지: 모든 경로가 동일한 변환 적용

⚠️ 개선 필요 사항

1. 정규식 패턴 불일치

// normalizePathTemplate (584줄)
:([A-Za-z0-9_]+)     // 언더스코어 포함

// extractPathParameterNames (598줄)
:([^/]+)              // 모든 문자 허용 (슬래시 제외)

문제: 두 메서드가 다른 파라미터 패턴을 매칭함

  • normalizePathTemplate: userId, user_id, user123 매칭
  • extractPathParameterNames: userId, user-id, user.id 모두 매칭

권장사항: 정규식 패턴을 상수로 추출하여 일관성 보장

private static readonly PATH_PARAM_PATTERN = /[A-Za-z0-9_-]+/
private static readonly COLON_PARAM_REGEX = new RegExp(`:([A-Za-z0-9_-]+)`, 'g')
private static readonly BRACE_PARAM_REGEX = /\{([^}]+)\}/g

private normalizePathTemplate(path: string): string {
    return path.replace(OpenAPIGenerator.COLON_PARAM_REGEX, "{$1}")
}

2. 헤더 검사 하드코딩 (499줄)

const authTestCase = results.find(
    (result) => result.request.headers && "authorization" in result.request.headers
)

문제:

  • 헤더 이름이 하드코딩되어 확장성 저하
  • RequestBuilder에서 소문자 변환하므로 문제없지만, 의존성이 암묵적

권장사항: 상수 또는 설정으로 관리

private static readonly AUTH_HEADER_NAME = 'authorization'

private selectRepresentativeResult(results: TestResult[]): TestResult {
    const authTestCase = results.find(
        (result) => result.request.headers &&
                   OpenAPIGenerator.AUTH_HEADER_NAME in result.request.headers
    )
    // ...
}

3. 엣지 케이스 미처리

현재 normalizePathTemplate은 다음 케이스를 고려하지 않음:

  • 중첩된 파라미터: /users/:userId/posts/:postId/comments/:commentId
  • 특수문자 포함: /files/:file-name (하이픈 미지원)
  • 정규식 패턴: /users/:userId(\\d+) (Express route constraint)

테스트 추가 권장:

// 추가 테스트 케이스
expect(normalizePathTemplate('/users/:userId/posts/:postId')).toBe('/users/{userId}/posts/{postId}')
expect(normalizePathTemplate('/files/:file-name')).toBe('/files/{file-name}')

2. lib/dsl/generator/builders/operation/SecurityBuilder.ts

변경 내용 (35-36줄)

// Before
if (result.request.headers && "Authorization" in result.request.headers) {
    const authHeaderValue = result.request.headers["Authorization"]

// After
if (result.request.headers && "authorization" in result.request.headers) {
    const authHeaderValue = result.request.headers["authorization"]

✅ 평가

  • 일관성: OpenAPIGenerator와 동일한 변경 적용
  • 단순 명확: 불필요한 복잡성 없음

⚠️ 개선 사항

타입 안전성 강화

현재 코드는 런타임에 헤더 존재 여부를 확인하지만, TypeScript의 이점을 충분히 활용하지 못함

권장사항:

private static readonly AUTH_HEADER = 'authorization' as const

public extractSecurityRequirements(result: TestResult): Array<Record<string, string[]>> {
    const security: Array<Record<string, string[]>> = []
    const headers = result.request.headers

    if (!headers) return security

    const authHeader = headers[SecurityBuilder.AUTH_HEADER]
    if (!authHeader) return security

    // 타입 가드로 string 보장
    const authValue = typeof authHeader === 'string'
        ? authHeader
        : authHeader.toString()

    // ... 나머지 로직
}

3. lib/dsl/test-builders/RequestBuilder.ts

변경 내용 (33-38줄)

public header(headers: Record<string, DSLField<string>>): this {
    const normalizedHeaders: Record<string, DSLField<string>> = {}

    Object.entries(headers).forEach(([headerName, headerValue]) => {
        normalizedHeaders[headerName.toLowerCase()] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

✅ 잘된 점

  • 근본 원인 해결: 입력 단계에서 정규화하여 일관성 보장
  • HTTP 스펙 준수: RFC 7230에 따라 헤더는 대소문자 구분 없음
  • 명확한 주석: 동작을 설명하는 JSDoc 업데이트

⚠️ 개선 필요 사항

1. 성능 고려

Object.entries(headers).forEach(([headerName, headerValue]) => {
    normalizedHeaders[headerName.toLowerCase()] = headerValue
})

문제: 매번 새 객체 생성 및 모든 키 순회

권장사항: 객체가 이미 정규화되어 있으면 스킵

public header(headers: Record<string, DSLField<string>>): this {
    // 이미 모두 소문자인지 확인
    const needsNormalization = Object.keys(headers).some(
        key => key !== key.toLowerCase()
    )

    if (!needsNormalization) {
        this.config.requestHeaders = headers
        return this
    }

    const normalizedHeaders: Record<string, DSLField<string>> = {}
    Object.entries(headers).forEach(([headerName, headerValue]) => {
        normalizedHeaders[headerName.toLowerCase()] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

2. 중복 헤더 처리 없음

header({
    'Content-Type': 'application/json',
    'content-type': 'text/plain'  // 어떤 값이 우선?
})

권장사항: 중복 감지 및 경고

public header(headers: Record<string, DSLField<string>>): this {
    const normalizedHeaders: Record<string, DSLField<string>> = {}
    const seen = new Set<string>()

    Object.entries(headers).forEach(([headerName, headerValue]) => {
        const normalized = headerName.toLowerCase()

        if (seen.has(normalized)) {
            logger.warn(`Duplicate header detected: "${headerName}" (already set)`)
            return
        }

        seen.add(normalized)
        normalizedHeaders[normalized] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

3. 불변성 위반 가능성

현재 구현은 headers 객체를 변경하지 않지만, headerValue 참조는 공유됨

권장사항: 명확한 불변성 보장이 필요하다면 deep copy 고려


4. examples/express/expected/oas.json

변경 내용

// Path 파라미터 (89, 386줄)
- "/users/:userId"
+ "/users/{userId}"

- "/users/:userId/friends/:friendName"
+ "/users/{userId}/friends/{friendName}"

// 헤더 이름 (649, 1191, 1200줄)
- "X-Request-ID" → "x-request-id"
- "Accept" → "accept"
- "Accept-Language" → "accept-language"

✅ 평가

  • 스펙 준수: OpenAPI 3.0 표준 형식 정확히 준수
  • 일관성: 모든 path 파라미터와 헤더가 통일된 형식
  • 검증 가능: Swagger/OpenAPI 도구로 syntax 에러 해결 확인 가능

🔍 추가 고려사항

1. 테스트 커버리지

현재 PR에서 테스트 코드 변경이 보이지 않음. 다음 테스트 추가 권장:

describe('OpenAPIGenerator - Path Normalization', () => {
    it('should convert Express-style params to OpenAPI format', () => {
        const generator = OpenAPIGenerator.getInstance()
        const normalized = generator['normalizePathTemplate']('/users/:userId/posts/:postId')
        expect(normalized).toBe('/users/{userId}/posts/{postId}')
    })

    it('should handle already normalized paths', () => {
        const generator = OpenAPIGenerator.getInstance()
        const normalized = generator['normalizePathTemplate']('/users/{userId}')
        expect(normalized).toBe('/users/{userId}')
    })

    it('should extract parameters from both formats', () => {
        const generator = OpenAPIGenerator.getInstance()
        expect(generator['extractPathParameterNames']('/users/:userId')).toEqual(['userId'])
        expect(generator['extractPathParameterNames']('/users/{userId}')).toEqual(['userId'])
    })
})

describe('RequestBuilder - Header Normalization', () => {
    it('should normalize header names to lowercase', () => {
        const builder = new RequestBuilder()
        builder.header({ 'Content-Type': 'application/json', 'X-Custom': 'value' })

        expect(builder['config'].requestHeaders).toEqual({
            'content-type': 'application/json',
            'x-custom': 'value'
        })
    })
})

2. 하위 호환성

기존에 :userId 형식으로 생성된 OpenAPI 문서가 있다면 마이그레이션 가이드 필요

권장: CHANGELOG.md 업데이트

## Breaking Changes

### OpenAPI Path Parameter Format
- **Before**: `/users/:userId`
- **After**: `/users/{userId}`
- **Action Required**: Regenerate OpenAPI specifications

3. 문서화

새로 추가된 메서드들의 JSDoc이 명확하지만, 다음 정보 추가 권장:

/**
 * Converts Express-style colon-prefixed parameters to OpenAPI-compatible brace templates.
 *
 * @param {string} path - Raw route path (e.g., "/users/:userId/posts/:postId")
 * @returns {string} Normalized OpenAPI path (e.g., "/users/{userId}/posts/{postId}")
 *
 * @example
 * normalizePathTemplate("/users/:userId") // Returns "/users/{userId}"
 * normalizePathTemplate("/users/{userId}") // Returns "/users/{userId}" (idempotent)
 */
private normalizePathTemplate(path: string): string {
    return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
}

🎯 우선순위별 권장사항

🔴 높음 (즉시 수정 권장)

  1. 정규식 패턴 통일: normalizePathTemplateextractPathParameterNames의 패턴 일치
  2. 테스트 추가: 핵심 변경사항에 대한 단위 테스트

🟡 중간 (다음 릴리스에서 고려)

  1. 중복 헤더 감지: RequestBuilder.header() 개선
  2. 상수 추출: 하드코딩된 문자열을 상수로 관리

🟢 낮음 (선택사항)

  1. 성능 최적화: 헤더 정규화 조건부 실행
  2. 문서화 강화: 예제 코드 추가

✅ 최종 평가

전반적 품질: ⭐⭐⭐⭐☆ (4/5)

장점:

  • OpenAPI 스펙 준수 문제를 정확히 해결
  • 코드 구조가 명확하고 읽기 쉬움
  • 단일 책임 원칙을 잘 따름
  • 후방 호환성 고려

개선 필요:

  • 정규식 패턴 일관성
  • 엣지 케이스 처리
  • 테스트 커버리지

결론: PR 승인 가능하나, 위 권장사항 중 높은 우선순위 항목은 반영 권장

@jaesong-blip
Copy link
Copy Markdown

Code Review: PR #253 (fix/oas-syntax)

Summary

Purpose: Fix syntax errors in generated OpenAPI specification to ensure compliance

  • Path parameter format: :userId{userId} (OpenAPI standard)
  • HTTP header normalization: Mixed case → lowercase

Scope: 4 files changed, +48 lines, -14 lines

Overall Assessment: Well-implemented solution with some areas for improvement


Detailed File Analysis

1. lib/dsl/generator/OpenAPIGenerator.ts

Strengths

New Methods Added (lines 578-600)

private normalizePathTemplate(path: string): string {
    return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
}

private extractPathParameterNames(path: string): string[] {
    const braceMatches = path.match(/\{([^}]+)\}/g) || []
    if (braceMatches.length > 0) {
        return braceMatches.map((param) => param.slice(1, -1))
    }
    const colonMatches = path.match(/:([^/]+)/g) || []
    return colonMatches.map((param) => param.slice(1))
}
  • Single Responsibility: Methods are properly separated by functionality
  • Backward Compatibility: Supports both :param and {param} formats
  • Clear Naming: Method names accurately describe their purpose

Integration Point (lines 133-141)

for (const [path, methods] of groupedResults) {
    const normalizedPath = this.normalizePathTemplate(path)
    paths[normalizedPath] = {}

    for (const [method, statusCodes] of methods) {
        paths[normalizedPath][method] = this.generateOperationObject(
            normalizedPath, method, statusCodes
        )
    }
}
  • Correct Placement: Normalization happens early in path processing
  • Consistency: All paths receive the same transformation

Areas for Improvement

1. Regex Pattern Inconsistency

// normalizePathTemplate (line 584)
:([A-Za-z0-9_]+)     // Includes underscore

// extractPathParameterNames (line 598)
:([^/]+)              // Allows all characters except slash

Issue: The two methods match different parameter patterns

  • normalizePathTemplate: Matches userId, user_id, user123
  • extractPathParameterNames: Matches userId, user-id, user.id

Recommendation: Extract regex patterns as constants for consistency

private static readonly PATH_PARAM_PATTERN = /[A-Za-z0-9_-]+/
private static readonly COLON_PARAM_REGEX = new RegExp(`:([A-Za-z0-9_-]+)`, 'g')
private static readonly BRACE_PARAM_REGEX = /\{([^}]+)\}/g

private normalizePathTemplate(path: string): string {
    return path.replace(OpenAPIGenerator.COLON_PARAM_REGEX, "{$1}")
}

2. Hardcoded Header Check (line 499)

const authTestCase = results.find(
    (result) => result.request.headers && "authorization" in result.request.headers
)

Issue:

  • Header name is hardcoded, reducing extensibility
  • Works fine since RequestBuilder normalizes to lowercase, but the dependency is implicit

Recommendation: Manage as a constant

private static readonly AUTH_HEADER_NAME = 'authorization'

private selectRepresentativeResult(results: TestResult[]): TestResult {
    const authTestCase = results.find(
        (result) => result.request.headers &&
                   OpenAPIGenerator.AUTH_HEADER_NAME in result.request.headers
    )
    // ...
}

3. Unhandled Edge Cases

The current normalizePathTemplate doesn't handle:

  • Nested parameters: /users/:userId/posts/:postId/comments/:commentId
  • Special characters: /files/:file-name (hyphen not supported in current regex)
  • Regex patterns: /users/:userId(\\d+) (Express route constraints)

Recommended Tests:

expect(normalizePathTemplate('/users/:userId/posts/:postId')).toBe('/users/{userId}/posts/{postId}')
expect(normalizePathTemplate('/files/:file-name')).toBe('/files/{file-name}')

2. lib/dsl/generator/builders/operation/SecurityBuilder.ts

Changes (lines 35-36)

// Before
if (result.request.headers && "Authorization" in result.request.headers) {
    const authHeaderValue = result.request.headers["Authorization"]

// After
if (result.request.headers && "authorization" in result.request.headers) {
    const authHeaderValue = result.request.headers["authorization"]

Assessment

  • Consistency: Same changes applied as in OpenAPIGenerator
  • Simplicity: No unnecessary complexity

Improvement Suggestions

Type Safety Enhancement

The current code checks header existence at runtime but doesn't fully leverage TypeScript's benefits

Recommendation:

private static readonly AUTH_HEADER = 'authorization' as const

public extractSecurityRequirements(result: TestResult): Array<Record<string, string[]>> {
    const security: Array<Record<string, string[]>> = []
    const headers = result.request.headers

    if (!headers) return security

    const authHeader = headers[SecurityBuilder.AUTH_HEADER]
    if (!authHeader) return security

    // Type guard to ensure string
    const authValue = typeof authHeader === 'string'
        ? authHeader
        : authHeader.toString()

    // ... rest of logic
}

3. lib/dsl/test-builders/RequestBuilder.ts

Changes (lines 33-38)

public header(headers: Record<string, DSLField<string>>): this {
    const normalizedHeaders: Record<string, DSLField<string>> = {}

    Object.entries(headers).forEach(([headerName, headerValue]) => {
        normalizedHeaders[headerName.toLowerCase()] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

Strengths

  • Root Cause Fix: Normalization at input ensures consistency
  • HTTP Spec Compliance: Headers are case-insensitive per RFC 7230
  • Clear Documentation: JSDoc updated to explain the behavior

Areas for Improvement

1. Performance Consideration

Object.entries(headers).forEach(([headerName, headerValue]) => {
    normalizedHeaders[headerName.toLowerCase()] = headerValue
})

Issue: Creates new object and iterates all keys every time

Recommendation: Skip if already normalized

public header(headers: Record<string, DSLField<string>>): this {
    // Check if already normalized
    const needsNormalization = Object.keys(headers).some(
        key => key !== key.toLowerCase()
    )

    if (!needsNormalization) {
        this.config.requestHeaders = headers
        return this
    }

    const normalizedHeaders: Record<string, DSLField<string>> = {}
    Object.entries(headers).forEach(([headerName, headerValue]) => {
        normalizedHeaders[headerName.toLowerCase()] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

2. No Duplicate Header Handling

header({
    'Content-Type': 'application/json',
    'content-type': 'text/plain'  // Which value takes precedence?
})

Recommendation: Detect duplicates and warn

public header(headers: Record<string, DSLField<string>>): this {
    const normalizedHeaders: Record<string, DSLField<string>> = {}
    const seen = new Set<string>()

    Object.entries(headers).forEach(([headerName, headerValue]) => {
        const normalized = headerName.toLowerCase()

        if (seen.has(normalized)) {
            logger.warn(`Duplicate header detected: "${headerName}" (already set)`)
            return
        }

        seen.add(normalized)
        normalizedHeaders[normalized] = headerValue
    })

    this.config.requestHeaders = normalizedHeaders
    return this
}

3. Potential Immutability Violation

Current implementation doesn't modify the headers object, but headerValue references are shared

Recommendation: Consider deep copy if strict immutability is required


4. examples/express/expected/oas.json

Changes

// Path parameters (lines 89, 386)
- "/users/:userId"
+ "/users/{userId}"

- "/users/:userId/friends/:friendName"
+ "/users/{userId}/friends/{friendName}"

// Header names (lines 649, 1191, 1200)
- "X-Request-ID" → "x-request-id"
- "Accept" → "accept"
- "Accept-Language" → "accept-language"

Assessment

  • Spec Compliance: Accurately follows OpenAPI 3.0 standard format
  • Consistency: All path parameters and headers use unified format
  • Verifiable: Syntax errors can be confirmed resolved using Swagger/OpenAPI tools

Additional Considerations

1. Test Coverage

No test code changes are visible in this PR. Recommend adding these tests:

describe('OpenAPIGenerator - Path Normalization', () => {
    it('should convert Express-style params to OpenAPI format', () => {
        const generator = OpenAPIGenerator.getInstance()
        const normalized = generator['normalizePathTemplate']('/users/:userId/posts/:postId')
        expect(normalized).toBe('/users/{userId}/posts/{postId}')
    })

    it('should handle already normalized paths', () => {
        const generator = OpenAPIGenerator.getInstance()
        const normalized = generator['normalizePathTemplate']('/users/{userId}')
        expect(normalized).toBe('/users/{userId}')
    })

    it('should extract parameters from both formats', () => {
        const generator = OpenAPIGenerator.getInstance()
        expect(generator['extractPathParameterNames']('/users/:userId')).toEqual(['userId'])
        expect(generator['extractPathParameterNames']('/users/{userId}')).toEqual(['userId'])
    })
})

describe('RequestBuilder - Header Normalization', () => {
    it('should normalize header names to lowercase', () => {
        const builder = new RequestBuilder()
        builder.header({ 'Content-Type': 'application/json', 'X-Custom': 'value' })

        expect(builder['config'].requestHeaders).toEqual({
            'content-type': 'application/json',
            'x-custom': 'value'
        })
    })
})

2. Backward Compatibility

If OpenAPI documents were previously generated with :userId format, a migration guide is needed

Recommendation: Update CHANGELOG.md

## Breaking Changes

### OpenAPI Path Parameter Format
- **Before**: `/users/:userId`
- **After**: `/users/{userId}`
- **Action Required**: Regenerate OpenAPI specifications

3. Documentation

The JSDoc for newly added methods is clear, but recommend adding this information:

/**
 * Converts Express-style colon-prefixed parameters to OpenAPI-compatible brace templates.
 *
 * @param {string} path - Raw route path (e.g., "/users/:userId/posts/:postId")
 * @returns {string} Normalized OpenAPI path (e.g., "/users/{userId}/posts/{postId}")
 *
 * @example
 * normalizePathTemplate("/users/:userId") // Returns "/users/{userId}"
 * normalizePathTemplate("/users/{userId}") // Returns "/users/{userId}" (idempotent)
 */
private normalizePathTemplate(path: string): string {
    return path.replace(/:([A-Za-z0-9_]+)/g, "{$1}")
}

Prioritized Recommendations

High Priority (Immediate Action Recommended)

  1. Regex Pattern Consistency: Align patterns between normalizePathTemplate and extractPathParameterNames
  2. Add Tests: Unit tests for core changes

Medium Priority (Consider for Next Release)

  1. Duplicate Header Detection: Improve RequestBuilder.header()
  2. Extract Constants: Manage hardcoded strings as constants

Low Priority (Optional)

  1. Performance Optimization: Conditional header normalization execution
  2. Enhanced Documentation: Add code examples

Final Assessment

Overall Quality: 4/5

Strengths:

  • Accurately solves OpenAPI spec compliance issues
  • Clear and readable code structure
  • Follows single responsibility principle well
  • Considers backward compatibility

Areas for Improvement:

  • Regex pattern consistency
  • Edge case handling
  • Test coverage

Conclusion: Approval recommended with suggestion to address high-priority items

Copy link
Copy Markdown
Collaborator

@json-choi json-choi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 엣지 케이스 미처리

현재 normalizePathTemplate은 다음 케이스를 고려하지 않음:

중첩된 파라미터: /users/:userId/posts/:postId/comments/:commentId
특수문자 포함: /files/:file-name (하이픈 미지원)
정규식 패턴: /users/:userId(\d+) (Express route constraint)

이 부분만 혹시 추가적으로 해결가능할까요?

@coderabbitai coderabbitai bot requested a review from json-choi October 5, 2025 08:02
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3998752 and 5cae1f6.

📒 Files selected for processing (2)
  • lib/__tests__/unit/dsl/OpenAPIGenerator.test.ts (1 hunks)
  • lib/dsl/generator/OpenAPIGenerator.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/dsl/generator/OpenAPIGenerator.ts
🧰 Additional context used
🧬 Code graph analysis (1)
lib/__tests__/unit/dsl/OpenAPIGenerator.test.ts (1)
lib/dsl/generator/OpenAPIGenerator.ts (1)
  • OpenAPIGenerator (41-654)
🔇 Additional comments (2)
lib/__tests__/unit/dsl/OpenAPIGenerator.test.ts (2)

236-240: LGTM!

The test correctly verifies that multiple Express-style parameters are converted to OpenAPI brace format.


242-246: LGTM!

The test correctly verifies that already normalized paths remain unchanged.

@PENEKhun
Copy link
Copy Markdown
Member Author

PENEKhun commented Oct 5, 2025

  1. 엣지 케이스 미처리

현재 normalizePathTemplate은 다음 케이스를 고려하지 않음:

중첩된 파라미터: /users/:userId/posts/:postId/comments/:commentId

특수문자 포함: /files/:file-name (하이픈 미지원)
정규식 패턴: /users/:userId(\d+) (Express route constraint)
이 부분만 혹시 추가적으로 해결가능할까요?

done

@PENEKhun PENEKhun merged commit fe7d3a2 into develop Oct 5, 2025
2 checks passed
@PENEKhun PENEKhun deleted the fix/oas-syntax branch October 5, 2025 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug bugbugbug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Generated oas syntax was wrong

3 participants