eazypi 0.0.1 → 0.0.3

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: bfe47c3c3c7c8c3ac25c0d67311957fb2672599ea81f48e6247f82b9fae6710b
4
+ data.tar.gz: 436ec6c096a12482add4ac1a0245b4c555e254fd3be329ba9549e6b1422a1154
5
5
  SHA512:
6
- metadata.gz: a96a3e253fea3076ba0953c166b9d31e295160c8c9ab34fbb0e72393ed1bfc647c39d7b9f2e98457c3f7c66b3ddc240cfb27aea0ca0fd25468cb51f41db8e5f7
7
- data.tar.gz: '0832c28ce9c3120b63d3d40d3b59e5aea7701dc6b050ffe89c49ce54dbfdb5f74ce05091a0e587c5c2407df95b5a0e4b082cf23441400ce9505f1d6f6692a61a'
6
+ metadata.gz: f849fdc4f64b2471955dcf1b8517e6bd445d2285b646384c6081b6aa9ffb87d78e9be72a093c4731436ea6fd4340561a2b1aa89886cdb3e53f0c8007bfd704d6
7
+ data.tar.gz: 62e8ba592f52624d36bedba28e96f463956c6ae3cb16e1aa2d89acfc060459bc897d2034bdfa8e7423b7222104f0f4bca0acdbe4729358d821abc9d3cf5a91ce
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
@@ -5,6 +5,7 @@ module Eazypi
5
5
  module ApiController
6
6
  def self.included(base)
7
7
  base.instance_variable_set(:@operations, [])
8
+ base.include(InstanceMethods)
8
9
  base.extend(ClassMethods)
9
10
  end
10
11
 
@@ -53,5 +54,20 @@ module Eazypi
53
54
  controller_method_name
54
55
  end
55
56
  end
57
+
58
+ # Instance methods to be used in your own API controller
59
+ module InstanceMethods
60
+ def respond_with(object, status: :ok)
61
+ response_type = @current_operation.response_for_response_code(status)
62
+
63
+ response_object_type = response_type.object_type_for_content_type
64
+
65
+ if response_object_type.nil?
66
+ render json: nil, status: status
67
+ else
68
+ render json: response_type.object_type_for_content_type.new(object).to_json, status: status
69
+ end
70
+ end
71
+ end
56
72
  end
57
73
  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
@@ -44,11 +44,18 @@ module Eazypi
44
44
  @responses.add_response(status_code, &block)
45
45
  end
46
46
 
47
+ def response_for_response_code(response_code)
48
+ response_code = Rack::Utils::SYMBOL_TO_STATUS_CODE[response_code] if response_code.is_a?(Symbol)
49
+
50
+ @responses.response_for_response_code(response_code)
51
+ end
52
+
47
53
  def render(&block)
48
54
  @renderer = block
49
55
  end
50
56
 
51
57
  def call(controller)
58
+ controller.instance_variable_set(:@current_operation, self)
52
59
  controller.instance_exec(&@renderer)
53
60
  end
54
61
 
@@ -74,6 +81,11 @@ module Eazypi
74
81
  normalized_path
75
82
  end
76
83
 
84
+ def collect_components(**kwargs)
85
+ @request_body&.collect_components(**kwargs)
86
+ @responses&.collect_components(**kwargs)
87
+ end
88
+
77
89
  private
78
90
 
79
91
  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,
@@ -9,11 +9,14 @@ module Eazypi
9
9
 
10
10
  def initialize
11
11
  @content = {}
12
+ @object_type = {}
12
13
 
13
14
  super
14
15
  end
15
16
 
16
17
  def content(schema, content_type = "application/json")
18
+ return if schema.nil?
19
+
17
20
  media_type = MediaType.new
18
21
 
19
22
  media_type.load do
@@ -21,12 +24,23 @@ module Eazypi
21
24
  end
22
25
 
23
26
  @content[content_type] = media_type
27
+ @object_type[content_type] = schema
28
+ end
29
+
30
+ def object_type_for_content_type(content_type = "application/json")
31
+ @object_type[content_type]
32
+ end
33
+
34
+ def collect_components(**kwargs)
35
+ @content.each_value do |media_type|
36
+ media_type.collect_components(**kwargs)
37
+ end
24
38
  end
25
39
 
26
40
  def to_openapi_spec
27
41
  {
28
42
  "description" => description,
29
- "content" => @content.transform_values(&:to_openapi_spec)
43
+ "content" => @content.empty? ? nil : @content.transform_values(&:to_openapi_spec)
30
44
  }.compact
31
45
  end
32
46
  end
@@ -14,6 +14,17 @@ module Eazypi
14
14
  @responses[status_code.to_s] = response
15
15
  end
16
16
 
17
+ def response_for_response_code(response_code)
18
+ # Future improvement could be partial match
19
+ @responses[response_code.to_s]
20
+ end
21
+
22
+ def collect_components(**kwargs)
23
+ @responses.each_value do |response|
24
+ response.collect_components(**kwargs)
25
+ end
26
+ end
27
+
17
28
  def to_openapi_spec
18
29
  @responses.transform_values(&:to_openapi_spec)
19
30
  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.3"
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.3
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-06 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