Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
bf7cef9
Alerts API implementation
lbkulinski Jul 7, 2026
687a05a
Route status API
lbkulinski Jul 11, 2026
2443c70
Route status API
lbkulinski Jul 13, 2026
570c79c
Route status API
lbkulinski Jul 13, 2026
7c52ae1
Route status API
lbkulinski Jul 16, 2026
4518c76
Route status API
lbkulinski Jul 17, 2026
d78e278
Add route status tests
lbkulinski Jul 18, 2026
d3f43c6
Merge remote-tracking branch 'origin/main' into v7.0.0
lbkulinski Jul 18, 2026
4e1611d
GitHub Actions version bumps
lbkulinski Jul 18, 2026
3616b27
GitHub Actions version bumps
lbkulinski Jul 18, 2026
dedcc04
Address GitHub Copilot comments
lbkulinski Jul 18, 2026
16e9929
Add README badges
lbkulinski Jul 18, 2026
4702ea1
gitignore updates
lbkulinski Jul 18, 2026
f9a39f3
Remove separate TrainRouteStatus model
lbkulinski Jul 19, 2026
7158160
Moved ServiceType into a common package and added AlertQuery
lbkulinski Jul 19, 2026
75dc9e2
Update Javadoc comments to replace "arrival information" with "arrivals"
lbkulinski Jul 19, 2026
a378249
Refactor alert-related classes and update package structure for detai…
lbkulinski Jul 19, 2026
2f2003a
Add detailed alert model classes and response handling
lbkulinski Jul 19, 2026
3d7a7f4
Add detailed documentation for Alert, ImpactedService, and Severity c…
lbkulinski Jul 19, 2026
f1bbf76
Update Javadoc comments for clarity and consistency across various cl…
lbkulinski Jul 19, 2026
aa7a55e
Refactor Javadoc comments for clarity in ImpactedService
lbkulinski Jul 19, 2026
423b881
Add detailed query classes for alerts: LineAlertsQuery, BusRouteAlert…
lbkulinski Jul 20, 2026
b3e3409
Add methods for querying alerts by bus route, line, and station IDs i…
lbkulinski Jul 21, 2026
ddd8049
Rename query classes for consistency: StopsPredictionsQuery to StopPr…
lbkulinski Jul 22, 2026
ccfed4b
API interface cleanup/refactor
lbkulinski Jul 22, 2026
8301873
Add DetailedAlertsApi and custom exception for detailed alerts handling
lbkulinski Jul 24, 2026
6578366
Add AlertMapper, BooleanParser, and TimestampParser utility classes f…
lbkulinski Jul 25, 2026
03f5903
Add ttim and guid fields to Alert and ImpactedService models; update …
lbkulinski Jul 26, 2026
d110112
Refactor mapping methods in AlertMapper and LocationTrainMapper for c…
lbkulinski Jul 28, 2026
0175e70
Implement DetailedAlertsApi and enhance AlertApiImpl; update paramete…
lbkulinski Jul 28, 2026
53c3837
Refactor DetailedAlertsApiImpl to streamline request parameters; repl…
lbkulinski Jul 28, 2026
c196f1e
Move TrainLine back to the train package and introduce AlertTrainLine…
lbkulinski Jul 28, 2026
14d66a4
Refactor CtaAlert and DetailedAlertsApiImpl to make ttim and guid fie…
lbkulinski Jul 28, 2026
bc25d3f
Refactor ArrivalMapper and TrainLocationsMapper; update package struc…
lbkulinski Jul 28, 2026
7a816c0
Add tests for DetailedAlertsApiImpl and AlertMapper; validate alert m…
lbkulinski Jul 28, 2026
4501db9
Fix test files to ensure proper newline at end of file; enhance reada…
lbkulinski Jul 28, 2026
4647414
added testing cleanup
lbkulinski Jul 28, 2026
2e0be0c
Markdown Javadoc comments
lbkulinski Jul 29, 2026
a62295e
Markdown Javadoc comments
lbkulinski Jul 29, 2026
90f1df7
Markdown Javadoc comments
lbkulinski Jul 29, 2026
2890781
Markdown Javadoc comments
lbkulinski Jul 29, 2026
a855271
Javadoc updates and package-info.java files
lbkulinski Jul 29, 2026
4213859
Javadoc consistency pass
lbkulinski Jul 30, 2026
3918fff
package-info.java newlines
lbkulinski Jul 30, 2026
4840fdd
API consistency review
lbkulinski Jul 31, 2026
c337a63
Update Javadoc comments to replace `@apiNote` with **NOTE:** for clarity
lbkulinski Jul 31, 2026
b626d1d
Add unknown train line after seeing CTA behavior
lbkulinski Jul 31, 2026
6b2036e
Add support for multiple map and stop IDs for the arrivals api
lbkulinski Aug 1, 2026
df2f853
Additional test coverage
lbkulinski Aug 1, 2026
6d7b9f5
Javadoc fixes
lbkulinski Aug 1, 2026
10ee69f
Nullable Javadoc fix and CLAUDE.md update
lbkulinski Aug 1, 2026
f52a22f
Remove unknown train line and make it a nullable field
lbkulinski Aug 1, 2026
2f155f2
Fix exception Javadoc
lbkulinski Aug 1, 2026
8497050
Update CHANGELOG and CLAUDE.md for 7.0.0 release; add AlertApi and re…
lbkulinski Aug 1, 2026
10a98ba
Remove UNKNOWN error codes in favor of null
lbkulinski Aug 1, 2026
5ae1ed9
Import ordering fixes
lbkulinski Aug 1, 2026
2215d0b
Pre-merge review fixes
lbkulinski Aug 1, 2026
3668a56
README updates
lbkulinski Aug 1, 2026
6dc82bf
Merge remote-tracking branch 'origin/main' into v7.0.0
lbkulinski Aug 2, 2026
7270e1e
CHANGELOG.md updates
lbkulinski Aug 2, 2026
b2bfcbd
Update Java version to 25 in build and release configurations
lbkulinski Aug 2, 2026
c3e9faa
Refactor LocationsApi methods
lbkulinski Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7

- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v5.6.0
with:
distribution: "corretto"
java-version: "21"
cache: "maven"
distribution: 'corretto'
java-version: '25'
cache: 'maven'

- name: Read project version from pom.xml
id: ver
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0
fetch-tags: true

- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v5.6.0
with:
distribution: corretto
java-version: '21'
java-version: '25'
server-id: central
server-username: OSSRH_USERNAME
server-password: OSSRH_PASSWORD
Expand Down
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ target/
.kotlin

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/
*.iws
*.iml
*.ipr
Expand Down Expand Up @@ -37,3 +34,6 @@ build/

### Mac OS ###
.DS_Store.superpowers/

### Misc ###
.env
58 changes: 57 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [7.0.0] - 2026-08-01

### Added

- A new `AlertApi` composed entry point for the CTA Customer Alerts API, unauthenticated and constructed via
`AlertApi.builder().build()` (no API key required).
- `RouteStatusApi`, exposing route/line service status (`list()`, `findByTypes`, `findByBusRouteIds`,
`findByLines`, `findByStationId`).
- `DetailedAlertsApi`, exposing detailed service alerts (`list(AlertsQuery)`, plus filtered lookups by bus
route IDs, train lines, and station IDs).
- New domain models (`RouteStatus`, `Alert`, `ImpactedService`, `Severity`, `AlertTrainLine`, `ServiceType`),
feature-specific exceptions (`Cta4jRouteStatusException`, `Cta4jDetailedAlertsException`, both extending
the new shared `Cta4jAlertException`), and error-code enums (`RouteStatusErrorCode`,
`DetailedAlertsErrorCode`).
- `ArrivalsApi.findByMapIds`/`findByStopIds` — multi-value lookups accepting up to 4 map/stop IDs per request
(`MapArrivalsQuery`/`StopArrivalsQuery`, plus `Collection<String>` convenience overloads), alongside the
existing single-ID `findByMapId`/`findByStopId`.
- `PredictionsApi.findByStopIds`/`findByVehicleIds` — convenience overloads accepting a bare
`Collection<String>` without requiring a full `StopPredictionsQuery`/`VehiclePredictionsQuery` to be
constructed, matching `ArrivalsApi`'s equivalent overloads.

### Changed

- `TrainLine.fromCode(String)` no longer throws `IllegalArgumentException` for an unrecognized code; it now
returns `null` and logs a warning, matching the `@Nullable`-based degrade pattern used elsewhere in the SDK.
- `ArrivalsErrorCode.fromCode`/`FollowErrorCode.fromCode`/`LocationsErrorCode.fromCode` no longer fall back to
an `UNKNOWN` constant for an unrecognized code; they now return `null`, matching the same degrade pattern as
`TrainLine.fromCode`.
- Renamed `VehiclesApi.findByIds`'s parameter from `ids` to `vehicleIds`, and `findById`'s parameter from `id`
to `vehicleId`, for consistency with `StopsApi`/`PatternsApi`'s equivalent methods.
- Bumped `tools.jackson.core:jackson-databind` from **3.2.0** → **3.2.1**
- Bumped `org.apache.httpcomponents.client5:httpclient5-fluent` from **5.6.1** → **5.6.2**
- Bumped `ch.qos.logback:logback-classic` from **1.5.38** → **1.6.0**

### Breaking Changes ⚠️

- **Builder constructors are now `private`** on all query-parameter builders (`StopPredictionsQuery`,
`VehiclePredictionsQuery`, `MapArrivalsQuery`, `StopArrivalsQuery`, `AlertsQuery`, `BusRouteAlertsQuery`,
`LineAlertsQuery`, `StationAlertsQuery`). Construct instances via the static `builder(...)` factory method
only.
- Renamed `StopsPredictionsQuery`/`VehiclesPredictionsQuery` to `StopPredictionsQuery`/`VehiclePredictionsQuery`
for consistency with the rest of the query-builder naming.
- `MapArrivalQuery`/`StopArrivalQuery` have been renamed and reshaped to `MapArrivalsQuery`/`StopArrivalsQuery`:
the single `String mapId`/`stopId` component is now a `List<String> mapIds`/`stopIds` component (max 4 IDs).
- `ArrivalsApi.findByMapId(MapArrivalQuery)`/`findByStopId(StopArrivalQuery)` have been renamed to
`findByMapIds(MapArrivalsQuery)`/`findByStopIds(StopArrivalsQuery)` to match the new query types.
- `ArrivalsErrorCode.UNKNOWN`/`FollowErrorCode.UNKNOWN`/`LocationsErrorCode.UNKNOWN` have been removed; code
that referenced these constants directly (e.g. `switch` statements, equality checks) must handle `null`
instead.
- `MapArrivalsQuery.Builder.maxResults`/`StopArrivalsQuery.Builder.maxResults` now take `int` instead of
`Integer`; passing `null` no longer compiles.
- `Arrival.line`/`TrainLocations.line` are now `@Nullable`, a direct consequence of `TrainLine.fromCode` no
longer throwing — code that assumed either was always non-null must add a null check.
- `LocationsApi.findAll()` has been renamed to `LocationsApi.list()`.

## [6.0.0] - 2026-07-05

### Added
Expand Down Expand Up @@ -267,7 +322,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `TrainClient` class with methods to interact with CTA Train API.
- `BusClient` class with methods to interact with CTA Bus API.

[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v6.0.0...HEAD
[Unreleased]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v7.0.0...HEAD
[7.0.0]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v6.0.0...v7.0.0
[6.0.0]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v5.0.0...v6.0.0
[5.0.0]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v4.1.0...v5.0.0
[4.1.0]: https://github.com/lbkulinski/cta4j-java-sdk/compare/v4.0.3...v4.1.0
Expand Down
80 changes: 71 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

## Project Overview

Java SDK for the CTA Bus Tracker and Train Tracker APIs. Published to Maven Central. Consumers instantiate `BusApi` or `TrainApi` via their respective builders and access sub-APIs from there.
Java SDK for the CTA Bus Tracker, Train Tracker, and Customer Alerts APIs. Published to Maven Central. Consumers instantiate `BusApi`, `TrainApi`, or `AlertApi` via their respective builders and access sub-APIs from there.

## Entry Points

```java
BusApi busApi = BusApi.builder("apiKey").build();
TrainApi trainApi = TrainApi.builder("apiKey").build();
AlertApi alertApi = AlertApi.builder().build();
```

Both builders accept an optional `.host(String)` override; `TrainApi.Builder` also accepts `.stationsUrl(String)`.
`BusApi`/`TrainApi` builders require an API key and accept an optional `.host(String)` override; `TrainApi.Builder` also accepts `.stationsUrl(String)`. The CTA Customer Alerts API is unauthenticated, so `AlertApi.builder()` takes no API key and only accepts an optional `.host(String)` override.

## API Surface

Expand All @@ -32,9 +33,13 @@ Both builders accept an optional `.host(String)` override; `TrainApi.Builder` al
- `follow()` — `FollowApi`
- `locations()` — `LocationsApi`

**Alert (`AlertApi`)**
- `routeStatus()` — `RouteStatusApi`
- `detailedAlerts()` — `DetailedAlertsApi`

## Package Layout

Transit type (`bus`, `train`) then feature then layer:
Transit type (`bus`, `train`, `alert`) then feature then layer:

```
com.cta4j.bus.route/
Expand All @@ -46,33 +51,39 @@ com.cta4j.bus.route/
mapper/RouteMapper.java ← MapStruct mapper
```

Shared types live in `bus/common/` or `train/common/`. Cross-cutting types (geo, exceptions) live in `common/` or `exception/`.
Shared types live in `bus/common/`, `train/common/`, or `alert/common/`. Cross-cutting types (geo, exceptions) live in `common/` or `exception/`.

## Wire Layer Conventions

- All wire records are `@ApiStatus.Internal` and not part of the public API.
- **Bus**: each feature has a typed `Cta<Feature>BustimeResponse` record (envelope field `bustimeResponse`, mapped from `"bustime-response"`) with `@Nullable` fields for both the data list and a typed `Cta<Feature>Error` list. Each `Cta<Feature>Error` implements `CtaError` and overrides `notFound()` using its own typed fields (e.g. `rt`, `stpid`, `vid`) to identify which input caused the error — do not collapse these into a generic map.
- **Train**: there is no shared `CtaError`-style record. Each feature's wire response record carries `errCd`/`errNm` fields directly; `errCd` is parsed to an `int` and mapped via `<Feature>ErrorCode.fromCode(int)` to a feature-specific enum (e.g. `ArrivalsErrorCode`) whose constants identify resource-specific ("not found") codes.
- **Alert**: like Train, there is no shared `CtaError`-style record, and each feature's error shape is its own — do not assume `RouteStatus` and `DetailedAlerts` match each other. `CtaRoutes` (route status) carries `ErrorCode`/`ErrorMessage` as `List<String>`, since the CTA API can return multiple distinct codes for a single request; `CtaAlerts` (detailed alerts) carries a single non-nullable `ErrorCode` `String` and a `@Nullable ErrorMessage` `String`. Both map their code(s) via a feature-specific `<Feature>ErrorCode.fromCode(int)` enum (`RouteStatusErrorCode`, `DetailedAlertsErrorCode`), following the Train pattern.
- All wire records use `@JsonIgnoreProperties(ignoreUnknown = true)`.
- The outer envelope is `CtaResponse<T>`, with a `bustimeResponse` field (bus) or a `ctatt` field (train).
- **Bus and Train** share a single generic `CtaResponse<T>` record per module (`bus/common/internal/wire/CtaResponse`, `train/common/internal/wire/CtaResponse`) with a fixed field name — `bustimeResponse` (bus) or `ctatt` (train).
- **Alert** has no shared generic envelope type; each feature declares its own concretely-typed response record with its own field name (e.g. `CtaRouteStatusResponse.ctaRoutes` mapped from `"CTARoutes"`, `CtaDetailedAlertsResponse.ctaAlerts` mapped from `"CTAAlerts"`).

## Error Handling Pattern

**Bus** `*ApiImpl` classes returning a `List` follow this pattern in `makeRequest`:

1. If the data list is non-null and non-empty → map and return it.
2. Otherwise, call `ApiUtils.checkErrors(errors, endpoint)` (`bus/common/internal/util/ApiUtils`): it logs a warn and returns if the error list is null/empty, returns if every error's `notFound()` is `true`, or throws `Cta4jBusException` otherwise.
2. Otherwise, call `BusApiUtils.checkErrors(errors, endpoint)` (`bus/common/internal/util/BusApiUtils`): it logs a warn and returns if the error list is null/empty, returns if every error's `notFound()` is `true`, or throws `Cta4jBusException` otherwise.
3. Return `List.of()`.

`SystemTimeApiImpl` is the one exception: it returns a single `Instant`, not a `List`, so a missing value has no valid "empty" result — it throws directly instead of calling `ApiUtils.checkErrors`.
`SystemTimeApiImpl` is the one exception: it returns a single `Instant`, not a `List`, so a missing value has no valid "empty" result — it throws directly instead of calling `BusApiUtils.checkErrors`.

**Train** `*ApiImpl` classes follow a related but distinct pattern (no shared helper — each impl inlines it with its own exception type): parse `errCd` to the feature's `*ErrorCode` enum; if it's a resource-specific not-found code → return an empty result (or `Optional.empty()`); if it isn't `OK` → throw the feature-specific exception (e.g. `Cta4jArrivalsException`) using `errNm` as the message, falling back to a default message when `errNm` is `null` or blank.

**Alert** `*ApiImpl` classes follow the same inlined, no-shared-helper pattern as Train, but each feature's `errCd`/`errNm` shape differs (see Wire Layer Conventions above): `RouteStatusApiImpl` reads the first of a possibly-multi-value error-code list (logging a warn if more than one distinct code is present) and falls back to `"An unknown error occurred."` when the message is null/blank; `DetailedAlertsApiImpl` reads the single scalar `errCd`/`errNm` directly. Both throw their feature-specific exception (`Cta4jRouteStatusException`/`Cta4jDetailedAlertsException`, both extending the shared `Cta4jAlertException`) for any code other than `OK`/not-found.

`find*` methods return an empty `List` (or `Optional.empty()`) for not-found; they never throw for missing resources.

## Annotation Ordering
## Annotations

Always apply annotations in this order: **Jackson/framework → `@ApiStatus.Internal` → `@NullMarked`**
- Annotations always stack one per line above the declaration.
- They should adhere to the following order: Jackson/framework → `@ApiStatus.Internal` → `@NullMarked`.
- This applies to classes, interfaces, records, fields, and methods alike.

```java
@JsonIgnoreProperties(ignoreUnknown = true)
Expand All @@ -81,6 +92,11 @@ Always apply annotations in this order: **Jackson/framework → `@ApiStatus.Inte
public record CtaFoo(...) { }
```

- **Exception — Record components:** A component with a single annotation may keep it inline with the type
(`@Nullable String foo`) instead of stacking. Two or more annotations on a component still stack one per
line above the type, and when any component in a record stacks, blank-line-separate every component in
that record (see `CtaLocation` vs. the single-annotation `CtaStation`).

## Null Safety

- All classes, records, interfaces, and enums in `src/main` must have `@NullMarked`.
Expand All @@ -99,3 +115,49 @@ MapStruct mappers live in `internal/mapper/`. They are interfaces annotated with
- No comments unless the why is non-obvious.
- No `Optional` for fields or parameters. Use method overloading or `@Nullable` fields instead.
- Prefer `List.of()` for empty returns; use `List.copyOf()` for defensive copies.
- All files must end with a trailing newline.

## Javadoc Conventions

This project uses Markdown documentation comments (`///`, JEP 467, JDK 23+)
instead of traditional `/** */` HTML Javadoc. Do not use `{@code}`, `{@link}`,
or HTML tags — use plain Markdown (backticks, `[Type]` links, etc.).

References:
- Content/style conventions (summary sentence, tag usage):
https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html
- Markdown comment syntax (`///`, CommonMark extensions, JDK 23+):
https://docs.oracle.com/en/java/javase/25/javadoc/using-markdown-documentation-comments.html

- **Summary sentence:** The first line is a standalone summary fragment
ending in a period, third-person descriptive ("Returns the active
arrivals for a station," not "This method returns...").
- **Single-item accessor verb:** Always "Returns the X" — no exceptions.
This applies uniformly to every single-value accessor, including
wire-code accessors on domain-value enums (e.g. `getCode()` on
`TransitMode`, `DynamicAction`, `TrainLine`) and numeric/status-code
accessors on error-code enums or exceptions alike. Do not use "Gets the
X" to distinguish the two.
- **Builder creator methods:** One template for every builder, top-level
client builders and query-parameter builders alike — no terser variant.
The static `builder(...)` method: "Creates a new `Builder` for
constructing a/an `X`.", `@return` tag "a new `Builder`". The `build()`
method: "Builds a configured `X` instance.", `@return` tag "a new `X`".
- **Builder setter `@return`:** Always backticked, "this `Builder` instance" —
never the unbacked "this builder instance" variant.
- **Tag order:** `@param` → `@return` → `@deprecated` → `@since` → `@throws`
→ `@see`.
- **@param / @throws descriptions:** Lowercase phrase, no trailing period.
- **Code references:** Use backtick spans (`` `RoutesApi` ``, `` `List<Route>` ``)
instead of `{@code}`. Use Markdown reference links (`[RoutesApi]`) instead
of `{@link}` only when the cross-reference meaningfully aids understanding.
- **What gets documented:** Public interfaces (`*Api`), public domain models
(`model/`), and builders always. Wire records (`internal/wire/`), mappers,
and `*ApiImpl` classes are `@ApiStatus.Internal` and are not documented
unless the "why" is non-obvious (per Code Style).
- **Package docs:** Every package containing at least one non-internal
type (public `*Api` interfaces, public domain models, builders — per
"What gets documented" above) gets a `package-info.java` with a
one-paragraph summary of the package's responsibility, written in the
same Markdown style. Packages containing only `internal/wire`, mapper,
or `*ApiImpl` classes do not require a package-info.java.
Loading
Loading