Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move model parser to separate class #74

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 92 additions & 57 deletions lib/swagger_yard/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,82 +3,117 @@ module SwaggerYard
# Carries id (the class name) and properties for a referenced
# complex model object as defined by swagger schema
#
class Model
include Example
attr_reader :id, :discriminator, :inherits,
:description, :properties, :additional_properties
Model = Struct.new(:id, :discriminator, :inherits, :description, :properties, :additional_properties, :example, keyword_init: true) do
def property(key)
properties.detect { |prop| prop.name == key }
end
end

class ModelParser
def model
return unless id

Model.new(
id: id,
discriminator: discriminator,
inherits: inherits,
description: @yard_object.docstring,
properties: properties,
example: example,
additional_properties: additional_properties,
)
end

def self.from_yard_object(yard_object)
new.tap do |model|
model.add_info(yard_object)
model.parse_tags(yard_object.tags)
yard_object.children.each do |child|
next unless child.is_a?(YARD::CodeObjects::MethodObject)
prop = Property.from_method(child)
model.properties << prop if prop
end
end
new(yard_object).model
end

def self.mangle(name)
name.gsub(/[^[:alnum:]_]+/, '_')
end

def initialize
@properties = []
@inherits = []
def initialize(yard_object)
@yard_object = yard_object
end

def valid?
!id.nil? && @has_model_tag
def id
return unless tag('model')

name = tag('model').text.presence || @yard_object.path

self.class.mangle(name)
end

def add_info(yard_object)
@description = yard_object.docstring
@id = Model.mangle(yard_object.path)
def inherits
tags('inherits').map(&:text)
end

def property(key)
properties.detect {|prop| prop.name == key }
def discriminator
return unless tag('discriminator')

Property.from_tag(tag('discriminator')).name
end

TAG_ORDER = %w(model inherits discriminator property example additional_properties)

def parse_tags(tags)
sorted_tags = tags.each_with_index.sort_by { |t,i|
[TAG_ORDER.index(t.tag_name), i] }.map(&:first)
sorted_tags.each do |tag|
case tag.tag_name
when "model"
@has_model_tag = true
@id = Model.mangle(tag.text) unless tag.text.empty?
when "property"
prop = Property.from_tag(tag)
@properties << prop if prop
when "discriminator"
prop = Property.from_tag(tag)
if prop
@properties << prop
@discriminator ||= prop.name
end
when "inherits"
@inherits << tag.text
when "example"
if tag.name && !tag.name.empty?
if (prop = property(tag.name))
prop.example = tag.text
else
SwaggerYard.log.warn("no property '#{tag.name}' defined yet to which to attach example: #{tag.text.inspect}")
end
else
self.example = tag.text
end
when "additional_properties"
@additional_properties = Type.new(tag.text).schema
def properties
return @properties if @properties

@properties = []

# Properties from the direct tags
tags('property').each do |property_tag|
property = Property.from_tag(property_tag)
@properties.push property if property
end

# Property from discriminator tag
@properties.push Property.from_tag(tag('discriminator')) if tag('discriminator')

# Properties from nested method definition
@yard_object.children.each do |child|
next unless child.is_a?(YARD::CodeObjects::MethodObject)
property = Property.from_method(child)
@properties.push property if property
end

# Search examples
tags('example').each do |example_tag|
next if example_tag.name.blank?

property = @properties.find { |prop| prop.name == example_tag.name }
if property
property.example = example_tag.text
else
SwaggerYard.log.warn <<~MESSAGE
no property '#{example_tag.name}' defined yet to which to attach example:

#{example_tag.text.inspect}

MESSAGE
end
end

self
@properties
end

def tag(key)
@yard_object.tags.find { |tag| tag.tag_name == key }
end

def tags(key)
@yard_object.tags.select { |tag| tag.tag_name == key }
end

def additional_properties
return unless tag('additional_properties')

Type.new(tag('additional_properties').text).schema
end

def example
tag = tags('example').find { |tag| tag.name.blank? }
return unless tag

JSON.parse(tag.text) rescue tag.text
end
end
end
4 changes: 2 additions & 2 deletions lib/swagger_yard/specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ def parse_models
@model_paths.map do |model_path|
Dir[model_path.to_s].map do |file_path|
SwaggerYard.yard_class_objects_from_file(file_path).map do |obj|
Model.from_yard_object(obj)
ModelParser.from_yard_object(obj)
end
end
end.flatten.compact.select(&:valid?)
end.flatten.compact
end

def parse_controllers
Expand Down
4 changes: 2 additions & 2 deletions lib/swagger_yard/type_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class Transform < Parslet::Transform
when "date-time", "date", "time", "uuid"
{ 'type' => 'string', 'format' => v }
else
name = Model.mangle(v)
name = ModelParser.mangle(v)
if /[[:upper:]]/.match(name)
{ '$ref' => "#{model_path}#{name}" }
else
Expand All @@ -96,7 +96,7 @@ class Transform < Parslet::Transform
rule(external_identifier: { namespace: simple(:namespace), identifier: simple(:identifier) }) do
prefix, name = namespace.to_s, identifier.to_s
url, fragment = resolve_uri.call(name, prefix)
{ '$ref' => "#{url}#{fragment}#{Model.mangle(name)}" }
{ '$ref' => "#{url}#{fragment}#{ModelParser.mangle(name)}" }
end

rule(formatted: { name: simple(:name), format: simple(:format) }) do
Expand Down
1 change: 1 addition & 0 deletions spec/fixtures/models/person.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# @model Person
# @property [Person] parent
# @discriminator myType(required) [string]
class Person
extend Forwardable

Expand Down
5 changes: 3 additions & 2 deletions spec/lib/swagger_yard/model_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spec_helper'

RSpec.describe SwaggerYard::Model do
RSpec.describe SwaggerYard::ModelParser do
let(:content) do
[ "@model MyModel",
"@discriminator myType(required) [string]" ].join("\n")
Expand Down Expand Up @@ -51,7 +51,7 @@
context "with no @model tag" do
let(:content) { "Some description without a SwaggerYard model tag" }

it { is_expected.to_not be_valid }
it { is_expected.to be_nil }
end

context "with an @example" do
Expand Down Expand Up @@ -100,6 +100,7 @@

its('properties') { is_expected.to include(a_property_named('address'),
a_property_named('parent'),
a_property_named('myType'),
a_property_named('age')) }

its('properties') { is_expected.to_not include(a_property_named('some_non_model_method'),
Expand Down
2 changes: 1 addition & 1 deletion spec/lib/swagger_yard/swagger_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
end

context "models" do
let(:model) { SwaggerYard::Model.from_yard_object(yard_class('MyModel', content)) }
let(:model) { SwaggerYard::ModelParser.from_yard_object(yard_class('MyModel', content)) }
let(:spec) { stub(path_objects: SwaggerYard::Paths.new([]), tag_objects: [],
security_objects: [], model_objects: { model.id => model }) }

Expand Down
2 changes: 1 addition & 1 deletion spec/support/shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
SwaggerYard.yard_class_objects_from_file((FIXTURE_PATH + 'models' + 'person.rb').to_s)
end

let(:model) { SwaggerYard::Model.from_yard_object(objects.first) }
let(:model) { SwaggerYard::ModelParser.from_yard_object(objects.first) }
end