Skip to content

Commit

Permalink
feat(swagger-object) : Improve object creation by ref and add enum
Browse files Browse the repository at this point in the history
Add management of enumeration
Add support of enumeration inside creation by instance of Swagger::Object
Add refs for other Swagger::Object for non primitive type in creation by instance

(Related to icyleaf#18)

TODO : add spec for enumeration addition
  • Loading branch information
MathiusD committed Dec 23, 2022
1 parent 2a1802d commit 83c6db7
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 15 deletions.
55 changes: 50 additions & 5 deletions spec/swagger/object_spec.cr
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
require "../spec_helper"

struct Author
property name

def initialize(@name : String)
end
end

enum VCS
GIT
SUBVERSION
MERCURIAL
FOSSIL
end

struct Project
property id, name, description, vcs, open_source
property id, name, description, vcs, open_source, author

def initialize(@id : Int32, @name : String, @vcs : String, @open_source : Bool, @description : String? = nil)
def initialize(@id : Int32, @name : String, @vcs : VCS, @open_source : Bool, @author : Author, @description : String? = nil)
end
end

Expand Down Expand Up @@ -49,23 +63,54 @@ describe Swagger::Object do
raw.items.should eq("Comment")
end

it "should generate schema of object from object instance" do
it "should generate schema of object with ref from object instance" do
raw = Swagger::Object.create_from_instance(
Project.new(1, "swagger", "git", true, "Swagger contains a OpenAPI / Swagger universal documentation generator and HTTP server handler.")
Project.new(1,
"swagger", VCS::GIT, true,
Author.new("icyleaf"),
"Swagger contains a OpenAPI / Swagger universal documentation generator and HTTP server handler."),
refs: {Author => "Author"},
)
raw.name.should eq "Project"
raw.type.should eq "object"
raw.items.should be nil
raw.properties.should eq [
Swagger::Property.new("id", "integer", "int32", example: 1, required: true),
Swagger::Property.new("name", example: "swagger", required: true),
Swagger::Property.new("vcs", example: "git", required: true),
Swagger::Property.new("vcs", "object", example: "GIT", required: true, enum_values: [
"GIT", "SUBVERSION", "MERCURIAL", "FOSSIL",
]),
Swagger::Property.new("open_source", "boolean", example: true, required: true),
Swagger::Property.new("author", "object", required: true, ref: "Author"),
Swagger::Property.new(
"description",
example: "Swagger contains a OpenAPI / Swagger universal documentation generator and HTTP server handler.",
required: false
),
]
end

it "shouldn't generate schema of object without ref from object instance" do
expect_raises(Swagger::Object::RefResolutionException, "No refs provided !") do
Swagger::Object.create_from_instance(
Project.new(1,
"swagger", VCS::GIT, true,
Author.new("icyleaf"),
"Swagger contains a OpenAPI / Swagger universal documentation generator and HTTP server handler.")
)
end
end

it "shouldn't generate schema of object without correct ref from object instance" do
expect_raises(Swagger::Object::RefResolutionException, "Ref for Author not found") do
Swagger::Object.create_from_instance(
Project.new(1,
"swagger", VCS::GIT, true,
Author.new("icyleaf"),
"Swagger contains a OpenAPI / Swagger universal documentation generator and HTTP server handler."),
refs: {Hash => "Hash"},
)
end
end
end
end
1 change: 1 addition & 0 deletions src/swagger/builder.cr
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ module Swagger
type: property.type,
description: property.description,
example: property.example,
enum_values: property.enum_values,
)
end
end
Expand Down
36 changes: 30 additions & 6 deletions src/swagger/object.cr
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,55 @@ module Swagger
@items : (self | String)? = nil)
end

def self.create_from_instance(reflecting instance : T, custom_name : String? = nil) forall T
def self.create_from_instance(reflecting instance : T, custom_name : String? = nil, refs : Hash(Class, (String | self))? = nil) forall T
{% begin %}
properties = [] of Property
{% for ivar in T.instance.instance_vars %}
{{ iname = ivar.name.stringify }}
swagger_data_type = Utils::SwaggerDataType.create_from_class({{ ivar.type }})
{{ irequired = !ivar.type.union? }}
value = {% if T.class? || T.struct? %} instance.{{ ivar.name }} {% else %} {{ ivar.default_value.stringify }} {% end %}
{% if ivar.type.union? %}
{{ type_ivar = ivar.type.union_types.find { |var| var != Nil } }}
{% else %}
{{ type_ivar = ivar.type }}
{% end %}
properties << Property.new(
{{ iname }},
swagger_data_type.type,
format: swagger_data_type.format,
{% if ivar.type <= String || ivar.type <= Int32 ||
ivar.type <= Int64 || ivar.type <= Float64 ||
ivar.type <= Bool %}
{% if type_ivar <= String || type_ivar <= Int32 ||
type_ivar <= Int64 || type_ivar <= Float64 ||
type_ivar <= Bool %}
example: value,
{% else %}
{% elsif type_ivar <= Enum %}
example: value.to_s,
enum_values: {{ type_ivar }}.names,
{% else %}
ref: resolve_ref({{ type_ivar }}, refs),
{% end %}
required: {{ irequired }}
required: {{ irequired }},
)
{% end %}

self.new(custom_name ? custom_name.as(String) : instance.class.name, "object", properties)
{% end %}
end

class RefResolutionException < Exception
end

private def self.resolve_ref(type : T.class, refs : Hash(Class, (String | self))? = nil) : String forall T
if refs.nil?
raise RefResolutionException.new("No refs provided !")
end

current_ref = refs[type]?
if current_ref.nil?
raise RefResolutionException.new("Ref for #{type} not found")
end

return current_ref.is_a?(String) ? current_ref : current_ref.name
end
end
end
5 changes: 4 additions & 1 deletion src/swagger/objects/property.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ module Swagger::Objects
getter default : (String | Int32 | Int64 | Float64 | Bool)? = nil
getter example : (String | Int32 | Int64 | Float64 | Bool)? = nil
getter required : Bool? = nil
@[JSON::Field(key: "enum")]
getter enum_values : Array(String)? = nil

@[JSON::Field(key: "$ref")]
getter ref : String? = nil

def initialize(@type : String? = nil, @description : String? = nil, @items : Schema? = nil,
@default : (String | Int32 | Int64 | Float64 | Bool)? = nil,
@example : (String | Int32 | Int64 | Float64 | Bool)? = nil,
@required : Bool? = nil, @ref : String? = nil,)
@required : Bool? = nil, @ref : String? = nil,
@enum_values : Array(String)? = nil)
end
end
end
4 changes: 3 additions & 1 deletion src/swagger/property.cr
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ module Swagger
property example
property required
property ref
property enum_values

def initialize(@name : String, @type : String = "string", @format : String? = nil,
@items : (Object | String)? = nil, @description : String? = nil,
@default_value : (String | Int32 | Int64 | Float64 | Bool)? = nil,
@example : (String | Int32 | Int64 | Float64 | Bool)? = nil,
@required : Bool? = nil, @ref : String? = nil)
@required : Bool? = nil, @ref : String? = nil,
@enum_values : Array(String)? = nil)
end
end
end
4 changes: 2 additions & 2 deletions src/swagger/utils.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Utils
return self.create_from_class({{ T.union_types.find { |var| var != Nil } }})
{% else %}
# Cf https://swagger.io/specification/#data-types
{% if T <= String || T <= Nil %}
{% if T <= String %}
swagger_type = "string"
swagger_format = nil
{% elsif T <= Int %}
Expand All @@ -37,7 +37,7 @@ module Utils
swagger_type = "boolean"
swagger_format = nil
{% else %}
swagger_type = {{ T.stringify }}
swagger_type = "object"
swagger_format = nil
{% end %}
return self.new(swagger_type, swagger_format)
Expand Down

0 comments on commit 83c6db7

Please sign in to comment.