Skip to content

Commit 2bd1bf5

Browse files
Merge pull request #1 from MarketDataApp/00_base_setup
initial Market Data Java SDK scaffold + CI
2 parents f94a704 + 44ee380 commit 2bd1bf5

40 files changed

Lines changed: 2183 additions & 0 deletions

.github/workflows/main.yml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
name: Main
2+
3+
# Runs only on push to main (i.e. when a PR is merged or someone pushes
4+
# directly). This is where the full forward-compat JDK matrix runs and
5+
# where we publish the canonical coverage snapshot that Codecov uses as
6+
# the base for PR diffs.
7+
on:
8+
push:
9+
branches: ['main']
10+
11+
permissions:
12+
contents: read
13+
14+
# Don't cancel main runs against each other — we want every merge to
15+
# produce a coverage baseline. Sequential is fine; main pushes are rare.
16+
concurrency:
17+
group: main
18+
cancel-in-progress: false
19+
20+
jobs:
21+
verify:
22+
name: Verify (JDK ${{ matrix.java }})
23+
runs-on: ubuntu-latest
24+
strategy:
25+
# Don't cancel siblings: if JDK 21 fails, we still want to know
26+
# whether 17 and 25 pass.
27+
fail-fast: false
28+
matrix:
29+
# ADR-002: tests run on JDK 17, 21, 25 to catch forward-compat
30+
# regressions. Compilation is always pinned to --release 17.
31+
java: ['17', '21', '25']
32+
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v4
36+
37+
# Install both JDK 17 (for compilation) and the matrix JDK (for
38+
# test execution). setup-java exports JAVA_HOME_<version>_<arch>;
39+
# Gradle's toolchain auto-detection picks them up.
40+
- name: Set up JDKs (compile=17, test=${{ matrix.java }})
41+
uses: actions/setup-java@v4
42+
with:
43+
distribution: temurin
44+
java-version: |
45+
17
46+
${{ matrix.java }}
47+
48+
- name: Set up Gradle
49+
uses: gradle/actions/setup-gradle@v4
50+
51+
- name: Build, test, lint, coverage
52+
run: ./gradlew build -PtestJdk=${{ matrix.java }} --stacktrace
53+
54+
- name: Upload test reports on failure
55+
if: failure()
56+
uses: actions/upload-artifact@v4
57+
with:
58+
name: test-reports-jdk${{ matrix.java }}
59+
path: |
60+
build/reports/tests/
61+
build/test-results/
62+
retention-days: 14
63+
64+
# The JDK 17 entry of the matrix is the canonical run for coverage:
65+
# its JaCoCo XML is uploaded to Codecov and becomes the base that
66+
# subsequent PR runs compare against (see codecov.yml).
67+
- name: Upload coverage to Codecov (JDK 17 only)
68+
if: success() && matrix.java == '17'
69+
uses: codecov/codecov-action@v5
70+
with:
71+
token: ${{ secrets.CODECOV_TOKEN }}
72+
files: build/reports/jacoco/test/jacocoTestReport.xml
73+
fail_ci_if_error: true
74+
75+
# Integration-tests job is intentionally not wired up yet:
76+
# SDK requirements §13 says they run on PRs and release pipelines, but
77+
# they hit the live API and require a MARKETDATA_TOKEN secret. Add this
78+
# job (gated on `if: ${{ secrets.MARKETDATA_TOKEN != '' }}`) once the
79+
# token is configured in the repo's GitHub Actions secrets.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
name: PR matrix (on demand)
2+
3+
# Manually triggered by commenting one of these slash commands on an
4+
# open PR:
5+
# /run-all-jdks
6+
# /jdk-matrix
7+
# /test-all
8+
# Runs the forward-compat matrix (JDK 21, 25) — JDK 17 already runs
9+
# automatically on every PR open/sync via pull-request.yml.
10+
#
11+
# Important security note: workflows triggered by `issue_comment` always
12+
# run from the *default branch's* version of the workflow file, not from
13+
# the PR. So adding/changing this file on a feature branch has no effect
14+
# until it lands on main.
15+
on:
16+
issue_comment:
17+
types: [created]
18+
19+
permissions:
20+
contents: read
21+
pull-requests: write # to react to the trigger comment
22+
23+
# Multiple "run all versions" comments on the same PR cancel earlier runs.
24+
concurrency:
25+
group: pr-on-demand-${{ github.event.issue.number }}
26+
cancel-in-progress: true
27+
28+
jobs:
29+
guard:
30+
name: Guard
31+
runs-on: ubuntu-latest
32+
# Only fire on PR comments (not generic issue comments) that contain
33+
# one of the three accepted slash commands. `contains` is substring
34+
# match — false positives are possible but unlikely in practice given
35+
# the leading slash and hyphen-rich shape of these tokens.
36+
if: |
37+
github.event.issue.pull_request != null && (
38+
contains(github.event.comment.body, '/run-all-jdks') ||
39+
contains(github.event.comment.body, '/jdk-matrix') ||
40+
contains(github.event.comment.body, '/test-all')
41+
)
42+
outputs:
43+
head_sha: ${{ steps.pr.outputs.head_sha }}
44+
steps:
45+
# Reject comments from anyone without write access. Otherwise an
46+
# external user commenting on a fork PR could burn our CI minutes
47+
# and potentially exfiltrate secrets via a malicious build.
48+
- name: Verify commenter has write permission
49+
uses: actions/github-script@v7
50+
with:
51+
script: |
52+
const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
53+
owner: context.repo.owner,
54+
repo: context.repo.repo,
55+
username: context.payload.comment.user.login,
56+
});
57+
const allowed = ['write', 'maintain', 'admin'].includes(perm.permission);
58+
if (!allowed) {
59+
core.setFailed(
60+
`@${context.payload.comment.user.login} (${perm.permission}) ` +
61+
`cannot trigger CI; write access required.`
62+
);
63+
}
64+
65+
# Visible feedback to the commenter that we picked up the trigger.
66+
- name: React 👀 to the trigger comment
67+
uses: actions/github-script@v7
68+
with:
69+
script: |
70+
await github.rest.reactions.createForIssueComment({
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
comment_id: context.payload.comment.id,
74+
content: 'eyes',
75+
});
76+
77+
# The issue_comment event payload doesn't include the PR's head SHA,
78+
# so look it up via the pulls API. We also confirm the PR is open;
79+
# firing on closed PRs is almost always a mistake.
80+
- name: Resolve PR head SHA
81+
id: pr
82+
uses: actions/github-script@v7
83+
with:
84+
script: |
85+
const { data: pr } = await github.rest.pulls.get({
86+
owner: context.repo.owner,
87+
repo: context.repo.repo,
88+
pull_number: context.payload.issue.number,
89+
});
90+
if (pr.state !== 'open') {
91+
core.setFailed(`PR #${pr.number} is ${pr.state}; refusing to run.`);
92+
return;
93+
}
94+
core.setOutput('head_sha', pr.head.sha);
95+
96+
verify:
97+
name: Verify (JDK ${{ matrix.java }})
98+
needs: guard
99+
runs-on: ubuntu-latest
100+
strategy:
101+
# If JDK 21 fails, we still want to know whether 25 passes.
102+
fail-fast: false
103+
matrix:
104+
java: ['21', '25']
105+
106+
steps:
107+
# Check out exactly the PR's HEAD commit, not the merge ref.
108+
- name: Checkout PR head
109+
uses: actions/checkout@v4
110+
with:
111+
ref: ${{ needs.guard.outputs.head_sha }}
112+
113+
# Compile=17, test=matrix JDK; same shape as main.yml.
114+
- name: Set up JDKs (compile=17, test=${{ matrix.java }})
115+
uses: actions/setup-java@v4
116+
with:
117+
distribution: temurin
118+
java-version: |
119+
17
120+
${{ matrix.java }}
121+
122+
- name: Set up Gradle
123+
uses: gradle/actions/setup-gradle@v4
124+
125+
- name: Test on JDK ${{ matrix.java }}
126+
run: ./gradlew test -PtestJdk=${{ matrix.java }} --stacktrace
127+
128+
- name: Upload test reports on failure
129+
if: failure()
130+
uses: actions/upload-artifact@v4
131+
with:
132+
name: test-reports-jdk${{ matrix.java }}
133+
path: |
134+
build/reports/tests/
135+
build/test-results/
136+
retention-days: 14
137+
138+
# Post a single comment summarizing the on-demand matrix result so it's
139+
# visible on the PR without diving into the Actions tab.
140+
report:
141+
name: Report
142+
needs: verify
143+
if: always() && needs.guard.result == 'success'
144+
runs-on: ubuntu-latest
145+
steps:
146+
- name: Comment outcome
147+
uses: actions/github-script@v7
148+
with:
149+
script: |
150+
const ok = '${{ needs.verify.result }}' === 'success';
151+
const emoji = ok ? '✅' : '❌';
152+
const status = ok ? 'passed' : 'failed';
153+
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
154+
await github.rest.issues.createComment({
155+
owner: context.repo.owner,
156+
repo: context.repo.repo,
157+
issue_number: context.payload.issue.number,
158+
body: `${emoji} On-demand JDK matrix \`{21, 25}\` ${status}. [View run](${runUrl}).`,
159+
});

.github/workflows/pull-request.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Pull Request
2+
3+
# Triggers only on pull request lifecycle events:
4+
# - opened (PR creation)
5+
# - synchronize (push to the PR branch while the PR is open)
6+
# - reopened
7+
# These are the default `pull_request` activity types — listed explicitly
8+
# here for clarity. Pre-PR pushes don't run CI by design (saves minutes
9+
# during early WIP commits).
10+
on:
11+
pull_request:
12+
types: [opened, synchronize, reopened]
13+
branches: ['**']
14+
15+
permissions:
16+
contents: read
17+
18+
# Cancel an in-progress run when a new commit lands on the same PR.
19+
concurrency:
20+
group: pr-${{ github.event.pull_request.number }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
verify:
25+
name: Verify (JDK 17)
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
# PRs only run on JDK 17 (the minimum target). Forward-compat
32+
# regressions on JDK 21/25 are caught post-merge by main.yml.
33+
- name: Set up JDK 17
34+
uses: actions/setup-java@v4
35+
with:
36+
distribution: temurin
37+
java-version: '17'
38+
39+
# Validates the wrapper jar hash and caches Gradle home + wrapper
40+
# dists between runs.
41+
- name: Set up Gradle
42+
uses: gradle/actions/setup-gradle@v4
43+
44+
- name: Build, test, lint, coverage
45+
run: ./gradlew build --stacktrace
46+
47+
- name: Upload test reports on failure
48+
if: failure()
49+
uses: actions/upload-artifact@v4
50+
with:
51+
name: test-reports
52+
path: |
53+
build/reports/tests/
54+
build/test-results/
55+
retention-days: 14
56+
57+
# Coverage ratchet lives in Codecov: codecov.yml at the repo root
58+
# configures `threshold: 5%` so a PR fails the Codecov status check
59+
# if line coverage drops more than 5 pp vs the base branch.
60+
- name: Upload coverage to Codecov
61+
uses: codecov/codecov-action@v5
62+
with:
63+
token: ${{ secrets.CODECOV_TOKEN }}
64+
files: build/reports/jacoco/test/jacocoTestReport.xml
65+
fail_ci_if_error: true

.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Gradle
2+
.gradle/
3+
build/
4+
!gradle/wrapper/gradle-wrapper.jar
5+
6+
# IDE — IntelliJ
7+
.idea/
8+
*.iml
9+
*.ipr
10+
*.iws
11+
out/
12+
13+
# IDE — Eclipse / VS Code
14+
.classpath
15+
.project
16+
.settings/
17+
bin/
18+
.vscode/
19+
20+
# OS
21+
.DS_Store
22+
Thumbs.db
23+
24+
# Local env
25+
.env
26+
.env.local
27+
28+
# Logs / coverage
29+
*.log
30+
hs_err_pid*
31+
replay_pid*

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
- Project scaffold per ADRs 001–006: Gradle Kotlin DSL build, JDK 17 toolchain,
12+
`integrationTest` source set, Spotless + JaCoCo, Vanniktech Maven Publish.
13+
- `MarketDataClient` skeleton with builder, default base URL
14+
(`https://api.marketdata.app`), default API version (`v1`), 99 s request /
15+
2 s connect timeouts, HTTP/2, demo mode, `validateOnStartup` toggle, and a
16+
50-permit concurrency semaphore (wiring lands with the request layer).
17+
- Configuration cascade: explicit builder values → `MARKETDATA_*` environment
18+
variables → `.env` file in CWD → built-in defaults.
19+
- Sealed `MarketDataException` hierarchy with the seven canonical subtypes
20+
(`AuthenticationError`, `BadRequestError`, `NotFoundError`, `RateLimitError`,
21+
`ServerError`, `NetworkError`, `ParseError`), each carrying support context
22+
(`requestId`, `requestUrl`, `statusCode`, `timestamp`) and a
23+
`getSupportInfo()` helper.
24+
- `RateLimits` record exposed via `MarketDataClient.getRateLimits()`.
25+
- JSpecify `@NullMarked` on every public package; JSpecify on `compileOnlyApi`
26+
so consumers get the annotations at compile time without a runtime dep.
27+
- Token redaction utility (`internal.Tokens`) for log output.
28+
- MIT license; SDK version auto-detected from the JAR manifest
29+
(`Implementation-Version`).

0 commit comments

Comments
 (0)