171 lines
7.9 KiB
Markdown
171 lines
7.9 KiB
Markdown
# PRD – CSV→OutputImageFormat Parser (Erweiterung zum „PRD – Java‑native Bildkonvertierung“)
|
||
|
||
**Ziel:** Robustes Parsen einer komma‑separierten Zeichenkette (CSV) in eine **ordnungserhaltende, doppeltfreie** `List<OutputImageFormat>` – **ohne** externe Abhängigkeiten. Unterstützt **Synonyme** (z. B. `jpg`→`JPEG`, `tif`→`TIFF`), führende Punkte (`.jpg`), **MIME‑Typen** (`image/jpeg`) und **ImageIO‑Formatnamen** (`jpeg`). Fehlerhafte Tokens führen wahlweise zu **Exception** oder werden „lenient“ ignoriert.
|
||
|
||
> Dieses PRD ergänzt das bestehende Dokument „PRD – Java‑native Bildkonvertierung (Spring Boot, OSS)“ und liefert die Spezifikation für den CSV‑Parser, der z. B. REST‑Parameter `allowed` (CSV) in `List<OutputImageFormat>` transformiert.
|
||
|
||
---
|
||
## 1. Scope
|
||
**In Scope**
|
||
- Utility‑Klasse zum Parsen einer CSV‑Liste in `List<OutputImageFormat>`.
|
||
- Case‑insensitive, Whitespace‑tolerant, führende `.` entfernen.
|
||
- Synonym‑Mapping für verbreitete Varianten (jpg/jpeg, tif/tiff, jp2/j2k/jpeg2000, webp, mime‑typen etc.).
|
||
- **Dedup** bei gleichzeitigem Erhalt der **Eingabereihenfolge**.
|
||
- Zwei Modi:
|
||
- **Strict:** Unbekannte Tokens → `IllegalArgumentException`.
|
||
- **Lenient:** Unbekannte Tokens werden **ignoriert**.
|
||
- Öffentliche Helper‑API für Single‑Token‑Lookup.
|
||
|
||
**Out of Scope**
|
||
- Validierung gegen tatsächlich installierte Writer/Reader (das übernimmt die Konvertierungsschicht).
|
||
- Automatisches Nachladen von externen Plugins.
|
||
|
||
---
|
||
## 2. Nicht‑funktionale Anforderungen
|
||
- **Zero‑Deps**: reine JDK‑Funktionen, keine Tika/Guava etc.
|
||
- **Performance**: O(n) über Anzahl Tokens; Lookup über vorkompilierte `Map`.
|
||
- **Threadsafety**: statische, unveränderliche Lookup‑Map (unmodifiable); Parser‑Methoden sind reentrant und thread‑safe.
|
||
- **Internationalisierung**: nicht relevant (Tokens sind technische Kürzel/MIME‑Typen).
|
||
|
||
---
|
||
## 3. Domänenmodell / Abhängigkeiten
|
||
- **`OutputImageFormat` (Enum)** aus der Haupt‑PRD: liefert `extension`, `mimeType`, `imageIoFormatName`, `name()`.
|
||
- Synonyme werden **aus dem Enum** abgeleitet und um gebräuchliche Aliasse ergänzt:
|
||
- `JPEG` ⇄ `jpg` / `jpeg` / `image/jpeg`
|
||
- `TIFF` ⇄ `tif` / `tiff` / `image/tiff`
|
||
- `PNG` ⇄ `png` / `image/png`
|
||
- `BMP` ⇄ `bmp` / `image/bmp`
|
||
- `GIF` ⇄ `gif` / `image/gif`
|
||
- `WEBP` ⇄ `webp` / `image/webp`
|
||
- `JP2` ⇄ `jp2` / `j2k` / `jpeg2000` / `image/jp2`
|
||
|
||
> Hinweis: Falls einzelne Enum‑Werte (WEBP/JP2) im Projekt deaktiviert werden, bleibt das Mapping konsistent – Keys auf nicht existierende Enum‑Werte entfallen.
|
||
|
||
---
|
||
## 4. Öffentliche API
|
||
```java
|
||
public final class ImageFormatParsers {
|
||
/** Strict: unbekannte Tokens -> IllegalArgumentException */
|
||
public static List<OutputImageFormat> parseCsv(String csv);
|
||
|
||
/** Lenient: unbekannte Tokens werden ignoriert */
|
||
public static List<OutputImageFormat> parseCsvLenient(String csv);
|
||
|
||
/** Einzel‑Token‑Lookup (case‑insensitive, . entfernt, Synonyme & MIME) */
|
||
public static Optional<OutputImageFormat> lookup(String token);
|
||
}
|
||
```
|
||
|
||
**Verhalten**
|
||
- `null` oder leere/blank CSV → leere Liste.
|
||
- Trennung: `,` (Komma). Whitespace wird getrimmt, führende `.` entfernt (z. B. `.jpg`).
|
||
- Reihenfolge: Erstes Vorkommen gewinnt; weitere Duplikate werden ignoriert (LinkedHashSet‑Semantik).
|
||
- **Strict**: Tokens, die keinem Format zugeordnet werden können, sammeln und am Ende `IllegalArgumentException` mit Auflistung werfen.
|
||
- **Lenient**: Unbekannte Tokens stillschweigend überspringen.
|
||
|
||
---
|
||
## 5. Implementierungsdetails
|
||
- Statische `Map<String, OutputImageFormat> LOOKUP` mit allen erlaubten Schlüsseln (kleingeschrieben):
|
||
- `enum.name()` → `png`, `jpeg`, … (kleinbuchstabig)
|
||
- `extension()` → `png`, `jpg`, `tiff`, …
|
||
- `imageIoFormatName()` → typ. `png`, `jpeg`, `tiff` …
|
||
- `mimeType()` → `image/png`, `image/jpeg`, …
|
||
- **Zusatz‑Synonyme** je nach Enumwert (siehe §3).
|
||
- `normalize(String)`:
|
||
- `trim()`, `toLowerCase(Locale.ROOT)`, führenden Punkt entfernen.
|
||
- Deduplizierung: `LinkedHashSet<OutputImageFormat>` → anschließend in `List.copyOf(...)`.
|
||
|
||
**Beispiel (Strict)**
|
||
```
|
||
"PNG, JPEG, .tif, image/webp, png, jpg" → [PNG, JPEG, TIFF, WEBP]
|
||
```
|
||
|
||
---
|
||
## 6. Teststrategie (JUnit 5)
|
||
**Testklasse:** `ImageFormatParsersTest`
|
||
|
||
**A. Positivfälle**
|
||
1. **Grundlegend**: `"PNG,JPEG"` → `[PNG, JPEG]` (Reihenfolge, Größe=2).
|
||
2. **Synonyme**: `"jpg,JPEG"` → `[JPEG]`; `"tif,TIFF"` → `[TIFF]`.
|
||
3. **MIME‑Tokens**: `"image/jpeg, image/tiff"` → `[JPEG, TIFF]`.
|
||
4. **ImageIO‑Formatname**: `"jpeg,tiff"` → `[JPEG, TIFF]`.
|
||
5. **Führender Punkt**: `".jpg, .tif"` → `[JPEG, TIFF]`.
|
||
6. **Whitespace & Case**: `" PnG , jPeG "` → `[PNG, JPEG]`.
|
||
7. **Dedup & Reihenfolge**: `"PNG, JPEG, png, jpeg"` → `[PNG, JPEG]`; `"jpeg,png,jpeg"` → `[JPEG, PNG]`.
|
||
8. **JP2‑Aliasse** (falls Enum enthält): `"j2k, jpeg2000, JP2"` → `[JP2]`.
|
||
|
||
**B. Strict‑Fehlerfälle**
|
||
9. **Unbekanntes Token**: `"foo,PNG"` → `IllegalArgumentException` mit Hinweis auf `foo`.
|
||
10. **Nur Unbekannt**: `"foo,bar"` → `IllegalArgumentException` (Liste aller unbekannten Tokens).
|
||
|
||
**C. Lenient‑Variante**
|
||
11. `"foo, PNG, bar"` → `[PNG]` (keine Exception).
|
||
|
||
**D. Kantenfälle**
|
||
12. **Leer/Null**: `null` → `[]`, `" "` → `[]`.
|
||
13. **Doppelte Kommas**: `","` bzw. `"PNG,,JPEG"` → `[PNG, JPEG]`.
|
||
|
||
**Hilfen**
|
||
- Parametrisierte Tests (`@ParameterizedTest`) für Synonym‑Mengen.
|
||
- AssertJ/JUnit Assertions für Reihenfolge & Inhalt.
|
||
|
||
---
|
||
## 7. Integration in die bestehende Lösung
|
||
- REST‑Controller: `allowed` (CSV) → `ImageFormatParsers.parseCsv(...)`; `preferred` (String) → `lookup(...)` + Fehler, wenn `preferred ∉ allowed`.
|
||
- `ImageFormatNegotiatorService`: Die geparste `allowedTargets`‑Liste und `preferredTarget` direkt übergeben.
|
||
- Konfiguration: Optionale Default‑Liste in `application.yml` (z. B. `images.allowed-default: "PNG,JPEG"`) – beim Booten parsen.
|
||
|
||
---
|
||
## 8. Akzeptanzkriterien (DoD)
|
||
- [ ] `parseCsv` (strict) wirft bei unbekannten Tokens eine `IllegalArgumentException` mit Token‑Liste.
|
||
- [ ] `parseCsvLenient` ignoriert unbekannte Tokens und gibt bekannte in Eingabereihenfolge (dedupliziert) zurück.
|
||
- [ ] Unterstützt Synonyme inkl. führender `.` und MIME‑Strings.
|
||
- [ ] Testabdeckung ≥ 90 % für `ImageFormatParsers` (Branches: strict/lenient, Synonyme, Kantenfälle).
|
||
- [ ] Thread‑safety gegeben (LOOKUP unveränderlich, Methoden reentrant).
|
||
|
||
---
|
||
## 9. Aufgabenliste (für Codex)
|
||
1. Klasse `ImageFormatParsers` anlegen, `LOOKUP` aus `OutputImageFormat` ableiten und Synonyme ergänzen.
|
||
2. Methoden `parseCsv`, `parseCsvLenient`, `lookup` implementieren.
|
||
3. Testklasse `ImageFormatParsersTest` mit den o. g. Fällen (JUnit 5, AssertJ optional) implementieren.
|
||
4. Beispiel‑Snippet in README/PRD (Verwendung aus REST‑Controller) ergänzen.
|
||
|
||
---
|
||
## 10. Beispiel‑Snippets
|
||
**Verwendung (REST)**
|
||
```java
|
||
List<OutputImageFormat> allowed = ImageFormatParsers.parseCsv(params.getAllowed());
|
||
OutputImageFormat preferred = ImageFormatParsers.lookup(params.getPreferred())
|
||
.orElseThrow(() -> new IllegalArgumentException("Unsupported preferred format"));
|
||
if (!allowed.contains(preferred)) {
|
||
throw new IllegalArgumentException("Preferred must be included in allowed");
|
||
}
|
||
```
|
||
|
||
**Lenient‑Konfiguration**
|
||
```java
|
||
List<OutputImageFormat> defaults = ImageFormatParsers.parseCsvLenient(env.getProperty("images.allowed-default"));
|
||
```
|
||
|
||
---
|
||
## 11. Paket & Dateistruktur (Vorschlag)
|
||
```
|
||
src/main/java/com/example/images/
|
||
OutputImageFormat.java
|
||
ImageFormatParsers.java
|
||
ImageConversionService.java
|
||
ImageFormatNegotiatorService.java
|
||
|
||
src/test/java/com/example/images/
|
||
ImageFormatParsersTest.java
|
||
```
|
||
|
||
---
|
||
## 12. Risiken & Gegenmaßnahmen
|
||
- **Token‑Drift**: Neue Enum‑Werte → Lookup beim Start aus Enum ableiten, Zusatz‑Synonyme zentral.
|
||
- **Fehlkonfiguration**: `preferred ∉ allowed` → Validierung in REST/Service‑Schicht.
|
||
- **Uneinheitliche Schreibweisen**: Abgefangen durch Normalisierung (trim, lowercase, Strip leading `.`).
|
||
|
||
— Ende —
|
||
|