eazypi 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b60b4097cbd524ad683d3bf4c25158620e491a0638649397da289fc4d63d21e1
4
- data.tar.gz: a18fa4781d32592d1dc933d1923dfc389237d04ae02a067420c364cd35c8772c
3
+ metadata.gz: 344baf3f0f1dacd826dae2caf2aeda6172c78dcd72c4b5a3a8e05f691ccf1a4e
4
+ data.tar.gz: 5ac7965a5e48ad5962dfbce582a61c54d72c19f5ac0740d2c5cea4e8020f905f
5
5
  SHA512:
6
- metadata.gz: a96a3e253fea3076ba0953c166b9d31e295160c8c9ab34fbb0e72393ed1bfc647c39d7b9f2e98457c3f7c66b3ddc240cfb27aea0ca0fd25468cb51f41db8e5f7
7
- data.tar.gz: '0832c28ce9c3120b63d3d40d3b59e5aea7701dc6b050ffe89c49ce54dbfdb5f74ce05091a0e587c5c2407df95b5a0e4b082cf23441400ce9505f1d6f6692a61a'
6
+ metadata.gz: 2bf75cf26c5890e8749311e3a65b32d2bd6d8834bbc54e8da77b828b8bbeb32d0e49aa33deda42cb31b733472d4def0044f3a4ef6f69aafa9fae0584952efd39
7
+ data.tar.gz: a41d5f22d742e653409a362d58cdf82684b1ded4b671400adf7f184b768eec3acb44f2ec4d9d7f2049c918d9806d8eab355012907576fc4ea470f56c4d62b90a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
- ## [Unreleased]
1
+ ## [unreleased] 0.0.2
2
+
3
+ - Allow boolean types
4
+ - Collect schemas in the components and make Object schemas named
5
+ - Automatically fill in servers if not explicitly given
6
+
7
+ ## [2024-02-22] 0.0.1
2
8
 
3
9
  - Initial release
data/eazypi.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/eazypi/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "eazypi"
7
+ spec.version = Eazypi::VERSION
8
+ spec.authors = ["Nathan Samson"]
9
+ spec.email = ["nathan@nathansamson.be"]
10
+ spec.license = "MIT"
11
+
12
+ spec.summary = "An opinionated framework to generate OpenAPI API's for Rails"
13
+ spec.homepage = "https://gitlab.com/nathansamson/eazypi/-/blob/main/README.md"
14
+ spec.required_ruby_version = ">= 3.0.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://gitlab.com/nathansamson/eazypi"
18
+ spec.metadata["changelog_uri"] = "https://gitlab.com/nathansamson/eazypi/-/blob/main/CHANGELOG.md"
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(__dir__) do
23
+ `git ls-files -z`.split("\x0").reject do |f|
24
+ (File.expand_path(f) == __FILE__) ||
25
+ f.start_with?(*%w[bin/ test/ spec/ features/ petstore-example/ .git .gitlab-ci.yml appveyor Gemfile])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "actionpack", "~> 7.0"
33
+ spec.add_dependency "activesupport", "~> 7.0"
34
+ spec.metadata["rubygems_mfa_required"] = "true"
35
+ end
data/lib/eazypi/api.rb CHANGED
@@ -26,6 +26,10 @@ module Eazypi
26
26
  paths[operation.path] ||= PathItem.new
27
27
 
28
28
  paths[operation.path].send(operation.method, operation)
29
+
30
+ operation.collect_components(
31
+ schemas: method(:reference_schema)
32
+ )
29
33
  end
30
34
  end
31
35
 
@@ -38,13 +42,19 @@ module Eazypi
38
42
  end
39
43
 
40
44
  def to_openapi_spec
45
+ to_openapi_spec_with_servers(servers)
46
+ end
47
+
48
+ def to_openapi_spec_with_servers(override_servers)
49
+ actual_servers = override_servers || servers
50
+
41
51
  {
42
52
  "openapi" => "3.0.3", # Future improvement allow different version
43
53
  "info" => info.to_openapi_spec,
44
54
  "paths" => paths.transform_keys do |path|
45
55
  Operation.normalized_path(path)
46
56
  end.transform_values(&:to_openapi_spec),
47
- "servers" => servers.map(&:to_openapi_spec),
57
+ "servers" => actual_servers.map(&:to_openapi_spec),
48
58
  "components" => components.to_openapi_spec
49
59
  }
50
60
  end
@@ -71,22 +81,36 @@ module Eazypi
71
81
 
72
82
  private
73
83
 
84
+ def reference_schema(schema)
85
+ return unless schema.is_a?(Eazypi::Schema::Object) && !schema.object_name.nil?
86
+
87
+ reference = components.add_schema(schema.object_name, schema)
88
+ schema.reference!(reference)
89
+ end
90
+
74
91
  def prepare_controller_class
75
92
  ancestors[0].const_set("EazypiController", controller_class)
76
93
  end
77
94
 
78
- def controller_class # rubocop:todo Metrics/MethodLength
95
+ def controller_class # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
79
96
  @controller_class ||= begin
80
97
  api_instance = self
81
98
 
82
99
  klass = Class.new(ActionController::Base)
83
- klass.define_method(:show) do
84
- puts "HERER AI MA "
100
+ klass.define_method(:show) do # rubocop:todo Metrics/MethodLength
101
+ override_servers = if api_instance.servers.empty?
102
+ s = Server.new
103
+ s.url request.url.split("/")[0..-2].join("/") # Probably a better way to do this
104
+ [
105
+ s
106
+ ]
107
+ end
85
108
 
86
109
  if params[:format] == "json"
87
- render json: api_instance.to_openapi_spec
110
+ render json: api_instance.to_openapi_spec_with_servers(override_servers)
88
111
  elsif params[:format] == "yaml"
89
- render plain: api_instance.to_openapi_spec.to_yaml, content_type: "text/yaml"
112
+ render plain: api_instance.to_openapi_spec_with_servers(override_servers).to_yaml,
113
+ content_type: "text/yaml"
90
114
  else
91
115
  raise "#{params[:format]} not supported"
92
116
  end
@@ -3,8 +3,20 @@
3
3
  module Eazypi
4
4
  # OpenAPI spec ComponentsObject
5
5
  class Components
6
+ def initialize
7
+ @schemas = {}
8
+ end
9
+
10
+ def add_schema(name, schema)
11
+ @schemas[name] = schema.dup # Make sure it never gets a reference
12
+
13
+ "#/components/schemas/#{name}"
14
+ end
15
+
6
16
  def to_openapi_spec
7
- {}
17
+ {
18
+ "schemas" => @schemas.empty? ? nil : @schemas.transform_values(&:to_openapi_spec)
19
+ }.compact
8
20
  end
9
21
  end
10
22
  end
@@ -7,6 +7,13 @@ module Eazypi
7
7
 
8
8
  spec_attribute :schema
9
9
 
10
+ def collect_components(schemas: nil, **kwargs)
11
+ return unless schema
12
+
13
+ schema.collect_components(schemas: schemas, **kwargs)
14
+ schemas&.call(schema)
15
+ end
16
+
10
17
  def to_openapi_spec
11
18
  {
12
19
  "schema" => schema.to_openapi_spec
@@ -74,6 +74,11 @@ module Eazypi
74
74
  normalized_path
75
75
  end
76
76
 
77
+ def collect_components(**kwargs)
78
+ @request_body&.collect_components(**kwargs)
79
+ @responses&.collect_components(**kwargs)
80
+ end
81
+
77
82
  private
78
83
 
79
84
  PATH_REGEX = %r{/:[a-zA-Z0-9_-]+}
@@ -20,6 +20,12 @@ module Eazypi
20
20
  end
21
21
  end
22
22
 
23
+ def collect_components(**kwargs)
24
+ @content&.each_value do |media_type|
25
+ media_type.collect_components(**kwargs)
26
+ end
27
+ end
28
+
23
29
  def to_openapi_spec
24
30
  {
25
31
  "description" => description,
@@ -14,6 +14,8 @@ module Eazypi
14
14
  end
15
15
 
16
16
  def content(schema, content_type = "application/json")
17
+ return if schema.nil?
18
+
17
19
  media_type = MediaType.new
18
20
 
19
21
  media_type.load do
@@ -23,10 +25,16 @@ module Eazypi
23
25
  @content[content_type] = media_type
24
26
  end
25
27
 
28
+ def collect_components(**kwargs)
29
+ @content.each_value do |media_type|
30
+ media_type.collect_components(**kwargs)
31
+ end
32
+ end
33
+
26
34
  def to_openapi_spec
27
35
  {
28
36
  "description" => description,
29
- "content" => @content.transform_values(&:to_openapi_spec)
37
+ "content" => @content.empty? ? nil : @content.transform_values(&:to_openapi_spec)
30
38
  }.compact
31
39
  end
32
40
  end
@@ -14,6 +14,12 @@ module Eazypi
14
14
  @responses[status_code.to_s] = response
15
15
  end
16
16
 
17
+ def collect_components(**kwargs)
18
+ @responses.each_value do |response|
19
+ response.collect_components(**kwargs)
20
+ end
21
+ end
22
+
17
23
  def to_openapi_spec
18
24
  @responses.transform_values(&:to_openapi_spec)
19
25
  end
@@ -10,6 +10,10 @@ module Eazypi
10
10
  @item_schema = item_schema
11
11
  end
12
12
 
13
+ def collect_components(schemas: nil, **_kwargs)
14
+ schemas&.call(@item_schema)
15
+ end
16
+
13
17
  def to_openapi_spec
14
18
  {
15
19
  "type" => "array",
@@ -4,9 +4,17 @@ module Eazypi
4
4
  module Schema
5
5
  # Object schema for Json
6
6
  class Object
7
- def initialize
7
+ attr_reader :object_name
8
+
9
+ def initialize(object_name = nil)
8
10
  @properties = {}
9
11
  @required = []
12
+ @object_name = object_name
13
+ @reference = nil
14
+ end
15
+
16
+ def reference!(reference)
17
+ @reference = reference
10
18
  end
11
19
 
12
20
  def property(name, schema, required: false)
@@ -14,7 +22,16 @@ module Eazypi
14
22
  @required << name if required
15
23
  end
16
24
 
25
+ def collect_components(schemas: nil, **kwargs)
26
+ @properties.each_value do |property_schema|
27
+ property_schema.collect_components(schemas: schemas, **kwargs)
28
+ schemas&.call(property_schema)
29
+ end
30
+ end
31
+
17
32
  def to_openapi_spec
33
+ return { "$ref" => @reference } if @reference
34
+
18
35
  {
19
36
  "type" => "object",
20
37
  "required" => @required.empty? ? nil : @required,
@@ -26,7 +43,8 @@ module Eazypi
26
43
  return false unless other.is_a?(Object)
27
44
 
28
45
  other.instance_variable_get(:@properties) == @properties &&
29
- other.instance_variable_get(:@required) == @required
46
+ other.instance_variable_get(:@required) == @required &&
47
+ other.object_name == object_name
30
48
  end
31
49
  end
32
50
  end
@@ -13,6 +13,8 @@ module Eazypi
13
13
  @format = format
14
14
  end
15
15
 
16
+ def collect_components(**_kwargs); end
17
+
16
18
  def to_openapi_spec
17
19
  {
18
20
  "type" => type,
data/lib/eazypi/schema.rb CHANGED
@@ -3,8 +3,10 @@
3
3
  module Eazypi
4
4
  # JSON schema
5
5
  module Schema
6
- def self.from_object(object) # rubocop:todo Metrics/MethodLength
7
- if object.is_a?(::Array)
6
+ def self.from_object(object) # rubocop:todo Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
7
+ if object == :boolean
8
+ Schema::Primitive.new(type: "boolean")
9
+ elsif object.is_a?(::Array)
8
10
  raise "Array needs to have one element" if object.length != 1
9
11
 
10
12
  Schema::Array.new(Schema.from_object(object[0]))
@@ -9,12 +9,23 @@ module Eazypi
9
9
  class Attribute
10
10
  attr_reader :name, :type, :required
11
11
 
12
- def initialize(name, type:, method_name:, required:)
12
+ def initialize(name, type:, required:, method:, method_name:)
13
13
  @name = name
14
14
  @type = type
15
+ @method = method
15
16
  @method_name = method_name
16
17
  @required = required
17
18
  end
19
+
20
+ def serialize(object)
21
+ if @method
22
+ @method.call(object)
23
+ elsif @method_name
24
+ object.send(@method_name)
25
+ else
26
+ object.send(@name)
27
+ end
28
+ end
18
29
  end
19
30
 
20
31
  def initialize(object)
@@ -23,17 +34,18 @@ module Eazypi
23
34
 
24
35
  def to_json(*_args)
25
36
  self.class.attributes.to_h do |attribute|
26
- [attribute.name, @object.send(attribute.name)]
37
+ [attribute.name, attribute.serialize(@object)]
27
38
  end
28
39
  end
29
40
 
30
41
  included do
31
42
  @attributes = []
32
43
 
33
- def self.attribute(attribute_name, type:, method_name: nil, required: false)
44
+ def self.attribute(attribute_name, type:, method: nil, method_name: nil, required: false)
34
45
  @attributes << Attribute.new(
35
46
  attribute_name,
36
47
  type: type,
48
+ method: method,
37
49
  method_name: method_name,
38
50
  required: required
39
51
  )
@@ -44,7 +56,7 @@ module Eazypi
44
56
  end
45
57
 
46
58
  def self.to_schema
47
- schema = Schema::Object.new
59
+ schema = Schema::Object.new(name.split("::").last)
48
60
 
49
61
  @attributes.each do |attribute|
50
62
  schema.property attribute.name.to_s, Schema.from_object(attribute.type), required: attribute.required
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Eazypi
4
+ # OpenAPI spec ServerObject
5
+ class Server
6
+ include SpecObject
7
+
8
+ spec_attribute :url
9
+ spec_attribute :description
10
+
11
+ def to_openapi_spec
12
+ {
13
+ "url" => url,
14
+ "description" => description
15
+ }.compact
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Eazypi
4
- VERSION = "0.0.1"
4
+ VERSION = "0.0.2"
5
5
  end
data/lib/eazypi.rb CHANGED
@@ -22,4 +22,5 @@ require "eazypi/response"
22
22
  require "eazypi/responses"
23
23
  require "eazypi/schema"
24
24
  require "eazypi/serializer"
25
+ require "eazypi/server"
25
26
  require "eazypi/version"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eazypi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Samson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-22 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -50,6 +50,7 @@ files:
50
50
  - CHANGELOG.md
51
51
  - README.md
52
52
  - Rakefile
53
+ - eazypi.gemspec
53
54
  - lib/eazypi.rb
54
55
  - lib/eazypi/api.rb
55
56
  - lib/eazypi/api_controller.rb
@@ -67,10 +68,12 @@ files:
67
68
  - lib/eazypi/schema/object.rb
68
69
  - lib/eazypi/schema/primitive.rb
69
70
  - lib/eazypi/serializer.rb
71
+ - lib/eazypi/server.rb
70
72
  - lib/eazypi/spec_object.rb
71
73
  - lib/eazypi/version.rb
72
74
  homepage: https://gitlab.com/nathansamson/eazypi/-/blob/main/README.md
73
- licenses: []
75
+ licenses:
76
+ - MIT
74
77
  metadata:
75
78
  homepage_uri: https://gitlab.com/nathansamson/eazypi/-/blob/main/README.md
76
79
  source_code_uri: https://gitlab.com/nathansamson/eazypi
@@ -91,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
94
  - !ruby/object:Gem::Version
92
95
  version: '0'
93
96
  requirements: []
94
- rubygems_version: 3.4.10
97
+ rubygems_version: 3.5.3
95
98
  signing_key:
96
99
  specification_version: 4
97
100
  summary: An opinionated framework to generate OpenAPI API's for Rails