Skip to content

Commit

Permalink
Merge branches 'oad_schemas', 'dev' and 'infra' into HEAD
Browse files Browse the repository at this point in the history
  • Loading branch information
notEthan committed Nov 27, 2024
3 parents c838c8e + 6a86ab4 + 37f8b77 commit e59e26e
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 61 deletions.
27 changes: 24 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
name: test
on:
- push
- pull_request
jobs:
gig:
runs-on: ubuntu-latest

env:
BUNDLE_WITHOUT: dev test doc # ruby/setup-ruby's invocation of bundle install will be without these groups

steps:

- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: head
bundler-cache: true

- run: bundle exec rake gig

test:
strategy:
fail-fast: false
Expand All @@ -19,9 +37,12 @@ jobs:

runs-on: ${{ matrix.runs-on }}

env:
BUNDLE_WITHOUT: dev doc # ruby/setup-ruby's invocation of bundle install will be without these groups

steps:

- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
submodules: recursive

Expand All @@ -33,7 +54,7 @@ jobs:
- run: bundle exec rake test:each_format

- name: Report to Coveralls
uses: coverallsapp/github-action@1.1.3
uses: coverallsapp/github-action@v2.3.4
with:
github-token: ${{ secrets.github_token }}
flag-name: "ruby: ${{ matrix.ruby-version }} os: ${{ matrix.runs-on }}"
Expand All @@ -45,7 +66,7 @@ jobs:
steps:

- name: Report completion to Coveralls
uses: coverallsapp/github-action@1.1.3
uses: coverallsapp/github-action@v2.3.4
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
74 changes: 52 additions & 22 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,59 @@ source 'https://rubygems.org'

gemspec

group(:dev) do
platform(:mri) do
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7')
gem('debug', '> 1')
else
gem('byebug')
end
end
platform(:jruby) { gem('ruby-debug') }
end

gem 'rake'
gem 'gig'
gem 'minitest', '~> 5.0'
gem 'minitest-around'
gem 'minitest-reporters'
gem 'simplecov'
gem 'simplecov-lcov'
gem 'sinatra', '~> 1.0'
gem 'rack', '~> 1.0'
gem 'rack-accept'
gem 'rack-test'
gem 'webrick'
gem 'api_hammer'
activerecord_version =
RUBY_ENGINE == 'truffleruby' ? '>= 6' : # TODO rm why is truffleruby using 5.x without this?
RUBY_ENGINE == 'jruby' ? '< 7.1' : # TODO rm. some incompatibility with activerecord-jdbc-adapter at 7.1
nil
gem('activerecord', *activerecord_version)
platform(:mri, :truffleruby) do
gem 'sqlite3', '~> 1.4' # loosen this in accordance with active_record/connection_adapters/sqlite3_adapter.rb

group(:test) do
gem('minitest', '~> 5.0')
gem('minitest-around')
gem('minitest-reporters')
gem('simplecov')
gem('simplecov-lcov')
gem('sinatra', '~> 1.0')
gem('rack', '~> 1.0')
gem('rack-accept')
gem('rack-test')
gem('webrick')
gem('api_hammer')

# sqlite3 version is in accordance with active_record/connection_adapters/sqlite3_adapter.rb
[
{activerecord: '~> 8.0', ruby: '3.2', sqlite: '>= 2.1'},
{activerecord: '~> 7.2', ruby: '3.1', sqlite: '>= 1.4'},
{activerecord: '~> 7.0', ruby: '2.7', sqlite: '>= 1.4'},
{activerecord: '~> 6.0', ruby: '2.5', sqlite: '~> 1.4'},
].map(&:values).each do |activerecord, ruby, sqlite|
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(ruby)
if RUBY_ENGINE == 'jruby'
# override. update this per released version of activerecord-jdbc-adapter, current latest 70.x corresponding to Rails 7.0.x
activerecord = '< 7.1'
end
gem('activerecord', activerecord)

platform(:mri, :truffleruby) do
gem('sqlite3', sqlite)
end
break
end
end
platform(:jruby) do
gem('activerecord-jdbcsqlite3-adapter')
end
gem('database_cleaner')
end
platform(:jruby) do
gem 'activerecord-jdbcsqlite3-adapter'

group(:doc) do
gem('yard')
end
gem 'database_cleaner'
gem 'yard'
2 changes: 1 addition & 1 deletion lib/scorpio/google_api_document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Scorpio
module Google
discovery_rest_description_doc = ::JSON.parse(Scorpio.root.join('documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest').read)
discovery_rest_description_doc = JSON.parse(Scorpio.root.join('documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest').read, freeze: true)
discovery_rest_description = JSI::MetaSchemaNode.new(
discovery_rest_description_doc,
metaschema_root_ptr: JSI::Ptr['schemas']['JsonSchema'],
Expand Down
24 changes: 18 additions & 6 deletions lib/scorpio/openapi/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ class << self
#
# @param instance [#to_hash] the document to represent as a Scorpio OpenAPI Document
# @return [Scorpio::OpenAPI::V2::Document, Scorpio::OpenAPI::V3::Document]
def from_instance(instance)
def from_instance(instance, **new_param)
if instance.is_a?(Scorpio::OpenAPI::Document)
instance
elsif instance.is_a?(JSI::Base)
raise(TypeError, "instance is unexpected JSI type: #{instance.class.inspect}")
elsif instance.respond_to?(:to_hash)
if instance['swagger'] =~ /\A2(\.|\z)/
instance = Scorpio::OpenAPI::V2::Document.new_jsi(instance)
elsif instance['openapi'] =~ /\A3(\.|\z)/
instance = Scorpio::OpenAPI::V3::Document.new_jsi(instance)
if (instance['swagger'].is_a?(String) && instance['swagger'] =~ /\A2(\.|\z)/) || instance['swagger'] == 2
instance = Scorpio::OpenAPI::V2::Document.new_jsi(instance, **new_param)
elsif (instance['openapi'].is_a?(String) && instance['openapi'] =~ /\A3\.0(\.|\z)/) || instance['openapi'] == 3.0
instance = Scorpio::OpenAPI::V3::Document.new_jsi(instance, **new_param)
else
raise(ArgumentError, "instance does not look like a recognized openapi document")
end
Expand Down Expand Up @@ -75,7 +75,19 @@ def v3?

def operations
return @operations if instance_variable_defined?(:@operations)
@operations = OperationsScope.new(self)
@operations = OperationsScope.new(each_operation)
end

def each_operation(&block)
return(to_enum(__method__)) unless block

paths.each do |path, path_item|
path_item.each do |http_method, operation|
if operation.is_a?(Scorpio::OpenAPI::Operation)
yield(operation)
end
end
end
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/scorpio/openapi/operation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,11 @@ def inferred_parameters
def request_accessor_module
return @request_accessor_module if instance_variable_defined?(:@request_accessor_module)
@request_accessor_module = begin
operation = self
params_by_name = inferred_parameters.group_by { |p| p['name'] }
Module.new do
instance_method_modules = [Request, Request::Configurables]
define_singleton_method(:inspect) { "(Scorpio param module for operation: #{operation.human_id})" }
instance_method_modules = [Request]
instance_method_names = instance_method_modules.map do |mod|
(mod.instance_methods + mod.private_instance_methods).map(&:to_s)
end.inject(Set.new, &:merge)
Expand Down
21 changes: 8 additions & 13 deletions lib/scorpio/openapi/operations_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

module Scorpio
module OpenAPI
# OperationsScope acts as an Enumerable of the Operations for an openapi_document,
# OperationsScope is an Enumerable for a collection of Operations,
# and offers subscripting by operationId.
class OperationsScope
# @param openapi_document [Scorpio::OpenAPI::Document]
def initialize(openapi_document)
@openapi_document = openapi_document
# @param enum [Enumerable]
def initialize(enum)
@enum = enum
@operations_by_id = Hash.new do |h, operationId|
op = detect { |operation| operation.operationId == operationId }
op = enum.detect { |operation| operation.operationId == operationId }
unless op
raise(::KeyError, "operationId not found: #{operationId.inspect}")
end
Expand All @@ -19,15 +19,10 @@ def initialize(openapi_document)
attr_reader :openapi_document

# @yield [Scorpio::OpenAPI::Operation]
def each
openapi_document.paths.each do |path, path_item|
path_item.each do |http_method, operation|
if operation.is_a?(Scorpio::OpenAPI::Operation)
yield operation
end
end
end
def each(&block)
@enum.each(&block)
end

include Enumerable

# finds an operation with the given `operationId`
Expand Down
4 changes: 2 additions & 2 deletions lib/scorpio/openapi/reference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def [](token, **kw)
# @yield [JSI::Base] if a block is given
# @return [JSI::Base]
def deref
return unless respond_to?(:to_hash) && self['$ref'].respond_to?(:to_str)
return unless respond_to?(:to_hash) && key?('$ref') && jsi_node_content['$ref'].respond_to?(:to_str)

ref_uri = Addressable::URI.parse(self['$ref'])
ref_uri = Addressable::URI.parse(jsi_node_content['$ref'])
ref_uri_nofrag = ref_uri.merge(fragment: nil)

if !ref_uri_nofrag.empty? || ref_uri.fragment.nil?
Expand Down
13 changes: 11 additions & 2 deletions lib/scorpio/openapi/tag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ module Tag
# operations in the openapi document which have a tag with this tag's name
# @return [Enumerable<Scorpio::OpenAPI::Operation>]
def operations
return(@operations) if instance_variable_defined?(:@operations)
@operations = OperationsScope.new(each_operation)
end

def each_operation(&block)
unless jsi_root_node.is_a?(OpenAPI::Document)
raise("Tag#operations cannot be used on a Tag that is not inside an OpenAPI document")
raise("Tag#each_operation cannot be used on a Tag that is not inside an OpenAPI document")
end

jsi_root_node.operations.select { |op| op.tags.respond_to?(:to_ary) && op.tags.include?(name) }
return(to_enum(__method__)) unless block

jsi_root_node.each_operation do |op|
yield(op) if op.tags.respond_to?(:to_ary) && op.tags.include?(name)
end
end
end
end
Expand Down
20 changes: 18 additions & 2 deletions lib/scorpio/request.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# frozen_string_literal: true

module Scorpio
# a Request from a {Scorpio::OpenAPI::Operation}.
# Base class, not directly instantiated; subclassed per operation, defining accessors for operation params.
# Used by {Scorpio::OpenAPI::Operation#build_request} and related methods.
class Request
# media types for which Scorpio has implemented generating / parsing between body
# and body_object (see {Request#body} and {Response#body_object})
Expand Down Expand Up @@ -130,10 +133,23 @@ def logger
include Configurables

@request_class_by_operation = Hash.new do |h, op|
h[op] = Class.new(Request) do
request_class = Class.new(Request) do
define_singleton_method(:inspect) { -"#{Request} (for operation: #{op.human_id})" }
define_method(:operation) { op }
include(op.request_accessor_module)
end

# naming the class helps with debugging and some built-in ruby error messages
const_name = JSI::Util::Private.const_name_from_parts([
op.openapi_document && (op.openapi_document.jsi_schema_base_uri ||
(op.openapi_document.info && op.openapi_document.info.title)),
*(op.operationId ? [op.operationId] : [op.http_method, op.path_template_str]),
].compact)
if const_name && !Request.const_defined?(const_name)
Request.const_set(const_name, request_class)
end

h[op] = request_class
end

def self.request_class_by_operation(operation)
Expand Down Expand Up @@ -318,7 +334,7 @@ def set_param_from(param_in, name, value)
elsif param_in == 'query'
self.query_params = (self.query_params || {}).merge(name => value)
elsif param_in == 'header'
self.headers = self.headers.merge(name => value)
self.headers = self.headers.merge(name => value.to_str)
elsif param_in == 'cookie'
raise(NotImplementedError, "cookies not implemented: #{name.inspect} => #{value.inspect}")
else
Expand Down
27 changes: 20 additions & 7 deletions lib/scorpio/resource_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def define_inheritable_accessor(accessor, default_value: nil, default_getter: ->
end
update_dynamic_methods
else
self.represented_schemas = JSI::SchemaSet.ensure_schema_set(represented_schemas)
self.represented_schemas = JSI::SchemaSet.new(represented_schemas)
end
end)
define_inheritable_accessor(:models_by_schema, default_value: {}.freeze)
Expand Down Expand Up @@ -168,8 +168,11 @@ def operation_for_resource_class?(operation)
request_response_schemas = operation.request_schemas | operation.response_schemas
# TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
# use all inplace applicators, not conditional on instance
all_request_response_schemas = request_response_schemas.each_inplace_applicator_schema(nil)
return true if all_request_response_schemas.any? { |s| represented_schemas.include?(s) }
request_response_schemas.each do |s|
s.each_inplace_applicator_schema(nil) do |ias|
return true if represented_schemas.include?(ias)
end
end

return false
end
Expand All @@ -181,8 +184,11 @@ def operation_for_resource_instance?(operation)
#
# TODO/FIX nil instance is wrong. works for $ref and allOf, not for others.
# use all inplace applicators, not conditional on instance
all_request_schemas = operation.request_schemas.each_inplace_applicator_schema(nil)
return true if all_request_schemas.any? { |s| represented_schemas.include?(s) }
operation.request_schemas.each do |s|
s.each_inplace_applicator_schema(nil) do |ias|
return true if represented_schemas.include?(ias)
end
end

# the below only apply if the operation has this resource's tag
return false unless tag_name && operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
Expand Down Expand Up @@ -346,8 +352,15 @@ def call_operation(operation, call_params: nil, model_attributes: nil)
request.body_object = other_params
else
if other_params.respond_to?(:to_hash)
# TODO pay more attention to 'parameters' api method attribute
request.query_params = other_params
other_params.to_hash.each_pair do |name, value|
param = request.param_for(name)
if param
request.set_param_from(param['in'], param['name'], value)
else
# set any params not described by operation params in the query
request.set_param_from('query', name, value)
end
end
else
raise
end
Expand Down
2 changes: 2 additions & 0 deletions lib/scorpio/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Scorpio
Response = Scorpio::Ur.properties['response']

# Scorpio::Response is a JSI schema module describing the same instances as ::Ur::Response.
# It relies on methods of that module.
module Response
# the schema for this response according to its OpenAPI doc
# @return [::JSI::Schema]
Expand Down
2 changes: 1 addition & 1 deletion test/scorpio_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_that_it_has_a_version_number
articles.patch
)
exp_ops = exp_opIds.map { |id| BlogModel.openapi_document.operations[id] }
act_ops = tag.operations
act_ops = tag.operations.to_a
assert_equal(exp_ops, act_ops)
end
end
Loading

0 comments on commit e59e26e

Please sign in to comment.