Skip to content

Commit

Permalink
Merge pull request #28 from nevinera/nev-add-rbs
Browse files Browse the repository at this point in the history
Add RBS/Steep type-declarations and checking
  • Loading branch information
nevinera authored Nov 14, 2023
2 parents bb9f937 + 40a118f commit f92d0ff
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/rspec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jobs:
fail-fast: false
matrix:
ruby-version: ['2.7', '3.0', '3.1', '3.2', 'head']
env:
NO_STEEP: "Because we need to test on versions that are older than steep supports"

steps:
- uses: actions/checkout@v3
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/type_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Steep Type Checking

on: push

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true

- name: Run Steep
run: bundle exec steep check
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
source "https://rubygems.org"

gemspec

unless ENV["NO_STEEP"]
gem "steep", "~> 1.6.0"
end
17 changes: 17 additions & 0 deletions Steepfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
D = Steep::Diagnostic

target :lib do
signature "sig"
check "lib"
configure_code_diagnostics(D::Ruby.default)
library "pathname"
library "date"
end

target :test do
signature "sig"
check "test"
configure_code_diagnostics(D::Ruby.default)
library "pathname"
library "date"
end
16 changes: 8 additions & 8 deletions lib/environment_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
# And we're compatible back to 2.6
require "set" # rubocop:disable Lint/RedundantRequireStatement

require_relative "./environment_helpers/access_helpers"
require_relative "./environment_helpers/string_helpers"
require_relative "./environment_helpers/boolean_helpers"
require_relative "./environment_helpers/range_helpers"
require_relative "./environment_helpers/numeric_helpers"
require_relative "./environment_helpers/file_helpers"
require_relative "./environment_helpers/datetime_helpers"
require_relative "./environment_helpers/enumerable_helpers"
require_relative "environment_helpers/access_helpers"
require_relative "environment_helpers/string_helpers"
require_relative "environment_helpers/boolean_helpers"
require_relative "environment_helpers/range_helpers"
require_relative "environment_helpers/numeric_helpers"
require_relative "environment_helpers/file_helpers"
require_relative "environment_helpers/datetime_helpers"
require_relative "environment_helpers/enumerable_helpers"

module EnvironmentHelpers
Error = Class.new(::StandardError)
Expand Down
25 changes: 13 additions & 12 deletions lib/environment_helpers/enumerable_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ module EnvironmentHelpers
module EnumerableHelpers
VALID_TYPES = %i[strings symbols integers]

TYPE_HANDLERS = {
integers: :to_i,
strings: :to_s,
symbols: :to_sym
}

TYPE_MAP = {
integers: Integer,
strings: String,
symbols: Symbol
}

def array(key, of: :strings, delimiter: ",", default: nil, required: false)
check_default_type(:array, default, Array)
check_valid_data_type!(of)
Expand All @@ -22,23 +34,12 @@ def check_valid_data_type!(type)
end

def check_default_data_types!(default, type)
return if default.nil?
invalid = Array(default).reject { |val| val.is_a? TYPE_MAP[type] }

unless invalid.empty?
fail(BadDefault, "Default array contains values not of type `#{type}': #{invalid.join(", ")}")
end
end

TYPE_HANDLERS = {
integers: :to_i,
strings: :to_s,
symbols: :to_sym
}

TYPE_MAP = {
integers: Integer,
strings: String,
symbols: Symbol
}
end
end
2 changes: 1 addition & 1 deletion lib/environment_helpers/range_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def integer_range(name, default: nil, required: false)
check_range_endpoint(:integer_range, default.end) if default

text = fetch_value(name, required: required)
range = parse_range_from(text)
range = text ? parse_range_from(text) : nil
return range if range
return default unless required
fail(InvalidRangeText, "Required Integer Range environment variable #{name} had inappropriate content '#{text}'")
Expand Down
2 changes: 1 addition & 1 deletion lib/environment_helpers/string_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def string(name, default: nil, required: false)

def symbol(name, default: nil, required: false)
check_default_type(:symbol, default, Symbol)
string(name, default: default, required: required)&.to_sym
string(name, default: default&.to_s, required: required)&.to_sym
end
end
end
96 changes: 96 additions & 0 deletions sig/environment_helpers.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
module EnvironmentHelpers
VERSION: String

class Error < StandardError
end

class MissingVariableError < Error
end

class BadDefault < Error
end

class BadFormat < Error
end

class InvalidType < Error
end

class InvalidValue < Error
end

class InvalidBooleanText < InvalidValue
end

class InvalidRangeText < InvalidValue
end

class InvalidIntegerText < InvalidValue
end

class InvalidDateText < InvalidValue
end

class InvalidDateTimeText < InvalidValue
end

module AccessHelpers
# (Actually provided by ENV, which these are all extended onto)
def fetch: (String name) -> String?
| (String name, String? default) -> String?

private def fetch_value: (String name, required: bool) -> String?
private def check_default_type: (String | Symbol context, untyped value, *Class types) -> void
private def check_default_value: (String | Symbol context, untyped value, allow: Enumerable[untyped]) -> void
end

module FileHelpers : AccessHelpers
def file_path: (String name, default: String?, required: bool) -> Pathname?
end

module DatetimeHelpers : AccessHelpers
type date_time_result = DateTime? | Time?
def date: (String name, format: String, default: Date?, required: bool) -> Date?
def date_time: (String name, format: String | Symbol, default: date_time_result, required: bool) -> date_time_result
private def parse_date_from: (String? text, format: String) -> Date?
private def parse_date_time_from: (String? text, format: String | Symbol) -> DateTime?
private def iso8601_date_time: (String) -> DateTime?
private def unix_date_time: (String) -> DateTime?
private def strptime_date_time: (String, format: String) -> DateTime?
end

module NumericHelpers : AccessHelpers
def integer: (String name, default: Integer?, required: bool) -> Integer?
end

module RangeHelpers : AccessHelpers
def integer_range: (String name, default: Range[Integer]?, required: bool) -> Range[Integer]?
private def check_range_endpoint: (String | Symbol context, untyped value) -> void
private def parse_range_bound_from: (String?) -> Integer?
private def parse_range_from: (String) -> Range[Integer]?
end

module BooleanHelpers : AccessHelpers
TRUTHY_STRINGS: Set[String]
FALSEY_STRINGS: Set[String]
BOOLEAN_VALUES: Set[bool]
def boolean: (String name, default: bool?, required: bool) -> boolish
private def truthy_text?: (String?) -> boolish
private def falsey_text?: (String?) -> boolish
end

module EnumerableHelpers : AccessHelpers
VALID_TYPES: Array[Symbol]
TYPE_HANDLERS: Hash[Symbol, Symbol]
TYPE_MAP: Hash[Symbol, Class]
type arrayable = Integer | String | Symbol
def array: (String key, of: Symbol, delimiter: String, default: Array[arrayable]?, required: bool) -> Array[arrayable]?
private def check_valid_data_type!: (Symbol) -> void
private def check_default_data_types!: (Array[arrayable]?, Symbol) -> void
end

module StringHelpers : AccessHelpers
def string: (String name, default: String?, required: bool) -> String?
def symbol: (String name, default: Symbol?, required: bool) -> Symbol?
end
end
6 changes: 6 additions & 0 deletions spec/environment_helpers/enumerable_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
subject(:array) { env.array(env_var, **params) }

with_env "FOO" => "a,bc,d"
without_env "FOOBAR"

context "when the value is not present" do
let(:env_var) { "FOOBAR" }
it { should be_nil }
end

describe "parameters" do
context "when passed an `of' param" do
Expand Down
2 changes: 1 addition & 1 deletion spec/environment_helpers/string_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
end

context "when the env value is not set" do
before { expect(ENV["FOO"]).to be_nil }
without_env "FOO"
it { is_expected.to be_nil }
end
end
Expand Down
12 changes: 12 additions & 0 deletions spec/support/with_environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ def with_env(overrides)
end
end
end

def without_env(*keys)
around(:each) do |ex|
orig_env = ENV.to_h
keys.each { |key| ENV.delete(key) }
begin
ex.run
ensure
ENV.replace(orig_env)
end
end
end
end

RSpec.configure do |config|
Expand Down

0 comments on commit f92d0ff

Please sign in to comment.