adressevaelger 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39ce3fca5b50b066546e6325522fb11677182fc4cf9a766a6baa138c937e0860
4
- data.tar.gz: d01d7d4289c3a52997c744bade5d6c094f398b52547a7135964c06d87774dcc8
3
+ metadata.gz: 6f38d1dc804e9e0f9f1a8644bbc9acf97b47a7844750d2fed638b507311a3724
4
+ data.tar.gz: 488b30cba10f238ea3a34b8260da78d1c287a3856a890543bf14a8ba317b673f
5
5
  SHA512:
6
- metadata.gz: 362d1a947156be0ecf9f8914596388136382e30748fd62289e13eaf05a836584092a9abd70bc86f9bb37a065451699e4ca310c8a0441f7fddcfdefc8d4528226
7
- data.tar.gz: 875591926ea41fcfb4ada343652cd567c47a4d43e668f994e853318278a6641196daaececc21921c585fc23dbff357732139d98f6ff44d5d27931acb0c79975f
6
+ metadata.gz: e84e2680b65699a9f15432d39f40f3296da598133c5a71180a9879c276b24fafecf368c4540daf6207c7af625cb9185612d1e2d1024bdd35bc05448042cb932a
7
+ data.tar.gz: bd3449f9a7aca184f5d59ed06380e66d68aa41a150f2eb95f0e69f495cd7ee4ec9ddb1a74fd01b825c26b4220e060abef7183b81c0bb71d70079b389a7d35ad9
data/.rubocop.yml ADDED
@@ -0,0 +1,55 @@
1
+ plugins:
2
+ - rubocop-minitest
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 3.3
8
+ SuggestExtensions: false
9
+ Exclude:
10
+ - "vendor/**/*"
11
+ - "tmp/**/*"
12
+ - "pkg/**/*"
13
+
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ Style/StringLiterals:
18
+ EnforcedStyle: double_quotes
19
+
20
+ Layout/LineLength:
21
+ Max: 120
22
+
23
+ Metrics/MethodLength:
24
+ Max: 35
25
+ Exclude:
26
+ - "test/**/*"
27
+
28
+ Metrics/ClassLength:
29
+ Max: 200
30
+ Exclude:
31
+ - "test/**/*"
32
+
33
+ Metrics/AbcSize:
34
+ Max: 50
35
+ Exclude:
36
+ - "test/**/*"
37
+
38
+ Metrics/CyclomaticComplexity:
39
+ Max: 25
40
+ Exclude:
41
+ - "test/**/*"
42
+
43
+ Metrics/PerceivedComplexity:
44
+ Max: 25
45
+ Exclude:
46
+ - "test/**/*"
47
+
48
+ Metrics/BlockLength:
49
+ Exclude:
50
+ - "test/**/*"
51
+ - "*.gemspec"
52
+ - "Rakefile"
53
+
54
+ Minitest/MultipleAssertions:
55
+ Max: 10
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ - Add RuboCop lint job and shared style config in CI. No public API changes.
6
+
3
7
  ## 0.1.0
4
8
 
5
9
  - Initial public release: Adressevælger client (`autocomplete` / `search`),
data/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # Adressevaelger
2
2
 
3
3
  Ruby client for Klimadatastyrelsen **Adressevælger** and **Adressevask**
4
- (Danmarks Adresseregister / DAR). Denmark only — no multi-country router,
5
- manual entry adapter, or commercial geocoder fallback.
4
+ (Danmarks Adresseregister / DAR).
6
5
 
7
6
  ## Install
8
7
 
@@ -76,6 +75,7 @@ production**. Obtain a proper token via [Dataforsyningen](https://dataforsyninge
76
75
 
77
76
  ```sh
78
77
  bundle exec rake test
78
+ bundle exec rake rubocop
79
79
  ```
80
80
 
81
81
  HTTP integration tests use [VCR](https://github.com/vcr/vcr) cassettes recorded
@@ -88,14 +88,6 @@ VCR_RECORD=all bundle exec rake test TEST=test/client_vcr_test.rb
88
88
 
89
89
  Never hand-write cassette response bodies.
90
90
 
91
- ## Scope
92
-
93
- **In:** Adressevælger search/autocomplete, resolve by husnummer/adresse id,
94
- Adressevask validate, optional PROJ transform.
95
-
96
- **Out:** Multi-country routing, Manual adapters, commercial fallback geocoders,
97
- Rails controllers/Stimulus, CRM persistence helpers.
98
-
99
91
  ## Attribution
100
92
 
101
93
  Address data via [Dataforsyningen](https://dataforsyningen.dk/) /
@@ -104,8 +96,6 @@ open-source client and is not affiliated with or endorsed by Klimadatastyrelsen.
104
96
 
105
97
  ## Releasing
106
98
 
107
- Version bumps are intentional — CI does not auto-bump on every `main` push.
108
-
109
99
  1. Bump `Adressevaelger::VERSION` in `lib/adressevaelger/version.rb` (gemspec reads it).
110
100
  2. Update `CHANGELOG.md` for that version.
111
101
  3. Commit and push to `main`.
@@ -116,27 +106,6 @@ Version bumps are intentional — CI does not auto-bump on every `main` push.
116
106
  publishes via [RubyGems Trusted Publishing](https://guides.rubygems.org/trusted-publishing/)
117
107
  (`rubygems/release-gem`, OIDC — no `RUBYGEMS_API_KEY` secret).
118
108
 
119
- ### One-time RubyGems Trusted Publisher setup
120
-
121
- The gem is not on RubyGems yet, so use a **pending** trusted publisher
122
- (Rasmus must click through the UI; no credentials belong in this repo):
123
-
124
- 1. Sign in at [rubygems.org](https://rubygems.org/) (MFA on).
125
- 2. Open [Pending trusted publishers](https://rubygems.org/profile/oidc/pending_trusted_publishers).
126
- 3. Click **Create**.
127
- 4. Fill in:
128
- - **Gem name:** `adressevaelger`
129
- - **Repository owner:** `bergholdt`
130
- - **Repository name:** `adressevaelger`
131
- - **Workflow filename:** `push_gem.yml`
132
- - **Environment name:** `release`
133
- 5. Click **Create Pending trusted publisher**.
134
- 6. In GitHub → repo **Settings → Environments**, create an environment named
135
- `release` (no required reviewers needed for a solo maintainer).
136
-
137
- After the first successful tag-triggered publish, the pending publisher becomes
138
- a normal trusted publisher and you own the gem on RubyGems.org.
139
-
140
109
  ## License
141
110
 
142
111
  MIT. See [LICENSE.txt](LICENSE.txt).
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.files = Dir.chdir(__dir__) do
28
28
  `git ls-files -z`.split("\x0").reject do |f|
29
- f.start_with?(*%w[bin/ test/ .git .github Gemfile Rakefile])
29
+ f.start_with?(*%w[bin/ test/ .git .github Gemfile Rakefile AGENTS.md])
30
30
  end
31
31
  end
32
32
  spec.require_paths = ["lib"]
@@ -16,13 +16,14 @@ module Adressevaelger
16
16
  # this shared demo token for exploration — replace it in production.
17
17
  DEFAULT_TOKEN = "adressevaelger123"
18
18
  DEFAULT_BASE_URL = "https://adressevaelger.dk"
19
- EPSG = 25832
19
+ EPSG = 25_832
20
20
 
21
21
  attr_reader :base_url, :token
22
22
 
23
23
  def initialize(token: nil, base_url: nil, http: nil)
24
- @token = present_string(token) || present_string(ENV["ADRESSEVAELGER_TOKEN"]) || DEFAULT_TOKEN
25
- @base_url = present_string(base_url) || present_string(ENV["ADRESSEVAELGER_BASE_URL"]) || DEFAULT_BASE_URL
24
+ @token = present_string(token) || present_string(ENV.fetch("ADRESSEVAELGER_TOKEN", nil)) || DEFAULT_TOKEN
25
+ @base_url = present_string(base_url) || present_string(ENV.fetch("ADRESSEVAELGER_BASE_URL",
26
+ nil)) || DEFAULT_BASE_URL
26
27
  @http = http
27
28
  end
28
29
 
@@ -44,9 +45,9 @@ module Adressevaelger
44
45
  # Resolve a husnummer or adresse by DAR id.
45
46
  def resolve(id:, type: "husnummer")
46
47
  path = case type.to_s
47
- when "adresse" then "/adresser/#{id}"
48
- else "/husnumre/#{id}"
49
- end
48
+ when "adresse" then "/adresser/#{id}"
49
+ else "/husnumre/#{id}"
50
+ end
50
51
 
51
52
  payload = get_json(path)
52
53
  build_resolved(payload, type: type.to_s)
@@ -77,147 +78,147 @@ module Adressevaelger
77
78
 
78
79
  private
79
80
 
80
- attr_reader :http
81
-
82
- def build_resolved(payload, type:)
83
- data = nested_entity(payload)
84
- vejnavn = scalar(pick(data, "vejnavn")) || dig_hash(data, "vejstykke", "navn") || dig_hash(data, "navngivenvej", "vejnavn")
85
- husnr = scalar(pick(data, "husnr", "husnummertekst", "husnummer"))
86
- postal = scalar(pick(data, "postnr")) || dig_hash(data, "postnummer", "postnr") || dig_hash(data, "postnummer", "nr")
87
- city = scalar(pick(data, "postnrnavn", "bynavn")) || dig_hash(data, "postnummer", "navn")
88
- etage = scalar(pick(data, "etagebetegnelse", "etage"))
89
- doer = scalar(pick(data, "doerbetegnelse", "dør", "doer"))
90
-
91
- point = data["adgangspunkt"] || dig_hash(data, "adgangsadresse", "adgangspunkt") || data["position"]
92
- easting, northing = extract_etrs89(point)
93
- latitude, longitude = transform_etrs89(easting, northing)
94
-
95
- id = pick(data, "id", "id_lokalid")
96
- husnummer_id = pick(data, "husnummer_id", "adgangsadresseid") || (type == "husnummer" ? id : nil)
97
- adresse_id = type == "adresse" ? id : pick(data, "adresse_id")
98
-
99
- ResolvedAddress.new(
100
- line1: [vejnavn, husnr].compact.reject { |part| blank?(part) }.join(" "),
101
- line2: nil,
102
- postal_code: postal.to_s,
103
- city: city.to_s,
104
- country: "DK",
105
- etage: etage,
106
- doer: doer,
107
- address_provider: Adressevaelger::PROVIDER,
108
- external_address_id: adresse_id || husnummer_id,
109
- external_building_id: husnummer_id,
110
- latitude: latitude,
111
- longitude: longitude,
112
- coord_easting: easting,
113
- coord_northing: northing,
114
- coord_epsg: easting ? EPSG : nil
115
- )
116
- end
81
+ attr_reader :http
82
+
83
+ def build_resolved(payload, type:)
84
+ data = nested_entity(payload)
85
+ vejnavn = scalar(pick(data,
86
+ "vejnavn")) || dig_hash(data, "vejstykke",
87
+ "navn") || dig_hash(data, "navngivenvej", "vejnavn")
88
+ husnr = scalar(pick(data, "husnr", "husnummertekst", "husnummer"))
89
+ postal = scalar(pick(data,
90
+ "postnr")) || dig_hash(data, "postnummer", "postnr") || dig_hash(data, "postnummer", "nr")
91
+ city = scalar(pick(data, "postnrnavn", "bynavn")) || dig_hash(data, "postnummer", "navn")
92
+ etage = scalar(pick(data, "etagebetegnelse", "etage"))
93
+ doer = scalar(pick(data, "doerbetegnelse", "dør", "doer"))
94
+
95
+ point = data["adgangspunkt"] || dig_hash(data, "adgangsadresse", "adgangspunkt") || data["position"]
96
+ easting, northing = extract_etrs89(point)
97
+ latitude, longitude = transform_etrs89(easting, northing)
98
+
99
+ id = pick(data, "id", "id_lokalid")
100
+ husnummer_id = pick(data, "husnummer_id", "adgangsadresseid") || (type == "husnummer" ? id : nil)
101
+ adresse_id = type == "adresse" ? id : pick(data, "adresse_id")
102
+
103
+ ResolvedAddress.new(
104
+ line1: [vejnavn, husnr].compact.reject { |part| blank?(part) }.join(" "),
105
+ line2: nil,
106
+ postal_code: postal.to_s,
107
+ city: city.to_s,
108
+ country: "DK",
109
+ etage: etage,
110
+ doer: doer,
111
+ address_provider: Adressevaelger::PROVIDER,
112
+ external_address_id: adresse_id || husnummer_id,
113
+ external_building_id: husnummer_id,
114
+ latitude: latitude,
115
+ longitude: longitude,
116
+ coord_easting: easting,
117
+ coord_northing: northing,
118
+ coord_epsg: easting ? EPSG : nil
119
+ )
120
+ end
117
121
 
118
- def nested_entity(payload)
119
- %w[data husnummer adresse].each do |key|
120
- value = payload[key]
121
- return value if value.is_a?(Hash)
122
- end
123
- payload
122
+ def nested_entity(payload)
123
+ %w[data husnummer adresse].each do |key|
124
+ value = payload[key]
125
+ return value if value.is_a?(Hash)
124
126
  end
127
+ payload
128
+ end
125
129
 
126
- def pick(hash, *keys)
127
- keys.each do |key|
128
- value = hash[key]
129
- return value if present?(value)
130
- end
131
- nil
130
+ def pick(hash, *keys)
131
+ keys.each do |key|
132
+ value = hash[key]
133
+ return value if present?(value)
132
134
  end
135
+ nil
136
+ end
133
137
 
134
- def dig_hash(hash, *keys)
135
- return nil unless hash.is_a?(Hash)
136
-
137
- hash.dig(*keys)
138
- end
138
+ def dig_hash(hash, *keys)
139
+ return nil unless hash.is_a?(Hash)
139
140
 
140
- def scalar(value)
141
- return nil if blank?(value) || value.is_a?(Hash) || value.is_a?(Array)
141
+ hash.dig(*keys)
142
+ end
142
143
 
143
- value
144
- end
144
+ def scalar(value)
145
+ return nil if blank?(value) || value.is_a?(Hash) || value.is_a?(Array)
145
146
 
146
- def extract_etrs89(point)
147
- return [nil, nil] if blank?(point)
147
+ value
148
+ end
148
149
 
149
- if point.is_a?(Array) && point.size >= 2
150
- return [point[0].to_f, point[1].to_f]
151
- end
150
+ def extract_etrs89(point)
151
+ return [nil, nil] if blank?(point)
152
152
 
153
- coords = point["koordinater"] || point["coordinates"] || point["position"] || dig_hash(point, "geometri", "coordinates")
154
- if coords.is_a?(Array) && coords.size >= 2
155
- return [coords[0].to_f, coords[1].to_f]
156
- end
157
- if coords.is_a?(Hash)
158
- easting = coords["x"] || coords["øst"] || coords["oest"] || coords["easting"]
159
- northing = coords["y"] || coords["nord"] || coords["northing"]
160
- return [easting.to_f, northing.to_f] if present?(easting) && present?(northing)
161
- end
153
+ return [point[0].to_f, point[1].to_f] if point.is_a?(Array) && point.size >= 2
162
154
 
163
- easting = point["øst"] || point["oest"] || point["easting"] || point["x"]
164
- northing = point["nord"] || point["northing"] || point["y"]
165
- return [nil, nil] if blank?(easting) || blank?(northing)
155
+ coords = point["koordinater"] || point["coordinates"] || point["position"] || dig_hash(point, "geometri",
156
+ "coordinates")
157
+ return [coords[0].to_f, coords[1].to_f] if coords.is_a?(Array) && coords.size >= 2
166
158
 
167
- [easting.to_f, northing.to_f]
159
+ if coords.is_a?(Hash)
160
+ easting = coords["x"] || coords["øst"] || coords["oest"] || coords["easting"]
161
+ northing = coords["y"] || coords["nord"] || coords["northing"]
162
+ return [easting.to_f, northing.to_f] if present?(easting) && present?(northing)
168
163
  end
169
164
 
170
- # Wired when optional rgeo-proj4 / PROJ is available; otherwise leave WGS84 nil.
171
- def transform_etrs89(easting, northing)
172
- return [nil, nil] if easting.nil? || northing.nil?
165
+ easting = point["øst"] || point["oest"] || point["easting"] || point["x"]
166
+ northing = point["nord"] || point["northing"] || point["y"]
167
+ return [nil, nil] if blank?(easting) || blank?(northing)
173
168
 
174
- Adressevaelger::Etrs89ToWgs84.call(easting: easting, northing: northing) || [nil, nil]
175
- end
169
+ [easting.to_f, northing.to_f]
170
+ end
171
+
172
+ # Wired when optional rgeo-proj4 / PROJ is available; otherwise leave WGS84 nil.
173
+ def transform_etrs89(easting, northing)
174
+ return [nil, nil] if easting.nil? || northing.nil?
175
+
176
+ Adressevaelger::Etrs89ToWgs84.call(easting: easting, northing: northing) || [nil, nil]
177
+ end
176
178
 
177
- def get_json(path, params = {})
178
- uri = URI.join("#{base_url}/", path.delete_prefix("/"))
179
- query = params.merge(token: token).compact
180
- uri.query = URI.encode_www_form(query)
179
+ def get_json(path, params = {})
180
+ uri = URI.join("#{base_url}/", path.delete_prefix("/"))
181
+ query = params.merge(token: token).compact
182
+ uri.query = URI.encode_www_form(query)
181
183
 
182
- request = Net::HTTP::Get.new(uri)
183
- request["Accept"] = "application/json"
184
- request["User-Agent"] = "Adressevaelger/#{VERSION}"
184
+ request = Net::HTTP::Get.new(uri)
185
+ request["Accept"] = "application/json"
186
+ request["User-Agent"] = "Adressevaelger/#{VERSION}"
185
187
 
186
- response = perform(uri, request)
187
- unless response.is_a?(Net::HTTPSuccess)
188
- raise ProviderError, "Adressevælger error (#{response.code})"
189
- end
188
+ response = perform(uri, request)
189
+ raise ProviderError, "Adressevælger error (#{response.code})" unless response.is_a?(Net::HTTPSuccess)
190
190
 
191
+ begin
191
192
  JSON.parse(response.body)
192
193
  rescue JSON::ParserError
193
194
  raise ProviderError, "Adressevælger returned invalid JSON"
194
195
  end
196
+ end
195
197
 
196
- def perform(uri, request)
197
- if http
198
- return http.call(uri, request)
199
- end
198
+ def perform(uri, request)
199
+ return http.call(uri, request) if http
200
200
 
201
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 10) do |client|
202
- client.request(request)
203
- end
204
- rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError, Socket::ResolutionError => e
205
- raise ProviderError, "Adressevælger unreachable (#{e.class})"
201
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5,
202
+ read_timeout: 10) do |client|
203
+ client.request(request)
206
204
  end
205
+ rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
206
+ raise ProviderError, "Adressevælger unreachable (#{e.class})"
207
+ end
207
208
 
208
- def blank?(value)
209
- value.nil? || (value.respond_to?(:empty?) && value.empty?) ||
210
- (value.is_a?(String) && value.strip.empty?)
211
- end
209
+ def blank?(value)
210
+ value.nil? || (value.respond_to?(:empty?) && value.empty?) ||
211
+ (value.is_a?(String) && value.strip.empty?)
212
+ end
212
213
 
213
- def present?(value)
214
- !blank?(value)
215
- end
214
+ def present?(value)
215
+ !blank?(value)
216
+ end
216
217
 
217
- def present_string(value)
218
- return nil if blank?(value)
218
+ def present_string(value)
219
+ return nil if blank?(value)
219
220
 
220
- value.to_s
221
- end
221
+ value.to_s
222
+ end
222
223
  end
223
224
  end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adressevaelger
4
- Error = Class.new(StandardError)
4
+ class Error < StandardError
5
+ end
6
+
5
7
  # Raised when the Adressevælger HTTP API fails, returns invalid JSON, or is unreachable.
6
- ProviderError = Class.new(Error)
8
+ class ProviderError < Error
9
+ end
7
10
  end
@@ -13,7 +13,7 @@ module Adressevaelger
13
13
 
14
14
  begin
15
15
  require "rgeo/proj4"
16
- @source = RGeo::CoordSys::Proj4.create(25832)
16
+ @source = RGeo::CoordSys::Proj4.create(25_832)
17
17
  @target = RGeo::CoordSys::Proj4.create(4326)
18
18
  @available = true
19
19
  rescue LoadError, StandardError
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adressevaelger
4
- # Provider-shaped resolved Danish address (no host-app persistence helpers).
4
+ # Resolved Danish address from Adressevælger.
5
5
  ResolvedAddress = Data.define(
6
6
  :line1,
7
7
  :line2,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adressevaelger
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -8,8 +8,5 @@ require_relative "adressevaelger/etrs89_to_wgs84"
8
8
  require_relative "adressevaelger/client"
9
9
 
10
10
  # Ruby client for Klimadatastyrelsen Adressevælger + Adressevask (DAR).
11
- #
12
- # Denmark only. Country routing, manual entry, and commercial geocoders belong
13
- # in the host application.
14
11
  module Adressevaelger
15
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adressevaelger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rasmus Bergholdt
@@ -19,6 +19,7 @@ executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - ".rubocop.yml"
22
23
  - CHANGELOG.md
23
24
  - LICENSE.txt
24
25
  - README.md
@@ -35,8 +36,8 @@ licenses:
35
36
  - MIT
36
37
  metadata:
37
38
  homepage_uri: https://github.com/bergholdt/adressevaelger
38
- source_code_uri: https://github.com/bergholdt/adressevaelger/tree/v0.1.0
39
- changelog_uri: https://github.com/bergholdt/adressevaelger/blob/v0.1.0/CHANGELOG.md
39
+ source_code_uri: https://github.com/bergholdt/adressevaelger/tree/v0.1.1
40
+ changelog_uri: https://github.com/bergholdt/adressevaelger/blob/v0.1.1/CHANGELOG.md
40
41
  bug_tracker_uri: https://github.com/bergholdt/adressevaelger/issues
41
42
  rubygems_mfa_required: 'true'
42
43
  rdoc_options: []