# Open API Spec 정합성 감사 보고서

> **작성일**: 2026-03-31
> **대상**: `open-api/build/docs/openapi.json` vs 실제 Java DTO 소스코드
> **Generator**: `gradle-restdoc-generator` v2.1.3

---

## 요약

| 카테고리 | 이슈 수 | 심각도 |
|----------|---------|--------|
| SessionData 필드 노출 | 28/47 endpoints | **CRITICAL** |
| BigDecimal → JSON Number (정밀도 손실) | 23 fields / 8 DTOs | **HIGH** |
| DateTime 포맷 불일치 (spec vs 실제) | 12 fields / 7 DTOs | **MEDIUM** |
| permissions 타입 불일치 | 1 field | **LOW** |
| 보안: partnerId 노출 | 1 endpoint | **MEDIUM** |

---

## 1. [CRITICAL] SessionData 필드 노출 (28 endpoints)

### 현상

openapi.json 스펙에서 28개 엔드포인트가 **내부 SessionData 필드를 query parameter로 노출**:

```
DEFAULT_TOKEN_EXPIRE_DAYS  ← required: true! (실제로는 내부 상수)
partnerCode, partnerName, partnerId
sessionId, createDate, FORMAT
```

### 영향받는 컨트롤러

| 타입 | 컨트롤러 수 | 세션 클래스 |
|------|------------|------------|
| Widget API (`/widgets/api/*`) | 8개 | `WidgetSessionData` |
| V1 API (`/api/v1/*`) | 6개 | `OpenApiSessionData` |
| 미영향 (Webhook/Public) | 3개 | 없음 |

### 원인

`gradle-restdoc-generator` v2.1.3가 컨트롤러 메서드의 세션 파라미터를 일반 DTO로 인식:

```java
// 이 세션 파라미터가 query parameter로 확장됨
public List<BalanceResponse> getBalance(WidgetSessionData session) { ... }
```

Generator가 `WidgetSessionData` → 부모 `SessionData`까지 reflect하여 모든 필드를 query param으로 노출.

### 수정 방안

**Option A**: `@XApiIgnore` 어노테이션을 세션 클래스에 적용

```java
@XApiIgnore  // ← 추가
public class WidgetSessionData extends SessionData { ... }

@XApiIgnore  // ← 추가
public class OpenApiSessionData extends SessionData { ... }
```

**Option B**: `build.gradle`에 excludeClasses 설정

```gradle
restMetaGenerator {
    excludeClasses = [
        'com.cryptoments.openapi.session.WidgetSessionData',
        'com.cryptoments.openapi.session.OpenApiSessionData',
        'one.axim.framework.rest.model.SessionData'
    ]
}
```

---

## 2. [HIGH] BigDecimal → JSON Number 정밀도 손실 (23 fields)

### 현상

Widget API는 금액을 **String**으로 반환 (정확):
```json
{ "balance": "145.000000000000000000" }
```

V1 API는 BigDecimal을 **JSON Number**로 반환 (정밀도 손실):
```json
{ "amount": 13.292244193287774 }  ← 소수점 이하 15자리 이후 손실
```

### 영향받는 V1 Response DTOs

| DTO | BigDecimal 필드 | 필드 수 |
|-----|----------------|---------|
| `TransactionHistoryResponse` | amount, priceKrw, priceUsd | 3 |
| `CurrencyPriceResponseV1` | priceKrw, priceUsd | 2 |
| `CurrencyConversionResponse` | fromAmount, toAmount, exchangeRate | 3 |
| `WithdrawalResponseV1` | tokenAmount, krwAmount, tokenKrwPrice | 3 |
| `DepositReservationResponse` | amountKrw, amountCrypto, exchangeRate, actualAmountCrypto, actualAmountKrw | 5 |
| `ExchangeRateResponse.RateInfo` | usdKrw, krwUsd | 2 |
| `AximPaymentResponse` | amount, priceKrw | 2 |
| `PaymentLinkInfoResponse` | amountCrypto, priceKrw, calculatedKrwAmount | 3 |
| **합계** | | **23** |

### Jackson 설정 확인

```yaml
# application.yml
spring.jackson.serialization.write-bigdecimal-as-plain: true
```

이 설정은 지수 표기법만 방지(`1.23E+5` → `123000`). **JSON Number 타입 자체는 변경 안 됨**.

### 수정 방안

V1 API도 Widget API와 동일하게 BigDecimal → String으로 통일:

**Option A**: 필드 타입을 String으로 변경 (Widget API 패턴)
```java
// Before
private BigDecimal amount;

// After
private String amount;  // Service에서 bigDecimal.toPlainString() 호출
```

**Option B**: `@JsonSerialize` 어노테이션 적용
```java
@JsonSerialize(using = ToStringSerializer.class)
private BigDecimal amount;
```

**Option C**: 전역 ObjectMapper 설정
```java
// BigDecimal을 항상 문자열로 직렬화
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_STRING, true);
// 주의: 이것은 deprecated — Jackson 2.15+에서는 다른 방법 필요
```

**권장**: Option B — 필드별 명시적 제어, 기존 Widget API 패턴과의 일관성 유지.

---

## 3. [MEDIUM] DateTime 포맷 불일치

### 현상

| openapi.json 스펙 | 실제 Java 출력 |
|-------------------|---------------|
| `"format": "date-time"` (ISO 8601) | `"yyyy-MM-dd HH:mm:ss"` |
| `"2026-03-31T14:30:45Z"` 기대 | `"2026-03-31 14:30:45"` 실제 |

### 영향받는 필드 (12개 / 7 DTOs)

| DTO | 필드 |
|-----|------|
| `TransactionHistoryResponse` | partnerConfirmedAt, createdAt, confirmedAt |
| `CurrencyPriceResponseV1` | lastUpdated |
| `CurrencyConversionResponse` | convertedAt |
| `WithdrawalResponseV1` | createdAt |
| `DepositWalletResponse` | createdAt |
| `ChainActivationResponse` | activatedAt |
| `CallbackTestResponse` | sentAt |

### 수정 방안

openapi.json 스펙 설명을 실제 포맷에 맞추거나, 코드를 ISO 8601로 변경:

- **현실적 선택**: 스펙 설명 수정 (`format: "date-time"` → `pattern: "yyyy-MM-dd HH:mm:ss"`)
  → V1 호환성 유지, 기존 클라이언트 영향 없음
- **이상적 선택**: Java `@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")`로 통일
  → V2 신규 API면 가능, 기존 V1 클라이언트 호환성 깨짐

---

## 4. [LOW] permissions 타입 불일치

### 현상

| 항목 | 값 |
|------|-----|
| **openapi.json** | `permissions: string` |
| **Java DTO** | `Set<String> permissions` |
| **실제 JSON** | `"permissions": ["DEPOSIT","WITHDRAWAL"]` (배열) |

### 위치

`WidgetTokenResponse.java` → `/widgets/auth/token` 응답

### 수정

openapi.json 스펙에서 `type: "array"` + `items: { type: "string" }` 로 수정해야 함.
(Generator가 `Set<String>`을 단일 `string`으로 잘못 해석)

---

## 5. [MEDIUM] 보안: partnerId 노출

### 현상

`/widgets/api/exchange-rates` 응답에 `partnerId`가 포함:

```json
{
  "partnerId": 7,
  "baseCurrency": "USD",
  "rates": { "usdKrw": 1380.5, "krwUsd": 0.000724 },
  "lastUpdated": 1711872000
}
```

Widget API는 파트너 사용자가 호출하는 API — 내부 `partnerId`(DB PK) 노출은 보안 우려.

### 수정

`ExchangeRateResponse`에서 `partnerId` 필드 제거 또는 `@JsonIgnore` 적용.

---

## 6. openapi.json에 누락된 스키마

다음 Java DTO가 openapi.json에 정의되어 있지 않음:

| Java DTO | 사용처 |
|----------|--------|
| `DepositCompleteResponse` | 입금 완료 응답 |
| `AuthResponse` | 인증 응답 |
| `WidgetConfigResponse` | 위젯 설정 응답 |

→ Generator가 해당 컨트롤러를 스캔하지 못했거나, `basePackage` 범위 밖일 가능성.

---

## 7. Widget vs V1 타입 컨벤션 정리

| 구분 | 금액 타입 | DateTime | 비고 |
|------|----------|----------|------|
| Widget API (`/widgets/api/*`) | **String** | String (ISO) | ✅ 정밀도 보존 |
| V1 Server API (`/api/v1/*`) | **BigDecimal (Number)** | `yyyy-MM-dd HH:mm:ss` | ⚠️ 정밀도 손실 |
| openapi.json 스펙 | `"format": "decimal"` (Number) | `"format": "date-time"` (ISO) | 불일치 |

**최종 권장**: V1 API도 금액을 String으로 통일 (Option B: `@JsonSerialize(using = ToStringSerializer.class)`)

---

## 수정 우선순위

| 순위 | 이슈 | 작업량 | 영향도 |
|------|------|--------|--------|
| **P0** | SessionData 노출 | `@XApiIgnore` 2줄 추가 → 재생성 | API 소비자가 잘못된 필수 파라미터 전송 |
| **P1** | BigDecimal 정밀도 | 23개 필드에 `@JsonSerialize` 추가 | JS 클라이언트 정밀도 손실 |
| **P2** | DateTime 포맷 | 스펙 설명 수정 or 코드 변경 | 클라이언트 파싱 오류 가능 |
| **P3** | partnerId 노출 | 1개 필드 `@JsonIgnore` | 보안 (낮은 위험) |
| **P4** | permissions 타입 | Generator 이슈 → 스펙 수동 수정 | Swagger UI 표시 오류 |
| **P5** | 누락 스키마 | basePackage 확인 | 문서 완성도 |
