diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 2c84a21..a28bf32 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['3.0', '3.2'] + ruby-version: ['2.6', '2.7', '3.0', '3.2', '3.3'] steps: - uses: actions/checkout@v3 diff --git a/.rubocop.yml b/.rubocop.yml index 867c413..cdb341b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ AllCops: - TargetRubyVersion: 3.0 + TargetRubyVersion: 2.6 Metrics/BlockLength: Exclude: diff --git a/CHANGES.md b/CHANGES.md index eb24c89..6273bcb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,9 @@ ## [Unreleased] +## 0.2.0 +- Upgrade to Open Weather One Call API 3.0 through configuration option (#8) + ## 0.1.6 - Relax required Ruby Version diff --git a/README.md b/README.md index c395164..4c2a993 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ![RSpec](https://github.com/qurasoft/open_weather_client/actions/workflows/ruby.yml/badge.svg) Welcome to Open Weather Client. -This gem allows you to easily request weather information from OpenWeatherMap. +This gem allows you to easily request weather information from Open Weather. It integrates in your rails project, when you are using bundler or even in plain ruby projects. ## Installation @@ -24,7 +24,7 @@ Or install it yourself as: $ gem install open_weather_client ## Usage -During configuration the OpenWeatherMap API key must be set. Afterwards it is as simple as calling `OpenWeatherClient.current(lat:, lon:)` to get the current weather at a location. +During configuration the Open Weather API key must be set. Afterwards it is as simple as calling `OpenWeatherClient.current(lat:, lon:)` to get the current weather at a location. ```ruby # OpenWeatherClient initializer @@ -36,13 +36,32 @@ end OpenWeatherClient::Weather.current(lat: 50.3569, lon: 7.5890) ``` +### Used Open Weather API version +Open Weather provides One Call API 3.0 and API 2.5, which is expected to be deprecated in June 2024. +Until API 2.5 is deprecated it is the default. With Open Weather Client version 1.0 the default will change to One Call API 3.0. + +The used API version is configurable through `OpenWeatherClient.configuration.api_version`. + +```ruby +# OpenWeatherClient initializer +OpenWeatherClient.configure do |config| + config.api_version = :v25 # (default) also supports :v30 +end +``` + ### Exceptions during requests When an error occurs during a request, an exception is raised. -If the request is not authorized `OpenWeatherClient::AutheniticationError` is raied. -When attributes like latitude or longitude are outside of the expected range a `RangeError` is raised. + +If the configured API version is not supported by the client, `OpenWeatherClient::APIVersionNotSupportedError` is raised. +If the request is not authorized, `OpenWeatherClient::AutheniticationError` is raised. +If attributes like latitude or longitude are outside of the expected range, `RangeError` is raised. +If the time is longer in the past than one hour, `OpenWeatherClient::NotCurrentError` is raised. ### Secure Configuration -In Rails provides the credentials functionality for [environmental security](https://edgeguides.rubyonrails.org/security.html#environmental-security). This mechanism can be used by OpenWeatherClient to load the API key from an encrypted file. This also allows easy separation of production and development channel configuration. +Rails provides the credentials functionality for [environmental security](https://edgeguides.rubyonrails.org/security.html#environmental-security). +This mechanism can be used by OpenWeatherClient to load the API key from an encrypted file. +This also allows easy separation of production and development api key configuration. + All settings are defined under the top-level entry `open_weather_client`. ```yaml # $ bin/rails credentials:edit @@ -50,7 +69,7 @@ open_weather_client: appid: "" ``` -After configuration of the credentials you can load the settings in your initializer with `#load_from_rails_configuration`. +After configuring the credentials, they can be loaded in the initializer with `#load_from_rails_configuration`. ```ruby # OpenWeatherClient initializer diff --git a/bin/console b/bin/console index 2f0b7e8..c5d9903 100755 --- a/bin/console +++ b/bin/console @@ -13,11 +13,10 @@ require 'open_weather_client' OpenWeatherClient.configure do |config| if File.exist?('config.yml') - # NOTE(Keune): The channels available in the console can be set in the file channels.yml. It contains a simple - # mapping of channel identifiers to webhook URLs. The channel identifier is loaded as a symbol. + # NOTE(Keune): The console automatically loads the appid from the config.yml file in the same directory # # Example: APPID: "" - puts 'Load channels specified by file' + puts 'Load appid specified by file' require 'yaml' settings = YAML.load_file 'config.yml' diff --git a/lib/open_weather_client.rb b/lib/open_weather_client.rb index b3ae765..7e15700 100644 --- a/lib/open_weather_client.rb +++ b/lib/open_weather_client.rb @@ -9,10 +9,13 @@ ## # Get weather data from OpenWeatherMap module OpenWeatherClient + # The configured Open Weather API Version is not supported + class APIVersionNotSupportedError < StandardError; end + # Error during authentication with the OpenWeatherMap servers class AuthenticationError < StandardError; end - # The rquested time is not current enough for getting weather data from the OpenWeatherMap server + # The requested time is not current enough for getting weather data from the OpenWeatherMap server class NotCurrentError < StandardError; end class << self diff --git a/lib/open_weather_client/caching/memory.rb b/lib/open_weather_client/caching/memory.rb index 084b599..e745d84 100644 --- a/lib/open_weather_client/caching/memory.rb +++ b/lib/open_weather_client/caching/memory.rb @@ -9,9 +9,9 @@ class Caching # When the limit is reached the least recently used entry is evicted. class Memory < OpenWeatherClient::Caching # Memory cache to store a hash of keys and request data - attr :memory_cache + attr_reader :memory_cache # Key registry of the memory cache. The first entry is the key of the least recently accessed data - attr :memory_keys + attr_reader :memory_keys ## # Initialize an empty cache diff --git a/lib/open_weather_client/configuration.rb b/lib/open_weather_client/configuration.rb index ab52ef1..e71566c 100644 --- a/lib/open_weather_client/configuration.rb +++ b/lib/open_weather_client/configuration.rb @@ -4,6 +4,8 @@ module OpenWeatherClient ## # Configuratin of OpenWeatherClient class Configuration + # [String] Used api version. One of :v25, :v30. Default :v25 + attr_accessor :api_version # [String] API key to access OpenWeatherMap attr_accessor :appid # Caching method. One of :none, :memory @@ -24,6 +26,7 @@ class Configuration ## # Initialize a new Configuration with the default settings def initialize + @api_version = :v25 @caching = OpenWeatherClient::Caching.new @lang = 'de' @max_memory_entries = 10_000 diff --git a/lib/open_weather_client/request.rb b/lib/open_weather_client/request.rb index 9bc1fa2..df7ac87 100644 --- a/lib/open_weather_client/request.rb +++ b/lib/open_weather_client/request.rb @@ -13,6 +13,7 @@ class Request # @param lon[Float] longitude of the requests location # @param time[Time] time of the request # + # @raise [APIVersionNotSupportedError] if the configured api version is not supported # @raise [AuthenticationError] if the request is not authorized, e.g in case the API key is not correct # @raise [NotCurrentError] if the requested time is older than 1 hour # @@ -21,7 +22,7 @@ def self.get(lat:, lon:, time: Time.now) raise OpenWeatherClient::NotCurrentError if time < Time.now - 1 * 60 * 60 begin - response = connection(lat, lon).get('2.5/weather') + response = connection(lat, lon).get(path) OpenWeatherClient.cache.store(lat: lat, lon: lon, data: response.body, time: time) rescue Faraday::UnauthorizedError raise OpenWeatherClient::AuthenticationError @@ -54,5 +55,16 @@ def self.params(lat, lon) units: OpenWeatherClient.configuration.units } end + + def self.path + case OpenWeatherClient.configuration.api_version + when :v25 + '2.5/weather' + when :v30 + '3.0/onecall' + else + raise OpenWeatherClient::APIVersionNotSupportedError + end + end end end diff --git a/lib/open_weather_client/version.rb b/lib/open_weather_client/version.rb index 38fb9aa..546c6d4 100644 --- a/lib/open_weather_client/version.rb +++ b/lib/open_weather_client/version.rb @@ -3,5 +3,5 @@ module OpenWeatherClient ## # Version of OpenWeatherClient - VERSION = '0.1.6' + VERSION = '0.2.0' end diff --git a/spec/open_weather_client/configuration_spec.rb b/spec/open_weather_client/configuration_spec.rb index 25b8eeb..5aa10ff 100644 --- a/spec/open_weather_client/configuration_spec.rb +++ b/spec/open_weather_client/configuration_spec.rb @@ -20,6 +20,7 @@ def self.application subject { OpenWeatherClient.configuration } it 'has a default configuration' do + is_expected.to have_attributes(api_version: :v25) is_expected.to have_attributes(appid: nil) is_expected.to have_attributes(caching: OpenWeatherClient::Caching) is_expected.to have_attributes(units: 'metric') diff --git a/spec/open_weather_client/request_spec.rb b/spec/open_weather_client/request_spec.rb index e7b6739..c2ec444 100644 --- a/spec/open_weather_client/request_spec.rb +++ b/spec/open_weather_client/request_spec.rb @@ -3,21 +3,48 @@ require 'open_weather_client/caching/memory' RSpec.describe OpenWeatherClient::Request do - describe 'current weather' do + let(:appid) { '1234567890' } + let(:lat) { 50.3569 } + let(:lon) { 7.5890 } + + subject { OpenWeatherClient::Request.get(lat: lat, lon: lon) } + + before :each do + OpenWeatherClient.configuration.appid = appid + end + + context 'when One Call API 3.0' do before :each do - OpenWeatherClient.configuration.appid = '1234567890' - stub_request(:get, 'https://api.openweathermap.org/data/2.5/weather').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json')) + OpenWeatherClient.configuration.api_version = :v30 + stub_request(:get, 'https://api.openweathermap.org/data/3.0/onecall').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json')) + end + + it 'requests the current weather from correct endpoint' do + subject + + expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/3.0/onecall').with query: { appid: appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units } end + end - let(:lat) { 50.3569 } - let(:lon) { 7.5890 } + context 'when unsupported API version' do + before :each do + OpenWeatherClient.configuration.api_version = :v666 + end - subject { OpenWeatherClient::Request.get(lat: lat, lon: lon) } + it 'raises not supported error' do + expect { subject }.to raise_error OpenWeatherClient::APIVersionNotSupportedError + end + end + + describe 'current weather with default configuration' do + before :each do + stub_request(:get, 'https://api.openweathermap.org/data/2.5/weather').with(query: hash_including(:appid, :lat, :lon, :lang, :units)).to_return(body: File.open('./spec/fixtures/weather/current.json')) + end it 'requests the current weather' do subject - expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/2.5/weather').with query: { appid: OpenWeatherClient.configuration.appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units } + expect(WebMock).to have_requested(:get, 'https://api.openweathermap.org/data/2.5/weather').with query: { appid: appid, lat: lat, lon: lon, lang: OpenWeatherClient.configuration.lang, units: OpenWeatherClient.configuration.units } end it 'has user agent' do