Skip to content

Commit 5ca4059

Browse files
committed
Add project guidelines and architecture documentation
1 parent 90b9833 commit 5ca4059

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

.github/copilot-instructions.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Project Guidelines
2+
3+
## Overview
4+
5+
RSS Reader is a zero-dependency Java 11+ library for parsing RSS and Atom feeds, published to Maven Central as `com.apptasticsoftware:rssreader`. It uses JPMS (`module com.apptasticsoftware.rssreader`) and StAX for XML parsing.
6+
7+
## Build and Test
8+
9+
```sh
10+
./gradlew build # compile + test + JaCoCo coverage
11+
./gradlew test # tests only
12+
./gradlew javadoc # generate API docs
13+
./gradlew jacocoTestReport # coverage report
14+
```
15+
16+
Java 11 source/target compatibility. No runtime dependencies — only test-scoped: JUnit 5, AssertJ, Mockito, EqualsVerifier.
17+
18+
## Key Files
19+
20+
| File | Purpose |
21+
|------|---------|
22+
| `src/main/java/module-info.java` | JPMS exports — update when adding packages |
23+
| `src/main/java/.../AbstractRssReader.java` | Base parser: StAX, HTTP, extension registry |
24+
| `src/main/java/.../FeedReader.java` | Unified reader registering ALL modules |
25+
| `src/main/java/.../FeedExtensionRegistry.java` | Tag → setter mapping API |
26+
| `src/main/java/.../util/Mapper.java` | Type conversion utilities |
27+
| `src/main/java/.../XMLInputFactorySecurity.java` | XXE protection hardening |
28+
29+
## Architecture
30+
31+
### Core class hierarchy
32+
33+
- `AbstractRssReader<C extends Channel, I extends Item>` — generic base handling StAX parsing, HTTP, filters, timeouts
34+
- `RssReader` — basic reader (no extensions)
35+
- `FeedReader` — unified reader registering ALL module extensions via composed `FeedChannel`/`FeedItem`
36+
- `Channel` / `Item` — public interfaces; `ChannelImpl` / `ItemImpl` — internal implementations
37+
38+
### Module extension pattern
39+
40+
Each namespace module lives in `src/main/java/com/apptasticsoftware/rssreader/module/<name>/` and follows this structure:
41+
42+
| File | Purpose |
43+
|------|---------|
44+
| `XyzChannel` | Interface extending `Channel` + `XyzChannelData` |
45+
| `XyzItem` | Interface extending `Item` + `XyzItemData` |
46+
| `XyzChannelData` / `XyzItemData` | Data interfaces with default getter/setter methods delegating to `getXyzItemData()` |
47+
| `XyzExtensions` | Static `register()` method wiring XML tags → setters via `FeedExtensionRegistry` |
48+
| `XyzFeedReader` | Standalone reader extending `AbstractRssReader` |
49+
| `internal/XyzChannelImpl` | Extends `ChannelImpl`, composes `XyzChannelDataImpl` |
50+
| `internal/XyzItemImpl` | Extends `ItemImpl`, composes `XyzItemDataImpl` |
51+
| `internal/XyzChannelDataImpl` / `XyzItemDataImpl` | Plain data holders with equals/hashCode |
52+
53+
Modules: atom, content, dc, georss, itunes, mediarss, opensearch, podcast, psc, slash, spotify, wfw, youtube.
54+
55+
When adding a new module, update these three locations:
56+
1. `module-info.java` — add `exports` for the new package
57+
2. `FeedReader.registerChannelTags()` — call `XyzExtensions.register(registry)`
58+
3. `FeedChannel`/`FeedItem` interfaces and their internal impls — compose new data interfaces
59+
60+
### Extension registration API
61+
62+
`FeedExtensionRegistry` provides these registration methods in `XyzExtensions.register()`:
63+
64+
```java
65+
// Map XML tag text content to a setter
66+
registry.addChannelExtension("ns:tag", XyzChannel::setSomeField);
67+
registry.addItemExtension("ns:tag", XyzItem::setSomeField);
68+
69+
// Map XML attribute value to a setter
70+
registry.addItemExtension("ns:tag", "attributeName", XyzItem::setSomeField);
71+
72+
// Callback on tag start (e.g., to initialize lists)
73+
registry.addOnItemTag("ns:tag", item -> { /* on START_ELEMENT */ });
74+
```
75+
76+
Use `Mapper` for non-String types: `(item, value) -> mapInteger(value, item::setCount)`
77+
78+
## Conventions
79+
80+
- **Getters return `Optional<T>`** for nullable fields; setters accept direct types
81+
- **Lists** for multi-valued fields (categories, enclosures)
82+
- **Internal packages** (`internal/`) are not exported — keep implementation details there
83+
- **Type conversions** use `Mapper` utilities (`mapBoolean`, `mapInteger`, `mapLong`, `mapDouble`)
84+
- **Deprecation**: Use `@Deprecated(since="X.Y.Z", forRemoval=true)` with migration guidance
85+
- **XML security**: `XMLInputFactorySecurity` hardens the StAX parser against XXE — never bypass it
86+
87+
## Testing
88+
89+
- **JUnit 5** with **AssertJ** assertions (including `hasValue()` for Optional assertions)
90+
- **Parameterized tests** via `@ParameterizedTest` + `@MethodSource` — test both the standalone `XyzFeedReader` AND the unified `FeedReader`
91+
- **EqualsVerifier** for equals/hashCode contracts on all `*Impl` and `*DataImpl` classes — ignore `"defaultComparator"` and `"dateTimeParser"` fields in `*ItemImpl`/`*ChannelImpl`
92+
- **Mockito** for HTTP response mocking
93+
- **Fixtures** in `src/test/resources/` — module-specific XML in `module/<name>/`
94+
- Load fixtures via `getClass().getClassLoader().getResourceAsStream(path)`
95+
96+
## Pitfalls
97+
98+
- **Namespace awareness is OFF** (`IS_NAMESPACE_AWARE=false`) — XML tags use their prefixed form (e.g., `slash:comments`, not just `comments`)
99+
- **`IS_COALESCING=true`** — adjacent text/CDATA are merged; don't rely on individual text events
100+
- **Data interface delegation pattern**: `XyzItemData.getXyzItemData()` returns `this` in the data impl but delegates in the composite — don't break this chain
101+
- **Module test fixture naming**: Place XML files in `src/test/resources/module/<name>/` matching the module name

0 commit comments

Comments
 (0)