jekyll-openapi 0.1.0 → 0.3.0

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.
@@ -1,7 +1,11 @@
1
+ require_relative "../schema"
2
+
1
3
  module JekyllOpenAPI
2
4
  module SchemaRenderer
3
5
  ##
4
6
  # Renders a JSON-Schema as a simplified TypeScript-like textual representation.
7
+ # Traversal, type inference and `$ref` following live in {Schema}; this class
8
+ # only decides how each shape prints.
5
9
  # @see https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-00
6
10
  class TypeScript
7
11
  def initialize(logger: JekyllOpenAPI.logger)
@@ -12,94 +16,100 @@ module JekyllOpenAPI
12
16
  new(logger: logger).render(schema)
13
17
  end
14
18
 
15
- # @param input [Hash] a JSON-Schema
19
+ # @param input [Hash, Reference, Schema] a JSON-Schema
16
20
  # @param prefix [String] current indentation
17
21
  # @return [String]
18
22
  def render(input, prefix = "")
19
- unless input.is_a?(Hash)
20
- @logger.warn("not a valid schema object: #{input.inspect}")
23
+ schema = Schema.wrap(input)
24
+
25
+ if schema.reference?
26
+ followed = schema.follow
27
+ # A cycle prints the schema's name (e.g. "Node") instead of looping.
28
+ return schema.name.to_s if followed == Schema::CYCLE
29
+ return "" if followed.nil?
30
+ return render(followed, prefix)
31
+ end
32
+
33
+ unless schema.mapping?
34
+ @logger.warn("not a valid schema object: #{schema.raw.inspect}")
21
35
  return ""
22
36
  end
23
- str = base(input, prefix)
37
+
38
+ str = base(schema, prefix)
24
39
  return "" if str.nil?
25
- annotate(str, input)
40
+ annotate(str, schema)
26
41
  end
27
42
 
28
43
  private
29
44
 
30
- def base(input, prefix)
31
- return union(input["oneOf"], prefix, " | ") if input["oneOf"]
32
- return union(input["anyOf"], prefix, " | ") if input["anyOf"]
33
- return union(input["allOf"], prefix, " & ") if input["allOf"]
34
-
35
- type = input["type"]
36
- return type_union(input, prefix) if type.is_a?(Array)
37
- # infer the type when omitted, as most tooling does
38
- type ||= "object" if input["properties"] || input["additionalProperties"]
39
- type ||= "array" if input["items"]
40
- type ||= "string" if input["enum"]
45
+ def base(schema, prefix)
46
+ if (composition = schema.composition)
47
+ keyword, members = composition
48
+ separator = keyword == "allOf" ? " & " : " | "
49
+ return members.map { |member| render(member, prefix) }.join(separator)
50
+ end
51
+ return type_union(schema, prefix) if schema.type_union?
41
52
 
42
- case type
43
- when "object" then object(input, prefix)
44
- when "array" then "[ #{render(input["items"], prefix)} ]"
45
- when "string" then string(input)
53
+ case schema.type
54
+ when "object" then object(schema, prefix)
55
+ when "array" then "[ #{render(schema.items, prefix)} ]"
56
+ when "string" then string(schema)
46
57
  when "null" then "null"
47
58
  when nil
48
- @logger.warn("not a valid schema object: #{input.inspect}")
59
+ @logger.warn("not a valid schema object: #{schema.raw.inspect}")
49
60
  nil
50
61
  else
51
- primitive(input, type)
62
+ primitive(schema)
52
63
  end
53
64
  end
54
65
 
55
- def union(schemas, prefix, separator)
56
- schemas.map { |schema| render(schema, prefix) }.join(separator)
57
- end
58
-
59
66
  # OpenAPI 3.1 / JSON-Schema type arrays, e.g. type: [string, "null"]
60
- def type_union(input, prefix)
61
- types = input["type"]
62
- rendered = (types - ["null"]).map { |t| base(input.merge("type" => t), prefix) }
67
+ def type_union(schema, prefix)
68
+ types = schema.declared_type
69
+ rendered = (types - ["null"]).map { |t| base(schema.with_type(t), prefix) }
63
70
  rendered.push("null") if types.include?("null")
64
71
  rendered.join(" | ")
65
72
  end
66
73
 
67
- def object(input, prefix)
74
+ def object(schema, prefix)
68
75
  str = "{"
69
- (input["properties"] || {}).each do |key, schema|
70
- required = Array(input["required"]).include?(key)
71
- str += "\n#{prefix} #{key}#{required ? "" : "?"}: #{render(schema, prefix + " ")}"
76
+ schema.properties.each do |key, property|
77
+ optional = schema.required?(key) ? "" : "?"
78
+ str += "\n#{prefix} #{key}#{optional}: #{render(property, prefix + " ")}"
72
79
  end
73
- props = input["additionalProperties"]
74
- if props.is_a?(Hash)
75
- str += "\n#{prefix} [#{props["type"]}]?: #{render(props, prefix + " ")}"
80
+ if (additional = schema.additional_properties)
81
+ str += "\n#{prefix} [#{additional.type}]?: #{render(additional, prefix + " ")}"
82
+ elsif schema["additionalProperties"] == true
83
+ # open map with an unconstrained value type; `false` is the closed
84
+ # default and needs no annotation
85
+ str += "\n#{prefix} [string]?: any"
76
86
  end
77
87
  "#{str}\n#{prefix}}"
78
88
  end
79
89
 
80
- def string(input)
81
- if input["enum"]
82
- input["enum"].map { |v| "\"#{v}\"" }.join("|")
83
- elsif input["format"] == "binary"
90
+ def string(schema)
91
+ if schema.enum
92
+ schema.enum.map { |value| "\"#{value}\"" }.join("|")
93
+ elsif schema.format == "binary"
84
94
  "Buffer"
85
95
  else
86
96
  "\"string\""
87
97
  end
88
98
  end
89
99
 
90
- def primitive(input, type)
91
- str = type.to_s
92
- str += " (#{input["format"]})" if input["format"]
100
+ def primitive(schema)
101
+ str = schema.type.to_s
102
+ str += " (#{schema.format})" if schema.format
93
103
  str
94
104
  end
95
105
 
96
- def annotate(str, input)
97
- str = "#{str} // /#{input["pattern"]}/" if input["pattern"]
98
- if input["summary"]
99
- str = "#{str} //#{input["summary"]}"
100
- elsif input["description"]
106
+ def annotate(str, schema)
107
+ str = "#{str} // /#{schema.pattern}/" if schema.pattern
108
+ if schema.summary
109
+ str = "#{str} //#{schema.summary}"
110
+ elsif schema.description
101
111
  padding = "".rjust(str.length, " ")
102
- str = "#{str} //#{input["description"].split("\n").join("\n#{padding} //")}"
112
+ str = "#{str} //#{schema.description.split("\n").join("\n#{padding} //")}"
103
113
  end
104
114
  str
105
115
  end
@@ -1,9 +1,11 @@
1
+ require_relative "../schema"
2
+
1
3
  module JekyllOpenAPI
2
4
  module SchemaRenderer
3
5
  ##
4
6
  # Renders a JSON-Schema as a simplified XML representation of the object,
5
7
  # honoring the OpenAPI `xml` metadata object (name, prefix, namespace,
6
- # attribute, wrapped).
8
+ # attribute, wrapped). Traversal and `$ref` following live in {Schema}.
7
9
  # @see https://spec.openapis.org/oas/v3.1.0.html#xml-object
8
10
  class XML
9
11
  def initialize(logger: JekyllOpenAPI.logger)
@@ -14,17 +16,27 @@ module JekyllOpenAPI
14
16
  new(logger: logger).render(schema)
15
17
  end
16
18
 
17
- # @param input [Hash] a JSON-Schema
19
+ # @param input [Hash, Reference, Schema] a JSON-Schema
18
20
  # @param prefix [String] current indentation
19
21
  # @param name [String] element name when the schema has no xml.name
20
22
  # @return [String]
21
23
  def render(input, prefix = "", name = "xml")
22
- unless input.is_a?(Hash) && (input["type"] || input["properties"] || input["items"])
23
- @logger.warn("not a valid schema object: #{input.inspect}")
24
+ schema = Schema.wrap(input)
25
+
26
+ if schema.reference?
27
+ followed = schema.follow
28
+ # A cyclic or unresolvable ref has no finite XML expansion here.
29
+ return "" if followed.nil? || followed == Schema::CYCLE
30
+ return render(followed, prefix, name)
31
+ end
32
+
33
+ unless schema.mapping? &&
34
+ (schema.declared_type || schema["properties"] || schema["items"] || schema["additionalProperties"])
35
+ @logger.warn("not a valid schema object: #{schema.raw.inspect}")
24
36
  return ""
25
37
  end
26
38
 
27
- xml_def = input["xml"] || {}
39
+ xml_def = schema.xml
28
40
  name = xml_def["name"] || name
29
41
  name = "#{xml_def["prefix"]}:#{name}" if xml_def["prefix"]
30
42
  attrs = {}
@@ -33,40 +45,44 @@ module JekyllOpenAPI
33
45
  attrs[ns_attr] = xml_def["namespace"]
34
46
  end
35
47
 
36
- type = input["type"]
37
- type ||= "object" if input["properties"]
38
- type ||= "array" if input["items"]
48
+ type = schema.declared_type
49
+ # A 3.1 type union (e.g. ["string","null"]) prints as one sample element:
50
+ # take the first non-null member rather than stringifying the array.
51
+ type = (type - ["null"]).first if type.is_a?(Array)
52
+ type ||= "object" if schema["properties"] || schema["additionalProperties"]
53
+ type ||= "array" if schema["items"]
39
54
 
40
55
  case type
41
56
  when "array"
42
- array(input, prefix, name, attrs, wrapped: xml_def["wrapped"])
57
+ array(schema, prefix, name, attrs, wrapped: xml_def["wrapped"])
43
58
  when "object"
44
59
  content = ""
45
- (input["properties"] || {}).each do |key, schema|
46
- if schema.is_a?(Hash) && schema.dig("xml", "attribute")
47
- attrs[schema.dig("xml", "name") || key] = attribute_value(schema)
60
+ schema.properties.each do |key, property|
61
+ if property.dig("xml", "attribute")
62
+ attrs[property.dig("xml", "name") || key] = attribute_value(property)
48
63
  else
49
- content += "\n#{render(schema, prefix + " ", key)}"
64
+ content += "\n#{render(property, prefix + " ", key)}"
50
65
  end
51
66
  end
52
67
  element(name, attrs, content, prefix)
53
68
  when "string"
54
- element(name, attrs, string_content(input), prefix)
69
+ element(name, attrs, string_content(schema), prefix)
55
70
  else
56
71
  content = type.to_s
57
- content += comment(" (#{input["format"]})") if input["format"]
58
- content += comment(input["summary"]) if input["summary"]
72
+ content += comment(" (#{schema.format})") if schema.format
73
+ content += comment(schema.summary) if schema.summary
59
74
  element(name, attrs, content, prefix)
60
75
  end
61
76
  end
62
77
 
63
78
  private
64
79
 
65
- def array(input, prefix, name, attrs, wrapped: false)
80
+ def array(schema, prefix, name, attrs, wrapped: false)
81
+ items = schema.items
66
82
  unless wrapped
67
- return "#{render(input["items"], prefix, name)}\n#{prefix}#{comment("Array of <#{name}>")}"
83
+ return "#{render(items, prefix, name)}\n#{prefix}#{comment("Array of <#{name}>")}"
68
84
  end
69
- item = render(input["items"], prefix + " ", singular(name))
85
+ item = render(items, prefix + " ", singular(name))
70
86
  content = "\n#{item}\n#{prefix + " "}#{comment("Array of <#{singular(name)}>")}"
71
87
  element(name, attrs, content, prefix)
72
88
  end
@@ -77,20 +93,23 @@ module JekyllOpenAPI
77
93
  "#{prefix}<#{name}#{str_attrs}>#{content}#{closing}</#{name}>"
78
94
  end
79
95
 
80
- def string_content(input)
81
- content = if input["format"] == "binary"
96
+ def string_content(schema)
97
+ content = if schema.format == "binary"
82
98
  "Buffer"
83
- elsif input["example"]
84
- input["example"].to_s
85
- elsif input["examples"].is_a?(Hash) && !input["examples"].empty?
86
- input["examples"].values.first["value"].to_s
99
+ elsif !schema.example.nil?
100
+ schema.example.to_s
101
+ elsif schema.examples.is_a?(Array) && !schema.examples.empty?
102
+ # OpenAPI 3.1 / JSON-Schema: a schema's `examples` is an array of values.
103
+ # The Example Object *map* is a media-type/parameter construct, not valid
104
+ # on a schema, so it is deliberately not interpreted here.
105
+ schema.examples.first.to_s
87
106
  else
88
107
  "string"
89
108
  end
90
- if input["summary"]
91
- content += " #{comment(input["summary"])}"
92
- elsif input["pattern"]
93
- content += comment("/#{input["pattern"]}/")
109
+ if schema.summary
110
+ content += " #{comment(schema.summary)}"
111
+ elsif schema.pattern
112
+ content += comment("/#{schema.pattern}/")
94
113
  end
95
114
  content
96
115
  end
@@ -1,3 +1,3 @@
1
1
  module JekyllOpenAPI
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,10 +1,17 @@
1
1
  require_relative "jekyll_openapi/version"
2
2
  require_relative "jekyll_openapi/json_pointer"
3
+ require_relative "jekyll_openapi/reference"
3
4
  require_relative "jekyll_openapi/dereferencer"
4
5
  require_relative "jekyll_openapi/parameters"
6
+ require_relative "jekyll_openapi/examples"
7
+ require_relative "jekyll_openapi/anchors"
8
+ require_relative "jekyll_openapi/content_type"
9
+ require_relative "jekyll_openapi/headers"
10
+ require_relative "jekyll_openapi/schema"
5
11
  require_relative "jekyll_openapi/document"
6
12
  require_relative "jekyll_openapi/schema_renderer/typescript"
7
13
  require_relative "jekyll_openapi/schema_renderer/xml"
14
+ require_relative "jekyll_openapi/schema_renderer/html"
8
15
  require_relative "jekyll_openapi/filters"
9
16
 
10
17
  module JekyllOpenAPI
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-openapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastien Dumetz
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: liquid
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -67,12 +81,19 @@ files:
67
81
  - README.md
68
82
  - lib/jekyll-openapi.rb
69
83
  - lib/jekyll_openapi.rb
84
+ - lib/jekyll_openapi/anchors.rb
85
+ - lib/jekyll_openapi/content_type.rb
70
86
  - lib/jekyll_openapi/dereferencer.rb
71
87
  - lib/jekyll_openapi/document.rb
88
+ - lib/jekyll_openapi/examples.rb
72
89
  - lib/jekyll_openapi/filters.rb
90
+ - lib/jekyll_openapi/headers.rb
73
91
  - lib/jekyll_openapi/jekyll_plugin.rb
74
92
  - lib/jekyll_openapi/json_pointer.rb
75
93
  - lib/jekyll_openapi/parameters.rb
94
+ - lib/jekyll_openapi/reference.rb
95
+ - lib/jekyll_openapi/schema.rb
96
+ - lib/jekyll_openapi/schema_renderer/html.rb
76
97
  - lib/jekyll_openapi/schema_renderer/typescript.rb
77
98
  - lib/jekyll_openapi/schema_renderer/xml.rb
78
99
  - lib/jekyll_openapi/version.rb