Skip to content

Commit beb3fdf

Browse files
committed
Initial release: UniRateAPI.jl v0.1.0
Official Julia client for the UniRate API. Zero third-party dependencies — HTTP via the stdlib Downloads module and a small internal JSON parser. Covers all nine canonical methods (rates, convert, currencies, historical rates/timeseries/limits, VAT) with a typed error hierarchy and an injectable HTTP transport for offline testing. 101 mock tests + 6 free-tier live tests.
0 parents  commit beb3fdf

14 files changed

Lines changed: 1555 additions & 0 deletions

File tree

‎.github/workflows/CI.yml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
tags: ['*']
7+
pull_request:
8+
branches: [main]
9+
10+
# Least-privilege: the test job only needs to read the repo.
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
17+
18+
jobs:
19+
test:
20+
name: Julia ${{ matrix.version }} - ${{ matrix.os }}
21+
runs-on: ${{ matrix.os }}
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
version:
26+
- '1.10' # LTS
27+
- '1.11' # current stable
28+
os:
29+
- ubuntu-latest
30+
steps:
31+
# actions pinned to commit SHAs (v-tag noted) for supply-chain safety
32+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
33+
- uses: julia-actions/setup-julia@4c0cb0fce8556fdb04a90347310e5db8b1f98fb9 # v2
34+
with:
35+
version: ${{ matrix.version }}
36+
- uses: julia-actions/cache@d10a6fd8f31b12404a54613ebad242900567f2b9 # v2
37+
- uses: julia-actions/julia-buildpkg@e3eb439fad4f9aba7da2667e7510e4a46ebc46e1 # v1
38+
- uses: julia-actions/julia-runtest@6e050c8013b833b1195105ff2fce9cd802f53271 # v1

‎.github/workflows/TagBot.yml‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: TagBot
2+
3+
# Creates the git tag + GitHub release automatically once a new version is
4+
# merged into the General registry. Triggered by the Registrator comment, with
5+
# an hourly fallback dispatch.
6+
on:
7+
issue_comment:
8+
types: [created]
9+
workflow_dispatch:
10+
inputs:
11+
lookback:
12+
default: '3'
13+
schedule:
14+
- cron: '0 * * * *'
15+
16+
permissions:
17+
contents: write
18+
actions: read
19+
20+
jobs:
21+
TagBot:
22+
# Only run on the Registrator trigger comment (or manual/scheduled dispatch),
23+
# never on arbitrary user comments.
24+
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.actor == 'JuliaTagBot'
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: JuliaRegistries/TagBot@cded32665b34ca0496d3ac10a67d08ddfc726963 # v1
28+
with:
29+
token: ${{ secrets.GITHUB_TOKEN }}

‎.gitignore‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Julia
2+
Manifest.toml
3+
/docs/build/
4+
*.jl.cov
5+
*.jl.*.cov
6+
*.jl.mem
7+
/.vscode/
8+
deps/deps.jl

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Unirate Team
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

‎Project.toml‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name = "UniRateAPI"
2+
uuid = "efab10e1-b413-46e0-a0c4-7c17cb59ee66"
3+
authors = ["Unirate Team"]
4+
version = "0.1.0"
5+
6+
[deps]
7+
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
8+
9+
[compat]
10+
julia = "1.10"
11+
12+
[extras]
13+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
14+
15+
[targets]
16+
test = ["Test"]

‎README.md‎

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# UniRateAPI.jl
2+
3+
[![CI](https://github.com/UniRate-API/UniRateAPI.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/UniRate-API/UniRateAPI.jl/actions/workflows/CI.yml)
4+
5+
Official Julia client for the [UniRate API](https://unirateapi.com) — free
6+
currency exchange rates, historical data, and VAT rates.
7+
8+
- **Zero third-party dependencies.** HTTP goes through the Julia standard
9+
library (`Downloads`) and JSON is parsed by a small internal parser, so
10+
installing this package pulls in nothing beyond the standard library.
11+
- Full parity with the other UniRate clients: current rates, conversion,
12+
supported currencies, historical rates/timeseries/limits, and VAT rates.
13+
- A typed error hierarchy and an injectable HTTP transport for offline testing.
14+
15+
## Installation
16+
17+
```julia
18+
using Pkg
19+
Pkg.add("UniRateAPI")
20+
```
21+
22+
or, in the Pkg REPL (`]`):
23+
24+
```
25+
pkg> add UniRateAPI
26+
```
27+
28+
## Quick start
29+
30+
Get a free API key at [unirateapi.com](https://unirateapi.com).
31+
32+
```julia
33+
using UniRateAPI
34+
35+
client = UniRateClient("your-api-key")
36+
37+
# Current rate
38+
rate = get_rate(client, "USD", "EUR") # 0.92
39+
40+
# Convert an amount
41+
eur = convert_amount(client, 100, "USD", "EUR") # 92.5
42+
43+
# All rates for a base currency
44+
rates = get_all_rates(client, "USD") # Dict("EUR" => 0.92, ...)
45+
46+
# Supported currencies
47+
currencies = get_supported_currencies(client) # ["USD", "EUR", ...]
48+
49+
# VAT rates
50+
vat = get_vat_rate(client, "DE")
51+
println(vat.vat_data.vat_rate) # 19.0
52+
```
53+
54+
The client reads no environment variables itself — pass the key explicitly.
55+
The bundled example reads it from `UNIRATE_API_KEY`:
56+
57+
```julia
58+
UNIRATE_API_KEY=your-key julia --project=. examples/basic.jl
59+
```
60+
61+
## API
62+
63+
Construct a client with `UniRateClient(api_key; timeout=30, base_url=..., transport=nothing)`.
64+
65+
| Method | Returns |
66+
|---|---|
67+
| `get_rate(client, from, to)` | `Float64` |
68+
| `get_all_rates(client, from="USD")` | `Dict{String,Float64}` |
69+
| `convert_amount(client, amount, from, to)` | `Float64` |
70+
| `get_supported_currencies(client)` | `Vector{String}` |
71+
| `get_historical_rate(client, date, from, to)` | `Float64` |
72+
| `get_historical_rates(client, date, base="USD")` | `Dict{String,Float64}` |
73+
| `convert_historical(client, amount, from, to, date)` | `Float64` |
74+
| `get_time_series(client, start_date, end_date; amount=1, base="USD", currencies=String[])` | `TimeSeriesData` |
75+
| `get_historical_limits(client)` | `HistoricalLimits` |
76+
| `get_vat_rates(client)` | `VatRatesResponse` |
77+
| `get_vat_rate(client, country)` | `VatCountryResponse` |
78+
79+
Every method also accepts `format` and `callback` keyword arguments, passed
80+
through as query parameters. Currency and country codes are uppercased
81+
automatically.
82+
83+
> The conversion method is named `convert_amount` (not `convert`) so it does
84+
> not shadow `Base.convert`.
85+
86+
### Historical endpoints require a Pro subscription
87+
88+
`get_historical_rate`, `get_historical_rates`, `convert_historical`,
89+
`get_time_series`, and `get_historical_limits` are **Pro-gated**. On the free
90+
tier they raise an `APIError` with `status == 403`.
91+
92+
## Error handling
93+
94+
Every error subtypes `UniRateError`, so you can catch that one type to handle
95+
all failures, or match a specific type to branch on the HTTP status.
96+
97+
```julia
98+
using UniRateAPI
99+
100+
try
101+
rate = get_rate(client, "USD", "EUR")
102+
catch e
103+
if e isa AuthenticationError
104+
# 401 — missing or invalid API key
105+
elseif e isa RateLimitError
106+
# 429 — rate limit exceeded
107+
elseif e isa InvalidCurrencyError
108+
# 404 — unknown currency or no data
109+
elseif e isa InvalidDateError
110+
# 400 — bad parameters
111+
elseif e isa APIError
112+
# e.status / e.body — includes 403 (Pro-gated) and 503
113+
elseif e isa TransportError
114+
# network failure or unparseable response
115+
end
116+
end
117+
```
118+
119+
| HTTP status | Error type |
120+
|---|---|
121+
| 400 | `InvalidDateError` |
122+
| 401 | `AuthenticationError` |
123+
| 403 | `APIError` (status = 403) |
124+
| 404 | `InvalidCurrencyError` |
125+
| 429 | `RateLimitError` |
126+
| 503 | `APIError` (status = 503) |
127+
| other | `APIError` |
128+
| network / parse | `TransportError` |
129+
130+
## Rate limits
131+
132+
The free tier is generous but rate-limited; a `429` raises `RateLimitError`.
133+
See [unirateapi.com](https://unirateapi.com) for current limits and Pro plans.
134+
135+
## Testing offline
136+
137+
The HTTP transport is injectable. Pass a callable
138+
`(url, headers) -> (status, body)` as `transport` to run without a network —
139+
this is exactly how the test suite works:
140+
141+
```julia
142+
client = UniRateClient("test-key";
143+
transport = (url, headers) -> (200, """{"rate": "0.92"}"""))
144+
get_rate(client, "USD", "EUR") # 0.92, no network call
145+
```
146+
147+
Run the tests with `julia --project=. -e 'using Pkg; Pkg.test()'`. Mock tests
148+
run by default; setting `UNIRATE_API_KEY` additionally runs the free-tier live
149+
tests.
150+
151+
## Related clients
152+
153+
UniRate ships official clients for many languages and frameworks — Python,
154+
Node/TypeScript, Go, Rust, Ruby, PHP, Java, Swift, .NET, Dart, and more. See
155+
[github.com/UniRate-API](https://github.com/UniRate-API).
156+
157+
## License
158+
159+
MIT © 2026 Unirate Team. See [LICENSE](LICENSE).

‎examples/basic.jl‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# A runnable example. Reads the API key from the UNIRATE_API_KEY environment
2+
# variable and exercises a few free-tier endpoints.
3+
#
4+
# Run from the package root with:
5+
# UNIRATE_API_KEY=your-key julia --project=. examples/basic.jl
6+
7+
using UniRateAPI
8+
9+
function main()
10+
api_key = get(ENV, "UNIRATE_API_KEY", "")
11+
if isempty(api_key)
12+
println("Set UNIRATE_API_KEY to run this example.")
13+
return
14+
end
15+
16+
client = UniRateClient(api_key)
17+
18+
rate = get_rate(client, "USD", "EUR")
19+
println("1 USD = $rate EUR")
20+
21+
amount = convert_amount(client, 100, "USD", "EUR")
22+
println("100 USD = $amount EUR")
23+
24+
currencies = get_supported_currencies(client)
25+
println("Supported currencies: $(length(currencies)) (e.g. $(join(first(currencies, 5), ", ")))")
26+
end
27+
28+
main()

‎src/UniRateAPI.jl‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
UniRateAPI
3+
4+
Official Julia client for the [UniRate API](https://unirateapi.com) — free
5+
currency exchange rates, historical data, and VAT rates.
6+
7+
The client depends only on the Julia standard library (`Downloads` for HTTP and
8+
a small internal JSON parser), so it pulls in **zero third-party packages**.
9+
10+
```julia
11+
using UniRateAPI
12+
13+
client = UniRateClient("your-api-key")
14+
15+
get_rate(client, "USD", "EUR") # 0.92
16+
convert_amount(client, 100, "USD", "EUR") # 92.5
17+
get_supported_currencies(client) # ["USD", "EUR", ...]
18+
```
19+
"""
20+
module UniRateAPI
21+
22+
include("json.jl")
23+
include("errors.jl")
24+
include("models.jl")
25+
include("client.jl")
26+
27+
export UniRateClient, Client
28+
export UniRateError, AuthenticationError, RateLimitError,
29+
InvalidCurrencyError, InvalidDateError, APIError, TransportError
30+
export TimeSeriesData, HistoricalLimit, HistoricalLimits,
31+
VatRate, VatRatesResponse, VatCountryResponse
32+
export get_rate, get_all_rates, convert_amount, get_supported_currencies,
33+
get_historical_rate, get_historical_rates, convert_historical,
34+
get_time_series, get_historical_limits, get_vat_rates, get_vat_rate
35+
36+
end # module UniRateAPI

0 commit comments

Comments
 (0)