jpzip 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +200 -43
- data/lib/jpzip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e31336d3681f33b63d8a9bec350e0f331d79ff3c7750925d990474fae96ba778
|
|
4
|
+
data.tar.gz: 00e64ba516632a9f7df717b709f2ec5332f01ee0a2710544847f631bd74b951d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 28923257f2ecde399a95e3c0f9abf5bf0439e9e455e1f889a2b463ae5f2b8dd30c7b0c6bf3ee489e118a20bb6d8d75140c3241cf78fbf2ebbd1589246fd17820
|
|
7
|
+
data.tar.gz: 173fc3721727c415b0bd7b7b78ae488440d6b7e358b11eac0057886e94464cbee431aa04a26879e094b1fc264f6d3c7c47cf0ad0cc93237458316517cfc828c0
|
data/README.md
CHANGED
|
@@ -1,90 +1,247 @@
|
|
|
1
|
-
# jpzip
|
|
1
|
+
# jpzip
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/jpzip)
|
|
4
|
+
[](https://rubygems.org/gems/jpzip)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/jpzip/ruby/actions/workflows/publish.yml)
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
> Ruby SDK for **jpzip** — a free, unlimited Japanese postal code (郵便番号) API.
|
|
9
|
+
> 日本の全郵便番号 120,677 件を CDN 配信 JSON から引く Ruby gem。
|
|
10
|
+
|
|
11
|
+
**English** | [日本語](./README.ja.md)
|
|
12
|
+
|
|
13
|
+
`jpzip` looks up Japanese postal codes (郵便番号) from `jpzip.nadai.dev`,
|
|
14
|
+
a CDN-hosted dataset built from Japan Post's `KEN_ALL.csv` and `KEN_ALL_ROME.csv`
|
|
15
|
+
normalized to JSON. No registration, no rate limits, no API key.
|
|
16
|
+
|
|
17
|
+
- 🇯🇵 **Complete dataset** — 120,677 entries with kanji, kana, romaji, and government codes (JIS X 0401 / 総務省地方公共団体コード)
|
|
18
|
+
- ⚡️ **Fast** — L1 LRU + optional L2 persistent cache; `preload` to serve lookups without per-request network round-trips
|
|
19
|
+
- 🛡️ **Resilient** — 3-attempt retry with exponential backoff on 5xx / network failures
|
|
20
|
+
- 🪶 **Zero runtime deps** — `net/http` + `json` + `monitor`, all stdlib
|
|
21
|
+
- 🆓 **Free forever** — backed by Cloudflare Pages' free tier (no billing axis exists)
|
|
22
|
+
- 🔌 **Drop-in** — same API surface across [every jpzip SDK](#other-languages)
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
Ruby 3.2+ (uses `Data.define`)
|
|
27
|
+
|
|
28
|
+
## Install
|
|
8
29
|
|
|
9
30
|
```ruby
|
|
10
31
|
# Gemfile
|
|
11
32
|
gem "jpzip"
|
|
12
33
|
```
|
|
13
34
|
|
|
14
|
-
|
|
35
|
+
Or directly:
|
|
15
36
|
|
|
16
37
|
```sh
|
|
17
38
|
gem install jpzip
|
|
18
39
|
```
|
|
19
40
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## 使い方
|
|
23
|
-
|
|
24
|
-
### モジュール関数 API
|
|
41
|
+
## Quick Start
|
|
25
42
|
|
|
26
43
|
```ruby
|
|
27
44
|
require "jpzip"
|
|
28
45
|
|
|
29
46
|
entry = Jpzip.lookup("2310017")
|
|
30
|
-
# entry
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
|
|
47
|
+
# entry is nil when not found
|
|
48
|
+
puts "#{entry.prefecture} #{entry.city} #{entry.towns.first.town}"
|
|
49
|
+
# => 神奈川県 横浜市中区 港町
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Romaji and government codes are included on the same entry:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
puts "#{entry.prefecture_roma} #{entry.city_roma} #{entry.towns.first.roma}"
|
|
56
|
+
# => Kanagawa Ken Yokohama Shi Naka Ku Minatocho
|
|
57
|
+
|
|
58
|
+
puts "#{entry.prefecture_code} #{entry.city_code}"
|
|
59
|
+
# => 14 14104
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Entries are immutable `Data.define` values:
|
|
34
63
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
```ruby
|
|
65
|
+
entry.prefecture = "x" # raises NoMethodError
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Use Cases
|
|
69
|
+
|
|
70
|
+
### Zipcode lookup HTTP endpoint (Rails)
|
|
38
71
|
|
|
39
|
-
|
|
40
|
-
|
|
72
|
+
```ruby
|
|
73
|
+
# config/routes.rb
|
|
74
|
+
get "/api/zipcode/:code", to: "zipcodes#show"
|
|
75
|
+
|
|
76
|
+
# app/controllers/zipcodes_controller.rb
|
|
77
|
+
class ZipcodesController < ApplicationController
|
|
78
|
+
def show
|
|
79
|
+
entry = Jpzip.lookup(params[:code])
|
|
80
|
+
return head :not_found if entry.nil?
|
|
81
|
+
|
|
82
|
+
render json: {
|
|
83
|
+
prefecture: entry.prefecture,
|
|
84
|
+
city: entry.city,
|
|
85
|
+
town: entry.towns.first&.town,
|
|
86
|
+
codes: { prefecture: entry.prefecture_code, city: entry.city_code }
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
41
90
|
```
|
|
42
91
|
|
|
43
|
-
###
|
|
92
|
+
### Zipcode lookup HTTP endpoint (Sinatra)
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
require "sinatra"
|
|
96
|
+
require "jpzip"
|
|
97
|
+
require "json"
|
|
98
|
+
|
|
99
|
+
get "/api/zipcode/:code" do
|
|
100
|
+
entry = Jpzip.lookup(params[:code])
|
|
101
|
+
halt 404 if entry.nil?
|
|
102
|
+
|
|
103
|
+
content_type :json
|
|
104
|
+
{
|
|
105
|
+
prefecture: entry.prefecture,
|
|
106
|
+
city: entry.city,
|
|
107
|
+
town: entry.towns.first&.town
|
|
108
|
+
}.to_json
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Batch validation
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
all = Jpzip.lookup_all # entire dataset in memory (~37 MiB JSON)
|
|
116
|
+
csv_zipcodes.each do |zip|
|
|
117
|
+
warn "invalid zipcode: #{zip}" unless all.key?(zip)
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Serve lookups from cache (BYO L2 backend)
|
|
122
|
+
|
|
123
|
+
The dataset is partitioned into 948 three-digit prefix buckets. The default
|
|
124
|
+
L1 (100 entries) keeps the hottest buckets; to cache the whole dataset, pair
|
|
125
|
+
`preload("all")` with an L2 cache or raise `memory_cache_size` above 948.
|
|
44
126
|
|
|
45
127
|
```ruby
|
|
46
128
|
client = Jpzip::Client.new(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cache: my_cache, # Jpzip::Cache サブクラス
|
|
50
|
-
on_spec_mismatch: ->(expected, got) {
|
|
51
|
-
warn "jpzip spec mismatch: expected=#{expected} got=#{got}"
|
|
52
|
-
}
|
|
129
|
+
memory_cache_size: 1024,
|
|
130
|
+
cache: my_file_cache # any Jpzip::Cache subclass
|
|
53
131
|
)
|
|
54
|
-
|
|
55
132
|
client.preload("all")
|
|
133
|
+
# Subsequent lookups are served from L1/L2 without hitting the network.
|
|
56
134
|
entry = client.lookup("2310017")
|
|
57
135
|
```
|
|
58
136
|
|
|
59
|
-
##
|
|
137
|
+
## API Reference
|
|
138
|
+
|
|
139
|
+
### Module functions (share a process-wide default Client)
|
|
140
|
+
|
|
141
|
+
| Function | Description |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `Jpzip.lookup(zipcode)` | Look up a single 7-digit zipcode. Returns `nil` if not found or malformed (no network call for malformed input). |
|
|
144
|
+
| `Jpzip.lookup_group(prefix)` | Look up by 1-, 2-, or 3-digit prefix. 1-digit fetches `/g/{d}.json`; 3-digit fetches `/p/{ddd}.json`; 2-digit fans out into 10 parallel 3-digit fetches and merges. |
|
|
145
|
+
| `Jpzip.lookup_all` | Fetch entire dataset (120k entries, ~37 MiB) in parallel across `/g/0..9.json`. |
|
|
146
|
+
| `Jpzip.meta` | Dataset version, generated-at, per-prefecture counts, spec version. Result is cached until the default client is reset. |
|
|
147
|
+
| `Jpzip.preload(scope)` | Warm L1 (and L2 when configured) for `"all"` or a specific prefix. |
|
|
148
|
+
| `Jpzip.valid_zipcode?(str)` | Pure syntax check (`\A\d{7}\z`) — no network. |
|
|
149
|
+
| `Jpzip.configure(**opts)` | Replace the singleton with a configured `Client` (e.g. to share an L2 cache through the module helpers). |
|
|
150
|
+
| `Jpzip.reset_default_client!` | Drop the singleton (mainly for tests). |
|
|
151
|
+
|
|
152
|
+
### `Jpzip::Client` (advanced)
|
|
153
|
+
|
|
154
|
+
`Client.new` returns a configurable instance; required for L2 caching, custom HTTP behavior, alternate base URL, or multiple isolated caches. Instances are thread-safe.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
client = Jpzip::Client.new(
|
|
158
|
+
base_url: "https://jpzip.nadai.dev",
|
|
159
|
+
memory_cache_size: 200, # L1 capacity in prefix buckets, default 100
|
|
160
|
+
cache: my_cache, # optional L2 (Jpzip::Cache subclass)
|
|
161
|
+
on_spec_mismatch: ->(expected, got) {
|
|
162
|
+
warn "jpzip spec mismatch: SDK=#{expected} server=#{got}"
|
|
163
|
+
}
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`Client` exposes `#lookup` / `#lookup_group` / `#lookup_all` / `#meta` / `#preload` plus:
|
|
168
|
+
|
|
169
|
+
| Method | Description |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `client.refresh` | Wipe L1 (and L2 when configured) and forget cached meta. |
|
|
172
|
+
|
|
173
|
+
When `meta` observes that `/meta.json`'s `version` has changed since the last successful fetch, L1 and L2 are cleared automatically — call `meta` periodically to pick up dataset rollovers.
|
|
174
|
+
|
|
175
|
+
### Errors
|
|
176
|
+
|
|
177
|
+
- `Jpzip::InvalidPrefixError < ArgumentError` — raised by `lookup_group` / `preload` when the prefix is not 1-3 digits.
|
|
178
|
+
- `Jpzip::Http::HttpError < StandardError` — raised on 4xx (other than 404, which yields `nil`) or after exhausted retries on 5xx.
|
|
179
|
+
- Transient network failures and 5xx responses are retried up to 3 attempts (initial + 2 retries) with exponential backoff sleeps of 400ms and 800ms.
|
|
180
|
+
|
|
181
|
+
### `Jpzip::Cache` interface
|
|
182
|
+
|
|
183
|
+
Bring your own L2 backend (file, Redis, Memcached, etc.) by subclassing `Jpzip::Cache`:
|
|
60
184
|
|
|
61
185
|
```ruby
|
|
62
186
|
class MyFileCache < Jpzip::Cache
|
|
63
|
-
def get(key)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
187
|
+
def get(key) # => String (raw JSON bytes) or nil
|
|
188
|
+
# ...
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def set(key, value) # value is a String of bytes
|
|
192
|
+
# ...
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def delete(key)
|
|
196
|
+
# ...
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def clear
|
|
200
|
+
# ...
|
|
201
|
+
end
|
|
67
202
|
end
|
|
68
203
|
```
|
|
69
204
|
|
|
70
|
-
|
|
205
|
+
Keys are the full prefix-bucket URLs (e.g. `https://jpzip.nadai.dev/p/231.json`); values are raw JSON bytes.
|
|
206
|
+
|
|
207
|
+
### Data types
|
|
208
|
+
|
|
209
|
+
`Jpzip::ZipcodeEntry` and `Jpzip::Town` are immutable `Data.define` classes with `from_hash` / `to_h` helpers. Fields: `prefecture`, `prefecture_kana`, `prefecture_roma`, `prefecture_code`, `city`, `city_kana`, `city_roma`, `city_code`, `towns` (Array of `Town`). `Town` has `town`, `kana`, `roma`, `note`.
|
|
210
|
+
|
|
211
|
+
## Why jpzip?
|
|
212
|
+
|
|
213
|
+
| | **jpzip** | [jpostcode][jpostcode] | [ken_all][kenall] | [zipcloud API][zipcloud] |
|
|
214
|
+
|---|---|---|---|---|
|
|
215
|
+
| Romaji (`Yokohama Shi`) | ✅ | ❌ | ❌ | ❌ |
|
|
216
|
+
| Government codes (JIS / 総務省) | ✅ | ⚠️ Prefecture only | ⚠️ JIS only | ❌ |
|
|
217
|
+
| No manual CSV / submodule sync | ✅ | ❌ Git submodule | ❌ Rake task | ✅ |
|
|
218
|
+
| Monthly updates | ✅ Auto | ⚠️ Manual submodule | ❌ Manual | ✅ |
|
|
219
|
+
| Offline after preload | ✅ | ✅ Local data | ✅ Local DB | ❌ |
|
|
220
|
+
| Rate-limit-free | ✅ | ✅ | ✅ | ⚠️ Discouraged |
|
|
221
|
+
| L1 + pluggable L2 cache | ✅ | ❌ | ❌ | ❌ |
|
|
222
|
+
| Zero runtime dependencies | ✅ | ⚠️ jpostcode-data submodule | ❌ Rails / activerecord-import / rubyzip / curses | n/a |
|
|
71
223
|
|
|
72
|
-
|
|
224
|
+
[jpostcode]: https://github.com/kufu/jpostcode-rb
|
|
225
|
+
[kenall]: https://github.com/ozin/ken_all
|
|
226
|
+
[zipcloud]: http://zipcloud.ibsnet.co.jp/doc/api
|
|
73
227
|
|
|
74
|
-
|
|
228
|
+
## Other Languages
|
|
75
229
|
|
|
76
|
-
|
|
230
|
+
Same API surface across all SDKs:
|
|
77
231
|
|
|
78
|
-
|
|
232
|
+
[Go](https://github.com/jpzip/go) · [TypeScript](https://github.com/jpzip/js) · [Python](https://github.com/jpzip/python) · [Rust](https://github.com/jpzip/rust) · [PHP](https://github.com/jpzip/php) · [Swift](https://github.com/jpzip/swift) · [Dart](https://github.com/jpzip/dart)
|
|
79
233
|
|
|
80
|
-
##
|
|
234
|
+
## Resources
|
|
81
235
|
|
|
82
|
-
|
|
236
|
+
- **Website** — https://jpzip.nadai.dev
|
|
237
|
+
- **Protocol spec** — [jpzip/spec](https://github.com/jpzip/spec)
|
|
238
|
+
- **Data ETL** — [jpzip/data](https://github.com/jpzip/data)
|
|
239
|
+
- **MCP server** — [jpzip/mcp](https://github.com/jpzip/mcp) — use jpzip from Claude / ChatGPT / Cursor
|
|
83
240
|
|
|
84
|
-
##
|
|
241
|
+
## Keywords
|
|
85
242
|
|
|
86
|
-
|
|
243
|
+
japanese postal code, japan zipcode, 郵便番号, KEN_ALL, KEN_ALL_ROME, address validation, japan address api, postal code lookup ruby, ruby japanese address gem, JIS X 0401, 総務省地方公共団体コード, rails postal code
|
|
87
244
|
|
|
88
|
-
##
|
|
245
|
+
## License
|
|
89
246
|
|
|
90
247
|
[MIT](./LICENSE)
|
data/lib/jpzip/version.rb
CHANGED