Skip to content

Matching Pipeline

JEONG edited this page Sep 7, 2026 · 1 revision

매칭 파이프라인

apicov scan 한 번이 실제로 하는 일입니다. 진입점은 Sources/StellaCore/Pipeline/Pipeline.swiftgenerate(_:now:) 하나입니다.

OpenAPI 로드 ─┐
              ├─→ 경로 매칭 ─→ git blame ─→ 담당자 결합 ─→ CoverageSnapshot
Router 스캔 ──┘

1. OpenAPI 로드 — OpenAPILoader

URL · 로컬 파일 · 메모리 데이터 세 가지 소스를 받습니다. URL 모드는 Basic 인증 헤더를 붙일 수 있고 2xx 가 아니면 openAPIHTTPStatus 로 끊습니다.

2. 스펙 파싱 — OpenAPIParser

JSON 만 받습니다. info.title · info.version · paths 가 없으면 실패합니다.

paths 를 훑으면서 (경로, 메서드) 쌍마다 오퍼레이션을 만듭니다. GET·POST·PUT·PATCH·DELETE 외의 메서드와 파싱할 수 없는 항목은 조용히 건너뜁니다. tags첫 번째 값만 씁니다.

요청·응답 예시 생성

스키마를 재귀로 걸어 예시 JSON 을 만듭니다. 우선순위는 이렇습니다.

  1. example → 2. default → 3. enum 의 첫 값 → 4. $ref 해석 후 재귀
  2. allOf 는 객체끼리 병합, oneOf / anyOf 는 첫 항목만
  3. array 는 원소 하나짜리 배열, object 는 프로퍼티별 재귀
  4. 그래도 안 정해지면 formattype 순으로 기본값 (integer0, string"string", date-time"2026-04-29T00:00:00Z" …)

재귀 깊이는 10 단계에서 끊습니다. 순환 참조가 있는 스키마도 멈춥니다.

3. Router 파일 찾기 — RouterFileFinder

프로젝트 루트를 훑으며 glob 에 걸리는 파일만 고릅니다. 지원하는 문법은 **(중첩 디렉터리)과 *(경로 한 조각) 두 개뿐이고 숨김 파일은 건너뜁니다.

프로젝트 glob
appProduct **/Router/*Router.swift
umcApp **/Data/Sources/*Router.swift

파일이 이 패턴에 안 걸리면 그 Router 는 존재하지 않는 셈이 됩니다. "매치 안됨"에도 안 뜨고 커버리지에서 그냥 빠집니다. 스캔 결과가 텅 비었다면 여기부터 의심하세요.

4. Router enum 파싱 — RouterEnumParser

SwiftSyntax 로 파싱합니다. 정규식이 아니라 구문 트리를 보므로 줄바꿈이나 주석에 흔들리지 않습니다.

Router 로 인정하는 조건

  • enum 이름이 Router 로 끝나거나
  • BaseTargetType / TargetType 을 상속하거나
  • XxxRouter: TargetType 형태의 extension (이름이 Router 로 끝나고 상속 절이 있어야 합니다)

뽑아내는 것

  • case 이름과 줄 번호 — 줄 번호는 나중에 git blame 좌표가 됩니다
  • var path: String 의 switch 각 갈래가 return 하는 문자열 리터럴
  • var method: …Method 의 switch 각 갈래가 return 하는 멤버 이름 (.postpost)
  • /// 문서 주석 — case 요약으로 붙습니다

프로퍼티는 enum 본체에 있든 extension 에 있든 같은 이름의 타입으로 합쳐집니다. path 는 타입이 정확히 String, method 는 이름이 Method 로 끝나는 타입이어야 인식합니다.

갈래가 return 문 하나로 값을 돌려주는 형태만 읽습니다. 중간 변수를 만들거나 삼항 연산자로 조립하면 그 case 는 path / method 가 비어 "missing path or method arm" 으로 남습니다.

5. 경로 정규화 — PathNormalizer

Swift 문자열 보간을 OpenAPI 경로 템플릿으로 바꿉니다.

입력 출력
/notices/\(id) /notices/{id}
/notices/\(notice.id) /notices/{id} (점 뒤 마지막 조각)
/notices/\(id, radix: 10) /notices/{id} (콤마 앞까지)
/notices?page=\(page) /notices (? 뒤는 잘라냄)

6. 매칭 — PathMatcher

case 하나마다 이 순서로 판정합니다.

  1. overrides 조회ignoreignored: <reason> 사유로 매치 안됨 처리, openAPI 매핑이면 그 키로 즉시 매치
  2. pathmethod 갈래가 없으면 → missing path or method arm
  3. 정확 매치 — (메서드, 정규화된 경로)가 스펙 키와 그대로 일치
  4. 템플릿 매치 — 세그먼트 개수가 같고 파라미터 자리({...})끼리는 이름이 달라도 같다고 봅니다. 후보가 여럿이면 경로 사전순으로 첫 번째를 고릅니다
  5. 다 실패하면 → no OpenAPI entry for GET /path

4번 덕분에 코드의 \(noticeId) 와 스펙의 {id} 가 붙습니다. 반대로 경로 오타나 버전 차이(/api/v1 vs /v1)는 절대 매치되지 않습니다.

7. 작성자 조회 — GitBlameRunner

매치된 case 마다 git blame -L <줄>,<줄> --porcelain -- <파일> 을 blame 루트에서 실행해 커밋 이메일·이름·SHA·작성 시각을 읽습니다.

실패해도 스캔은 멈추지 않습니다. 해당 연결의 작성자만 Unknown, SHA 는 빈 문자열, 날짜는 1970-01-01 로 남습니다. 작성자가 전부 Unknown 이면 --blame-root 가 잘못됐거나 얕은 클론(fetch-depth: 1)일 가능성이 높습니다.

8. 담당자 결합

  • authors.yml — blame 이메일을 표시명·GitHub username 으로 풉니다 (대소문자 무시)
  • owners.yml — 엔드포인트 지정이 태그 지정보다 우선합니다. 나온 이메일을 다시 authors.yml 로 풀어 이름을 붙입니다

9. 스냅샷 조립

  • endpoints스펙의 모든 오퍼레이션입니다. 프로젝트마다 연결이 있으면 Connection, 없으면 null 이 들어갑니다. 한 엔드포인트에 여러 case 가 매치되면 먼저 발견된 하나만 기록됩니다
  • unmatchedRouterCases 는 반대 방향입니다. 코드에는 있는데 스펙에 못 붙은 case
  • 정렬은 엔드포인트가 경로 → 메서드 순, 매치 안됨이 프로젝트.enum.case 순입니다. 같은 입력이면 같은 바이트가 나오므로 diff 가 깔끔합니다

집계 필드의 정확한 의미

프로젝트별 summary 는 이렇게 계산됩니다.

필드 계산
matched 매치된 Router case 수
missing 스펙 오퍼레이션 총수 − matched
unmatched 매치 안 된 Router case 수

matched 가 "연결된 엔드포인트 수"가 아니라 case 수라는 점을 기억하세요. 서로 다른 case 두 개가 같은 엔드포인트에 매치되면 matched 는 2 로 잡히고 missing 은 그만큼 줄어듭니다. 커버리지 퍼센트도 이 값을 그대로 씁니다. 중복 매핑이 많은 프로젝트에서는 수치가 실제보다 후하게 나올 수 있습니다.


관련: 스냅샷 스키마 · 매핑 YAML · 트러블슈팅

Clone this wiki locally